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