1use std::{
19 any::Any,
20 cell::{RefCell, UnsafeCell},
21 collections::HashMap,
22 fmt::Debug,
23 num::NonZeroUsize,
24 ops::{Deref, DerefMut},
25 rc::Rc,
26};
27
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 Params, from_pydict,
44 nanos::UnixNanos,
45 python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
46};
47use nautilus_model::{
48 data::{
49 Bar, BarType, CustomData, DataType, FundingRateUpdate, IndexPriceUpdate, InstrumentStatus,
50 MarkPriceUpdate, OrderBookDeltas, QuoteTick, TradeTick,
51 close::InstrumentClose,
52 option_chain::{OptionChainSlice, OptionGreeks},
53 },
54 enums::{BookType, OmsType, OrderSide, PositionSide, TimeInForce},
55 events::{
56 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
57 OrderExpired, OrderFilled, OrderInitialized, OrderModifyRejected, OrderPendingCancel,
58 OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted, OrderTriggered,
59 OrderUpdated, PositionChanged, PositionClosed, PositionOpened,
60 },
61 identifiers::{
62 AccountId, ActorId, ClientId, InstrumentId, OptionSeriesId, PositionId, StrategyId,
63 TraderId, Venue,
64 },
65 instruments::InstrumentAny,
66 orderbook::OrderBook,
67 orders::OrderAny,
68 position::Position,
69 python::{
70 data::option_chain::PyStrikeRange, instruments::instrument_any_to_pyobject,
71 orders::pyobject_to_order_any,
72 },
73 types::{Price, Quantity},
74};
75use nautilus_portfolio::portfolio::Portfolio;
76use pyo3::{prelude::*, types::PyDict};
77use ustr::Ustr;
78
79use crate::strategy::{ImportableStrategyConfig, Strategy, StrategyConfig, StrategyCore};
80
81#[pyo3::pymethods]
82#[pyo3_stub_gen::derive::gen_stub_pymethods]
83impl StrategyConfig {
84 #[new]
86 #[pyo3(signature = (
87 strategy_id=None,
88 order_id_tag=None,
89 oms_type=None,
90 external_order_claims=None,
91 manage_contingent_orders=false,
92 manage_gtd_expiry=false,
93 manage_stop=false,
94 market_exit_interval_ms=100,
95 market_exit_max_attempts=100,
96 market_exit_time_in_force=TimeInForce::Gtc,
97 market_exit_reduce_only=true,
98 use_uuid_client_order_ids=false,
99 use_hyphens_in_client_order_ids=true,
100 log_events=true,
101 log_commands=true,
102 log_rejected_due_post_only_as_warning=true,
103 **_kwargs
104 ))]
105 #[expect(clippy::too_many_arguments)]
106 fn py_new(
107 strategy_id: Option<StrategyId>,
108 order_id_tag: Option<String>,
109 oms_type: Option<OmsType>,
110 external_order_claims: Option<Vec<InstrumentId>>,
111 manage_contingent_orders: bool,
112 manage_gtd_expiry: bool,
113 manage_stop: bool,
114 market_exit_interval_ms: u64,
115 market_exit_max_attempts: u64,
116 market_exit_time_in_force: TimeInForce,
117 market_exit_reduce_only: bool,
118 use_uuid_client_order_ids: bool,
119 use_hyphens_in_client_order_ids: bool,
120 log_events: bool,
121 log_commands: bool,
122 log_rejected_due_post_only_as_warning: bool,
123 _kwargs: Option<&Bound<'_, PyDict>>,
124 ) -> Self {
125 Self {
126 strategy_id,
127 order_id_tag,
128 use_uuid_client_order_ids,
129 use_hyphens_in_client_order_ids,
130 oms_type,
131 external_order_claims,
132 manage_contingent_orders,
133 manage_gtd_expiry,
134 manage_stop,
135 market_exit_interval_ms,
136 market_exit_max_attempts,
137 market_exit_time_in_force,
138 market_exit_reduce_only,
139 log_events,
140 log_commands,
141 log_rejected_due_post_only_as_warning,
142 }
143 }
144
145 #[getter]
146 fn strategy_id(&self) -> Option<StrategyId> {
147 self.strategy_id
148 }
149
150 #[getter]
151 fn order_id_tag(&self) -> Option<&String> {
152 self.order_id_tag.as_ref()
153 }
154
155 #[getter]
156 fn oms_type(&self) -> Option<OmsType> {
157 self.oms_type
158 }
159
160 #[getter]
161 fn manage_contingent_orders(&self) -> bool {
162 self.manage_contingent_orders
163 }
164
165 #[getter]
166 fn manage_gtd_expiry(&self) -> bool {
167 self.manage_gtd_expiry
168 }
169
170 #[getter]
171 fn use_uuid_client_order_ids(&self) -> bool {
172 self.use_uuid_client_order_ids
173 }
174
175 #[getter]
176 fn use_hyphens_in_client_order_ids(&self) -> bool {
177 self.use_hyphens_in_client_order_ids
178 }
179
180 #[getter]
181 fn log_events(&self) -> bool {
182 self.log_events
183 }
184
185 #[getter]
186 fn log_commands(&self) -> bool {
187 self.log_commands
188 }
189
190 #[getter]
191 fn log_rejected_due_post_only_as_warning(&self) -> bool {
192 self.log_rejected_due_post_only_as_warning
193 }
194}
195
196#[pyo3::pymethods]
197#[pyo3_stub_gen::derive::gen_stub_pymethods]
198impl ImportableStrategyConfig {
199 #[new]
201 #[expect(clippy::needless_pass_by_value)]
202 fn py_new(strategy_path: String, config_path: String, config: Py<PyDict>) -> PyResult<Self> {
203 let json_config = Python::attach(|py| -> PyResult<HashMap<String, serde_json::Value>> {
204 let kwargs = PyDict::new(py);
205 kwargs.set_item("default", py.eval(pyo3::ffi::c_str!("str"), None, None)?)?;
206 let json_str: String = PyModule::import(py, "json")?
207 .call_method("dumps", (config.bind(py),), Some(&kwargs))?
208 .extract()?;
209
210 let json_value: serde_json::Value =
211 serde_json::from_str(&json_str).map_err(to_pyvalue_err)?;
212
213 if let serde_json::Value::Object(map) = json_value {
214 Ok(map.into_iter().collect())
215 } else {
216 Err(to_pyvalue_err("Config must be a dictionary"))
217 }
218 })?;
219
220 Ok(Self {
221 strategy_path,
222 config_path,
223 config: json_config,
224 })
225 }
226
227 #[getter]
228 fn strategy_path(&self) -> &String {
229 &self.strategy_path
230 }
231
232 #[getter]
233 fn config_path(&self) -> &String {
234 &self.config_path
235 }
236
237 #[getter]
238 fn config(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
239 let py_dict = PyDict::new(py);
240
241 for (key, value) in &self.config {
242 let json_str = serde_json::to_string(value).map_err(to_pyvalue_err)?;
243 let py_value = PyModule::import(py, "json")?.call_method("loads", (json_str,), None)?;
244 py_dict.set_item(key, py_value)?;
245 }
246 Ok(py_dict.unbind())
247 }
248}
249
250pub struct PyStrategyInner {
252 core: StrategyCore,
253 py_self: Option<Py<PyAny>>,
254 clock: PyClock,
255 logger: PyLogger,
256}
257
258impl Debug for PyStrategyInner {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 f.debug_struct(stringify!(PyStrategyInner))
261 .field("core", &self.core)
262 .field("py_self", &self.py_self.as_ref().map(|_| "<Py<PyAny>>"))
263 .field("clock", &self.clock)
264 .field("logger", &self.logger)
265 .finish()
266 }
267}
268
269#[expect(clippy::needless_pass_by_ref_mut)]
270impl PyStrategyInner {
271 fn dispatch_on_start(&self) -> PyResult<()> {
272 if let Some(ref py_self) = self.py_self {
273 Python::attach(|py| py_self.call_method0(py, "on_start"))?;
274 }
275 Ok(())
276 }
277
278 fn dispatch_on_stop(&self) -> PyResult<()> {
279 if let Some(ref py_self) = self.py_self {
280 Python::attach(|py| py_self.call_method0(py, "on_stop"))?;
281 }
282 Ok(())
283 }
284
285 fn dispatch_on_resume(&self) -> PyResult<()> {
286 if let Some(ref py_self) = self.py_self {
287 Python::attach(|py| py_self.call_method0(py, "on_resume"))?;
288 }
289 Ok(())
290 }
291
292 fn dispatch_on_reset(&self) -> PyResult<()> {
293 if let Some(ref py_self) = self.py_self {
294 Python::attach(|py| py_self.call_method0(py, "on_reset"))?;
295 }
296 Ok(())
297 }
298
299 fn dispatch_on_dispose(&self) -> PyResult<()> {
300 if let Some(ref py_self) = self.py_self {
301 Python::attach(|py| py_self.call_method0(py, "on_dispose"))?;
302 }
303 Ok(())
304 }
305
306 fn dispatch_on_degrade(&self) -> PyResult<()> {
307 if let Some(ref py_self) = self.py_self {
308 Python::attach(|py| py_self.call_method0(py, "on_degrade"))?;
309 }
310 Ok(())
311 }
312
313 fn dispatch_on_fault(&self) -> PyResult<()> {
314 if let Some(ref py_self) = self.py_self {
315 Python::attach(|py| py_self.call_method0(py, "on_fault"))?;
316 }
317 Ok(())
318 }
319
320 fn dispatch_on_time_event(&self, event: &TimeEvent) -> PyResult<()> {
321 if let Some(ref py_self) = self.py_self {
322 Python::attach(|py| {
323 py_self.call_method1(py, "on_time_event", (event.clone().into_py_any_unwrap(py),))
324 })?;
325 }
326 Ok(())
327 }
328
329 fn dispatch_on_order_initialized(&self, event: OrderInitialized) -> PyResult<()> {
330 if let Some(ref py_self) = self.py_self {
331 Python::attach(|py| {
332 py_self.call_method1(py, "on_order_initialized", (event.into_py_any_unwrap(py),))
333 })?;
334 }
335 Ok(())
336 }
337
338 fn dispatch_on_order_denied(&self, event: OrderDenied) -> PyResult<()> {
339 if let Some(ref py_self) = self.py_self {
340 Python::attach(|py| {
341 py_self.call_method1(py, "on_order_denied", (event.into_py_any_unwrap(py),))
342 })?;
343 }
344 Ok(())
345 }
346
347 fn dispatch_on_order_emulated(&self, event: OrderEmulated) -> PyResult<()> {
348 if let Some(ref py_self) = self.py_self {
349 Python::attach(|py| {
350 py_self.call_method1(py, "on_order_emulated", (event.into_py_any_unwrap(py),))
351 })?;
352 }
353 Ok(())
354 }
355
356 fn dispatch_on_order_released(&self, event: OrderReleased) -> PyResult<()> {
357 if let Some(ref py_self) = self.py_self {
358 Python::attach(|py| {
359 py_self.call_method1(py, "on_order_released", (event.into_py_any_unwrap(py),))
360 })?;
361 }
362 Ok(())
363 }
364
365 fn dispatch_on_order_submitted(&self, event: OrderSubmitted) -> PyResult<()> {
366 if let Some(ref py_self) = self.py_self {
367 Python::attach(|py| {
368 py_self.call_method1(py, "on_order_submitted", (event.into_py_any_unwrap(py),))
369 })?;
370 }
371 Ok(())
372 }
373
374 fn dispatch_on_order_rejected(&self, event: OrderRejected) -> PyResult<()> {
375 if let Some(ref py_self) = self.py_self {
376 Python::attach(|py| {
377 py_self.call_method1(py, "on_order_rejected", (event.into_py_any_unwrap(py),))
378 })?;
379 }
380 Ok(())
381 }
382
383 fn dispatch_on_order_accepted(&self, event: OrderAccepted) -> PyResult<()> {
384 if let Some(ref py_self) = self.py_self {
385 Python::attach(|py| {
386 py_self.call_method1(py, "on_order_accepted", (event.into_py_any_unwrap(py),))
387 })?;
388 }
389 Ok(())
390 }
391
392 fn dispatch_on_order_expired(&self, event: OrderExpired) -> PyResult<()> {
393 if let Some(ref py_self) = self.py_self {
394 Python::attach(|py| {
395 py_self.call_method1(py, "on_order_expired", (event.into_py_any_unwrap(py),))
396 })?;
397 }
398 Ok(())
399 }
400
401 fn dispatch_on_order_triggered(&self, event: OrderTriggered) -> PyResult<()> {
402 if let Some(ref py_self) = self.py_self {
403 Python::attach(|py| {
404 py_self.call_method1(py, "on_order_triggered", (event.into_py_any_unwrap(py),))
405 })?;
406 }
407 Ok(())
408 }
409
410 fn dispatch_on_order_pending_update(&self, event: OrderPendingUpdate) -> PyResult<()> {
411 if let Some(ref py_self) = self.py_self {
412 Python::attach(|py| {
413 py_self.call_method1(
414 py,
415 "on_order_pending_update",
416 (event.into_py_any_unwrap(py),),
417 )
418 })?;
419 }
420 Ok(())
421 }
422
423 fn dispatch_on_order_pending_cancel(&self, event: OrderPendingCancel) -> PyResult<()> {
424 if let Some(ref py_self) = self.py_self {
425 Python::attach(|py| {
426 py_self.call_method1(
427 py,
428 "on_order_pending_cancel",
429 (event.into_py_any_unwrap(py),),
430 )
431 })?;
432 }
433 Ok(())
434 }
435
436 fn dispatch_on_order_modify_rejected(&self, event: OrderModifyRejected) -> PyResult<()> {
437 if let Some(ref py_self) = self.py_self {
438 Python::attach(|py| {
439 py_self.call_method1(
440 py,
441 "on_order_modify_rejected",
442 (event.into_py_any_unwrap(py),),
443 )
444 })?;
445 }
446 Ok(())
447 }
448
449 fn dispatch_on_order_cancel_rejected(&self, event: OrderCancelRejected) -> PyResult<()> {
450 if let Some(ref py_self) = self.py_self {
451 Python::attach(|py| {
452 py_self.call_method1(
453 py,
454 "on_order_cancel_rejected",
455 (event.into_py_any_unwrap(py),),
456 )
457 })?;
458 }
459 Ok(())
460 }
461
462 fn dispatch_on_order_updated(&self, event: OrderUpdated) -> PyResult<()> {
463 if let Some(ref py_self) = self.py_self {
464 Python::attach(|py| {
465 py_self.call_method1(py, "on_order_updated", (event.into_py_any_unwrap(py),))
466 })?;
467 }
468 Ok(())
469 }
470
471 fn dispatch_on_order_canceled(&self, event: OrderCanceled) -> PyResult<()> {
472 if let Some(ref py_self) = self.py_self {
473 Python::attach(|py| {
474 py_self.call_method1(py, "on_order_canceled", (event.into_py_any_unwrap(py),))
475 })?;
476 }
477 Ok(())
478 }
479
480 fn dispatch_on_order_filled(&self, event: OrderFilled) -> PyResult<()> {
481 if let Some(ref py_self) = self.py_self {
482 Python::attach(|py| {
483 py_self.call_method1(py, "on_order_filled", (event.into_py_any_unwrap(py),))
484 })?;
485 }
486 Ok(())
487 }
488
489 fn dispatch_on_position_opened(&self, event: PositionOpened) -> PyResult<()> {
490 if let Some(ref py_self) = self.py_self {
491 Python::attach(|py| {
492 py_self.call_method1(py, "on_position_opened", (event.into_py_any_unwrap(py),))
493 })?;
494 }
495 Ok(())
496 }
497
498 fn dispatch_on_position_changed(&self, event: PositionChanged) -> PyResult<()> {
499 if let Some(ref py_self) = self.py_self {
500 Python::attach(|py| {
501 py_self.call_method1(py, "on_position_changed", (event.into_py_any_unwrap(py),))
502 })?;
503 }
504 Ok(())
505 }
506
507 fn dispatch_on_position_closed(&self, event: PositionClosed) -> PyResult<()> {
508 if let Some(ref py_self) = self.py_self {
509 Python::attach(|py| {
510 py_self.call_method1(py, "on_position_closed", (event.into_py_any_unwrap(py),))
511 })?;
512 }
513 Ok(())
514 }
515
516 fn dispatch_on_data(&mut self, data: Py<PyAny>) -> PyResult<()> {
517 if let Some(ref py_self) = self.py_self {
518 Python::attach(|py| py_self.call_method1(py, "on_data", (data,)))?;
519 }
520 Ok(())
521 }
522
523 fn dispatch_on_signal(&mut self, signal: &Signal) -> PyResult<()> {
524 if let Some(ref py_self) = self.py_self {
525 Python::attach(|py| {
526 py_self.call_method1(py, "on_signal", (signal.clone().into_py_any_unwrap(py),))
527 })?;
528 }
529 Ok(())
530 }
531
532 fn dispatch_on_instrument(&mut self, instrument: Py<PyAny>) -> PyResult<()> {
533 if let Some(ref py_self) = self.py_self {
534 Python::attach(|py| py_self.call_method1(py, "on_instrument", (instrument,)))?;
535 }
536 Ok(())
537 }
538
539 fn dispatch_on_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
540 if let Some(ref py_self) = self.py_self {
541 Python::attach(|py| {
542 py_self.call_method1(py, "on_quote", (quote.into_py_any_unwrap(py),))
543 })?;
544 }
545 Ok(())
546 }
547
548 fn dispatch_on_trade(&mut self, trade: TradeTick) -> PyResult<()> {
549 if let Some(ref py_self) = self.py_self {
550 Python::attach(|py| {
551 py_self.call_method1(py, "on_trade", (trade.into_py_any_unwrap(py),))
552 })?;
553 }
554 Ok(())
555 }
556
557 fn dispatch_on_bar(&mut self, bar: Bar) -> PyResult<()> {
558 if let Some(ref py_self) = self.py_self {
559 Python::attach(|py| py_self.call_method1(py, "on_bar", (bar.into_py_any_unwrap(py),)))?;
560 }
561 Ok(())
562 }
563
564 fn dispatch_on_book_deltas(&mut self, deltas: OrderBookDeltas) -> PyResult<()> {
565 if let Some(ref py_self) = self.py_self {
566 Python::attach(|py| {
567 py_self.call_method1(py, "on_book_deltas", (deltas.into_py_any_unwrap(py),))
568 })?;
569 }
570 Ok(())
571 }
572
573 fn dispatch_on_book(&mut self, book: &OrderBook) -> PyResult<()> {
574 if let Some(ref py_self) = self.py_self {
575 Python::attach(|py| {
576 py_self.call_method1(py, "on_book", (book.clone().into_py_any_unwrap(py),))
577 })?;
578 }
579 Ok(())
580 }
581
582 fn dispatch_on_mark_price(&mut self, mark_price: MarkPriceUpdate) -> PyResult<()> {
583 if let Some(ref py_self) = self.py_self {
584 Python::attach(|py| {
585 py_self.call_method1(py, "on_mark_price", (mark_price.into_py_any_unwrap(py),))
586 })?;
587 }
588 Ok(())
589 }
590
591 fn dispatch_on_index_price(&mut self, index_price: IndexPriceUpdate) -> PyResult<()> {
592 if let Some(ref py_self) = self.py_self {
593 Python::attach(|py| {
594 py_self.call_method1(py, "on_index_price", (index_price.into_py_any_unwrap(py),))
595 })?;
596 }
597 Ok(())
598 }
599
600 fn dispatch_on_funding_rate(&mut self, funding_rate: FundingRateUpdate) -> PyResult<()> {
601 if let Some(ref py_self) = self.py_self {
602 Python::attach(|py| {
603 py_self.call_method1(
604 py,
605 "on_funding_rate",
606 (funding_rate.into_py_any_unwrap(py),),
607 )
608 })?;
609 }
610 Ok(())
611 }
612
613 fn dispatch_on_instrument_status(&mut self, data: InstrumentStatus) -> PyResult<()> {
614 if let Some(ref py_self) = self.py_self {
615 Python::attach(|py| {
616 py_self.call_method1(py, "on_instrument_status", (data.into_py_any_unwrap(py),))
617 })?;
618 }
619 Ok(())
620 }
621
622 fn dispatch_on_instrument_close(&mut self, update: InstrumentClose) -> PyResult<()> {
623 if let Some(ref py_self) = self.py_self {
624 Python::attach(|py| {
625 py_self.call_method1(py, "on_instrument_close", (update.into_py_any_unwrap(py),))
626 })?;
627 }
628 Ok(())
629 }
630
631 fn dispatch_on_option_greeks(&mut self, greeks: OptionGreeks) -> PyResult<()> {
632 if let Some(ref py_self) = self.py_self {
633 Python::attach(|py| {
634 py_self.call_method1(py, "on_option_greeks", (greeks.into_py_any_unwrap(py),))
635 })?;
636 }
637 Ok(())
638 }
639
640 fn dispatch_on_option_chain(&mut self, slice: OptionChainSlice) -> PyResult<()> {
641 if let Some(ref py_self) = self.py_self {
642 Python::attach(|py| {
643 py_self.call_method1(py, "on_option_chain", (slice.into_py_any_unwrap(py),))
644 })?;
645 }
646 Ok(())
647 }
648
649 fn dispatch_on_historical_data(&mut self, data: Py<PyAny>) -> PyResult<()> {
650 if let Some(ref py_self) = self.py_self {
651 Python::attach(|py| py_self.call_method1(py, "on_historical_data", (data,)))?;
652 }
653 Ok(())
654 }
655
656 fn dispatch_on_historical_quotes(&mut self, quotes: Vec<QuoteTick>) -> PyResult<()> {
657 if let Some(ref py_self) = self.py_self {
658 Python::attach(|py| {
659 let py_quotes: Vec<_> = quotes
660 .into_iter()
661 .map(|quote| quote.into_py_any_unwrap(py))
662 .collect();
663 py_self.call_method1(py, "on_historical_quotes", (py_quotes,))
664 })?;
665 }
666 Ok(())
667 }
668
669 fn dispatch_on_historical_trades(&mut self, trades: Vec<TradeTick>) -> PyResult<()> {
670 if let Some(ref py_self) = self.py_self {
671 Python::attach(|py| {
672 let py_trades: Vec<_> = trades
673 .into_iter()
674 .map(|trade| trade.into_py_any_unwrap(py))
675 .collect();
676 py_self.call_method1(py, "on_historical_trades", (py_trades,))
677 })?;
678 }
679 Ok(())
680 }
681
682 fn dispatch_on_historical_funding_rates(
683 &mut self,
684 funding_rates: Vec<FundingRateUpdate>,
685 ) -> PyResult<()> {
686 if let Some(ref py_self) = self.py_self {
687 Python::attach(|py| {
688 let py_funding_rates: Vec<_> = funding_rates
689 .into_iter()
690 .map(|rate| rate.into_py_any_unwrap(py))
691 .collect();
692 py_self.call_method1(py, "on_historical_funding_rates", (py_funding_rates,))
693 })?;
694 }
695 Ok(())
696 }
697
698 fn dispatch_on_historical_bars(&mut self, bars: Vec<Bar>) -> PyResult<()> {
699 if let Some(ref py_self) = self.py_self {
700 Python::attach(|py| {
701 let py_bars: Vec<_> = bars
702 .into_iter()
703 .map(|bar| bar.into_py_any_unwrap(py))
704 .collect();
705 py_self.call_method1(py, "on_historical_bars", (py_bars,))
706 })?;
707 }
708 Ok(())
709 }
710
711 fn dispatch_on_historical_mark_prices(
712 &mut self,
713 mark_prices: Vec<MarkPriceUpdate>,
714 ) -> PyResult<()> {
715 if let Some(ref py_self) = self.py_self {
716 Python::attach(|py| {
717 let py_mark_prices: Vec<_> = mark_prices
718 .into_iter()
719 .map(|price| price.into_py_any_unwrap(py))
720 .collect();
721 py_self.call_method1(py, "on_historical_mark_prices", (py_mark_prices,))
722 })?;
723 }
724 Ok(())
725 }
726
727 fn dispatch_on_historical_index_prices(
728 &mut self,
729 index_prices: Vec<IndexPriceUpdate>,
730 ) -> PyResult<()> {
731 if let Some(ref py_self) = self.py_self {
732 Python::attach(|py| {
733 let py_index_prices: Vec<_> = index_prices
734 .into_iter()
735 .map(|price| price.into_py_any_unwrap(py))
736 .collect();
737 py_self.call_method1(py, "on_historical_index_prices", (py_index_prices,))
738 })?;
739 }
740 Ok(())
741 }
742}
743
744impl Deref for PyStrategyInner {
745 type Target = DataActorCore;
746
747 fn deref(&self) -> &Self::Target {
748 &self.core
749 }
750}
751
752impl DerefMut for PyStrategyInner {
753 fn deref_mut(&mut self) -> &mut Self::Target {
754 &mut self.core
755 }
756}
757
758impl Strategy for PyStrategyInner {
759 fn core(&self) -> &StrategyCore {
760 &self.core
761 }
762
763 fn core_mut(&mut self) -> &mut StrategyCore {
764 &mut self.core
765 }
766
767 fn on_order_initialized(&mut self, event: OrderInitialized) {
768 let _ = self.dispatch_on_order_initialized(event);
769 }
770
771 fn on_order_denied(&mut self, event: OrderDenied) {
772 let _ = self.dispatch_on_order_denied(event);
773 }
774
775 fn on_order_emulated(&mut self, event: OrderEmulated) {
776 let _ = self.dispatch_on_order_emulated(event);
777 }
778
779 fn on_order_released(&mut self, event: OrderReleased) {
780 let _ = self.dispatch_on_order_released(event);
781 }
782
783 fn on_order_submitted(&mut self, event: OrderSubmitted) {
784 let _ = self.dispatch_on_order_submitted(event);
785 }
786
787 fn on_order_rejected(&mut self, event: OrderRejected) {
788 let _ = self.dispatch_on_order_rejected(event);
789 }
790
791 fn on_order_accepted(&mut self, event: OrderAccepted) {
792 let _ = self.dispatch_on_order_accepted(event);
793 }
794
795 fn on_order_expired(&mut self, event: OrderExpired) {
796 let _ = self.dispatch_on_order_expired(event);
797 }
798
799 fn on_order_triggered(&mut self, event: OrderTriggered) {
800 let _ = self.dispatch_on_order_triggered(event);
801 }
802
803 fn on_order_pending_update(&mut self, event: OrderPendingUpdate) {
804 let _ = self.dispatch_on_order_pending_update(event);
805 }
806
807 fn on_order_pending_cancel(&mut self, event: OrderPendingCancel) {
808 let _ = self.dispatch_on_order_pending_cancel(event);
809 }
810
811 fn on_order_modify_rejected(&mut self, event: OrderModifyRejected) {
812 let _ = self.dispatch_on_order_modify_rejected(event);
813 }
814
815 fn on_order_cancel_rejected(&mut self, event: OrderCancelRejected) {
816 let _ = self.dispatch_on_order_cancel_rejected(event);
817 }
818
819 fn on_order_updated(&mut self, event: OrderUpdated) {
820 let _ = self.dispatch_on_order_updated(event);
821 }
822
823 fn on_position_opened(&mut self, event: PositionOpened) {
824 let _ = self.dispatch_on_position_opened(event);
825 }
826
827 fn on_position_changed(&mut self, event: PositionChanged) {
828 let _ = self.dispatch_on_position_changed(event);
829 }
830
831 fn on_position_closed(&mut self, event: PositionClosed) {
832 let _ = self.dispatch_on_position_closed(event);
833 }
834}
835
836impl DataActor for PyStrategyInner {
837 fn on_start(&mut self) -> anyhow::Result<()> {
838 Strategy::on_start(self)?;
839 self.dispatch_on_start()
840 .map_err(|e| anyhow::anyhow!("Python on_start failed: {e}"))
841 }
842
843 fn on_stop(&mut self) -> anyhow::Result<()> {
844 self.dispatch_on_stop()
845 .map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
846 }
847
848 fn on_resume(&mut self) -> anyhow::Result<()> {
849 self.dispatch_on_resume()
850 .map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
851 }
852
853 fn on_reset(&mut self) -> anyhow::Result<()> {
854 self.dispatch_on_reset()
855 .map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
856 }
857
858 fn on_dispose(&mut self) -> anyhow::Result<()> {
859 self.dispatch_on_dispose()
860 .map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
861 }
862
863 fn on_degrade(&mut self) -> anyhow::Result<()> {
864 self.dispatch_on_degrade()
865 .map_err(|e| anyhow::anyhow!("Python on_degrade failed: {e}"))
866 }
867
868 fn on_fault(&mut self) -> anyhow::Result<()> {
869 self.dispatch_on_fault()
870 .map_err(|e| anyhow::anyhow!("Python on_fault failed: {e}"))
871 }
872
873 fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
874 Strategy::on_time_event(self, event)?;
875 self.dispatch_on_time_event(event)
876 .map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
877 }
878
879 #[allow(unused_variables)]
880 fn on_data(&mut self, data: &CustomData) -> anyhow::Result<()> {
881 Python::attach(|py| {
882 let py_data: Py<PyAny> = Py::new(py, data.clone())?.into_any();
883 self.dispatch_on_data(py_data)
884 .map_err(|e| anyhow::anyhow!("Python on_data failed: {e}"))
885 })
886 }
887
888 fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
889 self.dispatch_on_signal(signal)
890 .map_err(|e| anyhow::anyhow!("Python on_signal failed: {e}"))
891 }
892
893 fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
894 Python::attach(|py| {
895 let py_instrument = instrument_any_to_pyobject(py, instrument.clone())
896 .map_err(|e| anyhow::anyhow!("Failed to convert InstrumentAny to Python: {e}"))?;
897 self.dispatch_on_instrument(py_instrument)
898 .map_err(|e| anyhow::anyhow!("Python on_instrument failed: {e}"))
899 })
900 }
901
902 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
903 self.dispatch_on_quote(*quote)
904 .map_err(|e| anyhow::anyhow!("Python on_quote failed: {e}"))
905 }
906
907 fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
908 self.dispatch_on_trade(*tick)
909 .map_err(|e| anyhow::anyhow!("Python on_trade failed: {e}"))
910 }
911
912 fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
913 self.dispatch_on_bar(*bar)
914 .map_err(|e| anyhow::anyhow!("Python on_bar failed: {e}"))
915 }
916
917 fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
918 self.dispatch_on_book_deltas(deltas.clone())
919 .map_err(|e| anyhow::anyhow!("Python on_book_deltas failed: {e}"))
920 }
921
922 fn on_book(&mut self, order_book: &OrderBook) -> anyhow::Result<()> {
923 self.dispatch_on_book(order_book)
924 .map_err(|e| anyhow::anyhow!("Python on_book failed: {e}"))
925 }
926
927 fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
928 self.dispatch_on_mark_price(*mark_price)
929 .map_err(|e| anyhow::anyhow!("Python on_mark_price failed: {e}"))
930 }
931
932 fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
933 self.dispatch_on_index_price(*index_price)
934 .map_err(|e| anyhow::anyhow!("Python on_index_price failed: {e}"))
935 }
936
937 fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
938 self.dispatch_on_funding_rate(*funding_rate)
939 .map_err(|e| anyhow::anyhow!("Python on_funding_rate failed: {e}"))
940 }
941
942 fn on_instrument_status(&mut self, data: &InstrumentStatus) -> anyhow::Result<()> {
943 self.dispatch_on_instrument_status(*data)
944 .map_err(|e| anyhow::anyhow!("Python on_instrument_status failed: {e}"))
945 }
946
947 fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
948 self.dispatch_on_instrument_close(*update)
949 .map_err(|e| anyhow::anyhow!("Python on_instrument_close failed: {e}"))
950 }
951
952 fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
953 self.dispatch_on_option_greeks(*greeks)
954 .map_err(|e| anyhow::anyhow!("Python on_option_greeks failed: {e}"))
955 }
956
957 fn on_option_chain(&mut self, slice: &OptionChainSlice) -> anyhow::Result<()> {
958 self.dispatch_on_option_chain(slice.clone())
959 .map_err(|e| anyhow::anyhow!("Python on_option_chain failed: {e}"))
960 }
961
962 fn on_historical_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
963 Python::attach(|py| {
964 let py_data: Py<PyAny> = if let Some(custom_data) = data.downcast_ref::<CustomData>() {
965 Py::new(py, custom_data.clone())?.into_any()
966 } else {
967 anyhow::bail!("Failed to convert historical data to Python: unsupported type");
968 };
969 self.dispatch_on_historical_data(py_data)
970 .map_err(|e| anyhow::anyhow!("Python on_historical_data failed: {e}"))
971 })
972 }
973
974 fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
975 self.dispatch_on_historical_quotes(quotes.to_vec())
976 .map_err(|e| anyhow::anyhow!("Python on_historical_quotes failed: {e}"))
977 }
978
979 fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
980 self.dispatch_on_historical_trades(trades.to_vec())
981 .map_err(|e| anyhow::anyhow!("Python on_historical_trades failed: {e}"))
982 }
983
984 fn on_historical_funding_rates(
985 &mut self,
986 funding_rates: &[FundingRateUpdate],
987 ) -> anyhow::Result<()> {
988 self.dispatch_on_historical_funding_rates(funding_rates.to_vec())
989 .map_err(|e| anyhow::anyhow!("Python on_historical_funding_rates failed: {e}"))
990 }
991
992 fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
993 self.dispatch_on_historical_bars(bars.to_vec())
994 .map_err(|e| anyhow::anyhow!("Python on_historical_bars failed: {e}"))
995 }
996
997 fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
998 self.dispatch_on_historical_mark_prices(mark_prices.to_vec())
999 .map_err(|e| anyhow::anyhow!("Python on_historical_mark_prices failed: {e}"))
1000 }
1001
1002 fn on_historical_index_prices(
1003 &mut self,
1004 index_prices: &[IndexPriceUpdate],
1005 ) -> anyhow::Result<()> {
1006 self.dispatch_on_historical_index_prices(index_prices.to_vec())
1007 .map_err(|e| anyhow::anyhow!("Python on_historical_index_prices failed: {e}"))
1008 }
1009
1010 fn on_order_filled(&mut self, event: &OrderFilled) -> anyhow::Result<()> {
1011 self.dispatch_on_order_filled(*event)
1012 .map_err(|e| anyhow::anyhow!("Python on_order_filled failed: {e}"))
1013 }
1014
1015 fn on_order_canceled(&mut self, event: &OrderCanceled) -> anyhow::Result<()> {
1016 self.dispatch_on_order_canceled(*event)
1017 .map_err(|e| anyhow::anyhow!("Python on_order_canceled failed: {e}"))
1018 }
1019}
1020
1021#[allow(non_camel_case_types)]
1023#[pyo3::pyclass(
1024 module = "nautilus_trader.trading",
1025 name = "Strategy",
1026 unsendable,
1027 subclass
1028)]
1029#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.trading")]
1030pub struct PyStrategy {
1031 inner: Rc<UnsafeCell<PyStrategyInner>>,
1032}
1033
1034impl Debug for PyStrategy {
1035 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1036 f.debug_struct(stringify!(PyStrategy))
1037 .field("inner", &self.inner())
1038 .finish()
1039 }
1040}
1041
1042impl PyStrategy {
1043 #[inline]
1044 #[allow(unsafe_code)]
1045 pub(crate) fn inner(&self) -> &PyStrategyInner {
1046 unsafe { &*self.inner.get() }
1049 }
1050
1051 #[inline]
1052 #[allow(unsafe_code, clippy::mut_from_ref)]
1053 pub(crate) fn inner_mut(&self) -> &mut PyStrategyInner {
1054 unsafe { &mut *self.inner.get() }
1057 }
1058}
1059
1060impl PyStrategy {
1061 pub fn new(config: Option<StrategyConfig>) -> Self {
1063 let config = config.unwrap_or_default();
1064 let core = StrategyCore::new(config);
1065 let clock = PyClock::new_test();
1066 let logger = PyLogger::new(core.actor.actor_id.as_str());
1067
1068 let inner = PyStrategyInner {
1069 core,
1070 py_self: None,
1071 clock,
1072 logger,
1073 };
1074
1075 Self {
1076 inner: Rc::new(UnsafeCell::new(inner)),
1077 }
1078 }
1079
1080 pub fn set_python_instance(&mut self, py_obj: Py<PyAny>) {
1082 self.inner_mut().py_self = Some(py_obj);
1083 }
1084
1085 pub fn set_strategy_id(&mut self, strategy_id: StrategyId) {
1089 let actor_id = ActorId::from(strategy_id.inner().as_str());
1090 let inner = self.inner_mut();
1091 inner.core.config.strategy_id = Some(strategy_id);
1092 inner.core.actor.config.actor_id = Some(actor_id);
1093 inner.core.actor.actor_id = actor_id;
1094 }
1095
1096 pub fn set_log_events(&mut self, log_events: bool) {
1098 let inner = self.inner_mut();
1099 inner.core.config.log_events = log_events;
1100 inner.core.actor.config.log_events = log_events;
1101 }
1102
1103 pub fn set_log_commands(&mut self, log_commands: bool) {
1105 let inner = self.inner_mut();
1106 inner.core.config.log_commands = log_commands;
1107 inner.core.actor.config.log_commands = log_commands;
1108 }
1109
1110 pub fn strategy_id(&self) -> StrategyId {
1112 StrategyId::from(self.inner().core.actor.actor_id.inner().as_str())
1113 }
1114
1115 pub fn is_registered(&self) -> bool {
1117 self.inner().core.actor.is_registered()
1118 }
1119
1120 pub fn register(
1126 &mut self,
1127 trader_id: TraderId,
1128 clock: Rc<RefCell<dyn Clock>>,
1129 cache: Rc<RefCell<Cache>>,
1130 portfolio: Rc<RefCell<Portfolio>>,
1131 ) -> anyhow::Result<()> {
1132 let inner = self.inner_mut();
1133 inner.core.register(trader_id, clock, cache, portfolio)?;
1134
1135 inner.clock = PyClock::from_rc(inner.core.actor.clock_rc());
1136
1137 let actor_id = inner.core.actor.actor_id.inner();
1138 let callback = TimeEventCallback::from(move |event: TimeEvent| {
1139 if let Some(mut strategy) = try_get_actor_unchecked::<PyStrategyInner>(&actor_id) {
1140 if let Err(e) = DataActor::on_time_event(&mut *strategy, &event) {
1141 log::error!("Python time event handler failed for strategy {actor_id}: {e}");
1142 }
1143 } else {
1144 log::error!("Strategy {actor_id} not found for time event handling");
1145 }
1146 });
1147
1148 inner.clock.inner_mut().register_default_handler(callback);
1149
1150 Component::initialize(inner)
1151 }
1152
1153 pub fn register_in_global_registries(&self) {
1155 let inner = self.inner();
1156 let component_id = Component::component_id(inner).inner();
1157 let actor_id = Actor::id(inner);
1158
1159 let inner_ref: Rc<UnsafeCell<PyStrategyInner>> = self.inner.clone();
1160
1161 let component_trait_ref: Rc<UnsafeCell<dyn Component>> = inner_ref.clone();
1162 get_component_registry().insert(component_id, component_trait_ref);
1163
1164 let actor_trait_ref: Rc<UnsafeCell<dyn Actor>> = inner_ref;
1165 get_actor_registry().insert(actor_id, actor_trait_ref);
1166 }
1167}
1168
1169#[pyo3::pymethods]
1170#[pyo3_stub_gen::derive::gen_stub_pymethods]
1171impl PyStrategy {
1172 #[new]
1183 #[pyo3(signature = (config=None))]
1184 fn py_new(config: Option<Py<PyAny>>) -> Self {
1185 let strategy_config =
1186 config.and_then(|obj| Python::attach(|py| obj.extract::<StrategyConfig>(py).ok()));
1187 Self::new(strategy_config)
1188 }
1189
1190 #[pyo3(signature = (config=None))]
1192 #[allow(unused_variables, clippy::needless_pass_by_value)]
1193 fn __init__(slf: &Bound<'_, Self>, config: Option<Py<PyAny>>) {
1194 let py_self: Py<PyAny> = slf.clone().unbind().into_any();
1195 slf.borrow_mut().set_python_instance(py_self);
1196 }
1197
1198 #[getter]
1199 #[pyo3(name = "trader_id")]
1200 fn py_trader_id(&self) -> Option<TraderId> {
1201 self.inner().core.trader_id()
1202 }
1203
1204 #[getter]
1205 #[pyo3(name = "strategy_id")]
1206 fn py_strategy_id(&self) -> StrategyId {
1207 StrategyId::from(self.inner().core.actor.actor_id.inner().as_str())
1208 }
1209
1210 #[getter]
1211 #[pyo3(name = "clock")]
1212 fn py_clock(&self) -> PyResult<PyClock> {
1213 let inner = self.inner();
1214 if inner.core.actor.is_registered() {
1215 Ok(inner.clock.clone())
1216 } else {
1217 Err(to_pyruntime_err(
1218 "Strategy must be registered with a trader before accessing clock",
1219 ))
1220 }
1221 }
1222
1223 #[getter]
1224 #[pyo3(name = "cache")]
1225 fn py_cache(&self) -> PyResult<PyCache> {
1226 let inner = self.inner();
1227 if inner.core.actor.is_registered() {
1228 Ok(PyCache::from_rc(inner.core.actor.cache_rc()))
1229 } else {
1230 Err(to_pyruntime_err(
1231 "Strategy must be registered with a trader before accessing cache",
1232 ))
1233 }
1234 }
1235
1236 #[getter]
1237 #[pyo3(name = "log")]
1238 fn py_log(&self) -> PyLogger {
1239 self.inner().logger.clone()
1240 }
1241
1242 #[pyo3(name = "state")]
1243 fn py_state(&self) -> ComponentState {
1244 self.inner().core.actor.state()
1245 }
1246
1247 #[pyo3(name = "is_ready")]
1248 fn py_is_ready(&self) -> bool {
1249 Component::is_ready(self.inner())
1250 }
1251
1252 #[pyo3(name = "is_running")]
1253 fn py_is_running(&self) -> bool {
1254 Component::is_running(self.inner())
1255 }
1256
1257 #[pyo3(name = "is_stopped")]
1258 fn py_is_stopped(&self) -> bool {
1259 Component::is_stopped(self.inner())
1260 }
1261
1262 #[pyo3(name = "is_disposed")]
1263 fn py_is_disposed(&self) -> bool {
1264 Component::is_disposed(self.inner())
1265 }
1266
1267 #[pyo3(name = "is_degraded")]
1268 fn py_is_degraded(&self) -> bool {
1269 Component::is_degraded(self.inner())
1270 }
1271
1272 #[pyo3(name = "is_faulted")]
1273 fn py_is_faulted(&self) -> bool {
1274 Component::is_faulted(self.inner())
1275 }
1276
1277 #[pyo3(name = "start")]
1278 fn py_start(&mut self) -> PyResult<()> {
1279 Component::start(self.inner_mut()).map_err(to_pyruntime_err)
1280 }
1281
1282 #[pyo3(name = "stop")]
1283 fn py_stop(&mut self) -> PyResult<()> {
1284 Component::stop(self.inner_mut()).map_err(to_pyruntime_err)
1285 }
1286
1287 #[pyo3(name = "resume")]
1288 fn py_resume(&mut self) -> PyResult<()> {
1289 Component::resume(self.inner_mut()).map_err(to_pyruntime_err)
1290 }
1291
1292 #[pyo3(name = "reset")]
1293 fn py_reset(&mut self) -> PyResult<()> {
1294 Component::reset(self.inner_mut()).map_err(to_pyruntime_err)
1295 }
1296
1297 #[pyo3(name = "dispose")]
1298 fn py_dispose(&mut self) -> PyResult<()> {
1299 Component::dispose(self.inner_mut()).map_err(to_pyruntime_err)
1300 }
1301
1302 #[pyo3(name = "degrade")]
1303 fn py_degrade(&mut self) -> PyResult<()> {
1304 Component::degrade(self.inner_mut()).map_err(to_pyruntime_err)
1305 }
1306
1307 #[pyo3(name = "fault")]
1308 fn py_fault(&mut self) -> PyResult<()> {
1309 Component::fault(self.inner_mut()).map_err(to_pyruntime_err)
1310 }
1311
1312 #[pyo3(name = "submit_order")]
1313 #[pyo3(signature = (order, position_id=None, client_id=None, params=None))]
1314 fn py_submit_order(
1315 &mut self,
1316 py: Python<'_>,
1317 order: Py<PyAny>,
1318 position_id: Option<PositionId>,
1319 client_id: Option<ClientId>,
1320 params: Option<Py<PyDict>>,
1321 ) -> PyResult<()> {
1322 let order = pyobject_to_order_any(py, order)?;
1323 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1324 match params {
1325 Some(dict) => from_pydict(py, dict),
1326 None => Ok(None),
1327 }
1328 })?;
1329 let inner = self.inner_mut();
1330 match params_map {
1331 Some(p) => Strategy::submit_order_with_params(inner, order, position_id, client_id, p),
1332 None => Strategy::submit_order(inner, order, position_id, client_id),
1333 }
1334 .map_err(to_pyruntime_err)
1335 }
1336
1337 #[pyo3(name = "modify_order")]
1338 #[pyo3(signature = (order, quantity=None, price=None, trigger_price=None, client_id=None, params=None))]
1339 #[expect(clippy::too_many_arguments)]
1340 fn py_modify_order(
1341 &mut self,
1342 py: Python<'_>,
1343 order: Py<PyAny>,
1344 quantity: Option<Quantity>,
1345 price: Option<Price>,
1346 trigger_price: Option<Price>,
1347 client_id: Option<ClientId>,
1348 params: Option<Py<PyDict>>,
1349 ) -> PyResult<()> {
1350 let order = pyobject_to_order_any(py, order)?;
1351 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1352 match params {
1353 Some(dict) => from_pydict(py, dict),
1354 None => Ok(None),
1355 }
1356 })?;
1357 let inner = self.inner_mut();
1358
1359 match params_map {
1360 Some(p) => Strategy::modify_order_with_params(
1361 inner,
1362 order,
1363 quantity,
1364 price,
1365 trigger_price,
1366 client_id,
1367 p,
1368 ),
1369 None => Strategy::modify_order(inner, order, quantity, price, trigger_price, client_id),
1370 }
1371 .map_err(to_pyruntime_err)
1372 }
1373
1374 #[pyo3(name = "cancel_order")]
1375 #[pyo3(signature = (order, client_id=None, params=None))]
1376 fn py_cancel_order(
1377 &mut self,
1378 py: Python<'_>,
1379 order: Py<PyAny>,
1380 client_id: Option<ClientId>,
1381 params: Option<Py<PyDict>>,
1382 ) -> PyResult<()> {
1383 let order = pyobject_to_order_any(py, order)?;
1384 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1385 match params {
1386 Some(dict) => from_pydict(py, dict),
1387 None => Ok(None),
1388 }
1389 })?;
1390 let inner = self.inner_mut();
1391 match params_map {
1392 Some(p) => Strategy::cancel_order_with_params(inner, order, client_id, p),
1393 None => Strategy::cancel_order(inner, order, client_id),
1394 }
1395 .map_err(to_pyruntime_err)
1396 }
1397
1398 #[pyo3(name = "cancel_orders")]
1399 #[pyo3(signature = (orders, client_id=None, params=None))]
1400 fn py_cancel_orders(
1401 &mut self,
1402 py: Python<'_>,
1403 orders: Vec<Py<PyAny>>,
1404 client_id: Option<ClientId>,
1405 params: Option<Py<PyDict>>,
1406 ) -> PyResult<()> {
1407 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1408 match params {
1409 Some(dict) => from_pydict(py, dict),
1410 None => Ok(None),
1411 }
1412 })?;
1413 let orders: Vec<OrderAny> = orders
1414 .into_iter()
1415 .map(|o| pyobject_to_order_any(py, o))
1416 .collect::<PyResult<Vec<_>>>()?;
1417 Strategy::cancel_orders(self.inner_mut(), orders, client_id, params_map)
1418 .map_err(to_pyruntime_err)
1419 }
1420
1421 #[pyo3(name = "cancel_all_orders")]
1422 #[pyo3(signature = (instrument_id, order_side=None, client_id=None, params=None))]
1423 fn py_cancel_all_orders(
1424 &mut self,
1425 instrument_id: InstrumentId,
1426 order_side: Option<OrderSide>,
1427 client_id: Option<ClientId>,
1428 params: Option<Py<PyDict>>,
1429 ) -> PyResult<()> {
1430 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1431 match params {
1432 Some(dict) => from_pydict(py, dict),
1433 None => Ok(None),
1434 }
1435 })?;
1436 let inner = self.inner_mut();
1437
1438 match params_map {
1439 Some(p) => Strategy::cancel_all_orders_with_params(
1440 inner,
1441 instrument_id,
1442 order_side,
1443 client_id,
1444 p,
1445 ),
1446 None => Strategy::cancel_all_orders(inner, instrument_id, order_side, client_id),
1447 }
1448 .map_err(to_pyruntime_err)
1449 }
1450
1451 #[pyo3(name = "close_position")]
1452 #[pyo3(signature = (position, client_id=None, tags=None, time_in_force=None, reduce_only=None, quote_quantity=None))]
1453 fn py_close_position(
1454 &mut self,
1455 position: &Position,
1456 client_id: Option<ClientId>,
1457 tags: Option<Vec<String>>,
1458 time_in_force: Option<TimeInForce>,
1459 reduce_only: Option<bool>,
1460 quote_quantity: Option<bool>,
1461 ) -> PyResult<()> {
1462 let tags = tags.map(|t| t.into_iter().map(|s| Ustr::from(&s)).collect());
1463 Strategy::close_position(
1464 self.inner_mut(),
1465 position,
1466 client_id,
1467 tags,
1468 time_in_force,
1469 reduce_only,
1470 quote_quantity,
1471 )
1472 .map_err(to_pyruntime_err)
1473 }
1474
1475 #[pyo3(name = "close_all_positions")]
1476 #[pyo3(signature = (instrument_id, position_side=None, client_id=None, tags=None, time_in_force=None, reduce_only=None, quote_quantity=None))]
1477 #[expect(clippy::too_many_arguments)]
1478 fn py_close_all_positions(
1479 &mut self,
1480 instrument_id: InstrumentId,
1481 position_side: Option<PositionSide>,
1482 client_id: Option<ClientId>,
1483 tags: Option<Vec<String>>,
1484 time_in_force: Option<TimeInForce>,
1485 reduce_only: Option<bool>,
1486 quote_quantity: Option<bool>,
1487 ) -> PyResult<()> {
1488 let tags = tags.map(|t| t.into_iter().map(|s| Ustr::from(&s)).collect());
1489 Strategy::close_all_positions(
1490 self.inner_mut(),
1491 instrument_id,
1492 position_side,
1493 client_id,
1494 tags,
1495 time_in_force,
1496 reduce_only,
1497 quote_quantity,
1498 )
1499 .map_err(to_pyruntime_err)
1500 }
1501
1502 #[pyo3(name = "query_account")]
1503 #[pyo3(signature = (account_id, client_id=None, params=None))]
1504 fn py_query_account(
1505 &mut self,
1506 py: Python<'_>,
1507 account_id: AccountId,
1508 client_id: Option<ClientId>,
1509 params: Option<Py<PyDict>>,
1510 ) -> PyResult<()> {
1511 let params_map = match params {
1512 Some(dict) => from_pydict(py, dict)?,
1513 None => None,
1514 };
1515 Strategy::query_account(self.inner_mut(), account_id, client_id, params_map)
1516 .map_err(to_pyruntime_err)
1517 }
1518
1519 #[pyo3(name = "query_order")]
1520 #[pyo3(signature = (order, client_id=None, params=None))]
1521 fn py_query_order(
1522 &mut self,
1523 py: Python<'_>,
1524 order: Py<PyAny>,
1525 client_id: Option<ClientId>,
1526 params: Option<Py<PyDict>>,
1527 ) -> PyResult<()> {
1528 let order = pyobject_to_order_any(py, order)?;
1529 let params_map = match params {
1530 Some(dict) => from_pydict(py, dict)?,
1531 None => None,
1532 };
1533 Strategy::query_order(self.inner_mut(), &order, client_id, params_map)
1534 .map_err(to_pyruntime_err)
1535 }
1536
1537 #[pyo3(name = "on_start")]
1538 fn py_on_start(&mut self) {}
1539
1540 #[pyo3(name = "on_stop")]
1541 fn py_on_stop(&mut self) {}
1542
1543 #[pyo3(name = "on_resume")]
1544 fn py_on_resume(&mut self) {}
1545
1546 #[pyo3(name = "on_reset")]
1547 fn py_on_reset(&mut self) {}
1548
1549 #[pyo3(name = "on_dispose")]
1550 fn py_on_dispose(&mut self) {}
1551
1552 #[pyo3(name = "on_degrade")]
1553 fn py_on_degrade(&mut self) {}
1554
1555 #[pyo3(name = "on_fault")]
1556 fn py_on_fault(&mut self) {}
1557
1558 #[allow(unused_variables, clippy::needless_pass_by_value)]
1559 #[pyo3(name = "on_time_event")]
1560 fn py_on_time_event(&mut self, event: TimeEvent) {}
1561
1562 #[allow(unused_variables, clippy::needless_pass_by_value)]
1563 #[pyo3(name = "on_data")]
1564 fn py_on_data(&mut self, data: Py<PyAny>) {}
1565
1566 #[allow(unused_variables)]
1567 #[pyo3(name = "on_signal")]
1568 fn py_on_signal(&mut self, signal: &Signal) {}
1569
1570 #[allow(unused_variables, clippy::needless_pass_by_value)]
1571 #[pyo3(name = "on_instrument")]
1572 fn py_on_instrument(&mut self, instrument: Py<PyAny>) {}
1573
1574 #[allow(unused_variables)]
1575 #[pyo3(name = "on_quote")]
1576 fn py_on_quote(&mut self, quote: QuoteTick) {}
1577
1578 #[allow(unused_variables)]
1579 #[pyo3(name = "on_trade")]
1580 fn py_on_trade(&mut self, trade: TradeTick) {}
1581
1582 #[allow(unused_variables)]
1583 #[pyo3(name = "on_bar")]
1584 fn py_on_bar(&mut self, bar: Bar) {}
1585
1586 #[allow(unused_variables, clippy::needless_pass_by_value)]
1587 #[pyo3(name = "on_book_deltas")]
1588 fn py_on_book_deltas(&mut self, deltas: OrderBookDeltas) {}
1589
1590 #[allow(unused_variables)]
1591 #[pyo3(name = "on_book")]
1592 fn py_on_book(&mut self, book: &OrderBook) {}
1593
1594 #[allow(unused_variables)]
1595 #[pyo3(name = "on_mark_price")]
1596 fn py_on_mark_price(&mut self, mark_price: MarkPriceUpdate) {}
1597
1598 #[allow(unused_variables)]
1599 #[pyo3(name = "on_index_price")]
1600 fn py_on_index_price(&mut self, index_price: IndexPriceUpdate) {}
1601
1602 #[allow(unused_variables)]
1603 #[pyo3(name = "on_funding_rate")]
1604 fn py_on_funding_rate(&mut self, funding_rate: FundingRateUpdate) {}
1605
1606 #[allow(unused_variables)]
1607 #[pyo3(name = "on_instrument_status")]
1608 fn py_on_instrument_status(&mut self, status: InstrumentStatus) {}
1609
1610 #[allow(unused_variables)]
1611 #[pyo3(name = "on_instrument_close")]
1612 fn py_on_instrument_close(&mut self, close: InstrumentClose) {}
1613
1614 #[allow(unused_variables)]
1615 #[pyo3(name = "on_option_greeks")]
1616 fn py_on_option_greeks(&mut self, greeks: OptionGreeks) {}
1617
1618 #[allow(unused_variables, clippy::needless_pass_by_value)]
1619 #[pyo3(name = "on_option_chain")]
1620 fn py_on_option_chain(&mut self, slice: OptionChainSlice) {}
1621
1622 #[allow(unused_variables, clippy::needless_pass_by_value)]
1623 #[pyo3(name = "on_order_initialized")]
1624 fn py_on_order_initialized(&mut self, event: OrderInitialized) {}
1625
1626 #[allow(unused_variables)]
1627 #[pyo3(name = "on_order_denied")]
1628 fn py_on_order_denied(&mut self, event: OrderDenied) {}
1629
1630 #[allow(unused_variables)]
1631 #[pyo3(name = "on_order_emulated")]
1632 fn py_on_order_emulated(&mut self, event: OrderEmulated) {}
1633
1634 #[allow(unused_variables)]
1635 #[pyo3(name = "on_order_released")]
1636 fn py_on_order_released(&mut self, event: OrderReleased) {}
1637
1638 #[allow(unused_variables)]
1639 #[pyo3(name = "on_order_submitted")]
1640 fn py_on_order_submitted(&mut self, event: OrderSubmitted) {}
1641
1642 #[allow(unused_variables)]
1643 #[pyo3(name = "on_order_rejected")]
1644 fn py_on_order_rejected(&mut self, event: OrderRejected) {}
1645
1646 #[allow(unused_variables)]
1647 #[pyo3(name = "on_order_accepted")]
1648 fn py_on_order_accepted(&mut self, event: OrderAccepted) {}
1649
1650 #[allow(unused_variables)]
1651 #[pyo3(name = "on_order_expired")]
1652 fn py_on_order_expired(&mut self, event: OrderExpired) {}
1653
1654 #[allow(unused_variables)]
1655 #[pyo3(name = "on_order_triggered")]
1656 fn py_on_order_triggered(&mut self, event: OrderTriggered) {}
1657
1658 #[allow(unused_variables)]
1659 #[pyo3(name = "on_order_pending_update")]
1660 fn py_on_order_pending_update(&mut self, event: OrderPendingUpdate) {}
1661
1662 #[allow(unused_variables)]
1663 #[pyo3(name = "on_order_pending_cancel")]
1664 fn py_on_order_pending_cancel(&mut self, event: OrderPendingCancel) {}
1665
1666 #[allow(unused_variables)]
1667 #[pyo3(name = "on_order_modify_rejected")]
1668 fn py_on_order_modify_rejected(&mut self, event: OrderModifyRejected) {}
1669
1670 #[allow(unused_variables)]
1671 #[pyo3(name = "on_order_cancel_rejected")]
1672 fn py_on_order_cancel_rejected(&mut self, event: OrderCancelRejected) {}
1673
1674 #[allow(unused_variables)]
1675 #[pyo3(name = "on_order_updated")]
1676 fn py_on_order_updated(&mut self, event: OrderUpdated) {}
1677
1678 #[allow(unused_variables)]
1679 #[pyo3(name = "on_order_canceled")]
1680 fn py_on_order_canceled(&mut self, event: OrderCanceled) {}
1681
1682 #[allow(unused_variables)]
1683 #[pyo3(name = "on_order_filled")]
1684 fn py_on_order_filled(&mut self, event: OrderFilled) {}
1685
1686 #[allow(unused_variables, clippy::needless_pass_by_value)]
1687 #[pyo3(name = "on_position_opened")]
1688 fn py_on_position_opened(&mut self, event: PositionOpened) {}
1689
1690 #[allow(unused_variables, clippy::needless_pass_by_value)]
1691 #[pyo3(name = "on_position_changed")]
1692 fn py_on_position_changed(&mut self, event: PositionChanged) {}
1693
1694 #[allow(unused_variables, clippy::needless_pass_by_value)]
1695 #[pyo3(name = "on_position_closed")]
1696 fn py_on_position_closed(&mut self, event: PositionClosed) {}
1697
1698 #[allow(unused_variables, clippy::needless_pass_by_value)]
1699 #[pyo3(name = "on_historical_data")]
1700 fn py_on_historical_data(&mut self, data: Py<PyAny>) {
1701 }
1703
1704 #[allow(unused_variables, clippy::needless_pass_by_value)]
1705 #[pyo3(name = "on_historical_quotes")]
1706 fn py_on_historical_quotes(&mut self, quotes: Vec<QuoteTick>) {
1707 }
1709
1710 #[allow(unused_variables, clippy::needless_pass_by_value)]
1711 #[pyo3(name = "on_historical_trades")]
1712 fn py_on_historical_trades(&mut self, trades: Vec<TradeTick>) {
1713 }
1715
1716 #[allow(unused_variables, clippy::needless_pass_by_value)]
1717 #[pyo3(name = "on_historical_funding_rates")]
1718 fn py_on_historical_funding_rates(&mut self, funding_rates: Vec<FundingRateUpdate>) {
1719 }
1721
1722 #[allow(unused_variables, clippy::needless_pass_by_value)]
1723 #[pyo3(name = "on_historical_bars")]
1724 fn py_on_historical_bars(&mut self, bars: Vec<Bar>) {
1725 }
1727
1728 #[allow(unused_variables, clippy::needless_pass_by_value)]
1729 #[pyo3(name = "on_historical_mark_prices")]
1730 fn py_on_historical_mark_prices(&mut self, mark_prices: Vec<MarkPriceUpdate>) {
1731 }
1733
1734 #[allow(unused_variables, clippy::needless_pass_by_value)]
1735 #[pyo3(name = "on_historical_index_prices")]
1736 fn py_on_historical_index_prices(&mut self, index_prices: Vec<IndexPriceUpdate>) {
1737 }
1739
1740 #[pyo3(name = "subscribe_data")]
1741 #[pyo3(signature = (data_type, client_id=None, params=None))]
1742 fn py_subscribe_data(
1743 &mut self,
1744 data_type: DataType,
1745 client_id: Option<ClientId>,
1746 params: Option<Py<PyDict>>,
1747 ) -> PyResult<()> {
1748 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1749 match params {
1750 Some(dict) => from_pydict(py, dict),
1751 None => Ok(None),
1752 }
1753 })?;
1754 DataActor::subscribe_data(self.inner_mut(), data_type, client_id, params_map);
1755 Ok(())
1756 }
1757
1758 #[pyo3(name = "subscribe_instruments")]
1759 #[pyo3(signature = (venue, client_id=None, params=None))]
1760 fn py_subscribe_instruments(
1761 &mut self,
1762 venue: Venue,
1763 client_id: Option<ClientId>,
1764 params: Option<Py<PyDict>>,
1765 ) -> PyResult<()> {
1766 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1767 match params {
1768 Some(dict) => from_pydict(py, dict),
1769 None => Ok(None),
1770 }
1771 })?;
1772 DataActor::subscribe_instruments(self.inner_mut(), venue, client_id, params_map);
1773 Ok(())
1774 }
1775
1776 #[pyo3(name = "subscribe_instrument")]
1777 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1778 fn py_subscribe_instrument(
1779 &mut self,
1780 instrument_id: InstrumentId,
1781 client_id: Option<ClientId>,
1782 params: Option<Py<PyDict>>,
1783 ) -> PyResult<()> {
1784 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1785 match params {
1786 Some(dict) => from_pydict(py, dict),
1787 None => Ok(None),
1788 }
1789 })?;
1790 DataActor::subscribe_instrument(self.inner_mut(), instrument_id, client_id, params_map);
1791 Ok(())
1792 }
1793
1794 #[pyo3(name = "subscribe_book_deltas")]
1795 #[pyo3(signature = (instrument_id, book_type, depth=None, client_id=None, managed=false, params=None))]
1796 fn py_subscribe_book_deltas(
1797 &mut self,
1798 instrument_id: InstrumentId,
1799 book_type: BookType,
1800 depth: Option<usize>,
1801 client_id: Option<ClientId>,
1802 managed: bool,
1803 params: Option<Py<PyDict>>,
1804 ) -> PyResult<()> {
1805 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1806 match params {
1807 Some(dict) => from_pydict(py, dict),
1808 None => Ok(None),
1809 }
1810 })?;
1811 let depth = depth.and_then(NonZeroUsize::new);
1812 DataActor::subscribe_book_deltas(
1813 self.inner_mut(),
1814 instrument_id,
1815 book_type,
1816 depth,
1817 client_id,
1818 managed,
1819 params_map,
1820 );
1821 Ok(())
1822 }
1823
1824 #[pyo3(name = "subscribe_book_at_interval")]
1825 #[pyo3(signature = (instrument_id, book_type, interval_ms, depth=None, client_id=None, params=None))]
1826 fn py_subscribe_book_at_interval(
1827 &mut self,
1828 instrument_id: InstrumentId,
1829 book_type: BookType,
1830 interval_ms: usize,
1831 depth: Option<usize>,
1832 client_id: Option<ClientId>,
1833 params: Option<Py<PyDict>>,
1834 ) -> PyResult<()> {
1835 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1836 match params {
1837 Some(dict) => from_pydict(py, dict),
1838 None => Ok(None),
1839 }
1840 })?;
1841 let depth = depth.and_then(NonZeroUsize::new);
1842 let interval_ms = NonZeroUsize::new(interval_ms)
1843 .ok_or_else(|| to_pyvalue_err("interval_ms must be > 0"))?;
1844
1845 DataActor::subscribe_book_at_interval(
1846 self.inner_mut(),
1847 instrument_id,
1848 book_type,
1849 depth,
1850 interval_ms,
1851 client_id,
1852 params_map,
1853 );
1854 Ok(())
1855 }
1856
1857 #[pyo3(name = "subscribe_quotes")]
1858 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1859 fn py_subscribe_quotes(
1860 &mut self,
1861 instrument_id: InstrumentId,
1862 client_id: Option<ClientId>,
1863 params: Option<Py<PyDict>>,
1864 ) -> PyResult<()> {
1865 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1866 match params {
1867 Some(dict) => from_pydict(py, dict),
1868 None => Ok(None),
1869 }
1870 })?;
1871 DataActor::subscribe_quotes(self.inner_mut(), instrument_id, client_id, params_map);
1872 Ok(())
1873 }
1874
1875 #[pyo3(name = "subscribe_trades")]
1876 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1877 fn py_subscribe_trades(
1878 &mut self,
1879 instrument_id: InstrumentId,
1880 client_id: Option<ClientId>,
1881 params: Option<Py<PyDict>>,
1882 ) -> PyResult<()> {
1883 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1884 match params {
1885 Some(dict) => from_pydict(py, dict),
1886 None => Ok(None),
1887 }
1888 })?;
1889 DataActor::subscribe_trades(self.inner_mut(), instrument_id, client_id, params_map);
1890 Ok(())
1891 }
1892
1893 #[pyo3(name = "subscribe_bars")]
1894 #[pyo3(signature = (bar_type, client_id=None, params=None))]
1895 fn py_subscribe_bars(
1896 &mut self,
1897 bar_type: BarType,
1898 client_id: Option<ClientId>,
1899 params: Option<Py<PyDict>>,
1900 ) -> PyResult<()> {
1901 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1902 match params {
1903 Some(dict) => from_pydict(py, dict),
1904 None => Ok(None),
1905 }
1906 })?;
1907 DataActor::subscribe_bars(self.inner_mut(), bar_type, client_id, params_map);
1908 Ok(())
1909 }
1910
1911 #[pyo3(name = "subscribe_mark_prices")]
1912 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1913 fn py_subscribe_mark_prices(
1914 &mut self,
1915 instrument_id: InstrumentId,
1916 client_id: Option<ClientId>,
1917 params: Option<Py<PyDict>>,
1918 ) -> PyResult<()> {
1919 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1920 match params {
1921 Some(dict) => from_pydict(py, dict),
1922 None => Ok(None),
1923 }
1924 })?;
1925 DataActor::subscribe_mark_prices(self.inner_mut(), instrument_id, client_id, params_map);
1926 Ok(())
1927 }
1928
1929 #[pyo3(name = "subscribe_index_prices")]
1930 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1931 fn py_subscribe_index_prices(
1932 &mut self,
1933 instrument_id: InstrumentId,
1934 client_id: Option<ClientId>,
1935 params: Option<Py<PyDict>>,
1936 ) -> PyResult<()> {
1937 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1938 match params {
1939 Some(dict) => from_pydict(py, dict),
1940 None => Ok(None),
1941 }
1942 })?;
1943 DataActor::subscribe_index_prices(self.inner_mut(), instrument_id, client_id, params_map);
1944 Ok(())
1945 }
1946
1947 #[pyo3(name = "subscribe_funding_rates")]
1948 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1949 fn py_subscribe_funding_rates(
1950 &mut self,
1951 instrument_id: InstrumentId,
1952 client_id: Option<ClientId>,
1953 params: Option<Py<PyDict>>,
1954 ) -> PyResult<()> {
1955 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1956 match params {
1957 Some(dict) => from_pydict(py, dict),
1958 None => Ok(None),
1959 }
1960 })?;
1961 DataActor::subscribe_funding_rates(self.inner_mut(), instrument_id, client_id, params_map);
1962 Ok(())
1963 }
1964
1965 #[pyo3(name = "subscribe_option_greeks")]
1966 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1967 fn py_subscribe_option_greeks(
1968 &mut self,
1969 instrument_id: InstrumentId,
1970 client_id: Option<ClientId>,
1971 params: Option<Py<PyDict>>,
1972 ) -> PyResult<()> {
1973 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1974 match params {
1975 Some(dict) => from_pydict(py, dict),
1976 None => Ok(None),
1977 }
1978 })?;
1979 DataActor::subscribe_option_greeks(self.inner_mut(), instrument_id, client_id, params_map);
1980 Ok(())
1981 }
1982
1983 #[pyo3(name = "subscribe_instrument_status")]
1984 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1985 fn py_subscribe_instrument_status(
1986 &mut self,
1987 instrument_id: InstrumentId,
1988 client_id: Option<ClientId>,
1989 params: Option<Py<PyDict>>,
1990 ) -> PyResult<()> {
1991 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
1992 match params {
1993 Some(dict) => from_pydict(py, dict),
1994 None => Ok(None),
1995 }
1996 })?;
1997 DataActor::subscribe_instrument_status(
1998 self.inner_mut(),
1999 instrument_id,
2000 client_id,
2001 params_map,
2002 );
2003 Ok(())
2004 }
2005
2006 #[pyo3(name = "subscribe_instrument_close")]
2007 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2008 fn py_subscribe_instrument_close(
2009 &mut self,
2010 instrument_id: InstrumentId,
2011 client_id: Option<ClientId>,
2012 params: Option<Py<PyDict>>,
2013 ) -> PyResult<()> {
2014 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2015 match params {
2016 Some(dict) => from_pydict(py, dict),
2017 None => Ok(None),
2018 }
2019 })?;
2020 DataActor::subscribe_instrument_close(
2021 self.inner_mut(),
2022 instrument_id,
2023 client_id,
2024 params_map,
2025 );
2026 Ok(())
2027 }
2028
2029 #[pyo3(name = "subscribe_option_chain")]
2030 #[pyo3(signature = (series_id, strike_range, snapshot_interval_ms=None, client_id=None, params=None))]
2031 fn py_subscribe_option_chain(
2032 &mut self,
2033 py: Python<'_>,
2034 series_id: OptionSeriesId,
2035 strike_range: PyStrikeRange,
2036 snapshot_interval_ms: Option<u64>,
2037 client_id: Option<ClientId>,
2038 params: Option<Py<PyDict>>,
2039 ) -> PyResult<()> {
2040 let params_map = match params {
2041 Some(dict) => from_pydict(py, dict)?,
2042 None => None,
2043 };
2044 DataActor::subscribe_option_chain(
2045 self.inner_mut(),
2046 series_id,
2047 strike_range.inner,
2048 snapshot_interval_ms,
2049 client_id,
2050 params_map,
2051 );
2052 Ok(())
2053 }
2054
2055 #[pyo3(name = "subscribe_order_fills")]
2056 #[pyo3(signature = (instrument_id))]
2057 fn py_subscribe_order_fills(&mut self, instrument_id: InstrumentId) {
2058 DataActor::subscribe_order_fills(self.inner_mut(), instrument_id);
2059 }
2060
2061 #[pyo3(name = "subscribe_order_cancels")]
2062 #[pyo3(signature = (instrument_id))]
2063 fn py_subscribe_order_cancels(&mut self, instrument_id: InstrumentId) {
2064 DataActor::subscribe_order_cancels(self.inner_mut(), instrument_id);
2065 }
2066
2067 #[pyo3(name = "unsubscribe_data")]
2068 #[pyo3(signature = (data_type, client_id=None, params=None))]
2069 fn py_unsubscribe_data(
2070 &mut self,
2071 data_type: DataType,
2072 client_id: Option<ClientId>,
2073 params: Option<Py<PyDict>>,
2074 ) -> PyResult<()> {
2075 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2076 match params {
2077 Some(dict) => from_pydict(py, dict),
2078 None => Ok(None),
2079 }
2080 })?;
2081 DataActor::unsubscribe_data(self.inner_mut(), data_type, client_id, params_map);
2082 Ok(())
2083 }
2084
2085 #[pyo3(name = "unsubscribe_instruments")]
2086 #[pyo3(signature = (venue, client_id=None, params=None))]
2087 fn py_unsubscribe_instruments(
2088 &mut self,
2089 venue: Venue,
2090 client_id: Option<ClientId>,
2091 params: Option<Py<PyDict>>,
2092 ) -> PyResult<()> {
2093 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2094 match params {
2095 Some(dict) => from_pydict(py, dict),
2096 None => Ok(None),
2097 }
2098 })?;
2099 DataActor::unsubscribe_instruments(self.inner_mut(), venue, client_id, params_map);
2100 Ok(())
2101 }
2102
2103 #[pyo3(name = "unsubscribe_instrument")]
2104 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2105 fn py_unsubscribe_instrument(
2106 &mut self,
2107 instrument_id: InstrumentId,
2108 client_id: Option<ClientId>,
2109 params: Option<Py<PyDict>>,
2110 ) -> PyResult<()> {
2111 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2112 match params {
2113 Some(dict) => from_pydict(py, dict),
2114 None => Ok(None),
2115 }
2116 })?;
2117 DataActor::unsubscribe_instrument(self.inner_mut(), instrument_id, client_id, params_map);
2118 Ok(())
2119 }
2120
2121 #[pyo3(name = "unsubscribe_book_deltas")]
2122 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2123 fn py_unsubscribe_book_deltas(
2124 &mut self,
2125 instrument_id: InstrumentId,
2126 client_id: Option<ClientId>,
2127 params: Option<Py<PyDict>>,
2128 ) -> PyResult<()> {
2129 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2130 match params {
2131 Some(dict) => from_pydict(py, dict),
2132 None => Ok(None),
2133 }
2134 })?;
2135 DataActor::unsubscribe_book_deltas(self.inner_mut(), instrument_id, client_id, params_map);
2136 Ok(())
2137 }
2138
2139 #[pyo3(name = "unsubscribe_book_at_interval")]
2140 #[pyo3(signature = (instrument_id, interval_ms, client_id=None, params=None))]
2141 fn py_unsubscribe_book_at_interval(
2142 &mut self,
2143 instrument_id: InstrumentId,
2144 interval_ms: usize,
2145 client_id: Option<ClientId>,
2146 params: Option<Py<PyDict>>,
2147 ) -> PyResult<()> {
2148 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2149 match params {
2150 Some(dict) => from_pydict(py, dict),
2151 None => Ok(None),
2152 }
2153 })?;
2154 let interval_ms = NonZeroUsize::new(interval_ms)
2155 .ok_or_else(|| to_pyvalue_err("interval_ms must be > 0"))?;
2156
2157 DataActor::unsubscribe_book_at_interval(
2158 self.inner_mut(),
2159 instrument_id,
2160 interval_ms,
2161 client_id,
2162 params_map,
2163 );
2164 Ok(())
2165 }
2166
2167 #[pyo3(name = "unsubscribe_quotes")]
2168 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2169 fn py_unsubscribe_quotes(
2170 &mut self,
2171 instrument_id: InstrumentId,
2172 client_id: Option<ClientId>,
2173 params: Option<Py<PyDict>>,
2174 ) -> PyResult<()> {
2175 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2176 match params {
2177 Some(dict) => from_pydict(py, dict),
2178 None => Ok(None),
2179 }
2180 })?;
2181 DataActor::unsubscribe_quotes(self.inner_mut(), instrument_id, client_id, params_map);
2182 Ok(())
2183 }
2184
2185 #[pyo3(name = "unsubscribe_trades")]
2186 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2187 fn py_unsubscribe_trades(
2188 &mut self,
2189 instrument_id: InstrumentId,
2190 client_id: Option<ClientId>,
2191 params: Option<Py<PyDict>>,
2192 ) -> PyResult<()> {
2193 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2194 match params {
2195 Some(dict) => from_pydict(py, dict),
2196 None => Ok(None),
2197 }
2198 })?;
2199 DataActor::unsubscribe_trades(self.inner_mut(), instrument_id, client_id, params_map);
2200 Ok(())
2201 }
2202
2203 #[pyo3(name = "unsubscribe_bars")]
2204 #[pyo3(signature = (bar_type, client_id=None, params=None))]
2205 fn py_unsubscribe_bars(
2206 &mut self,
2207 bar_type: BarType,
2208 client_id: Option<ClientId>,
2209 params: Option<Py<PyDict>>,
2210 ) -> PyResult<()> {
2211 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2212 match params {
2213 Some(dict) => from_pydict(py, dict),
2214 None => Ok(None),
2215 }
2216 })?;
2217 DataActor::unsubscribe_bars(self.inner_mut(), bar_type, client_id, params_map);
2218 Ok(())
2219 }
2220
2221 #[pyo3(name = "unsubscribe_mark_prices")]
2222 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2223 fn py_unsubscribe_mark_prices(
2224 &mut self,
2225 instrument_id: InstrumentId,
2226 client_id: Option<ClientId>,
2227 params: Option<Py<PyDict>>,
2228 ) -> PyResult<()> {
2229 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2230 match params {
2231 Some(dict) => from_pydict(py, dict),
2232 None => Ok(None),
2233 }
2234 })?;
2235 DataActor::unsubscribe_mark_prices(self.inner_mut(), instrument_id, client_id, params_map);
2236 Ok(())
2237 }
2238
2239 #[pyo3(name = "unsubscribe_index_prices")]
2240 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2241 fn py_unsubscribe_index_prices(
2242 &mut self,
2243 instrument_id: InstrumentId,
2244 client_id: Option<ClientId>,
2245 params: Option<Py<PyDict>>,
2246 ) -> PyResult<()> {
2247 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2248 match params {
2249 Some(dict) => from_pydict(py, dict),
2250 None => Ok(None),
2251 }
2252 })?;
2253 DataActor::unsubscribe_index_prices(self.inner_mut(), instrument_id, client_id, params_map);
2254 Ok(())
2255 }
2256
2257 #[pyo3(name = "unsubscribe_funding_rates")]
2258 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2259 fn py_unsubscribe_funding_rates(
2260 &mut self,
2261 instrument_id: InstrumentId,
2262 client_id: Option<ClientId>,
2263 params: Option<Py<PyDict>>,
2264 ) -> PyResult<()> {
2265 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2266 match params {
2267 Some(dict) => from_pydict(py, dict),
2268 None => Ok(None),
2269 }
2270 })?;
2271 DataActor::unsubscribe_funding_rates(
2272 self.inner_mut(),
2273 instrument_id,
2274 client_id,
2275 params_map,
2276 );
2277 Ok(())
2278 }
2279
2280 #[pyo3(name = "unsubscribe_option_greeks")]
2281 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2282 fn py_unsubscribe_option_greeks(
2283 &mut self,
2284 instrument_id: InstrumentId,
2285 client_id: Option<ClientId>,
2286 params: Option<Py<PyDict>>,
2287 ) -> PyResult<()> {
2288 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2289 match params {
2290 Some(dict) => from_pydict(py, dict),
2291 None => Ok(None),
2292 }
2293 })?;
2294 DataActor::unsubscribe_option_greeks(
2295 self.inner_mut(),
2296 instrument_id,
2297 client_id,
2298 params_map,
2299 );
2300 Ok(())
2301 }
2302
2303 #[pyo3(name = "unsubscribe_instrument_status")]
2304 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2305 fn py_unsubscribe_instrument_status(
2306 &mut self,
2307 instrument_id: InstrumentId,
2308 client_id: Option<ClientId>,
2309 params: Option<Py<PyDict>>,
2310 ) -> PyResult<()> {
2311 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2312 match params {
2313 Some(dict) => from_pydict(py, dict),
2314 None => Ok(None),
2315 }
2316 })?;
2317 DataActor::unsubscribe_instrument_status(
2318 self.inner_mut(),
2319 instrument_id,
2320 client_id,
2321 params_map,
2322 );
2323 Ok(())
2324 }
2325
2326 #[pyo3(name = "unsubscribe_instrument_close")]
2327 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
2328 fn py_unsubscribe_instrument_close(
2329 &mut self,
2330 instrument_id: InstrumentId,
2331 client_id: Option<ClientId>,
2332 params: Option<Py<PyDict>>,
2333 ) -> PyResult<()> {
2334 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2335 match params {
2336 Some(dict) => from_pydict(py, dict),
2337 None => Ok(None),
2338 }
2339 })?;
2340 DataActor::unsubscribe_instrument_close(
2341 self.inner_mut(),
2342 instrument_id,
2343 client_id,
2344 params_map,
2345 );
2346 Ok(())
2347 }
2348
2349 #[pyo3(name = "unsubscribe_option_chain")]
2350 #[pyo3(signature = (series_id, client_id=None))]
2351 fn py_unsubscribe_option_chain(
2352 &mut self,
2353 series_id: OptionSeriesId,
2354 client_id: Option<ClientId>,
2355 ) {
2356 DataActor::unsubscribe_option_chain(self.inner_mut(), series_id, client_id);
2357 }
2358
2359 #[pyo3(name = "unsubscribe_order_fills")]
2360 #[pyo3(signature = (instrument_id))]
2361 fn py_unsubscribe_order_fills(&mut self, instrument_id: InstrumentId) {
2362 DataActor::unsubscribe_order_fills(self.inner_mut(), instrument_id);
2363 }
2364
2365 #[pyo3(name = "unsubscribe_order_cancels")]
2366 #[pyo3(signature = (instrument_id))]
2367 fn py_unsubscribe_order_cancels(&mut self, instrument_id: InstrumentId) {
2368 DataActor::unsubscribe_order_cancels(self.inner_mut(), instrument_id);
2369 }
2370
2371 #[pyo3(name = "request_data")]
2372 #[pyo3(signature = (data_type, client_id, start=None, end=None, limit=None, params=None))]
2373 fn py_request_data(
2374 &mut self,
2375 data_type: DataType,
2376 client_id: ClientId,
2377 start: Option<u64>,
2378 end: Option<u64>,
2379 limit: Option<usize>,
2380 params: Option<Py<PyDict>>,
2381 ) -> PyResult<String> {
2382 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2383 match params {
2384 Some(dict) => from_pydict(py, dict),
2385 None => Ok(None),
2386 }
2387 })?;
2388 let limit = limit.and_then(NonZeroUsize::new);
2389 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2390 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2391
2392 let request_id = DataActor::request_data(
2393 self.inner_mut(),
2394 data_type,
2395 client_id,
2396 start,
2397 end,
2398 limit,
2399 params_map,
2400 )
2401 .map_err(to_pyvalue_err)?;
2402 Ok(request_id.to_string())
2403 }
2404
2405 #[pyo3(name = "request_instrument")]
2406 #[pyo3(signature = (instrument_id, start=None, end=None, client_id=None, params=None))]
2407 fn py_request_instrument(
2408 &mut self,
2409 instrument_id: InstrumentId,
2410 start: Option<u64>,
2411 end: Option<u64>,
2412 client_id: Option<ClientId>,
2413 params: Option<Py<PyDict>>,
2414 ) -> PyResult<String> {
2415 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2416 match params {
2417 Some(dict) => from_pydict(py, dict),
2418 None => Ok(None),
2419 }
2420 })?;
2421 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2422 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2423
2424 let request_id = DataActor::request_instrument(
2425 self.inner_mut(),
2426 instrument_id,
2427 start,
2428 end,
2429 client_id,
2430 params_map,
2431 )
2432 .map_err(to_pyvalue_err)?;
2433 Ok(request_id.to_string())
2434 }
2435
2436 #[pyo3(name = "request_instruments")]
2437 #[pyo3(signature = (venue=None, start=None, end=None, client_id=None, params=None))]
2438 fn py_request_instruments(
2439 &mut self,
2440 venue: Option<Venue>,
2441 start: Option<u64>,
2442 end: Option<u64>,
2443 client_id: Option<ClientId>,
2444 params: Option<Py<PyDict>>,
2445 ) -> PyResult<String> {
2446 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2447 match params {
2448 Some(dict) => from_pydict(py, dict),
2449 None => Ok(None),
2450 }
2451 })?;
2452 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2453 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2454
2455 let request_id = DataActor::request_instruments(
2456 self.inner_mut(),
2457 venue,
2458 start,
2459 end,
2460 client_id,
2461 params_map,
2462 )
2463 .map_err(to_pyvalue_err)?;
2464 Ok(request_id.to_string())
2465 }
2466
2467 #[pyo3(name = "request_book_snapshot")]
2468 #[pyo3(signature = (instrument_id, depth=None, client_id=None, params=None))]
2469 fn py_request_book_snapshot(
2470 &mut self,
2471 instrument_id: InstrumentId,
2472 depth: Option<usize>,
2473 client_id: Option<ClientId>,
2474 params: Option<Py<PyDict>>,
2475 ) -> PyResult<String> {
2476 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2477 match params {
2478 Some(dict) => from_pydict(py, dict),
2479 None => Ok(None),
2480 }
2481 })?;
2482 let depth = depth.and_then(NonZeroUsize::new);
2483
2484 let request_id = DataActor::request_book_snapshot(
2485 self.inner_mut(),
2486 instrument_id,
2487 depth,
2488 client_id,
2489 params_map,
2490 )
2491 .map_err(to_pyvalue_err)?;
2492 Ok(request_id.to_string())
2493 }
2494
2495 #[pyo3(name = "request_quotes")]
2496 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
2497 fn py_request_quotes(
2498 &mut self,
2499 instrument_id: InstrumentId,
2500 start: Option<u64>,
2501 end: Option<u64>,
2502 limit: Option<usize>,
2503 client_id: Option<ClientId>,
2504 params: Option<Py<PyDict>>,
2505 ) -> PyResult<String> {
2506 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2507 match params {
2508 Some(dict) => from_pydict(py, dict),
2509 None => Ok(None),
2510 }
2511 })?;
2512 let limit = limit.and_then(NonZeroUsize::new);
2513 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2514 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2515
2516 let request_id = DataActor::request_quotes(
2517 self.inner_mut(),
2518 instrument_id,
2519 start,
2520 end,
2521 limit,
2522 client_id,
2523 params_map,
2524 )
2525 .map_err(to_pyvalue_err)?;
2526 Ok(request_id.to_string())
2527 }
2528
2529 #[pyo3(name = "request_trades")]
2530 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
2531 fn py_request_trades(
2532 &mut self,
2533 instrument_id: InstrumentId,
2534 start: Option<u64>,
2535 end: Option<u64>,
2536 limit: Option<usize>,
2537 client_id: Option<ClientId>,
2538 params: Option<Py<PyDict>>,
2539 ) -> PyResult<String> {
2540 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2541 match params {
2542 Some(dict) => from_pydict(py, dict),
2543 None => Ok(None),
2544 }
2545 })?;
2546 let limit = limit.and_then(NonZeroUsize::new);
2547 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2548 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2549
2550 let request_id = DataActor::request_trades(
2551 self.inner_mut(),
2552 instrument_id,
2553 start,
2554 end,
2555 limit,
2556 client_id,
2557 params_map,
2558 )
2559 .map_err(to_pyvalue_err)?;
2560 Ok(request_id.to_string())
2561 }
2562
2563 #[pyo3(name = "request_funding_rates")]
2564 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
2565 fn py_request_funding_rates(
2566 &mut self,
2567 instrument_id: InstrumentId,
2568 start: Option<u64>,
2569 end: Option<u64>,
2570 limit: Option<usize>,
2571 client_id: Option<ClientId>,
2572 params: Option<Py<PyDict>>,
2573 ) -> PyResult<String> {
2574 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2575 match params {
2576 Some(dict) => from_pydict(py, dict),
2577 None => Ok(None),
2578 }
2579 })?;
2580 let limit = limit.and_then(NonZeroUsize::new);
2581 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2582 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2583
2584 let request_id = DataActor::request_funding_rates(
2585 self.inner_mut(),
2586 instrument_id,
2587 start,
2588 end,
2589 limit,
2590 client_id,
2591 params_map,
2592 )
2593 .map_err(to_pyvalue_err)?;
2594 Ok(request_id.to_string())
2595 }
2596
2597 #[pyo3(name = "request_bars")]
2598 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, client_id=None, params=None))]
2599 fn py_request_bars(
2600 &mut self,
2601 bar_type: BarType,
2602 start: Option<u64>,
2603 end: Option<u64>,
2604 limit: Option<usize>,
2605 client_id: Option<ClientId>,
2606 params: Option<Py<PyDict>>,
2607 ) -> PyResult<String> {
2608 let params_map = Python::attach(|py| -> PyResult<Option<Params>> {
2609 match params {
2610 Some(dict) => from_pydict(py, dict),
2611 None => Ok(None),
2612 }
2613 })?;
2614 let limit = limit.and_then(NonZeroUsize::new);
2615 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2616 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
2617
2618 let request_id = DataActor::request_bars(
2619 self.inner_mut(),
2620 bar_type,
2621 start,
2622 end,
2623 limit,
2624 client_id,
2625 params_map,
2626 )
2627 .map_err(to_pyvalue_err)?;
2628 Ok(request_id.to_string())
2629 }
2630}
2631
2632#[cfg(test)]
2633mod tests {
2634 use std::{cell::RefCell, rc::Rc, str::FromStr};
2635
2636 use nautilus_common::{
2637 actor::DataActor,
2638 cache::Cache,
2639 clock::{Clock, TestClock},
2640 signal::Signal,
2641 timer::TimeEvent,
2642 };
2643 use nautilus_core::{UUID4, UnixNanos};
2644 use nautilus_model::{
2645 data::{
2646 Bar, BarType, CustomData, FundingRateUpdate, IndexPriceUpdate, InstrumentStatus,
2647 MarkPriceUpdate, OrderBookDelta, OrderBookDeltas, QuoteTick, TradeTick,
2648 close::InstrumentClose,
2649 greeks::OptionGreekValues,
2650 option_chain::{OptionChainSlice, OptionGreeks},
2651 stubs::stub_custom_data,
2652 },
2653 enums::{
2654 AggressorSide, BookType, GreeksConvention, InstrumentCloseType, MarketStatusAction,
2655 OrderSide, PositionSide,
2656 },
2657 events::{
2658 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
2659 OrderExpired, OrderInitialized, OrderModifyRejected, OrderPendingCancel,
2660 OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted, OrderTriggered,
2661 OrderUpdated, PositionChanged, PositionClosed, PositionOpened,
2662 order::spec::OrderFilledSpec,
2663 },
2664 identifiers::{
2665 AccountId, ClientOrderId, InstrumentId, OptionSeriesId, PositionId, StrategyId,
2666 TradeId, TraderId, Venue,
2667 },
2668 instruments::{CurrencyPair, InstrumentAny, stubs::audusd_sim},
2669 orderbook::OrderBook,
2670 types::{Currency, Money, Price, Quantity},
2671 };
2672 use nautilus_portfolio::portfolio::Portfolio;
2673 use pyo3::{Py, PyAny, PyResult, Python, ffi::c_str, types::PyAnyMethods};
2674 use ustr::Ustr;
2675
2676 use super::PyStrategy;
2677 use crate::strategy::Strategy;
2678
2679 const TRACKING_STRATEGY_CODE: &std::ffi::CStr = c_str!(
2680 r#"
2681class TrackingStrategy:
2682 TRACKED_METHODS = {
2683 "on_start",
2684 "on_stop",
2685 "on_resume",
2686 "on_reset",
2687 "on_dispose",
2688 "on_degrade",
2689 "on_fault",
2690 "on_time_event",
2691 "on_data",
2692 "on_signal",
2693 "on_instrument",
2694 "on_quote",
2695 "on_trade",
2696 "on_bar",
2697 "on_book_deltas",
2698 "on_book",
2699 "on_mark_price",
2700 "on_index_price",
2701 "on_funding_rate",
2702 "on_instrument_status",
2703 "on_instrument_close",
2704 "on_option_greeks",
2705 "on_option_chain",
2706 "on_historical_data",
2707 "on_historical_quotes",
2708 "on_historical_trades",
2709 "on_historical_funding_rates",
2710 "on_historical_bars",
2711 "on_historical_mark_prices",
2712 "on_historical_index_prices",
2713 "on_order_initialized",
2714 "on_order_denied",
2715 "on_order_emulated",
2716 "on_order_released",
2717 "on_order_submitted",
2718 "on_order_rejected",
2719 "on_order_accepted",
2720 "on_order_expired",
2721 "on_order_triggered",
2722 "on_order_pending_update",
2723 "on_order_pending_cancel",
2724 "on_order_modify_rejected",
2725 "on_order_cancel_rejected",
2726 "on_order_updated",
2727 "on_order_canceled",
2728 "on_order_filled",
2729 "on_position_opened",
2730 "on_position_changed",
2731 "on_position_closed",
2732 }
2733
2734 def __init__(self):
2735 self.calls = []
2736
2737 def _record(self, method_name, *args):
2738 self.calls.append((method_name, args))
2739
2740 def was_called(self, method_name):
2741 return any(call[0] == method_name for call in self.calls)
2742
2743 def call_count(self, method_name):
2744 return sum(1 for call in self.calls if call[0] == method_name)
2745
2746 def __getattr__(self, name):
2747 if name in self.TRACKED_METHODS:
2748 return lambda *args: self._record(name, *args)
2749 raise AttributeError(name)
2750"#
2751 );
2752
2753 fn create_tracking_python_strategy(py: Python<'_>) -> PyResult<Py<PyAny>> {
2754 py.run(TRACKING_STRATEGY_CODE, None, None)?;
2755 let tracking_strategy_class = py.eval(c_str!("TrackingStrategy"), None, None)?;
2756 let instance = tracking_strategy_class.call0()?;
2757 Ok(instance.unbind())
2758 }
2759
2760 fn python_method_was_called(
2761 py_strategy: &Py<PyAny>,
2762 py: Python<'_>,
2763 method_name: &str,
2764 ) -> bool {
2765 py_strategy
2766 .call_method1(py, "was_called", (method_name,))
2767 .and_then(|result| result.extract::<bool>(py))
2768 .unwrap_or(false)
2769 }
2770
2771 fn python_method_call_count(py_strategy: &Py<PyAny>, py: Python<'_>, method_name: &str) -> i32 {
2772 py_strategy
2773 .call_method1(py, "call_count", (method_name,))
2774 .and_then(|result| result.extract::<i32>(py))
2775 .unwrap_or(0)
2776 }
2777
2778 fn sample_instrument() -> CurrencyPair {
2779 audusd_sim()
2780 }
2781
2782 fn sample_time_event() -> TimeEvent {
2783 TimeEvent::new(
2784 Ustr::from("test_timer"),
2785 UUID4::new(),
2786 UnixNanos::default(),
2787 UnixNanos::default(),
2788 )
2789 }
2790
2791 fn sample_data() -> CustomData {
2792 stub_custom_data(1, 42, None, None)
2793 }
2794
2795 fn sample_signal() -> Signal {
2796 Signal::new(
2797 Ustr::from("test_signal"),
2798 "1.0".to_string(),
2799 UnixNanos::default(),
2800 UnixNanos::default(),
2801 )
2802 }
2803
2804 fn sample_quote() -> QuoteTick {
2805 let instrument = sample_instrument();
2806 QuoteTick::new(
2807 instrument.id,
2808 Price::from("1.00000"),
2809 Price::from("1.00001"),
2810 Quantity::from(100_000),
2811 Quantity::from(100_000),
2812 UnixNanos::default(),
2813 UnixNanos::default(),
2814 )
2815 }
2816
2817 fn sample_trade() -> TradeTick {
2818 let instrument = sample_instrument();
2819 TradeTick::new(
2820 instrument.id,
2821 Price::from("1.00000"),
2822 Quantity::from(100_000),
2823 AggressorSide::Buyer,
2824 TradeId::new("123456"),
2825 UnixNanos::default(),
2826 UnixNanos::default(),
2827 )
2828 }
2829
2830 fn sample_bar() -> Bar {
2831 let instrument = sample_instrument();
2832 let bar_type =
2833 BarType::from_str(&format!("{}-1-MINUTE-LAST-INTERNAL", instrument.id)).unwrap();
2834 Bar::new(
2835 bar_type,
2836 Price::from("1.00000"),
2837 Price::from("1.00010"),
2838 Price::from("0.99990"),
2839 Price::from("1.00005"),
2840 Quantity::from(100_000),
2841 UnixNanos::default(),
2842 UnixNanos::default(),
2843 )
2844 }
2845
2846 fn sample_book() -> OrderBook {
2847 OrderBook::new(sample_instrument().id, BookType::L2_MBP)
2848 }
2849
2850 fn sample_book_deltas() -> OrderBookDeltas {
2851 let instrument = sample_instrument();
2852 let delta =
2853 OrderBookDelta::clear(instrument.id, 0, UnixNanos::default(), UnixNanos::default());
2854 OrderBookDeltas::new(instrument.id, vec![delta])
2855 }
2856
2857 fn sample_mark_price() -> MarkPriceUpdate {
2858 MarkPriceUpdate::new(
2859 sample_instrument().id,
2860 Price::from("1.00000"),
2861 UnixNanos::default(),
2862 UnixNanos::default(),
2863 )
2864 }
2865
2866 fn sample_index_price() -> IndexPriceUpdate {
2867 IndexPriceUpdate::new(
2868 sample_instrument().id,
2869 Price::from("1.00000"),
2870 UnixNanos::default(),
2871 UnixNanos::default(),
2872 )
2873 }
2874
2875 fn sample_funding_rate() -> FundingRateUpdate {
2876 FundingRateUpdate::new(
2877 sample_instrument().id,
2878 "0.0001".parse().unwrap(),
2879 None,
2880 None,
2881 UnixNanos::default(),
2882 UnixNanos::default(),
2883 )
2884 }
2885
2886 fn sample_instrument_status() -> InstrumentStatus {
2887 InstrumentStatus::new(
2888 sample_instrument().id,
2889 MarketStatusAction::Trading,
2890 UnixNanos::default(),
2891 UnixNanos::default(),
2892 None,
2893 None,
2894 None,
2895 None,
2896 None,
2897 )
2898 }
2899
2900 fn sample_instrument_close() -> InstrumentClose {
2901 InstrumentClose::new(
2902 sample_instrument().id,
2903 Price::from("1.00000"),
2904 InstrumentCloseType::EndOfSession,
2905 UnixNanos::default(),
2906 UnixNanos::default(),
2907 )
2908 }
2909
2910 fn sample_option_greeks() -> OptionGreeks {
2911 OptionGreeks {
2912 instrument_id: InstrumentId::from("AUD/USD.SIM"),
2913 convention: GreeksConvention::BlackScholes,
2914 greeks: OptionGreekValues {
2915 delta: 0.55,
2916 gamma: 0.03,
2917 vega: 0.12,
2918 theta: -0.05,
2919 rho: 0.01,
2920 },
2921 mark_iv: Some(0.25),
2922 bid_iv: None,
2923 ask_iv: None,
2924 underlying_price: None,
2925 open_interest: None,
2926 ts_event: UnixNanos::default(),
2927 ts_init: UnixNanos::default(),
2928 }
2929 }
2930
2931 fn sample_option_chain() -> OptionChainSlice {
2932 OptionChainSlice {
2933 series_id: OptionSeriesId::new(
2934 Venue::from("SIM"),
2935 Ustr::from("AUD"),
2936 Ustr::from("USD"),
2937 UnixNanos::from(1_711_036_800_000_000_000),
2938 ),
2939 atm_strike: None,
2940 calls: Default::default(),
2941 puts: Default::default(),
2942 ts_event: UnixNanos::default(),
2943 ts_init: UnixNanos::default(),
2944 }
2945 }
2946
2947 fn sample_position_opened() -> PositionOpened {
2948 PositionOpened {
2949 trader_id: TraderId::from("TRADER-001"),
2950 strategy_id: StrategyId::from("TEST-001"),
2951 instrument_id: InstrumentId::from("BTCUSDT.BINANCE"),
2952 position_id: PositionId::from("P-001"),
2953 account_id: AccountId::from("ACC-001"),
2954 opening_order_id: ClientOrderId::from("O-001"),
2955 entry: OrderSide::Buy,
2956 side: PositionSide::Long,
2957 signed_qty: 1.0,
2958 quantity: Quantity::from(1),
2959 last_qty: Quantity::from(1),
2960 last_px: Price::from("1.00000"),
2961 currency: Currency::from("USD"),
2962 avg_px_open: 1.0,
2963 event_id: UUID4::new(),
2964 ts_event: UnixNanos::default(),
2965 ts_init: UnixNanos::default(),
2966 }
2967 }
2968
2969 fn sample_position_changed() -> PositionChanged {
2970 PositionChanged {
2971 trader_id: TraderId::from("TRADER-001"),
2972 strategy_id: StrategyId::from("TEST-001"),
2973 instrument_id: InstrumentId::from("BTCUSDT.BINANCE"),
2974 position_id: PositionId::from("P-001"),
2975 account_id: AccountId::from("ACC-001"),
2976 opening_order_id: ClientOrderId::from("O-001"),
2977 entry: OrderSide::Buy,
2978 side: PositionSide::Long,
2979 signed_qty: 2.0,
2980 quantity: Quantity::from(2),
2981 peak_quantity: Quantity::from(2),
2982 last_qty: Quantity::from(1),
2983 last_px: Price::from("1.10000"),
2984 currency: Currency::from("USD"),
2985 avg_px_open: 1.05,
2986 avg_px_close: None,
2987 realized_return: 0.0,
2988 realized_pnl: None,
2989 unrealized_pnl: Money::new(0.0, Currency::USD()),
2990 event_id: UUID4::new(),
2991 ts_opened: UnixNanos::default(),
2992 ts_event: UnixNanos::default(),
2993 ts_init: UnixNanos::default(),
2994 }
2995 }
2996
2997 fn sample_position_closed() -> PositionClosed {
2998 PositionClosed {
2999 trader_id: TraderId::from("TRADER-001"),
3000 strategy_id: StrategyId::from("TEST-001"),
3001 instrument_id: InstrumentId::from("BTCUSDT.BINANCE"),
3002 position_id: PositionId::from("P-001"),
3003 account_id: AccountId::from("ACC-001"),
3004 opening_order_id: ClientOrderId::from("O-001"),
3005 closing_order_id: Some(ClientOrderId::from("O-002")),
3006 entry: OrderSide::Buy,
3007 side: PositionSide::Flat,
3008 signed_qty: 0.0,
3009 quantity: Quantity::from(0),
3010 peak_quantity: Quantity::from(2),
3011 last_qty: Quantity::from(2),
3012 last_px: Price::from("1.20000"),
3013 currency: Currency::from("USD"),
3014 avg_px_open: 1.05,
3015 avg_px_close: Some(1.20),
3016 realized_return: 0.1,
3017 realized_pnl: Some(Money::new(0.1, Currency::USD())),
3018 unrealized_pnl: Money::new(0.0, Currency::USD()),
3019 duration: 1,
3020 event_id: UUID4::new(),
3021 ts_opened: UnixNanos::default(),
3022 ts_closed: Some(UnixNanos::default()),
3023 ts_event: UnixNanos::default(),
3024 ts_init: UnixNanos::default(),
3025 }
3026 }
3027
3028 fn create_registered_tracking_strategy(py: Python<'_>) -> (Py<PyAny>, PyStrategy) {
3029 let py_strategy = create_tracking_python_strategy(py).unwrap();
3030 let mut rust_strategy = PyStrategy::new(None);
3031 rust_strategy.set_python_instance(py_strategy.clone_ref(py));
3032
3033 let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
3034 let cache = Rc::new(RefCell::new(Cache::new(None, None)));
3035 let portfolio = Rc::new(RefCell::new(Portfolio::new(
3036 cache.clone(),
3037 clock.clone(),
3038 None,
3039 )));
3040
3041 rust_strategy
3042 .register(TraderId::from("TRADER-001"), clock, cache, portfolio)
3043 .unwrap();
3044
3045 (py_strategy, rust_strategy)
3046 }
3047
3048 fn assert_python_dispatch<F>(py: Python<'_>, method_name: &str, invoke: F)
3049 where
3050 F: FnOnce(&mut PyStrategy) -> anyhow::Result<()>,
3051 {
3052 let (py_strategy, mut rust_strategy) = create_registered_tracking_strategy(py);
3053 let result = invoke(&mut rust_strategy);
3054
3055 assert!(result.is_ok());
3056 assert!(python_method_was_called(&py_strategy, py, method_name));
3057 assert_eq!(python_method_call_count(&py_strategy, py, method_name), 1);
3058 }
3059
3060 #[rstest::rstest]
3061 #[case("on_start")]
3062 #[case("on_stop")]
3063 #[case("on_resume")]
3064 #[case("on_reset")]
3065 #[case("on_dispose")]
3066 #[case("on_degrade")]
3067 #[case("on_fault")]
3068 fn test_python_dispatch_lifecycle_matrix(#[case] method_name: &str) {
3069 pyo3::Python::initialize();
3070 Python::attach(|py| {
3071 assert_python_dispatch(py, method_name, |rust_strategy| match method_name {
3072 "on_start" => DataActor::on_start(rust_strategy.inner_mut()),
3073 "on_stop" => DataActor::on_stop(rust_strategy.inner_mut()),
3074 "on_resume" => DataActor::on_resume(rust_strategy.inner_mut()),
3075 "on_reset" => DataActor::on_reset(rust_strategy.inner_mut()),
3076 "on_dispose" => DataActor::on_dispose(rust_strategy.inner_mut()),
3077 "on_degrade" => DataActor::on_degrade(rust_strategy.inner_mut()),
3078 "on_fault" => DataActor::on_fault(rust_strategy.inner_mut()),
3079 _ => unreachable!("unhandled lifecycle case: {method_name}"),
3080 });
3081 });
3082 }
3083
3084 #[rstest::rstest]
3085 #[case("on_time_event")]
3086 #[case("on_data")]
3087 #[case("on_signal")]
3088 #[case("on_instrument")]
3089 #[case("on_quote")]
3090 #[case("on_trade")]
3091 #[case("on_bar")]
3092 #[case("on_book_deltas")]
3093 #[case("on_book")]
3094 #[case("on_mark_price")]
3095 #[case("on_index_price")]
3096 #[case("on_funding_rate")]
3097 #[case("on_instrument_status")]
3098 #[case("on_instrument_close")]
3099 #[case("on_option_greeks")]
3100 #[case("on_option_chain")]
3101 #[case("on_historical_data")]
3102 #[case("on_historical_quotes")]
3103 #[case("on_historical_trades")]
3104 #[case("on_historical_funding_rates")]
3105 #[case("on_historical_bars")]
3106 #[case("on_historical_mark_prices")]
3107 #[case("on_historical_index_prices")]
3108 fn test_python_dispatch_data_callback_matrix(#[case] method_name: &str) {
3109 pyo3::Python::initialize();
3110 Python::attach(|py| {
3111 assert_python_dispatch(py, method_name, |rust_strategy| match method_name {
3112 "on_time_event" => {
3113 let event = sample_time_event();
3114 DataActor::on_time_event(rust_strategy.inner_mut(), &event)
3115 }
3116 "on_data" => {
3117 let data = sample_data();
3118 rust_strategy.inner_mut().on_data(&data)
3119 }
3120 "on_signal" => {
3121 let signal = sample_signal();
3122 rust_strategy.inner_mut().on_signal(&signal)
3123 }
3124 "on_instrument" => {
3125 let instrument = InstrumentAny::CurrencyPair(sample_instrument());
3126 rust_strategy.inner_mut().on_instrument(&instrument)
3127 }
3128 "on_quote" => {
3129 let quote = sample_quote();
3130 rust_strategy.inner_mut().on_quote("e)
3131 }
3132 "on_trade" => {
3133 let trade = sample_trade();
3134 rust_strategy.inner_mut().on_trade(&trade)
3135 }
3136 "on_bar" => {
3137 let bar = sample_bar();
3138 rust_strategy.inner_mut().on_bar(&bar)
3139 }
3140 "on_book_deltas" => {
3141 let deltas = sample_book_deltas();
3142 rust_strategy.inner_mut().on_book_deltas(&deltas)
3143 }
3144 "on_book" => {
3145 let book = sample_book();
3146 rust_strategy.inner_mut().on_book(&book)
3147 }
3148 "on_mark_price" => {
3149 let mark_price = sample_mark_price();
3150 rust_strategy.inner_mut().on_mark_price(&mark_price)
3151 }
3152 "on_index_price" => {
3153 let index_price = sample_index_price();
3154 rust_strategy.inner_mut().on_index_price(&index_price)
3155 }
3156 "on_funding_rate" => {
3157 let funding_rate = sample_funding_rate();
3158 rust_strategy.inner_mut().on_funding_rate(&funding_rate)
3159 }
3160 "on_instrument_status" => {
3161 let status = sample_instrument_status();
3162 rust_strategy.inner_mut().on_instrument_status(&status)
3163 }
3164 "on_instrument_close" => {
3165 let close = sample_instrument_close();
3166 rust_strategy.inner_mut().on_instrument_close(&close)
3167 }
3168 "on_option_greeks" => {
3169 let greeks = sample_option_greeks();
3170 DataActor::on_option_greeks(rust_strategy.inner_mut(), &greeks)
3171 }
3172 "on_option_chain" => {
3173 let slice = sample_option_chain();
3174 DataActor::on_option_chain(rust_strategy.inner_mut(), &slice)
3175 }
3176 "on_historical_data" => {
3177 let data = sample_data();
3178 rust_strategy.inner_mut().on_historical_data(&data)
3179 }
3180 "on_historical_quotes" => {
3181 let quotes = vec![sample_quote()];
3182 rust_strategy.inner_mut().on_historical_quotes("es)
3183 }
3184 "on_historical_trades" => {
3185 let trades = vec![sample_trade()];
3186 rust_strategy.inner_mut().on_historical_trades(&trades)
3187 }
3188 "on_historical_funding_rates" => {
3189 let funding_rates = vec![sample_funding_rate()];
3190 rust_strategy
3191 .inner_mut()
3192 .on_historical_funding_rates(&funding_rates)
3193 }
3194 "on_historical_bars" => {
3195 let bars = vec![sample_bar()];
3196 rust_strategy.inner_mut().on_historical_bars(&bars)
3197 }
3198 "on_historical_mark_prices" => {
3199 let mark_prices = vec![sample_mark_price()];
3200 rust_strategy
3201 .inner_mut()
3202 .on_historical_mark_prices(&mark_prices)
3203 }
3204 "on_historical_index_prices" => {
3205 let index_prices = vec![sample_index_price()];
3206 rust_strategy
3207 .inner_mut()
3208 .on_historical_index_prices(&index_prices)
3209 }
3210 _ => unreachable!("unhandled data callback case: {method_name}"),
3211 });
3212 });
3213 }
3214
3215 #[rstest::rstest]
3216 #[case("on_order_initialized")]
3217 #[case("on_order_denied")]
3218 #[case("on_order_emulated")]
3219 #[case("on_order_released")]
3220 #[case("on_order_submitted")]
3221 #[case("on_order_rejected")]
3222 #[case("on_order_accepted")]
3223 #[case("on_order_expired")]
3224 #[case("on_order_triggered")]
3225 #[case("on_order_pending_update")]
3226 #[case("on_order_pending_cancel")]
3227 #[case("on_order_modify_rejected")]
3228 #[case("on_order_cancel_rejected")]
3229 #[case("on_order_updated")]
3230 #[case("on_order_canceled")]
3231 #[case("on_order_filled")]
3232 fn test_python_dispatch_order_callback_matrix(#[case] method_name: &str) {
3233 pyo3::Python::initialize();
3234 Python::attach(|py| {
3235 assert_python_dispatch(py, method_name, |rust_strategy| match method_name {
3236 "on_order_initialized" => {
3237 Strategy::on_order_initialized(
3238 rust_strategy.inner_mut(),
3239 OrderInitialized::default(),
3240 );
3241 Ok(())
3242 }
3243 "on_order_denied" => {
3244 Strategy::on_order_denied(rust_strategy.inner_mut(), OrderDenied::default());
3245 Ok(())
3246 }
3247 "on_order_emulated" => {
3248 Strategy::on_order_emulated(
3249 rust_strategy.inner_mut(),
3250 OrderEmulated::default(),
3251 );
3252 Ok(())
3253 }
3254 "on_order_released" => {
3255 Strategy::on_order_released(
3256 rust_strategy.inner_mut(),
3257 OrderReleased::default(),
3258 );
3259 Ok(())
3260 }
3261 "on_order_submitted" => {
3262 Strategy::on_order_submitted(
3263 rust_strategy.inner_mut(),
3264 OrderSubmitted::default(),
3265 );
3266 Ok(())
3267 }
3268 "on_order_rejected" => {
3269 Strategy::on_order_rejected(
3270 rust_strategy.inner_mut(),
3271 OrderRejected::default(),
3272 );
3273 Ok(())
3274 }
3275 "on_order_accepted" => {
3276 Strategy::on_order_accepted(
3277 rust_strategy.inner_mut(),
3278 OrderAccepted::default(),
3279 );
3280 Ok(())
3281 }
3282 "on_order_expired" => {
3283 Strategy::on_order_expired(rust_strategy.inner_mut(), OrderExpired::default());
3284 Ok(())
3285 }
3286 "on_order_triggered" => {
3287 Strategy::on_order_triggered(
3288 rust_strategy.inner_mut(),
3289 OrderTriggered::default(),
3290 );
3291 Ok(())
3292 }
3293 "on_order_pending_update" => {
3294 Strategy::on_order_pending_update(
3295 rust_strategy.inner_mut(),
3296 OrderPendingUpdate::default(),
3297 );
3298 Ok(())
3299 }
3300 "on_order_pending_cancel" => {
3301 Strategy::on_order_pending_cancel(
3302 rust_strategy.inner_mut(),
3303 OrderPendingCancel::default(),
3304 );
3305 Ok(())
3306 }
3307 "on_order_modify_rejected" => {
3308 Strategy::on_order_modify_rejected(
3309 rust_strategy.inner_mut(),
3310 OrderModifyRejected::default(),
3311 );
3312 Ok(())
3313 }
3314 "on_order_cancel_rejected" => {
3315 Strategy::on_order_cancel_rejected(
3316 rust_strategy.inner_mut(),
3317 OrderCancelRejected::default(),
3318 );
3319 Ok(())
3320 }
3321 "on_order_updated" => {
3322 Strategy::on_order_updated(rust_strategy.inner_mut(), OrderUpdated::default());
3323 Ok(())
3324 }
3325 "on_order_canceled" => {
3326 let event = OrderCanceled::default();
3327 DataActor::on_order_canceled(rust_strategy.inner_mut(), &event)
3328 }
3329 "on_order_filled" => {
3330 let event = OrderFilledSpec::builder().build();
3331 DataActor::on_order_filled(rust_strategy.inner_mut(), &event)
3332 }
3333 _ => unreachable!("unhandled order callback case: {method_name}"),
3334 });
3335 });
3336 }
3337
3338 #[rstest::rstest]
3339 #[case("on_position_opened")]
3340 #[case("on_position_changed")]
3341 #[case("on_position_closed")]
3342 fn test_python_dispatch_position_callback_matrix(#[case] method_name: &str) {
3343 pyo3::Python::initialize();
3344 Python::attach(|py| {
3345 assert_python_dispatch(py, method_name, |rust_strategy| match method_name {
3346 "on_position_opened" => {
3347 Strategy::on_position_opened(
3348 rust_strategy.inner_mut(),
3349 sample_position_opened(),
3350 );
3351 Ok(())
3352 }
3353 "on_position_changed" => {
3354 Strategy::on_position_changed(
3355 rust_strategy.inner_mut(),
3356 sample_position_changed(),
3357 );
3358 Ok(())
3359 }
3360 "on_position_closed" => {
3361 Strategy::on_position_closed(
3362 rust_strategy.inner_mut(),
3363 sample_position_closed(),
3364 );
3365 Ok(())
3366 }
3367 _ => unreachable!("unhandled position callback case: {method_name}"),
3368 });
3369 });
3370 }
3371}