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