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