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