1use std::{cell::RefCell, rc::Rc};
19
20use nautilus_core::python::to_pyvalue_err;
21#[cfg(feature = "defi")]
22use nautilus_model::defi::{Pool, PoolProfiler};
23use nautilus_model::{
24 data::{
25 Bar, BarType, FundingRateUpdate, QuoteTick, TradeTick,
26 prices::{IndexPriceUpdate, MarkPriceUpdate},
27 },
28 enums::{OmsType, OrderSide, PositionSide},
29 identifiers::{
30 AccountId, ClientId, ClientOrderId, InstrumentId, PositionId, StrategyId, Venue,
31 },
32 instruments::SyntheticInstrument,
33 orderbook::OrderBook,
34 position::Position,
35 python::{
36 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
37 orders::{order_any_to_pyobject, pyobject_to_order_any},
38 },
39 types::Currency,
40};
41use pyo3::prelude::*;
42
43use crate::{
44 cache::{Cache, CacheConfig},
45 enums::SerializationEncoding,
46};
47
48#[allow(non_camel_case_types)]
53#[pyo3::pyclass(
54 module = "nautilus_trader.core.nautilus_pyo3.common",
55 unsendable,
56 from_py_object
57)]
58#[derive(Debug, Clone)]
59pub struct PyCache(Rc<RefCell<Cache>>);
60
61impl PyCache {
62 #[must_use]
64 pub fn from_rc(rc: Rc<RefCell<Cache>>) -> Self {
65 Self(rc)
66 }
67}
68
69#[pymethods]
70impl PyCache {
71 #[pyo3(name = "instrument")]
72 fn py_instrument(
73 &self,
74 py: Python,
75 instrument_id: InstrumentId,
76 ) -> PyResult<Option<Py<PyAny>>> {
77 let cache = self.0.borrow();
78 match cache.instrument(&instrument_id) {
79 Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument.clone())?)),
80 None => Ok(None),
81 }
82 }
83
84 #[pyo3(name = "quote")]
85 fn py_quote(&self, instrument_id: InstrumentId) -> Option<QuoteTick> {
86 self.0.borrow().quote(&instrument_id).copied()
87 }
88
89 #[pyo3(name = "trade")]
90 fn py_trade(&self, instrument_id: InstrumentId) -> Option<TradeTick> {
91 self.0.borrow().trade(&instrument_id).copied()
92 }
93
94 #[pyo3(name = "bar")]
95 fn py_bar(&self, bar_type: BarType) -> Option<Bar> {
96 self.0.borrow().bar(&bar_type).copied()
97 }
98
99 #[pyo3(name = "order_book")]
100 fn py_order_book(&self, instrument_id: InstrumentId) -> Option<OrderBook> {
101 self.0.borrow().order_book(&instrument_id).cloned()
102 }
103
104 #[cfg(feature = "defi")]
105 #[pyo3(name = "pool")]
106 fn py_pool(&self, instrument_id: InstrumentId) -> Option<Pool> {
107 self.0
108 .try_borrow()
109 .ok()
110 .and_then(|cache| cache.pool(&instrument_id).cloned())
111 }
112
113 #[cfg(feature = "defi")]
114 #[pyo3(name = "pool_profiler")]
115 fn py_pool_profiler(&self, instrument_id: InstrumentId) -> Option<PoolProfiler> {
116 self.0
117 .try_borrow()
118 .ok()
119 .and_then(|cache| cache.pool_profiler(&instrument_id).cloned())
120 }
121}
122
123#[pymethods]
124impl CacheConfig {
125 #[new]
126 #[allow(clippy::too_many_arguments)]
127 fn py_new(
128 encoding: Option<SerializationEncoding>,
129 timestamps_as_iso8601: Option<bool>,
130 buffer_interval_ms: Option<usize>,
131 bulk_read_batch_size: Option<usize>,
132 use_trader_prefix: Option<bool>,
133 use_instance_id: Option<bool>,
134 flush_on_start: Option<bool>,
135 drop_instruments_on_reset: Option<bool>,
136 tick_capacity: Option<usize>,
137 bar_capacity: Option<usize>,
138 save_market_data: Option<bool>,
139 ) -> Self {
140 Self::new(
141 None, encoding.unwrap_or(SerializationEncoding::MsgPack),
143 timestamps_as_iso8601.unwrap_or(false),
144 buffer_interval_ms,
145 bulk_read_batch_size,
146 use_trader_prefix.unwrap_or(true),
147 use_instance_id.unwrap_or(false),
148 flush_on_start.unwrap_or(false),
149 drop_instruments_on_reset.unwrap_or(true),
150 tick_capacity.unwrap_or(10_000),
151 bar_capacity.unwrap_or(10_000),
152 save_market_data.unwrap_or(false),
153 )
154 }
155
156 fn __str__(&self) -> String {
157 format!("{self:?}")
158 }
159
160 fn __repr__(&self) -> String {
161 format!("{self:?}")
162 }
163
164 #[getter]
165 fn encoding(&self) -> SerializationEncoding {
166 self.encoding
167 }
168
169 #[getter]
170 fn timestamps_as_iso8601(&self) -> bool {
171 self.timestamps_as_iso8601
172 }
173
174 #[getter]
175 fn buffer_interval_ms(&self) -> Option<usize> {
176 self.buffer_interval_ms
177 }
178
179 #[getter]
180 fn bulk_read_batch_size(&self) -> Option<usize> {
181 self.bulk_read_batch_size
182 }
183
184 #[getter]
185 fn use_trader_prefix(&self) -> bool {
186 self.use_trader_prefix
187 }
188
189 #[getter]
190 fn use_instance_id(&self) -> bool {
191 self.use_instance_id
192 }
193
194 #[getter]
195 fn flush_on_start(&self) -> bool {
196 self.flush_on_start
197 }
198
199 #[getter]
200 fn drop_instruments_on_reset(&self) -> bool {
201 self.drop_instruments_on_reset
202 }
203
204 #[getter]
205 fn tick_capacity(&self) -> usize {
206 self.tick_capacity
207 }
208
209 #[getter]
210 fn bar_capacity(&self) -> usize {
211 self.bar_capacity
212 }
213
214 #[getter]
215 fn save_market_data(&self) -> bool {
216 self.save_market_data
217 }
218}
219
220#[pymethods]
221impl Cache {
222 #[new]
223 fn py_new(config: Option<CacheConfig>) -> Self {
224 Self::new(config, None)
225 }
226
227 fn __repr__(&self) -> String {
228 format!("{self:?}")
229 }
230
231 #[pyo3(name = "reset")]
232 fn py_reset(&mut self) {
233 self.reset();
234 }
235
236 #[pyo3(name = "dispose")]
237 fn py_dispose(&mut self) {
238 self.dispose();
239 }
240
241 #[pyo3(name = "add_currency")]
242 fn py_add_currency(&mut self, currency: Currency) -> PyResult<()> {
243 self.add_currency(currency).map_err(to_pyvalue_err)
244 }
245
246 #[pyo3(name = "add_instrument")]
247 fn py_add_instrument(&mut self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
248 let instrument_any = pyobject_to_instrument_any(py, instrument)?;
249 self.add_instrument(instrument_any).map_err(to_pyvalue_err)
250 }
251
252 #[pyo3(name = "instrument")]
253 fn py_instrument(
254 &self,
255 py: Python,
256 instrument_id: InstrumentId,
257 ) -> PyResult<Option<Py<PyAny>>> {
258 match self.instrument(&instrument_id) {
259 Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument.clone())?)),
260 None => Ok(None),
261 }
262 }
263
264 #[pyo3(name = "instrument_ids")]
265 fn py_instrument_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
266 self.instrument_ids(venue.as_ref())
267 .into_iter()
268 .copied()
269 .collect()
270 }
271
272 #[pyo3(name = "instruments")]
273 fn py_instruments(&self, py: Python, venue: Option<Venue>) -> PyResult<Vec<Py<PyAny>>> {
274 let mut py_instruments = Vec::new();
275
276 match venue {
277 Some(venue) => {
278 let instruments = self.instruments(&venue, None);
279 for instrument in instruments {
280 py_instruments.push(instrument_any_to_pyobject(py, (*instrument).clone())?);
281 }
282 }
283 None => {
284 let instrument_ids = self.instrument_ids(None);
286 for instrument_id in instrument_ids {
287 if let Some(instrument) = self.instrument(instrument_id) {
288 py_instruments.push(instrument_any_to_pyobject(py, instrument.clone())?);
289 }
290 }
291 }
292 }
293
294 Ok(py_instruments)
295 }
296
297 #[pyo3(name = "add_order")]
298 fn py_add_order(
299 &mut self,
300 py: Python,
301 order: Py<PyAny>,
302 position_id: Option<PositionId>,
303 client_id: Option<ClientId>,
304 replace_existing: Option<bool>,
305 ) -> PyResult<()> {
306 let order_any = pyobject_to_order_any(py, order)?;
307 self.add_order(
308 order_any,
309 position_id,
310 client_id,
311 replace_existing.unwrap_or(false),
312 )
313 .map_err(to_pyvalue_err)
314 }
315
316 #[pyo3(name = "order")]
317 fn py_order(&self, py: Python, client_order_id: ClientOrderId) -> PyResult<Option<Py<PyAny>>> {
318 match self.order(&client_order_id) {
319 Some(order) => Ok(Some(order_any_to_pyobject(py, order.clone())?)),
320 None => Ok(None),
321 }
322 }
323
324 #[pyo3(name = "order_exists")]
325 fn py_order_exists(&self, client_order_id: ClientOrderId) -> bool {
326 self.order_exists(&client_order_id)
327 }
328
329 #[pyo3(name = "is_order_open")]
330 fn py_is_order_open(&self, client_order_id: ClientOrderId) -> bool {
331 self.is_order_open(&client_order_id)
332 }
333
334 #[pyo3(name = "is_order_closed")]
335 fn py_is_order_closed(&self, client_order_id: ClientOrderId) -> bool {
336 self.is_order_closed(&client_order_id)
337 }
338
339 #[pyo3(name = "orders_open_count")]
340 fn py_orders_open_count(
341 &self,
342 venue: Option<Venue>,
343 instrument_id: Option<InstrumentId>,
344 strategy_id: Option<StrategyId>,
345 account_id: Option<AccountId>,
346 side: Option<OrderSide>,
347 ) -> usize {
348 self.orders_open_count(
349 venue.as_ref(),
350 instrument_id.as_ref(),
351 strategy_id.as_ref(),
352 account_id.as_ref(),
353 side,
354 )
355 }
356
357 #[pyo3(name = "orders_closed_count")]
358 fn py_orders_closed_count(
359 &self,
360 venue: Option<Venue>,
361 instrument_id: Option<InstrumentId>,
362 strategy_id: Option<StrategyId>,
363 account_id: Option<AccountId>,
364 side: Option<OrderSide>,
365 ) -> usize {
366 self.orders_closed_count(
367 venue.as_ref(),
368 instrument_id.as_ref(),
369 strategy_id.as_ref(),
370 account_id.as_ref(),
371 side,
372 )
373 }
374
375 #[pyo3(name = "orders_total_count")]
376 fn py_orders_total_count(
377 &self,
378 venue: Option<Venue>,
379 instrument_id: Option<InstrumentId>,
380 strategy_id: Option<StrategyId>,
381 account_id: Option<AccountId>,
382 side: Option<OrderSide>,
383 ) -> usize {
384 self.orders_total_count(
385 venue.as_ref(),
386 instrument_id.as_ref(),
387 strategy_id.as_ref(),
388 account_id.as_ref(),
389 side,
390 )
391 }
392
393 #[pyo3(name = "add_position")]
394 fn py_add_position(
395 &mut self,
396 py: Python,
397 position: Py<PyAny>,
398 oms_type: OmsType,
399 ) -> PyResult<()> {
400 let position_obj = position.extract::<Position>(py)?;
401 self.add_position(position_obj, oms_type)
402 .map_err(to_pyvalue_err)
403 }
404
405 #[pyo3(name = "position")]
406 fn py_position(&self, py: Python, position_id: PositionId) -> PyResult<Option<Py<PyAny>>> {
407 match self.position(&position_id) {
408 Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
409 None => Ok(None),
410 }
411 }
412
413 #[pyo3(name = "position_exists")]
414 fn py_position_exists(&self, position_id: PositionId) -> bool {
415 self.position_exists(&position_id)
416 }
417
418 #[pyo3(name = "is_position_open")]
419 fn py_is_position_open(&self, position_id: PositionId) -> bool {
420 self.is_position_open(&position_id)
421 }
422
423 #[pyo3(name = "is_position_closed")]
424 fn py_is_position_closed(&self, position_id: PositionId) -> bool {
425 self.is_position_closed(&position_id)
426 }
427
428 #[pyo3(name = "positions_open_count")]
429 fn py_positions_open_count(
430 &self,
431 venue: Option<Venue>,
432 instrument_id: Option<InstrumentId>,
433 strategy_id: Option<StrategyId>,
434 account_id: Option<AccountId>,
435 side: Option<PositionSide>,
436 ) -> usize {
437 self.positions_open_count(
438 venue.as_ref(),
439 instrument_id.as_ref(),
440 strategy_id.as_ref(),
441 account_id.as_ref(),
442 side,
443 )
444 }
445
446 #[pyo3(name = "positions_closed_count")]
447 fn py_positions_closed_count(
448 &self,
449 venue: Option<Venue>,
450 instrument_id: Option<InstrumentId>,
451 strategy_id: Option<StrategyId>,
452 account_id: Option<AccountId>,
453 side: Option<PositionSide>,
454 ) -> usize {
455 self.positions_closed_count(
456 venue.as_ref(),
457 instrument_id.as_ref(),
458 strategy_id.as_ref(),
459 account_id.as_ref(),
460 side,
461 )
462 }
463
464 #[pyo3(name = "positions_total_count")]
465 fn py_positions_total_count(
466 &self,
467 venue: Option<Venue>,
468 instrument_id: Option<InstrumentId>,
469 strategy_id: Option<StrategyId>,
470 account_id: Option<AccountId>,
471 side: Option<PositionSide>,
472 ) -> usize {
473 self.positions_total_count(
474 venue.as_ref(),
475 instrument_id.as_ref(),
476 strategy_id.as_ref(),
477 account_id.as_ref(),
478 side,
479 )
480 }
481
482 #[pyo3(name = "add_quote")]
483 fn py_add_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
484 self.add_quote(quote).map_err(to_pyvalue_err)
485 }
486
487 #[pyo3(name = "add_trade")]
488 fn py_add_trade(&mut self, trade: TradeTick) -> PyResult<()> {
489 self.add_trade(trade).map_err(to_pyvalue_err)
490 }
491
492 #[pyo3(name = "add_bar")]
493 fn py_add_bar(&mut self, bar: Bar) -> PyResult<()> {
494 self.add_bar(bar).map_err(to_pyvalue_err)
495 }
496
497 #[pyo3(name = "quote")]
498 fn py_quote(&self, instrument_id: InstrumentId) -> Option<QuoteTick> {
499 self.quote(&instrument_id).copied()
500 }
501
502 #[pyo3(name = "trade")]
503 fn py_trade(&self, instrument_id: InstrumentId) -> Option<TradeTick> {
504 self.trade(&instrument_id).copied()
505 }
506
507 #[pyo3(name = "bar")]
508 fn py_bar(&self, bar_type: BarType) -> Option<Bar> {
509 self.bar(&bar_type).copied()
510 }
511
512 #[pyo3(name = "quotes")]
513 fn py_quotes(&self, instrument_id: InstrumentId) -> Option<Vec<QuoteTick>> {
514 self.quotes(&instrument_id)
515 }
516
517 #[pyo3(name = "trades")]
518 fn py_trades(&self, instrument_id: InstrumentId) -> Option<Vec<TradeTick>> {
519 self.trades(&instrument_id)
520 }
521
522 #[pyo3(name = "bars")]
523 fn py_bars(&self, bar_type: BarType) -> Option<Vec<Bar>> {
524 self.bars(&bar_type)
525 }
526
527 #[pyo3(name = "has_quote_ticks")]
528 fn py_has_quote_ticks(&self, instrument_id: InstrumentId) -> bool {
529 self.has_quote_ticks(&instrument_id)
530 }
531
532 #[pyo3(name = "has_trade_ticks")]
533 fn py_has_trade_ticks(&self, instrument_id: InstrumentId) -> bool {
534 self.has_trade_ticks(&instrument_id)
535 }
536
537 #[pyo3(name = "has_bars")]
538 fn py_has_bars(&self, bar_type: BarType) -> bool {
539 self.has_bars(&bar_type)
540 }
541
542 #[pyo3(name = "quote_count")]
543 fn py_quote_count(&self, instrument_id: InstrumentId) -> usize {
544 self.quote_count(&instrument_id)
545 }
546
547 #[pyo3(name = "trade_count")]
548 fn py_trade_count(&self, instrument_id: InstrumentId) -> usize {
549 self.trade_count(&instrument_id)
550 }
551
552 #[pyo3(name = "bar_count")]
553 fn py_bar_count(&self, bar_type: BarType) -> usize {
554 self.bar_count(&bar_type)
555 }
556
557 #[pyo3(name = "mark_price")]
558 fn py_mark_price(&self, instrument_id: InstrumentId) -> Option<MarkPriceUpdate> {
559 self.mark_price(&instrument_id).copied()
560 }
561
562 #[pyo3(name = "mark_prices")]
563 fn py_mark_prices(&self, instrument_id: InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
564 self.mark_prices(&instrument_id)
565 }
566
567 #[pyo3(name = "index_price")]
568 fn py_index_price(&self, instrument_id: InstrumentId) -> Option<IndexPriceUpdate> {
569 self.index_price(&instrument_id).copied()
570 }
571
572 #[pyo3(name = "index_prices")]
573 fn py_index_prices(&self, instrument_id: InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
574 self.index_prices(&instrument_id)
575 }
576
577 #[pyo3(name = "funding_rate")]
578 fn py_funding_rate(&self, instrument_id: InstrumentId) -> Option<FundingRateUpdate> {
579 self.funding_rate(&instrument_id).copied()
580 }
581
582 #[pyo3(name = "order_book")]
583 fn py_order_book(&self, instrument_id: InstrumentId) -> Option<OrderBook> {
584 self.order_book(&instrument_id).cloned()
585 }
586
587 #[pyo3(name = "has_order_book")]
588 fn py_has_order_book(&self, instrument_id: InstrumentId) -> bool {
589 self.has_order_book(&instrument_id)
590 }
591
592 #[pyo3(name = "book_update_count")]
593 fn py_book_update_count(&self, instrument_id: InstrumentId) -> usize {
594 self.book_update_count(&instrument_id)
595 }
596
597 #[pyo3(name = "synthetic")]
598 fn py_synthetic(&self, instrument_id: InstrumentId) -> Option<SyntheticInstrument> {
599 self.synthetic(&instrument_id).cloned()
600 }
601
602 #[pyo3(name = "synthetic_ids")]
603 fn py_synthetic_ids(&self) -> Vec<InstrumentId> {
604 self.synthetic_ids().into_iter().copied().collect()
605 }
606
607 #[cfg(feature = "defi")]
608 #[pyo3(name = "add_pool")]
609 fn py_add_pool(&mut self, pool: Pool) -> PyResult<()> {
610 self.add_pool(pool).map_err(to_pyvalue_err)
611 }
612
613 #[cfg(feature = "defi")]
614 #[pyo3(name = "pool")]
615 fn py_pool(&self, instrument_id: InstrumentId) -> Option<Pool> {
616 self.pool(&instrument_id).cloned()
617 }
618
619 #[cfg(feature = "defi")]
620 #[pyo3(name = "pool_ids")]
621 fn py_pool_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
622 self.pool_ids(venue.as_ref())
623 }
624
625 #[cfg(feature = "defi")]
626 #[pyo3(name = "pools")]
627 fn py_pools(&self, venue: Option<Venue>) -> Vec<Pool> {
628 self.pools(venue.as_ref()).into_iter().cloned().collect()
629 }
630
631 #[cfg(feature = "defi")]
632 #[pyo3(name = "add_pool_profiler")]
633 fn py_add_pool_profiler(&mut self, pool_profiler: PoolProfiler) -> PyResult<()> {
634 self.add_pool_profiler(pool_profiler)
635 .map_err(to_pyvalue_err)
636 }
637
638 #[cfg(feature = "defi")]
639 #[pyo3(name = "pool_profiler")]
640 fn py_pool_profiler(&self, instrument_id: InstrumentId) -> Option<PoolProfiler> {
641 self.pool_profiler(&instrument_id).cloned()
642 }
643
644 #[cfg(feature = "defi")]
645 #[pyo3(name = "pool_profiler_ids")]
646 fn py_pool_profiler_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
647 self.pool_profiler_ids(venue.as_ref())
648 }
649
650 #[cfg(feature = "defi")]
651 #[pyo3(name = "pool_profilers")]
652 fn py_pool_profilers(&self, venue: Option<Venue>) -> Vec<PoolProfiler> {
653 self.pool_profilers(venue.as_ref())
654 .into_iter()
655 .cloned()
656 .collect()
657 }
658}