1use std::{
19 any::Any,
20 cell::{RefCell, UnsafeCell},
21 fmt::Debug,
22 num::NonZeroUsize,
23 ops::{Deref, DerefMut},
24 rc::Rc,
25};
26
27use indexmap::IndexMap;
28use nautilus_common::{
29 actor::{
30 Actor, DataActor,
31 data_actor::DataActorCore,
32 registry::{get_actor_registry, try_get_actor_unchecked},
33 },
34 cache::Cache,
35 clock::Clock,
36 component::{Component, get_component_registry},
37 enums::ComponentState,
38 python::{cache::PyCache, clock::PyClock, logging::PyLogger},
39 signal::Signal,
40 timer::{TimeEvent, TimeEventCallback},
41};
42use nautilus_core::{
43 nanos::UnixNanos,
44 python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
45};
46use nautilus_model::{
47 data::{
48 Bar, BarType, DataType, FundingRateUpdate, IndexPriceUpdate, InstrumentStatus,
49 MarkPriceUpdate, OrderBookDeltas, QuoteTick, TradeTick, close::InstrumentClose,
50 },
51 enums::{BookType, OmsType, OrderSide, PositionSide, TimeInForce},
52 events::{
53 OrderAccepted, OrderCancelRejected, OrderDenied, OrderEmulated, OrderExpired,
54 OrderInitialized, OrderModifyRejected, OrderPendingCancel, OrderPendingUpdate,
55 OrderRejected, OrderReleased, OrderSubmitted, OrderTriggered, OrderUpdated,
56 PositionChanged, PositionClosed, PositionOpened,
57 },
58 identifiers::{AccountId, ClientId, InstrumentId, PositionId, StrategyId, TraderId, Venue},
59 instruments::InstrumentAny,
60 orderbook::OrderBook,
61 orders::OrderAny,
62 position::Position,
63 python::{instruments::instrument_any_to_pyobject, orders::pyobject_to_order_any},
64 types::{Price, Quantity},
65};
66use nautilus_portfolio::portfolio::Portfolio;
67use pyo3::prelude::*;
68use ustr::Ustr;
69
70use crate::strategy::{Strategy, StrategyConfig, StrategyCore};
71
72#[pyo3::pymethods]
73impl StrategyConfig {
74 #[new]
75 #[pyo3(signature = (
76 strategy_id=None,
77 order_id_tag=None,
78 oms_type=None,
79 external_order_claims=None,
80 manage_contingent_orders=false,
81 manage_gtd_expiry=false,
82 manage_stop=false,
83 market_exit_interval_ms=100,
84 market_exit_max_attempts=100,
85 market_exit_time_in_force=TimeInForce::Gtc,
86 market_exit_reduce_only=true,
87 use_uuid_client_order_ids=false,
88 use_hyphens_in_client_order_ids=true,
89 log_events=true,
90 log_commands=true,
91 log_rejected_due_post_only_as_warning=true
92 ))]
93 #[allow(clippy::too_many_arguments)]
94 fn py_new(
95 strategy_id: Option<StrategyId>,
96 order_id_tag: Option<String>,
97 oms_type: Option<OmsType>,
98 external_order_claims: Option<Vec<InstrumentId>>,
99 manage_contingent_orders: bool,
100 manage_gtd_expiry: bool,
101 manage_stop: bool,
102 market_exit_interval_ms: u64,
103 market_exit_max_attempts: u64,
104 market_exit_time_in_force: TimeInForce,
105 market_exit_reduce_only: bool,
106 use_uuid_client_order_ids: bool,
107 use_hyphens_in_client_order_ids: bool,
108 log_events: bool,
109 log_commands: bool,
110 log_rejected_due_post_only_as_warning: bool,
111 ) -> Self {
112 Self {
113 strategy_id,
114 order_id_tag,
115 use_uuid_client_order_ids,
116 use_hyphens_in_client_order_ids,
117 oms_type,
118 external_order_claims,
119 manage_contingent_orders,
120 manage_gtd_expiry,
121 manage_stop,
122 market_exit_interval_ms,
123 market_exit_max_attempts,
124 market_exit_time_in_force,
125 market_exit_reduce_only,
126 log_events,
127 log_commands,
128 log_rejected_due_post_only_as_warning,
129 }
130 }
131
132 #[getter]
133 fn strategy_id(&self) -> Option<StrategyId> {
134 self.strategy_id
135 }
136
137 #[getter]
138 fn order_id_tag(&self) -> Option<&String> {
139 self.order_id_tag.as_ref()
140 }
141
142 #[getter]
143 fn oms_type(&self) -> Option<OmsType> {
144 self.oms_type
145 }
146
147 #[getter]
148 fn manage_contingent_orders(&self) -> bool {
149 self.manage_contingent_orders
150 }
151
152 #[getter]
153 fn manage_gtd_expiry(&self) -> bool {
154 self.manage_gtd_expiry
155 }
156
157 #[getter]
158 fn use_uuid_client_order_ids(&self) -> bool {
159 self.use_uuid_client_order_ids
160 }
161
162 #[getter]
163 fn use_hyphens_in_client_order_ids(&self) -> bool {
164 self.use_hyphens_in_client_order_ids
165 }
166
167 #[getter]
168 fn log_events(&self) -> bool {
169 self.log_events
170 }
171
172 #[getter]
173 fn log_commands(&self) -> bool {
174 self.log_commands
175 }
176
177 #[getter]
178 fn log_rejected_due_post_only_as_warning(&self) -> bool {
179 self.log_rejected_due_post_only_as_warning
180 }
181}
182
183pub struct PyStrategyInner {
185 core: StrategyCore,
186 py_self: Option<Py<PyAny>>,
187 clock: PyClock,
188 logger: PyLogger,
189}
190
191impl Debug for PyStrategyInner {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.debug_struct(stringify!(PyStrategyInner))
194 .field("core", &self.core)
195 .field("py_self", &self.py_self.as_ref().map(|_| "<Py<PyAny>>"))
196 .field("clock", &self.clock)
197 .field("logger", &self.logger)
198 .finish()
199 }
200}
201
202impl PyStrategyInner {
203 fn dispatch_on_start(&self) -> PyResult<()> {
204 if let Some(ref py_self) = self.py_self {
205 Python::attach(|py| py_self.call_method0(py, "on_start"))?;
206 }
207 Ok(())
208 }
209
210 fn dispatch_on_stop(&self) -> PyResult<()> {
211 if let Some(ref py_self) = self.py_self {
212 Python::attach(|py| py_self.call_method0(py, "on_stop"))?;
213 }
214 Ok(())
215 }
216
217 fn dispatch_on_resume(&self) -> PyResult<()> {
218 if let Some(ref py_self) = self.py_self {
219 Python::attach(|py| py_self.call_method0(py, "on_resume"))?;
220 }
221 Ok(())
222 }
223
224 fn dispatch_on_reset(&self) -> PyResult<()> {
225 if let Some(ref py_self) = self.py_self {
226 Python::attach(|py| py_self.call_method0(py, "on_reset"))?;
227 }
228 Ok(())
229 }
230
231 fn dispatch_on_dispose(&self) -> PyResult<()> {
232 if let Some(ref py_self) = self.py_self {
233 Python::attach(|py| py_self.call_method0(py, "on_dispose"))?;
234 }
235 Ok(())
236 }
237
238 fn dispatch_on_degrade(&self) -> PyResult<()> {
239 if let Some(ref py_self) = self.py_self {
240 Python::attach(|py| py_self.call_method0(py, "on_degrade"))?;
241 }
242 Ok(())
243 }
244
245 fn dispatch_on_fault(&self) -> PyResult<()> {
246 if let Some(ref py_self) = self.py_self {
247 Python::attach(|py| py_self.call_method0(py, "on_fault"))?;
248 }
249 Ok(())
250 }
251
252 fn dispatch_on_time_event(&self, event: &TimeEvent) -> PyResult<()> {
253 if let Some(ref py_self) = self.py_self {
254 Python::attach(|py| {
255 py_self.call_method1(py, "on_time_event", (event.clone().into_py_any_unwrap(py),))
256 })?;
257 }
258 Ok(())
259 }
260
261 fn dispatch_on_order_initialized(&self, event: OrderInitialized) -> PyResult<()> {
262 if let Some(ref py_self) = self.py_self {
263 Python::attach(|py| {
264 py_self.call_method1(py, "on_order_initialized", (event.into_py_any_unwrap(py),))
265 })?;
266 }
267 Ok(())
268 }
269
270 fn dispatch_on_order_denied(&self, event: OrderDenied) -> PyResult<()> {
271 if let Some(ref py_self) = self.py_self {
272 Python::attach(|py| {
273 py_self.call_method1(py, "on_order_denied", (event.into_py_any_unwrap(py),))
274 })?;
275 }
276 Ok(())
277 }
278
279 fn dispatch_on_order_emulated(&self, event: OrderEmulated) -> PyResult<()> {
280 if let Some(ref py_self) = self.py_self {
281 Python::attach(|py| {
282 py_self.call_method1(py, "on_order_emulated", (event.into_py_any_unwrap(py),))
283 })?;
284 }
285 Ok(())
286 }
287
288 fn dispatch_on_order_released(&self, event: OrderReleased) -> PyResult<()> {
289 if let Some(ref py_self) = self.py_self {
290 Python::attach(|py| {
291 py_self.call_method1(py, "on_order_released", (event.into_py_any_unwrap(py),))
292 })?;
293 }
294 Ok(())
295 }
296
297 fn dispatch_on_order_submitted(&self, event: OrderSubmitted) -> PyResult<()> {
298 if let Some(ref py_self) = self.py_self {
299 Python::attach(|py| {
300 py_self.call_method1(py, "on_order_submitted", (event.into_py_any_unwrap(py),))
301 })?;
302 }
303 Ok(())
304 }
305
306 fn dispatch_on_order_rejected(&self, event: OrderRejected) -> PyResult<()> {
307 if let Some(ref py_self) = self.py_self {
308 Python::attach(|py| {
309 py_self.call_method1(py, "on_order_rejected", (event.into_py_any_unwrap(py),))
310 })?;
311 }
312 Ok(())
313 }
314
315 fn dispatch_on_order_accepted(&self, event: OrderAccepted) -> PyResult<()> {
316 if let Some(ref py_self) = self.py_self {
317 Python::attach(|py| {
318 py_self.call_method1(py, "on_order_accepted", (event.into_py_any_unwrap(py),))
319 })?;
320 }
321 Ok(())
322 }
323
324 fn dispatch_on_order_expired(&self, event: OrderExpired) -> PyResult<()> {
325 if let Some(ref py_self) = self.py_self {
326 Python::attach(|py| {
327 py_self.call_method1(py, "on_order_expired", (event.into_py_any_unwrap(py),))
328 })?;
329 }
330 Ok(())
331 }
332
333 fn dispatch_on_order_triggered(&self, event: OrderTriggered) -> PyResult<()> {
334 if let Some(ref py_self) = self.py_self {
335 Python::attach(|py| {
336 py_self.call_method1(py, "on_order_triggered", (event.into_py_any_unwrap(py),))
337 })?;
338 }
339 Ok(())
340 }
341
342 fn dispatch_on_order_pending_update(&self, event: OrderPendingUpdate) -> PyResult<()> {
343 if let Some(ref py_self) = self.py_self {
344 Python::attach(|py| {
345 py_self.call_method1(
346 py,
347 "on_order_pending_update",
348 (event.into_py_any_unwrap(py),),
349 )
350 })?;
351 }
352 Ok(())
353 }
354
355 fn dispatch_on_order_pending_cancel(&self, event: OrderPendingCancel) -> PyResult<()> {
356 if let Some(ref py_self) = self.py_self {
357 Python::attach(|py| {
358 py_self.call_method1(
359 py,
360 "on_order_pending_cancel",
361 (event.into_py_any_unwrap(py),),
362 )
363 })?;
364 }
365 Ok(())
366 }
367
368 fn dispatch_on_order_modify_rejected(&self, event: OrderModifyRejected) -> PyResult<()> {
369 if let Some(ref py_self) = self.py_self {
370 Python::attach(|py| {
371 py_self.call_method1(
372 py,
373 "on_order_modify_rejected",
374 (event.into_py_any_unwrap(py),),
375 )
376 })?;
377 }
378 Ok(())
379 }
380
381 fn dispatch_on_order_cancel_rejected(&self, event: OrderCancelRejected) -> PyResult<()> {
382 if let Some(ref py_self) = self.py_self {
383 Python::attach(|py| {
384 py_self.call_method1(
385 py,
386 "on_order_cancel_rejected",
387 (event.into_py_any_unwrap(py),),
388 )
389 })?;
390 }
391 Ok(())
392 }
393
394 fn dispatch_on_order_updated(&self, event: OrderUpdated) -> PyResult<()> {
395 if let Some(ref py_self) = self.py_self {
396 Python::attach(|py| {
397 py_self.call_method1(py, "on_order_updated", (event.into_py_any_unwrap(py),))
398 })?;
399 }
400 Ok(())
401 }
402
403 fn dispatch_on_position_opened(&self, _event: PositionOpened) -> PyResult<()> {
405 Ok(())
406 }
407
408 fn dispatch_on_position_changed(&self, _event: PositionChanged) -> PyResult<()> {
409 Ok(())
410 }
411
412 fn dispatch_on_position_closed(&self, _event: PositionClosed) -> PyResult<()> {
413 Ok(())
414 }
415
416 fn dispatch_on_data(&mut self, data: Py<PyAny>) -> PyResult<()> {
417 if let Some(ref py_self) = self.py_self {
418 Python::attach(|py| py_self.call_method1(py, "on_data", (data,)))?;
419 }
420 Ok(())
421 }
422
423 fn dispatch_on_signal(&mut self, signal: &Signal) -> PyResult<()> {
424 if let Some(ref py_self) = self.py_self {
425 Python::attach(|py| {
426 py_self.call_method1(py, "on_signal", (signal.clone().into_py_any_unwrap(py),))
427 })?;
428 }
429 Ok(())
430 }
431
432 fn dispatch_on_instrument(&mut self, instrument: Py<PyAny>) -> PyResult<()> {
433 if let Some(ref py_self) = self.py_self {
434 Python::attach(|py| py_self.call_method1(py, "on_instrument", (instrument,)))?;
435 }
436 Ok(())
437 }
438
439 fn dispatch_on_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
440 if let Some(ref py_self) = self.py_self {
441 Python::attach(|py| {
442 py_self.call_method1(py, "on_quote", (quote.into_py_any_unwrap(py),))
443 })?;
444 }
445 Ok(())
446 }
447
448 fn dispatch_on_trade(&mut self, trade: TradeTick) -> PyResult<()> {
449 if let Some(ref py_self) = self.py_self {
450 Python::attach(|py| {
451 py_self.call_method1(py, "on_trade", (trade.into_py_any_unwrap(py),))
452 })?;
453 }
454 Ok(())
455 }
456
457 fn dispatch_on_bar(&mut self, bar: Bar) -> PyResult<()> {
458 if let Some(ref py_self) = self.py_self {
459 Python::attach(|py| py_self.call_method1(py, "on_bar", (bar.into_py_any_unwrap(py),)))?;
460 }
461 Ok(())
462 }
463
464 fn dispatch_on_book_deltas(&mut self, deltas: OrderBookDeltas) -> PyResult<()> {
465 if let Some(ref py_self) = self.py_self {
466 Python::attach(|py| {
467 py_self.call_method1(py, "on_book_deltas", (deltas.into_py_any_unwrap(py),))
468 })?;
469 }
470 Ok(())
471 }
472
473 fn dispatch_on_book(&mut self, book: &OrderBook) -> PyResult<()> {
474 if let Some(ref py_self) = self.py_self {
475 Python::attach(|py| {
476 py_self.call_method1(py, "on_book", (book.clone().into_py_any_unwrap(py),))
477 })?;
478 }
479 Ok(())
480 }
481
482 fn dispatch_on_mark_price(&mut self, mark_price: MarkPriceUpdate) -> PyResult<()> {
483 if let Some(ref py_self) = self.py_self {
484 Python::attach(|py| {
485 py_self.call_method1(py, "on_mark_price", (mark_price.into_py_any_unwrap(py),))
486 })?;
487 }
488 Ok(())
489 }
490
491 fn dispatch_on_index_price(&mut self, index_price: IndexPriceUpdate) -> PyResult<()> {
492 if let Some(ref py_self) = self.py_self {
493 Python::attach(|py| {
494 py_self.call_method1(py, "on_index_price", (index_price.into_py_any_unwrap(py),))
495 })?;
496 }
497 Ok(())
498 }
499
500 fn dispatch_on_funding_rate(&mut self, funding_rate: FundingRateUpdate) -> PyResult<()> {
501 if let Some(ref py_self) = self.py_self {
502 Python::attach(|py| {
503 py_self.call_method1(
504 py,
505 "on_funding_rate",
506 (funding_rate.into_py_any_unwrap(py),),
507 )
508 })?;
509 }
510 Ok(())
511 }
512
513 fn dispatch_on_instrument_status(&mut self, data: InstrumentStatus) -> PyResult<()> {
514 if let Some(ref py_self) = self.py_self {
515 Python::attach(|py| {
516 py_self.call_method1(py, "on_instrument_status", (data.into_py_any_unwrap(py),))
517 })?;
518 }
519 Ok(())
520 }
521
522 fn dispatch_on_instrument_close(&mut self, update: InstrumentClose) -> PyResult<()> {
523 if let Some(ref py_self) = self.py_self {
524 Python::attach(|py| {
525 py_self.call_method1(py, "on_instrument_close", (update.into_py_any_unwrap(py),))
526 })?;
527 }
528 Ok(())
529 }
530}
531
532impl Deref for PyStrategyInner {
533 type Target = DataActorCore;
534
535 fn deref(&self) -> &Self::Target {
536 &self.core
537 }
538}
539
540impl DerefMut for PyStrategyInner {
541 fn deref_mut(&mut self) -> &mut Self::Target {
542 &mut self.core
543 }
544}
545
546impl Strategy for PyStrategyInner {
547 fn core(&self) -> &StrategyCore {
548 &self.core
549 }
550
551 fn core_mut(&mut self) -> &mut StrategyCore {
552 &mut self.core
553 }
554
555 fn on_order_initialized(&mut self, event: OrderInitialized) {
556 let _ = self.dispatch_on_order_initialized(event);
557 }
558
559 fn on_order_denied(&mut self, event: OrderDenied) {
560 let _ = self.dispatch_on_order_denied(event);
561 }
562
563 fn on_order_emulated(&mut self, event: OrderEmulated) {
564 let _ = self.dispatch_on_order_emulated(event);
565 }
566
567 fn on_order_released(&mut self, event: OrderReleased) {
568 let _ = self.dispatch_on_order_released(event);
569 }
570
571 fn on_order_submitted(&mut self, event: OrderSubmitted) {
572 let _ = self.dispatch_on_order_submitted(event);
573 }
574
575 fn on_order_rejected(&mut self, event: OrderRejected) {
576 let _ = self.dispatch_on_order_rejected(event);
577 }
578
579 fn on_order_accepted(&mut self, event: OrderAccepted) {
580 let _ = self.dispatch_on_order_accepted(event);
581 }
582
583 fn on_order_expired(&mut self, event: OrderExpired) {
584 let _ = self.dispatch_on_order_expired(event);
585 }
586
587 fn on_order_triggered(&mut self, event: OrderTriggered) {
588 let _ = self.dispatch_on_order_triggered(event);
589 }
590
591 fn on_order_pending_update(&mut self, event: OrderPendingUpdate) {
592 let _ = self.dispatch_on_order_pending_update(event);
593 }
594
595 fn on_order_pending_cancel(&mut self, event: OrderPendingCancel) {
596 let _ = self.dispatch_on_order_pending_cancel(event);
597 }
598
599 fn on_order_modify_rejected(&mut self, event: OrderModifyRejected) {
600 let _ = self.dispatch_on_order_modify_rejected(event);
601 }
602
603 fn on_order_cancel_rejected(&mut self, event: OrderCancelRejected) {
604 let _ = self.dispatch_on_order_cancel_rejected(event);
605 }
606
607 fn on_order_updated(&mut self, event: OrderUpdated) {
608 let _ = self.dispatch_on_order_updated(event);
609 }
610
611 fn on_position_opened(&mut self, event: PositionOpened) {
612 let _ = self.dispatch_on_position_opened(event);
613 }
614
615 fn on_position_changed(&mut self, event: PositionChanged) {
616 let _ = self.dispatch_on_position_changed(event);
617 }
618
619 fn on_position_closed(&mut self, event: PositionClosed) {
620 let _ = self.dispatch_on_position_closed(event);
621 }
622}
623
624impl DataActor for PyStrategyInner {
625 fn on_start(&mut self) -> anyhow::Result<()> {
626 Strategy::on_start(self)?;
627 self.dispatch_on_start()
628 .map_err(|e| anyhow::anyhow!("Python on_start failed: {e}"))
629 }
630
631 fn on_stop(&mut self) -> anyhow::Result<()> {
632 self.dispatch_on_stop()
633 .map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
634 }
635
636 fn on_resume(&mut self) -> anyhow::Result<()> {
637 self.dispatch_on_resume()
638 .map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
639 }
640
641 fn on_reset(&mut self) -> anyhow::Result<()> {
642 self.dispatch_on_reset()
643 .map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
644 }
645
646 fn on_dispose(&mut self) -> anyhow::Result<()> {
647 self.dispatch_on_dispose()
648 .map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
649 }
650
651 fn on_degrade(&mut self) -> anyhow::Result<()> {
652 self.dispatch_on_degrade()
653 .map_err(|e| anyhow::anyhow!("Python on_degrade failed: {e}"))
654 }
655
656 fn on_fault(&mut self) -> anyhow::Result<()> {
657 self.dispatch_on_fault()
658 .map_err(|e| anyhow::anyhow!("Python on_fault failed: {e}"))
659 }
660
661 fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
662 Strategy::on_time_event(self, event)?;
663 self.dispatch_on_time_event(event)
664 .map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
665 }
666
667 #[allow(unused_variables)]
668 fn on_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
669 Python::attach(|py| {
670 let py_data = py.None();
671 self.dispatch_on_data(py_data)
672 .map_err(|e| anyhow::anyhow!("Python on_data failed: {e}"))
673 })
674 }
675
676 fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
677 self.dispatch_on_signal(signal)
678 .map_err(|e| anyhow::anyhow!("Python on_signal failed: {e}"))
679 }
680
681 fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
682 Python::attach(|py| {
683 let py_instrument = instrument_any_to_pyobject(py, instrument.clone())
684 .map_err(|e| anyhow::anyhow!("Failed to convert InstrumentAny to Python: {e}"))?;
685 self.dispatch_on_instrument(py_instrument)
686 .map_err(|e| anyhow::anyhow!("Python on_instrument failed: {e}"))
687 })
688 }
689
690 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
691 self.dispatch_on_quote(*quote)
692 .map_err(|e| anyhow::anyhow!("Python on_quote failed: {e}"))
693 }
694
695 fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
696 self.dispatch_on_trade(*tick)
697 .map_err(|e| anyhow::anyhow!("Python on_trade failed: {e}"))
698 }
699
700 fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
701 self.dispatch_on_bar(*bar)
702 .map_err(|e| anyhow::anyhow!("Python on_bar failed: {e}"))
703 }
704
705 fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
706 self.dispatch_on_book_deltas(deltas.clone())
707 .map_err(|e| anyhow::anyhow!("Python on_book_deltas failed: {e}"))
708 }
709
710 fn on_book(&mut self, order_book: &OrderBook) -> anyhow::Result<()> {
711 self.dispatch_on_book(order_book)
712 .map_err(|e| anyhow::anyhow!("Python on_book failed: {e}"))
713 }
714
715 fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
716 self.dispatch_on_mark_price(*mark_price)
717 .map_err(|e| anyhow::anyhow!("Python on_mark_price failed: {e}"))
718 }
719
720 fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
721 self.dispatch_on_index_price(*index_price)
722 .map_err(|e| anyhow::anyhow!("Python on_index_price failed: {e}"))
723 }
724
725 fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
726 self.dispatch_on_funding_rate(*funding_rate)
727 .map_err(|e| anyhow::anyhow!("Python on_funding_rate failed: {e}"))
728 }
729
730 fn on_instrument_status(&mut self, data: &InstrumentStatus) -> anyhow::Result<()> {
731 self.dispatch_on_instrument_status(*data)
732 .map_err(|e| anyhow::anyhow!("Python on_instrument_status failed: {e}"))
733 }
734
735 fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
736 self.dispatch_on_instrument_close(*update)
737 .map_err(|e| anyhow::anyhow!("Python on_instrument_close failed: {e}"))
738 }
739}
740
741#[allow(non_camel_case_types)]
743#[pyo3::pyclass(
744 module = "nautilus_trader.trading",
745 name = "Strategy",
746 unsendable,
747 subclass
748)]
749pub struct PyStrategy {
750 inner: Rc<UnsafeCell<PyStrategyInner>>,
751}
752
753impl Debug for PyStrategy {
754 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
755 f.debug_struct(stringify!(PyStrategy))
756 .field("inner", &self.inner())
757 .finish()
758 }
759}
760
761impl PyStrategy {
762 #[inline]
763 #[allow(unsafe_code)]
764 pub(crate) fn inner(&self) -> &PyStrategyInner {
765 unsafe { &*self.inner.get() }
766 }
767
768 #[inline]
769 #[allow(unsafe_code, clippy::mut_from_ref)]
770 pub(crate) fn inner_mut(&self) -> &mut PyStrategyInner {
771 unsafe { &mut *self.inner.get() }
772 }
773}
774
775impl PyStrategy {
776 pub fn new(config: Option<StrategyConfig>) -> Self {
778 let config = config.unwrap_or_default();
779 let core = StrategyCore::new(config);
780 let clock = PyClock::new_test();
781 let logger = PyLogger::new(core.actor.actor_id.as_str());
782
783 let inner = PyStrategyInner {
784 core,
785 py_self: None,
786 clock,
787 logger,
788 };
789
790 Self {
791 inner: Rc::new(UnsafeCell::new(inner)),
792 }
793 }
794
795 pub fn set_python_instance(&mut self, py_obj: Py<PyAny>) {
797 self.inner_mut().py_self = Some(py_obj);
798 }
799
800 pub fn is_registered(&self) -> bool {
802 self.inner().core.actor.is_registered()
803 }
804
805 pub fn register(
811 &mut self,
812 trader_id: TraderId,
813 clock: Rc<RefCell<dyn Clock>>,
814 cache: Rc<RefCell<Cache>>,
815 portfolio: Rc<RefCell<Portfolio>>,
816 ) -> anyhow::Result<()> {
817 let inner = self.inner_mut();
818 inner.core.register(trader_id, clock, cache, portfolio)?;
819
820 inner.clock = PyClock::from_rc(inner.core.actor.clock_rc());
821
822 let actor_id = inner.core.actor.actor_id.inner();
823 let callback = TimeEventCallback::from(move |event: TimeEvent| {
824 if let Some(mut strategy) = try_get_actor_unchecked::<PyStrategyInner>(&actor_id) {
825 if let Err(e) = DataActor::on_time_event(&mut *strategy, &event) {
826 log::error!("Python time event handler failed for strategy {actor_id}: {e}");
827 }
828 } else {
829 log::error!("Strategy {actor_id} not found for time event handling");
830 }
831 });
832
833 inner.clock.inner_mut().register_default_handler(callback);
834
835 Component::initialize(inner)
836 }
837
838 pub fn register_in_global_registries(&self) {
840 let inner = self.inner();
841 let component_id = Component::component_id(inner).inner();
842 let actor_id = Actor::id(inner);
843
844 let inner_ref: Rc<UnsafeCell<PyStrategyInner>> = self.inner.clone();
845
846 let component_trait_ref: Rc<UnsafeCell<dyn Component>> = inner_ref.clone();
847 get_component_registry().insert(component_id, component_trait_ref);
848
849 let actor_trait_ref: Rc<UnsafeCell<dyn Actor>> = inner_ref;
850 get_actor_registry().insert(actor_id, actor_trait_ref);
851 }
852}
853
854#[pyo3::pymethods]
855impl PyStrategy {
856 #[new]
857 #[pyo3(signature = (config=None))]
858 fn py_new(config: Option<StrategyConfig>) -> Self {
859 Self::new(config)
860 }
861
862 #[pyo3(signature = (config=None))]
864 #[allow(unused_variables)]
865 fn __init__(slf: &Bound<'_, Self>, config: Option<StrategyConfig>) {
866 let py_self: Py<PyAny> = slf.clone().unbind().into_any();
867 slf.borrow_mut().set_python_instance(py_self);
868 }
869
870 #[getter]
871 #[pyo3(name = "trader_id")]
872 fn py_trader_id(&self) -> Option<TraderId> {
873 self.inner().core.trader_id()
874 }
875
876 #[getter]
877 #[pyo3(name = "strategy_id")]
878 fn py_strategy_id(&self) -> StrategyId {
879 StrategyId::from(self.inner().core.actor.actor_id.inner().as_str())
880 }
881
882 #[getter]
883 #[pyo3(name = "clock")]
884 fn py_clock(&self) -> PyResult<PyClock> {
885 let inner = self.inner();
886 if inner.core.actor.is_registered() {
887 Ok(inner.clock.clone())
888 } else {
889 Err(to_pyruntime_err(
890 "Strategy must be registered with a trader before accessing clock",
891 ))
892 }
893 }
894
895 #[getter]
896 #[pyo3(name = "cache")]
897 fn py_cache(&self) -> PyResult<PyCache> {
898 let inner = self.inner();
899 if inner.core.actor.is_registered() {
900 Ok(PyCache::from_rc(inner.core.actor.cache_rc()))
901 } else {
902 Err(to_pyruntime_err(
903 "Strategy must be registered with a trader before accessing cache",
904 ))
905 }
906 }
907
908 #[getter]
909 #[pyo3(name = "log")]
910 fn py_log(&self) -> PyLogger {
911 self.inner().logger.clone()
912 }
913
914 #[pyo3(name = "state")]
915 fn py_state(&self) -> ComponentState {
916 self.inner().core.actor.state()
917 }
918
919 #[pyo3(name = "is_ready")]
920 fn py_is_ready(&self) -> bool {
921 Component::is_ready(self.inner())
922 }
923
924 #[pyo3(name = "is_running")]
925 fn py_is_running(&self) -> bool {
926 Component::is_running(self.inner())
927 }
928
929 #[pyo3(name = "is_stopped")]
930 fn py_is_stopped(&self) -> bool {
931 Component::is_stopped(self.inner())
932 }
933
934 #[pyo3(name = "is_disposed")]
935 fn py_is_disposed(&self) -> bool {
936 Component::is_disposed(self.inner())
937 }
938
939 #[pyo3(name = "is_degraded")]
940 fn py_is_degraded(&self) -> bool {
941 Component::is_degraded(self.inner())
942 }
943
944 #[pyo3(name = "is_faulted")]
945 fn py_is_faulted(&self) -> bool {
946 Component::is_faulted(self.inner())
947 }
948
949 #[pyo3(name = "start")]
950 fn py_start(&mut self) -> PyResult<()> {
951 Component::start(self.inner_mut()).map_err(to_pyruntime_err)
952 }
953
954 #[pyo3(name = "stop")]
955 fn py_stop(&mut self) -> PyResult<()> {
956 Component::stop(self.inner_mut()).map_err(to_pyruntime_err)
957 }
958
959 #[pyo3(name = "resume")]
960 fn py_resume(&mut self) -> PyResult<()> {
961 Component::resume(self.inner_mut()).map_err(to_pyruntime_err)
962 }
963
964 #[pyo3(name = "reset")]
965 fn py_reset(&mut self) -> PyResult<()> {
966 Component::reset(self.inner_mut()).map_err(to_pyruntime_err)
967 }
968
969 #[pyo3(name = "dispose")]
970 fn py_dispose(&mut self) -> PyResult<()> {
971 Component::dispose(self.inner_mut()).map_err(to_pyruntime_err)
972 }
973
974 #[pyo3(name = "degrade")]
975 fn py_degrade(&mut self) -> PyResult<()> {
976 Component::degrade(self.inner_mut()).map_err(to_pyruntime_err)
977 }
978
979 #[pyo3(name = "fault")]
980 fn py_fault(&mut self) -> PyResult<()> {
981 Component::fault(self.inner_mut()).map_err(to_pyruntime_err)
982 }
983
984 #[pyo3(name = "submit_order")]
985 #[pyo3(signature = (order, position_id=None, client_id=None, params=None))]
986 fn py_submit_order(
987 &mut self,
988 py: Python<'_>,
989 order: Py<PyAny>,
990 position_id: Option<PositionId>,
991 client_id: Option<ClientId>,
992 params: Option<IndexMap<String, String>>,
993 ) -> PyResult<()> {
994 let order = pyobject_to_order_any(py, order)?;
995 let inner = self.inner_mut();
996 match params {
997 Some(p) => Strategy::submit_order_with_params(inner, order, position_id, client_id, p),
998 None => Strategy::submit_order(inner, order, position_id, client_id),
999 }
1000 .map_err(to_pyruntime_err)
1001 }
1002
1003 #[pyo3(name = "modify_order")]
1004 #[pyo3(signature = (order, quantity=None, price=None, trigger_price=None, client_id=None, params=None))]
1005 #[allow(clippy::too_many_arguments)]
1006 fn py_modify_order(
1007 &mut self,
1008 py: Python<'_>,
1009 order: Py<PyAny>,
1010 quantity: Option<Quantity>,
1011 price: Option<Price>,
1012 trigger_price: Option<Price>,
1013 client_id: Option<ClientId>,
1014 params: Option<IndexMap<String, String>>,
1015 ) -> PyResult<()> {
1016 let order = pyobject_to_order_any(py, order)?;
1017 let inner = self.inner_mut();
1018 match params {
1019 Some(p) => Strategy::modify_order_with_params(
1020 inner,
1021 order,
1022 quantity,
1023 price,
1024 trigger_price,
1025 client_id,
1026 p,
1027 ),
1028 None => Strategy::modify_order(inner, order, quantity, price, trigger_price, client_id),
1029 }
1030 .map_err(to_pyruntime_err)
1031 }
1032
1033 #[pyo3(name = "cancel_order")]
1034 #[pyo3(signature = (order, client_id=None, params=None))]
1035 fn py_cancel_order(
1036 &mut self,
1037 py: Python<'_>,
1038 order: Py<PyAny>,
1039 client_id: Option<ClientId>,
1040 params: Option<IndexMap<String, String>>,
1041 ) -> PyResult<()> {
1042 let order = pyobject_to_order_any(py, order)?;
1043 let inner = self.inner_mut();
1044 match params {
1045 Some(p) => Strategy::cancel_order_with_params(inner, order, client_id, p),
1046 None => Strategy::cancel_order(inner, order, client_id),
1047 }
1048 .map_err(to_pyruntime_err)
1049 }
1050
1051 #[pyo3(name = "cancel_orders")]
1052 #[pyo3(signature = (orders, client_id=None, params=None))]
1053 fn py_cancel_orders(
1054 &mut self,
1055 py: Python<'_>,
1056 orders: Vec<Py<PyAny>>,
1057 client_id: Option<ClientId>,
1058 params: Option<IndexMap<String, String>>,
1059 ) -> PyResult<()> {
1060 let orders: Vec<OrderAny> = orders
1061 .into_iter()
1062 .map(|o| pyobject_to_order_any(py, o))
1063 .collect::<PyResult<Vec<_>>>()?;
1064 Strategy::cancel_orders(self.inner_mut(), orders, client_id, params)
1065 .map_err(to_pyruntime_err)
1066 }
1067
1068 #[pyo3(name = "cancel_all_orders")]
1069 #[pyo3(signature = (instrument_id, order_side=None, client_id=None, params=None))]
1070 fn py_cancel_all_orders(
1071 &mut self,
1072 instrument_id: InstrumentId,
1073 order_side: Option<OrderSide>,
1074 client_id: Option<ClientId>,
1075 params: Option<IndexMap<String, String>>,
1076 ) -> PyResult<()> {
1077 let inner = self.inner_mut();
1078 match params {
1079 Some(p) => Strategy::cancel_all_orders_with_params(
1080 inner,
1081 instrument_id,
1082 order_side,
1083 client_id,
1084 p,
1085 ),
1086 None => Strategy::cancel_all_orders(inner, instrument_id, order_side, client_id),
1087 }
1088 .map_err(to_pyruntime_err)
1089 }
1090
1091 #[pyo3(name = "close_position")]
1092 #[pyo3(signature = (position, client_id=None, tags=None, time_in_force=None, reduce_only=None, quote_quantity=None))]
1093 fn py_close_position(
1094 &mut self,
1095 position: &Position,
1096 client_id: Option<ClientId>,
1097 tags: Option<Vec<String>>,
1098 time_in_force: Option<TimeInForce>,
1099 reduce_only: Option<bool>,
1100 quote_quantity: Option<bool>,
1101 ) -> PyResult<()> {
1102 let tags = tags.map(|t| t.into_iter().map(|s| Ustr::from(&s)).collect());
1103 Strategy::close_position(
1104 self.inner_mut(),
1105 position,
1106 client_id,
1107 tags,
1108 time_in_force,
1109 reduce_only,
1110 quote_quantity,
1111 )
1112 .map_err(to_pyruntime_err)
1113 }
1114
1115 #[pyo3(name = "close_all_positions")]
1116 #[pyo3(signature = (instrument_id, position_side=None, client_id=None, tags=None, time_in_force=None, reduce_only=None, quote_quantity=None))]
1117 #[allow(clippy::too_many_arguments)]
1118 fn py_close_all_positions(
1119 &mut self,
1120 instrument_id: InstrumentId,
1121 position_side: Option<PositionSide>,
1122 client_id: Option<ClientId>,
1123 tags: Option<Vec<String>>,
1124 time_in_force: Option<TimeInForce>,
1125 reduce_only: Option<bool>,
1126 quote_quantity: Option<bool>,
1127 ) -> PyResult<()> {
1128 let tags = tags.map(|t| t.into_iter().map(|s| Ustr::from(&s)).collect());
1129 Strategy::close_all_positions(
1130 self.inner_mut(),
1131 instrument_id,
1132 position_side,
1133 client_id,
1134 tags,
1135 time_in_force,
1136 reduce_only,
1137 quote_quantity,
1138 )
1139 .map_err(to_pyruntime_err)
1140 }
1141
1142 #[pyo3(name = "query_account")]
1143 #[pyo3(signature = (account_id, client_id=None))]
1144 fn py_query_account(
1145 &mut self,
1146 account_id: AccountId,
1147 client_id: Option<ClientId>,
1148 ) -> PyResult<()> {
1149 Strategy::query_account(self.inner_mut(), account_id, client_id).map_err(to_pyruntime_err)
1150 }
1151
1152 #[pyo3(name = "query_order")]
1153 #[pyo3(signature = (order, client_id=None))]
1154 fn py_query_order(
1155 &mut self,
1156 py: Python<'_>,
1157 order: Py<PyAny>,
1158 client_id: Option<ClientId>,
1159 ) -> PyResult<()> {
1160 let order = pyobject_to_order_any(py, order)?;
1161 Strategy::query_order(self.inner_mut(), &order, client_id).map_err(to_pyruntime_err)
1162 }
1163
1164 #[pyo3(name = "on_start")]
1165 fn py_on_start(&mut self) -> PyResult<()> {
1166 self.inner_mut().dispatch_on_start()
1167 }
1168
1169 #[pyo3(name = "on_stop")]
1170 fn py_on_stop(&mut self) -> PyResult<()> {
1171 self.inner_mut().dispatch_on_stop()
1172 }
1173
1174 #[pyo3(name = "on_resume")]
1175 fn py_on_resume(&mut self) -> PyResult<()> {
1176 self.inner_mut().dispatch_on_resume()
1177 }
1178
1179 #[pyo3(name = "on_reset")]
1180 fn py_on_reset(&mut self) -> PyResult<()> {
1181 self.inner_mut().dispatch_on_reset()
1182 }
1183
1184 #[pyo3(name = "on_dispose")]
1185 fn py_on_dispose(&mut self) -> PyResult<()> {
1186 self.inner_mut().dispatch_on_dispose()
1187 }
1188
1189 #[pyo3(name = "on_degrade")]
1190 fn py_on_degrade(&mut self) -> PyResult<()> {
1191 self.inner_mut().dispatch_on_degrade()
1192 }
1193
1194 #[pyo3(name = "on_fault")]
1195 fn py_on_fault(&mut self) -> PyResult<()> {
1196 self.inner_mut().dispatch_on_fault()
1197 }
1198
1199 #[pyo3(name = "on_data")]
1200 fn py_on_data(&mut self, data: Py<PyAny>) -> PyResult<()> {
1201 self.inner_mut().dispatch_on_data(data)
1202 }
1203
1204 #[pyo3(name = "on_signal")]
1205 fn py_on_signal(&mut self, signal: &Signal) -> PyResult<()> {
1206 self.inner_mut().dispatch_on_signal(signal)
1207 }
1208
1209 #[pyo3(name = "on_instrument")]
1210 fn py_on_instrument(&mut self, instrument: Py<PyAny>) -> PyResult<()> {
1211 self.inner_mut().dispatch_on_instrument(instrument)
1212 }
1213
1214 #[pyo3(name = "on_quote")]
1215 fn py_on_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
1216 self.inner_mut().dispatch_on_quote(quote)
1217 }
1218
1219 #[pyo3(name = "on_trade")]
1220 fn py_on_trade(&mut self, trade: TradeTick) -> PyResult<()> {
1221 self.inner_mut().dispatch_on_trade(trade)
1222 }
1223
1224 #[pyo3(name = "on_bar")]
1225 fn py_on_bar(&mut self, bar: Bar) -> PyResult<()> {
1226 self.inner_mut().dispatch_on_bar(bar)
1227 }
1228
1229 #[pyo3(name = "on_book_deltas")]
1230 fn py_on_book_deltas(&mut self, deltas: OrderBookDeltas) -> PyResult<()> {
1231 self.inner_mut().dispatch_on_book_deltas(deltas)
1232 }
1233
1234 #[pyo3(name = "on_book")]
1235 fn py_on_book(&mut self, book: &OrderBook) -> PyResult<()> {
1236 self.inner_mut().dispatch_on_book(book)
1237 }
1238
1239 #[pyo3(name = "on_mark_price")]
1240 fn py_on_mark_price(&mut self, mark_price: MarkPriceUpdate) -> PyResult<()> {
1241 self.inner_mut().dispatch_on_mark_price(mark_price)
1242 }
1243
1244 #[pyo3(name = "on_index_price")]
1245 fn py_on_index_price(&mut self, index_price: IndexPriceUpdate) -> PyResult<()> {
1246 self.inner_mut().dispatch_on_index_price(index_price)
1247 }
1248
1249 #[pyo3(name = "on_funding_rate")]
1250 fn py_on_funding_rate(&mut self, funding_rate: FundingRateUpdate) -> PyResult<()> {
1251 self.inner_mut().dispatch_on_funding_rate(funding_rate)
1252 }
1253
1254 #[pyo3(name = "on_instrument_status")]
1255 fn py_on_instrument_status(&mut self, status: InstrumentStatus) -> PyResult<()> {
1256 self.inner_mut().dispatch_on_instrument_status(status)
1257 }
1258
1259 #[pyo3(name = "on_instrument_close")]
1260 fn py_on_instrument_close(&mut self, close: InstrumentClose) -> PyResult<()> {
1261 self.inner_mut().dispatch_on_instrument_close(close)
1262 }
1263
1264 #[pyo3(name = "subscribe_data")]
1265 #[pyo3(signature = (data_type, client_id=None, params=None))]
1266 fn py_subscribe_data(
1267 &mut self,
1268 data_type: DataType,
1269 client_id: Option<ClientId>,
1270 params: Option<IndexMap<String, String>>,
1271 ) -> PyResult<()> {
1272 DataActor::subscribe_data(self.inner_mut(), data_type, client_id, params);
1273 Ok(())
1274 }
1275
1276 #[pyo3(name = "subscribe_instruments")]
1277 #[pyo3(signature = (venue, client_id=None, params=None))]
1278 fn py_subscribe_instruments(
1279 &mut self,
1280 venue: Venue,
1281 client_id: Option<ClientId>,
1282 params: Option<IndexMap<String, String>>,
1283 ) -> PyResult<()> {
1284 DataActor::subscribe_instruments(self.inner_mut(), venue, client_id, params);
1285 Ok(())
1286 }
1287
1288 #[pyo3(name = "subscribe_instrument")]
1289 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1290 fn py_subscribe_instrument(
1291 &mut self,
1292 instrument_id: InstrumentId,
1293 client_id: Option<ClientId>,
1294 params: Option<IndexMap<String, String>>,
1295 ) -> PyResult<()> {
1296 DataActor::subscribe_instrument(self.inner_mut(), instrument_id, client_id, params);
1297 Ok(())
1298 }
1299
1300 #[pyo3(name = "subscribe_book_deltas")]
1301 #[pyo3(signature = (instrument_id, book_type, depth=None, client_id=None, managed=false, params=None))]
1302 fn py_subscribe_book_deltas(
1303 &mut self,
1304 instrument_id: InstrumentId,
1305 book_type: BookType,
1306 depth: Option<usize>,
1307 client_id: Option<ClientId>,
1308 managed: bool,
1309 params: Option<IndexMap<String, String>>,
1310 ) -> PyResult<()> {
1311 let depth = depth.and_then(NonZeroUsize::new);
1312 DataActor::subscribe_book_deltas(
1313 self.inner_mut(),
1314 instrument_id,
1315 book_type,
1316 depth,
1317 client_id,
1318 managed,
1319 params,
1320 );
1321 Ok(())
1322 }
1323
1324 #[pyo3(name = "subscribe_book_at_interval")]
1325 #[pyo3(signature = (instrument_id, book_type, interval_ms, depth=None, client_id=None, params=None))]
1326 fn py_subscribe_book_at_interval(
1327 &mut self,
1328 instrument_id: InstrumentId,
1329 book_type: BookType,
1330 interval_ms: usize,
1331 depth: Option<usize>,
1332 client_id: Option<ClientId>,
1333 params: Option<IndexMap<String, String>>,
1334 ) -> PyResult<()> {
1335 let depth = depth.and_then(NonZeroUsize::new);
1336 let interval_ms = NonZeroUsize::new(interval_ms)
1337 .ok_or_else(|| to_pyvalue_err("interval_ms must be > 0"))?;
1338
1339 DataActor::subscribe_book_at_interval(
1340 self.inner_mut(),
1341 instrument_id,
1342 book_type,
1343 depth,
1344 interval_ms,
1345 client_id,
1346 params,
1347 );
1348 Ok(())
1349 }
1350
1351 #[pyo3(name = "subscribe_quotes")]
1352 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1353 fn py_subscribe_quotes(
1354 &mut self,
1355 instrument_id: InstrumentId,
1356 client_id: Option<ClientId>,
1357 params: Option<IndexMap<String, String>>,
1358 ) -> PyResult<()> {
1359 DataActor::subscribe_quotes(self.inner_mut(), instrument_id, client_id, params);
1360 Ok(())
1361 }
1362
1363 #[pyo3(name = "subscribe_trades")]
1364 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1365 fn py_subscribe_trades(
1366 &mut self,
1367 instrument_id: InstrumentId,
1368 client_id: Option<ClientId>,
1369 params: Option<IndexMap<String, String>>,
1370 ) -> PyResult<()> {
1371 DataActor::subscribe_trades(self.inner_mut(), instrument_id, client_id, params);
1372 Ok(())
1373 }
1374
1375 #[pyo3(name = "subscribe_bars")]
1376 #[pyo3(signature = (bar_type, client_id=None, params=None))]
1377 fn py_subscribe_bars(
1378 &mut self,
1379 bar_type: BarType,
1380 client_id: Option<ClientId>,
1381 params: Option<IndexMap<String, String>>,
1382 ) -> PyResult<()> {
1383 DataActor::subscribe_bars(self.inner_mut(), bar_type, client_id, params);
1384 Ok(())
1385 }
1386
1387 #[pyo3(name = "subscribe_mark_prices")]
1388 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1389 fn py_subscribe_mark_prices(
1390 &mut self,
1391 instrument_id: InstrumentId,
1392 client_id: Option<ClientId>,
1393 params: Option<IndexMap<String, String>>,
1394 ) -> PyResult<()> {
1395 DataActor::subscribe_mark_prices(self.inner_mut(), instrument_id, client_id, params);
1396 Ok(())
1397 }
1398
1399 #[pyo3(name = "subscribe_index_prices")]
1400 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1401 fn py_subscribe_index_prices(
1402 &mut self,
1403 instrument_id: InstrumentId,
1404 client_id: Option<ClientId>,
1405 params: Option<IndexMap<String, String>>,
1406 ) -> PyResult<()> {
1407 DataActor::subscribe_index_prices(self.inner_mut(), instrument_id, client_id, params);
1408 Ok(())
1409 }
1410
1411 #[pyo3(name = "subscribe_instrument_status")]
1412 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1413 fn py_subscribe_instrument_status(
1414 &mut self,
1415 instrument_id: InstrumentId,
1416 client_id: Option<ClientId>,
1417 params: Option<IndexMap<String, String>>,
1418 ) -> PyResult<()> {
1419 DataActor::subscribe_instrument_status(self.inner_mut(), instrument_id, client_id, params);
1420 Ok(())
1421 }
1422
1423 #[pyo3(name = "subscribe_instrument_close")]
1424 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1425 fn py_subscribe_instrument_close(
1426 &mut self,
1427 instrument_id: InstrumentId,
1428 client_id: Option<ClientId>,
1429 params: Option<IndexMap<String, String>>,
1430 ) -> PyResult<()> {
1431 DataActor::subscribe_instrument_close(self.inner_mut(), instrument_id, client_id, params);
1432 Ok(())
1433 }
1434
1435 #[pyo3(name = "request_data")]
1436 #[pyo3(signature = (data_type, client_id, start=None, end=None, limit=None, params=None))]
1437 fn py_request_data(
1438 &mut self,
1439 data_type: DataType,
1440 client_id: ClientId,
1441 start: Option<u64>,
1442 end: Option<u64>,
1443 limit: Option<usize>,
1444 params: Option<IndexMap<String, String>>,
1445 ) -> PyResult<String> {
1446 let limit = limit.and_then(NonZeroUsize::new);
1447 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1448 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1449
1450 let request_id = DataActor::request_data(
1451 self.inner_mut(),
1452 data_type,
1453 client_id,
1454 start,
1455 end,
1456 limit,
1457 params,
1458 )
1459 .map_err(to_pyvalue_err)?;
1460 Ok(request_id.to_string())
1461 }
1462
1463 #[pyo3(name = "request_instrument")]
1464 #[pyo3(signature = (instrument_id, start=None, end=None, client_id=None, params=None))]
1465 fn py_request_instrument(
1466 &mut self,
1467 instrument_id: InstrumentId,
1468 start: Option<u64>,
1469 end: Option<u64>,
1470 client_id: Option<ClientId>,
1471 params: Option<IndexMap<String, String>>,
1472 ) -> PyResult<String> {
1473 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1474 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1475
1476 let request_id = DataActor::request_instrument(
1477 self.inner_mut(),
1478 instrument_id,
1479 start,
1480 end,
1481 client_id,
1482 params,
1483 )
1484 .map_err(to_pyvalue_err)?;
1485 Ok(request_id.to_string())
1486 }
1487
1488 #[pyo3(name = "request_instruments")]
1489 #[pyo3(signature = (venue=None, start=None, end=None, client_id=None, params=None))]
1490 fn py_request_instruments(
1491 &mut self,
1492 venue: Option<Venue>,
1493 start: Option<u64>,
1494 end: Option<u64>,
1495 client_id: Option<ClientId>,
1496 params: Option<IndexMap<String, String>>,
1497 ) -> PyResult<String> {
1498 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1499 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1500
1501 let request_id =
1502 DataActor::request_instruments(self.inner_mut(), venue, start, end, client_id, params)
1503 .map_err(to_pyvalue_err)?;
1504 Ok(request_id.to_string())
1505 }
1506
1507 #[pyo3(name = "request_book_snapshot")]
1508 #[pyo3(signature = (instrument_id, depth=None, client_id=None, params=None))]
1509 fn py_request_book_snapshot(
1510 &mut self,
1511 instrument_id: InstrumentId,
1512 depth: Option<usize>,
1513 client_id: Option<ClientId>,
1514 params: Option<IndexMap<String, String>>,
1515 ) -> PyResult<String> {
1516 let depth = depth.and_then(NonZeroUsize::new);
1517
1518 let request_id = DataActor::request_book_snapshot(
1519 self.inner_mut(),
1520 instrument_id,
1521 depth,
1522 client_id,
1523 params,
1524 )
1525 .map_err(to_pyvalue_err)?;
1526 Ok(request_id.to_string())
1527 }
1528
1529 #[pyo3(name = "request_quotes")]
1530 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
1531 fn py_request_quotes(
1532 &mut self,
1533 instrument_id: InstrumentId,
1534 start: Option<u64>,
1535 end: Option<u64>,
1536 limit: Option<usize>,
1537 client_id: Option<ClientId>,
1538 params: Option<IndexMap<String, String>>,
1539 ) -> PyResult<String> {
1540 let limit = limit.and_then(NonZeroUsize::new);
1541 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1542 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1543
1544 let request_id = DataActor::request_quotes(
1545 self.inner_mut(),
1546 instrument_id,
1547 start,
1548 end,
1549 limit,
1550 client_id,
1551 params,
1552 )
1553 .map_err(to_pyvalue_err)?;
1554 Ok(request_id.to_string())
1555 }
1556
1557 #[pyo3(name = "request_trades")]
1558 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
1559 fn py_request_trades(
1560 &mut self,
1561 instrument_id: InstrumentId,
1562 start: Option<u64>,
1563 end: Option<u64>,
1564 limit: Option<usize>,
1565 client_id: Option<ClientId>,
1566 params: Option<IndexMap<String, String>>,
1567 ) -> PyResult<String> {
1568 let limit = limit.and_then(NonZeroUsize::new);
1569 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1570 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1571
1572 let request_id = DataActor::request_trades(
1573 self.inner_mut(),
1574 instrument_id,
1575 start,
1576 end,
1577 limit,
1578 client_id,
1579 params,
1580 )
1581 .map_err(to_pyvalue_err)?;
1582 Ok(request_id.to_string())
1583 }
1584
1585 #[pyo3(name = "request_bars")]
1586 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, client_id=None, params=None))]
1587 fn py_request_bars(
1588 &mut self,
1589 bar_type: BarType,
1590 start: Option<u64>,
1591 end: Option<u64>,
1592 limit: Option<usize>,
1593 client_id: Option<ClientId>,
1594 params: Option<IndexMap<String, String>>,
1595 ) -> PyResult<String> {
1596 let limit = limit.and_then(NonZeroUsize::new);
1597 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1598 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1599
1600 let request_id = DataActor::request_bars(
1601 self.inner_mut(),
1602 bar_type,
1603 start,
1604 end,
1605 limit,
1606 client_id,
1607 params,
1608 )
1609 .map_err(to_pyvalue_err)?;
1610 Ok(request_id.to_string())
1611 }
1612
1613 #[pyo3(name = "unsubscribe_data")]
1614 #[pyo3(signature = (data_type, client_id=None, params=None))]
1615 fn py_unsubscribe_data(
1616 &mut self,
1617 data_type: DataType,
1618 client_id: Option<ClientId>,
1619 params: Option<IndexMap<String, String>>,
1620 ) -> PyResult<()> {
1621 DataActor::unsubscribe_data(self.inner_mut(), data_type, client_id, params);
1622 Ok(())
1623 }
1624
1625 #[pyo3(name = "unsubscribe_instruments")]
1626 #[pyo3(signature = (venue, client_id=None, params=None))]
1627 fn py_unsubscribe_instruments(
1628 &mut self,
1629 venue: Venue,
1630 client_id: Option<ClientId>,
1631 params: Option<IndexMap<String, String>>,
1632 ) -> PyResult<()> {
1633 DataActor::unsubscribe_instruments(self.inner_mut(), venue, client_id, params);
1634 Ok(())
1635 }
1636
1637 #[pyo3(name = "unsubscribe_instrument")]
1638 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1639 fn py_unsubscribe_instrument(
1640 &mut self,
1641 instrument_id: InstrumentId,
1642 client_id: Option<ClientId>,
1643 params: Option<IndexMap<String, String>>,
1644 ) -> PyResult<()> {
1645 DataActor::unsubscribe_instrument(self.inner_mut(), instrument_id, client_id, params);
1646 Ok(())
1647 }
1648
1649 #[pyo3(name = "unsubscribe_book_deltas")]
1650 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1651 fn py_unsubscribe_book_deltas(
1652 &mut self,
1653 instrument_id: InstrumentId,
1654 client_id: Option<ClientId>,
1655 params: Option<IndexMap<String, String>>,
1656 ) -> PyResult<()> {
1657 DataActor::unsubscribe_book_deltas(self.inner_mut(), instrument_id, client_id, params);
1658 Ok(())
1659 }
1660
1661 #[pyo3(name = "unsubscribe_book_at_interval")]
1662 #[pyo3(signature = (instrument_id, interval_ms, client_id=None, params=None))]
1663 fn py_unsubscribe_book_at_interval(
1664 &mut self,
1665 instrument_id: InstrumentId,
1666 interval_ms: usize,
1667 client_id: Option<ClientId>,
1668 params: Option<IndexMap<String, String>>,
1669 ) -> PyResult<()> {
1670 let interval_ms = NonZeroUsize::new(interval_ms)
1671 .ok_or_else(|| to_pyvalue_err("interval_ms must be > 0"))?;
1672
1673 DataActor::unsubscribe_book_at_interval(
1674 self.inner_mut(),
1675 instrument_id,
1676 interval_ms,
1677 client_id,
1678 params,
1679 );
1680 Ok(())
1681 }
1682
1683 #[pyo3(name = "unsubscribe_quotes")]
1684 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1685 fn py_unsubscribe_quotes(
1686 &mut self,
1687 instrument_id: InstrumentId,
1688 client_id: Option<ClientId>,
1689 params: Option<IndexMap<String, String>>,
1690 ) -> PyResult<()> {
1691 DataActor::unsubscribe_quotes(self.inner_mut(), instrument_id, client_id, params);
1692 Ok(())
1693 }
1694
1695 #[pyo3(name = "unsubscribe_trades")]
1696 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1697 fn py_unsubscribe_trades(
1698 &mut self,
1699 instrument_id: InstrumentId,
1700 client_id: Option<ClientId>,
1701 params: Option<IndexMap<String, String>>,
1702 ) -> PyResult<()> {
1703 DataActor::unsubscribe_trades(self.inner_mut(), instrument_id, client_id, params);
1704 Ok(())
1705 }
1706
1707 #[pyo3(name = "unsubscribe_bars")]
1708 #[pyo3(signature = (bar_type, client_id=None, params=None))]
1709 fn py_unsubscribe_bars(
1710 &mut self,
1711 bar_type: BarType,
1712 client_id: Option<ClientId>,
1713 params: Option<IndexMap<String, String>>,
1714 ) -> PyResult<()> {
1715 DataActor::unsubscribe_bars(self.inner_mut(), bar_type, client_id, params);
1716 Ok(())
1717 }
1718
1719 #[pyo3(name = "unsubscribe_mark_prices")]
1720 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1721 fn py_unsubscribe_mark_prices(
1722 &mut self,
1723 instrument_id: InstrumentId,
1724 client_id: Option<ClientId>,
1725 params: Option<IndexMap<String, String>>,
1726 ) -> PyResult<()> {
1727 DataActor::unsubscribe_mark_prices(self.inner_mut(), instrument_id, client_id, params);
1728 Ok(())
1729 }
1730
1731 #[pyo3(name = "unsubscribe_index_prices")]
1732 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1733 fn py_unsubscribe_index_prices(
1734 &mut self,
1735 instrument_id: InstrumentId,
1736 client_id: Option<ClientId>,
1737 params: Option<IndexMap<String, String>>,
1738 ) -> PyResult<()> {
1739 DataActor::unsubscribe_index_prices(self.inner_mut(), instrument_id, client_id, params);
1740 Ok(())
1741 }
1742
1743 #[pyo3(name = "unsubscribe_instrument_status")]
1744 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1745 fn py_unsubscribe_instrument_status(
1746 &mut self,
1747 instrument_id: InstrumentId,
1748 client_id: Option<ClientId>,
1749 params: Option<IndexMap<String, String>>,
1750 ) -> PyResult<()> {
1751 DataActor::unsubscribe_instrument_status(
1752 self.inner_mut(),
1753 instrument_id,
1754 client_id,
1755 params,
1756 );
1757 Ok(())
1758 }
1759
1760 #[pyo3(name = "unsubscribe_instrument_close")]
1761 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1762 fn py_unsubscribe_instrument_close(
1763 &mut self,
1764 instrument_id: InstrumentId,
1765 client_id: Option<ClientId>,
1766 params: Option<IndexMap<String, String>>,
1767 ) -> PyResult<()> {
1768 DataActor::unsubscribe_instrument_close(self.inner_mut(), instrument_id, client_id, params);
1769 Ok(())
1770 }
1771}