1use std::{
19 any::Any,
20 cell::RefCell,
21 collections::HashMap,
22 num::NonZeroUsize,
23 ops::{Deref, DerefMut},
24 rc::Rc,
25};
26
27use indexmap::IndexMap;
28use nautilus_core::{
29 nanos::UnixNanos,
30 python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
31};
32#[cfg(feature = "defi")]
33use nautilus_model::defi::{Block, Blockchain, Pool, PoolLiquidityUpdate, PoolSwap};
34use nautilus_model::{
35 data::{
36 Bar, BarType, DataType, FundingRateUpdate, IndexPriceUpdate, InstrumentStatus,
37 MarkPriceUpdate, OrderBookDeltas, QuoteTick, TradeTick, close::InstrumentClose,
38 },
39 enums::BookType,
40 identifiers::{ActorId, ClientId, InstrumentId, TraderId, Venue},
41 instruments::InstrumentAny,
42 orderbook::OrderBook,
43 python::instruments::instrument_any_to_pyobject,
44};
45use pyo3::{exceptions::PyValueError, prelude::*, types::PyDict};
46
47use crate::{
48 actor::{
49 DataActor,
50 data_actor::{DataActorConfig, DataActorCore, ImportableActorConfig},
51 registry::try_get_actor_unchecked,
52 },
53 cache::Cache,
54 clock::Clock,
55 component::Component,
56 enums::ComponentState,
57 python::{clock::PyClock, logging::PyLogger},
58 signal::Signal,
59 timer::{TimeEvent, TimeEventCallback},
60};
61
62#[pyo3::pymethods]
63impl DataActorConfig {
64 #[new]
65 #[pyo3(signature = (actor_id=None, log_events=true, log_commands=true))]
66 fn py_new(actor_id: Option<ActorId>, log_events: bool, log_commands: bool) -> Self {
67 Self {
68 actor_id,
69 log_events,
70 log_commands,
71 }
72 }
73}
74
75#[pyo3::pymethods]
76impl ImportableActorConfig {
77 #[new]
78 fn py_new(actor_path: String, config_path: String, config: Py<PyDict>) -> PyResult<Self> {
79 let json_config = Python::attach(|py| -> PyResult<HashMap<String, serde_json::Value>> {
80 let json_str: String = PyModule::import(py, "json")?
81 .call_method("dumps", (config.bind(py),), None)?
82 .extract()?;
83
84 let json_value: serde_json::Value = serde_json::from_str(&json_str)
85 .map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
86
87 if let serde_json::Value::Object(map) = json_value {
88 Ok(map.into_iter().collect())
89 } else {
90 Err(PyErr::new::<PyValueError, _>("Config must be a dictionary"))
91 }
92 })?;
93
94 Ok(Self {
95 actor_path,
96 config_path,
97 config: json_config,
98 })
99 }
100
101 #[getter]
102 fn actor_path(&self) -> &String {
103 &self.actor_path
104 }
105
106 #[getter]
107 fn config_path(&self) -> &String {
108 &self.config_path
109 }
110
111 #[getter]
112 fn config(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
113 let py_dict = PyDict::new(py);
115 for (key, value) in &self.config {
116 let json_str = serde_json::to_string(value)
118 .map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))?;
119 let py_value = PyModule::import(py, "json")?.call_method("loads", (json_str,), None)?;
120 py_dict.set_item(key, py_value)?;
121 }
122 Ok(py_dict.unbind())
123 }
124}
125
126#[allow(non_camel_case_types)]
127#[pyo3::pyclass(
128 module = "nautilus_trader.common",
129 name = "DataActor",
130 unsendable,
131 subclass
132)]
133#[derive(Debug)]
134pub struct PyDataActor {
135 core: DataActorCore,
136 py_self: Option<Py<PyAny>>,
137 clock: PyClock,
138 logger: PyLogger,
139}
140
141impl Deref for PyDataActor {
142 type Target = DataActorCore;
143
144 fn deref(&self) -> &Self::Target {
145 &self.core
146 }
147}
148
149impl DerefMut for PyDataActor {
150 fn deref_mut(&mut self) -> &mut Self::Target {
151 &mut self.core
152 }
153}
154
155impl PyDataActor {
156 pub fn new(config: Option<DataActorConfig>) -> Self {
158 let config = config.unwrap_or_default();
159 let core = DataActorCore::new(config);
160 let clock = PyClock::new_test(); let logger = PyLogger::new(core.actor_id().as_str());
162
163 Self {
164 core,
165 py_self: None,
166 clock,
167 logger,
168 }
169 }
170
171 pub fn set_python_instance(&mut self, py_obj: Py<PyAny>) {
178 self.py_self = Some(py_obj);
179 }
180
181 pub fn set_actor_id(&mut self, actor_id: ActorId) {
190 self.core.config.actor_id = Some(actor_id);
191 self.core.actor_id = actor_id;
192 }
193
194 pub fn set_log_events(&mut self, log_events: bool) {
196 self.core.config.log_events = log_events;
197 }
198
199 pub fn set_log_commands(&mut self, log_commands: bool) {
201 self.core.config.log_commands = log_commands;
202 }
203 pub fn mem_address(&self) -> String {
205 self.core.mem_address()
206 }
207
208 pub fn is_registered(&self) -> bool {
210 self.core.is_registered()
211 }
212
213 pub fn register(
220 &mut self,
221 trader_id: TraderId,
222 clock: Rc<RefCell<dyn Clock>>,
223 cache: Rc<RefCell<Cache>>,
224 ) -> anyhow::Result<()> {
225 self.core.register(trader_id, clock, cache)?;
226
227 self.clock = PyClock::from_rc(self.core.clock_rc());
228
229 let actor_id = self.actor_id().inner();
231 let callback = TimeEventCallback::Rust(Rc::new(move |event: TimeEvent| {
232 if let Some(actor) = try_get_actor_unchecked::<Self>(&actor_id) {
233 if let Err(e) = actor.on_time_event(&event) {
234 log::error!("Python time event handler failed for actor {actor_id}: {e}");
235 }
236 } else {
237 log::error!("Actor {actor_id} not found for time event handling");
238 }
239 }));
240
241 self.clock.inner_mut().register_default_handler(callback);
242
243 self.initialize()
244 }
245}
246
247impl DataActor for PyDataActor {
248 fn on_start(&mut self) -> anyhow::Result<()> {
249 self.py_on_start()
250 .map_err(|e| anyhow::anyhow!("Python on_start failed: {e}"))
251 }
252
253 fn on_stop(&mut self) -> anyhow::Result<()> {
254 self.py_on_stop()
255 .map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
256 }
257
258 fn on_resume(&mut self) -> anyhow::Result<()> {
259 self.py_on_resume()
260 .map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
261 }
262
263 fn on_reset(&mut self) -> anyhow::Result<()> {
264 self.py_on_reset()
265 .map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
266 }
267
268 fn on_dispose(&mut self) -> anyhow::Result<()> {
269 self.py_on_dispose()
270 .map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
271 }
272
273 fn on_degrade(&mut self) -> anyhow::Result<()> {
274 self.py_on_degrade()
275 .map_err(|e| anyhow::anyhow!("Python on_degrade failed: {e}"))
276 }
277
278 fn on_fault(&mut self) -> anyhow::Result<()> {
279 self.py_on_fault()
280 .map_err(|e| anyhow::anyhow!("Python on_fault failed: {e}"))
281 }
282
283 fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
284 self.py_on_time_event(event.clone())
285 .map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
286 }
287
288 #[allow(unused_variables)]
289 fn on_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
290 Python::attach(|py| {
291 let py_data = py.None();
294
295 self.py_on_data(py_data)
296 .map_err(|e| anyhow::anyhow!("Python on_data failed: {e}"))
297 })
298 }
299
300 fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
301 self.py_on_signal(signal)
302 .map_err(|e| anyhow::anyhow!("Python on_signal failed: {e}"))
303 }
304
305 fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
306 Python::attach(|py| {
307 let py_instrument = instrument_any_to_pyobject(py, instrument.clone())
308 .map_err(|e| anyhow::anyhow!("Failed to convert InstrumentAny to Python: {e}"))?;
309 self.py_on_instrument(py_instrument)
310 .map_err(|e| anyhow::anyhow!("Python on_instrument failed: {e}"))
311 })
312 }
313
314 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
315 self.py_on_quote(*quote)
316 .map_err(|e| anyhow::anyhow!("Python on_quote failed: {e}"))
317 }
318
319 fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
320 self.py_on_trade(*tick)
321 .map_err(|e| anyhow::anyhow!("Python on_trade failed: {e}"))
322 }
323
324 fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
325 self.py_on_bar(*bar)
326 .map_err(|e| anyhow::anyhow!("Python on_bar failed: {e}"))
327 }
328
329 fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
330 self.py_on_book_deltas(deltas.clone())
331 .map_err(|e| anyhow::anyhow!("Python on_book_deltas failed: {e}"))
332 }
333
334 fn on_book(&mut self, order_book: &OrderBook) -> anyhow::Result<()> {
335 self.py_on_book(order_book)
336 .map_err(|e| anyhow::anyhow!("Python on_book failed: {e}"))
337 }
338
339 fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
340 self.py_on_mark_price(*mark_price)
341 .map_err(|e| anyhow::anyhow!("Python on_mark_price failed: {e}"))
342 }
343
344 fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
345 self.py_on_index_price(*index_price)
346 .map_err(|e| anyhow::anyhow!("Python on_index_price failed: {e}"))
347 }
348
349 fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
350 self.py_on_funding_rate(*funding_rate)
351 .map_err(|e| anyhow::anyhow!("Python on_funding_rate failed: {e}"))
352 }
353
354 fn on_instrument_status(&mut self, data: &InstrumentStatus) -> anyhow::Result<()> {
355 self.py_on_instrument_status(*data)
356 .map_err(|e| anyhow::anyhow!("Python on_instrument_status failed: {e}"))
357 }
358
359 fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
360 self.py_on_instrument_close(*update)
361 .map_err(|e| anyhow::anyhow!("Python on_instrument_close failed: {e}"))
362 }
363
364 #[cfg(feature = "defi")]
365 fn on_block(&mut self, block: &Block) -> anyhow::Result<()> {
366 self.py_on_block(block.clone())
367 .map_err(|e| anyhow::anyhow!("Python on_block failed: {e}"))
368 }
369
370 #[cfg(feature = "defi")]
371 fn on_pool(&mut self, pool: &Pool) -> anyhow::Result<()> {
372 self.py_on_pool(pool.clone())
373 .map_err(|e| anyhow::anyhow!("Python on_pool failed: {e}"))
374 }
375
376 #[cfg(feature = "defi")]
377 fn on_pool_swap(&mut self, swap: &PoolSwap) -> anyhow::Result<()> {
378 self.py_on_pool_swap(swap.clone())
379 .map_err(|e| anyhow::anyhow!("Python on_pool_swap failed: {e}"))
380 }
381
382 #[cfg(feature = "defi")]
383 fn on_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) -> anyhow::Result<()> {
384 self.py_on_pool_liquidity_update(update.clone())
385 .map_err(|e| anyhow::anyhow!("Python on_pool_liquidity_update failed: {e}"))
386 }
387
388 fn on_historical_data(&mut self, _data: &dyn Any) -> anyhow::Result<()> {
389 Python::attach(|py| {
390 let py_data = py.None();
391 self.py_on_historical_data(py_data)
392 .map_err(|e| anyhow::anyhow!("Python on_historical_data failed: {e}"))
393 })
394 }
395
396 fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
397 self.py_on_historical_quotes(quotes.to_vec())
398 .map_err(|e| anyhow::anyhow!("Python on_historical_quotes failed: {e}"))
399 }
400
401 fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
402 self.py_on_historical_trades(trades.to_vec())
403 .map_err(|e| anyhow::anyhow!("Python on_historical_trades failed: {e}"))
404 }
405
406 fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
407 self.py_on_historical_bars(bars.to_vec())
408 .map_err(|e| anyhow::anyhow!("Python on_historical_bars failed: {e}"))
409 }
410
411 fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
412 self.py_on_historical_mark_prices(mark_prices.to_vec())
413 .map_err(|e| anyhow::anyhow!("Python on_historical_mark_prices failed: {e}"))
414 }
415
416 fn on_historical_index_prices(
417 &mut self,
418 index_prices: &[IndexPriceUpdate],
419 ) -> anyhow::Result<()> {
420 self.py_on_historical_index_prices(index_prices.to_vec())
421 .map_err(|e| anyhow::anyhow!("Python on_historical_index_prices failed: {e}"))
422 }
423}
424
425#[pymethods]
426impl PyDataActor {
427 #[new]
428 #[pyo3(signature = (config=None))]
429 fn py_new(config: Option<DataActorConfig>) -> PyResult<Self> {
430 Ok(Self::new(config))
431 }
432
433 #[getter]
434 #[pyo3(name = "clock")]
435 fn py_clock(&self) -> PyResult<PyClock> {
436 if !self.core.is_registered() {
437 Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
438 "Actor must be registered with a trader before accessing clock",
439 ))
440 } else {
441 Ok(self.clock.clone())
442 }
443 }
444
445 #[getter]
446 #[pyo3(name = "log")]
447 fn py_log(&self) -> PyLogger {
448 self.logger.clone()
449 }
450
451 #[getter]
452 #[pyo3(name = "actor_id")]
453 fn py_actor_id(&self) -> ActorId {
454 self.actor_id
455 }
456
457 #[getter]
458 #[pyo3(name = "trader_id")]
459 fn py_trader_id(&self) -> Option<TraderId> {
460 self.trader_id()
461 }
462
463 #[pyo3(name = "state")]
464 fn py_state(&self) -> ComponentState {
465 self.state()
466 }
467
468 #[pyo3(name = "is_ready")]
469 fn py_is_ready(&self) -> bool {
470 self.is_ready()
471 }
472
473 #[pyo3(name = "is_running")]
474 fn py_is_running(&self) -> bool {
475 self.is_running()
476 }
477
478 #[pyo3(name = "is_stopped")]
479 fn py_is_stopped(&self) -> bool {
480 self.is_stopped()
481 }
482
483 #[pyo3(name = "is_degraded")]
484 fn py_is_degraded(&self) -> bool {
485 self.is_degraded()
486 }
487
488 #[pyo3(name = "is_faulted")]
489 fn py_is_faulted(&self) -> bool {
490 self.is_faulted()
491 }
492
493 #[pyo3(name = "is_disposed")]
494 fn py_is_disposed(&self) -> bool {
495 self.is_disposed()
496 }
497
498 #[pyo3(name = "start")]
499 fn py_start(&mut self) -> PyResult<()> {
500 self.start().map_err(to_pyruntime_err)
501 }
502
503 #[pyo3(name = "stop")]
504 fn py_stop(&mut self) -> PyResult<()> {
505 self.stop().map_err(to_pyruntime_err)
506 }
507
508 #[pyo3(name = "resume")]
509 fn py_resume(&mut self) -> PyResult<()> {
510 self.resume().map_err(to_pyruntime_err)
511 }
512
513 #[pyo3(name = "reset")]
514 fn py_reset(&mut self) -> PyResult<()> {
515 self.reset().map_err(to_pyruntime_err)
516 }
517
518 #[pyo3(name = "dispose")]
519 fn py_dispose(&mut self) -> PyResult<()> {
520 self.dispose().map_err(to_pyruntime_err)
521 }
522
523 #[pyo3(name = "degrade")]
524 fn py_degrade(&mut self) -> PyResult<()> {
525 self.degrade().map_err(to_pyruntime_err)
526 }
527
528 #[pyo3(name = "fault")]
529 fn py_fault(&mut self) -> PyResult<()> {
530 self.fault().map_err(to_pyruntime_err)
531 }
532
533 #[pyo3(name = "shutdown_system")]
534 #[pyo3(signature = (reason=None))]
535 fn py_shutdown_system(&self, reason: Option<String>) -> PyResult<()> {
536 self.shutdown_system(reason);
537 Ok(())
538 }
539
540 #[pyo3(name = "on_start")]
541 fn py_on_start(&self) -> PyResult<()> {
542 if let Some(ref py_self) = self.py_self {
544 Python::attach(|py| py_self.call_method0(py, "on_start"))?;
545 }
546 Ok(())
547 }
548
549 #[pyo3(name = "on_stop")]
550 fn py_on_stop(&mut self) -> PyResult<()> {
551 if let Some(ref py_self) = self.py_self {
553 Python::attach(|py| py_self.call_method0(py, "on_stop"))?;
554 }
555 Ok(())
556 }
557
558 #[pyo3(name = "on_resume")]
559 fn py_on_resume(&mut self) -> PyResult<()> {
560 if let Some(ref py_self) = self.py_self {
562 Python::attach(|py| py_self.call_method0(py, "on_resume"))?;
563 }
564 Ok(())
565 }
566
567 #[pyo3(name = "on_reset")]
568 fn py_on_reset(&mut self) -> PyResult<()> {
569 if let Some(ref py_self) = self.py_self {
571 Python::attach(|py| py_self.call_method0(py, "on_reset"))?;
572 }
573 Ok(())
574 }
575
576 #[pyo3(name = "on_dispose")]
577 fn py_on_dispose(&mut self) -> PyResult<()> {
578 if let Some(ref py_self) = self.py_self {
580 Python::attach(|py| py_self.call_method0(py, "on_dispose"))?;
581 }
582 Ok(())
583 }
584
585 #[pyo3(name = "on_degrade")]
586 fn py_on_degrade(&mut self) -> PyResult<()> {
587 if let Some(ref py_self) = self.py_self {
589 Python::attach(|py| py_self.call_method0(py, "on_degrade"))?;
590 }
591 Ok(())
592 }
593
594 #[pyo3(name = "on_fault")]
595 fn py_on_fault(&mut self) -> PyResult<()> {
596 if let Some(ref py_self) = self.py_self {
598 Python::attach(|py| py_self.call_method0(py, "on_fault"))?;
599 }
600 Ok(())
601 }
602
603 #[allow(unused_variables)]
604 #[pyo3(name = "on_time_event")]
605 fn py_on_time_event(&mut self, event: TimeEvent) -> PyResult<()> {
606 if let Some(ref py_self) = self.py_self {
608 Python::attach(|py| {
609 py_self.call_method1(py, "on_time_event", (event.into_py_any_unwrap(py),))
610 })?;
611 }
612 Ok(())
613 }
614
615 #[pyo3(name = "on_data")]
616 fn py_on_data(&mut self, data: Py<PyAny>) -> PyResult<()> {
617 if let Some(ref py_self) = self.py_self {
619 Python::attach(|py| py_self.call_method1(py, "on_data", (data,)))?;
620 }
621 Ok(())
622 }
623
624 #[allow(unused_variables)]
625 #[pyo3(name = "on_signal")]
626 fn py_on_signal(&mut self, signal: &Signal) -> PyResult<()> {
627 if let Some(ref py_self) = self.py_self {
629 Python::attach(|py| {
630 py_self.call_method1(py, "on_signal", (signal.clone().into_py_any_unwrap(py),))
631 })?;
632 }
633 Ok(())
634 }
635
636 #[pyo3(name = "on_instrument")]
637 fn py_on_instrument(&mut self, instrument: Py<PyAny>) -> PyResult<()> {
638 if let Some(ref py_self) = self.py_self {
640 Python::attach(|py| py_self.call_method1(py, "on_instrument", (instrument,)))?;
641 }
642 Ok(())
643 }
644
645 #[allow(unused_variables)]
646 #[pyo3(name = "on_quote")]
647 fn py_on_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
648 if let Some(ref py_self) = self.py_self {
650 Python::attach(|py| {
651 py_self.call_method1(py, "on_quote", (quote.into_py_any_unwrap(py),))
652 })?;
653 }
654 Ok(())
655 }
656
657 #[allow(unused_variables)]
658 #[pyo3(name = "on_trade")]
659 fn py_on_trade(&mut self, trade: TradeTick) -> PyResult<()> {
660 if let Some(ref py_self) = self.py_self {
662 Python::attach(|py| {
663 py_self.call_method1(py, "on_trade", (trade.into_py_any_unwrap(py),))
664 })?;
665 }
666 Ok(())
667 }
668
669 #[allow(unused_variables)]
670 #[pyo3(name = "on_bar")]
671 fn py_on_bar(&mut self, bar: Bar) -> PyResult<()> {
672 if let Some(ref py_self) = self.py_self {
674 Python::attach(|py| py_self.call_method1(py, "on_bar", (bar.into_py_any_unwrap(py),)))?;
675 }
676 Ok(())
677 }
678
679 #[allow(unused_variables)]
680 #[pyo3(name = "on_book_deltas")]
681 fn py_on_book_deltas(&mut self, deltas: OrderBookDeltas) -> PyResult<()> {
682 if let Some(ref py_self) = self.py_self {
684 Python::attach(|py| {
685 py_self.call_method1(py, "on_book_deltas", (deltas.into_py_any_unwrap(py),))
686 })?;
687 }
688 Ok(())
689 }
690
691 #[allow(unused_variables)]
692 #[pyo3(name = "on_book")]
693 fn py_on_book(&mut self, book: &OrderBook) -> PyResult<()> {
694 if let Some(ref py_self) = self.py_self {
696 Python::attach(|py| {
697 py_self.call_method1(py, "on_book", (book.clone().into_py_any_unwrap(py),))
698 })?;
699 }
700 Ok(())
701 }
702
703 #[allow(unused_variables)]
704 #[pyo3(name = "on_mark_price")]
705 fn py_on_mark_price(&mut self, mark_price: MarkPriceUpdate) -> PyResult<()> {
706 if let Some(ref py_self) = self.py_self {
708 Python::attach(|py| {
709 py_self.call_method1(py, "on_mark_price", (mark_price.into_py_any_unwrap(py),))
710 })?;
711 }
712 Ok(())
713 }
714
715 #[allow(unused_variables)]
716 #[pyo3(name = "on_index_price")]
717 fn py_on_index_price(&mut self, index_price: IndexPriceUpdate) -> PyResult<()> {
718 if let Some(ref py_self) = self.py_self {
720 Python::attach(|py| {
721 py_self.call_method1(py, "on_index_price", (index_price.into_py_any_unwrap(py),))
722 })?;
723 }
724 Ok(())
725 }
726
727 #[allow(unused_variables)]
728 #[pyo3(name = "on_funding_rate")]
729 fn py_on_funding_rate(&mut self, funding_rate: FundingRateUpdate) -> PyResult<()> {
730 if let Some(ref py_self) = self.py_self {
732 Python::attach(|py| {
733 py_self.call_method1(
734 py,
735 "on_funding_rate",
736 (funding_rate.into_py_any_unwrap(py),),
737 )
738 })?;
739 }
740 Ok(())
741 }
742
743 #[allow(unused_variables)]
744 #[pyo3(name = "on_instrument_status")]
745 fn py_on_instrument_status(&mut self, status: InstrumentStatus) -> PyResult<()> {
746 if let Some(ref py_self) = self.py_self {
748 Python::attach(|py| {
749 py_self.call_method1(py, "on_instrument_status", (status.into_py_any_unwrap(py),))
750 })?;
751 }
752 Ok(())
753 }
754
755 #[allow(unused_variables)]
756 #[pyo3(name = "on_instrument_close")]
757 fn py_on_instrument_close(&mut self, close: InstrumentClose) -> PyResult<()> {
758 if let Some(ref py_self) = self.py_self {
760 Python::attach(|py| {
761 py_self.call_method1(py, "on_instrument_close", (close.into_py_any_unwrap(py),))
762 })?;
763 }
764 Ok(())
765 }
766
767 #[cfg(feature = "defi")]
768 #[allow(unused_variables)]
769 #[pyo3(name = "on_block")]
770 fn py_on_block(&mut self, block: Block) -> PyResult<()> {
771 if let Some(ref py_self) = self.py_self {
773 Python::attach(|py| {
774 py_self.call_method1(py, "on_block", (block.into_py_any_unwrap(py),))
775 })?;
776 }
777 Ok(())
778 }
779
780 #[cfg(feature = "defi")]
781 #[allow(unused_variables)]
782 #[pyo3(name = "on_pool")]
783 fn py_on_pool(&mut self, pool: Pool) -> PyResult<()> {
784 if let Some(ref py_self) = self.py_self {
786 Python::attach(|py| {
787 py_self.call_method1(py, "on_pool", (pool.into_py_any_unwrap(py),))
788 })?;
789 }
790 Ok(())
791 }
792
793 #[cfg(feature = "defi")]
794 #[allow(unused_variables)]
795 #[pyo3(name = "on_pool_swap")]
796 fn py_on_pool_swap(&mut self, swap: PoolSwap) -> PyResult<()> {
797 if let Some(ref py_self) = self.py_self {
799 Python::attach(|py| {
800 py_self.call_method1(py, "on_pool_swap", (swap.into_py_any_unwrap(py),))
801 })?;
802 }
803 Ok(())
804 }
805
806 #[cfg(feature = "defi")]
807 #[allow(unused_variables)]
808 #[pyo3(name = "on_pool_liquidity_update")]
809 fn py_on_pool_liquidity_update(&mut self, update: PoolLiquidityUpdate) -> PyResult<()> {
810 if let Some(ref py_self) = self.py_self {
812 Python::attach(|py| {
813 py_self.call_method1(
814 py,
815 "on_pool_liquidity_update",
816 (update.into_py_any_unwrap(py),),
817 )
818 })?;
819 }
820 Ok(())
821 }
822
823 #[pyo3(name = "subscribe_data")]
824 #[pyo3(signature = (data_type, client_id=None, params=None))]
825 fn py_subscribe_data(
826 &mut self,
827 data_type: DataType,
828 client_id: Option<ClientId>,
829 params: Option<IndexMap<String, String>>,
830 ) -> PyResult<()> {
831 self.subscribe_data(data_type, client_id, params);
832 Ok(())
833 }
834
835 #[pyo3(name = "subscribe_instruments")]
836 #[pyo3(signature = (venue, client_id=None, params=None))]
837 fn py_subscribe_instruments(
838 &mut self,
839 venue: Venue,
840 client_id: Option<ClientId>,
841 params: Option<IndexMap<String, String>>,
842 ) -> PyResult<()> {
843 self.subscribe_instruments(venue, client_id, params);
844 Ok(())
845 }
846
847 #[pyo3(name = "subscribe_instrument")]
848 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
849 fn py_subscribe_instrument(
850 &mut self,
851 instrument_id: InstrumentId,
852 client_id: Option<ClientId>,
853 params: Option<IndexMap<String, String>>,
854 ) -> PyResult<()> {
855 self.subscribe_instrument(instrument_id, client_id, params);
856 Ok(())
857 }
858
859 #[pyo3(name = "subscribe_book_deltas")]
860 #[pyo3(signature = (instrument_id, book_type, depth=None, client_id=None, managed=false, params=None))]
861 fn py_subscribe_book_deltas(
862 &mut self,
863 instrument_id: InstrumentId,
864 book_type: BookType,
865 depth: Option<usize>,
866 client_id: Option<ClientId>,
867 managed: bool,
868 params: Option<IndexMap<String, String>>,
869 ) -> PyResult<()> {
870 let depth = depth.and_then(NonZeroUsize::new);
871 self.subscribe_book_deltas(instrument_id, book_type, depth, client_id, managed, params);
872 Ok(())
873 }
874
875 #[pyo3(name = "subscribe_book_at_interval")]
876 #[pyo3(signature = (instrument_id, book_type, interval_ms, depth=None, client_id=None, params=None))]
877 fn py_subscribe_book_at_interval(
878 &mut self,
879 instrument_id: InstrumentId,
880 book_type: BookType,
881 interval_ms: usize,
882 depth: Option<usize>,
883 client_id: Option<ClientId>,
884 params: Option<IndexMap<String, String>>,
885 ) -> PyResult<()> {
886 let depth = depth.and_then(NonZeroUsize::new);
887 let interval_ms = NonZeroUsize::new(interval_ms)
888 .ok_or_else(|| PyErr::new::<PyValueError, _>("interval_ms must be > 0"))?;
889
890 self.subscribe_book_at_interval(
891 instrument_id,
892 book_type,
893 depth,
894 interval_ms,
895 client_id,
896 params,
897 );
898 Ok(())
899 }
900
901 #[pyo3(name = "subscribe_quotes")]
902 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
903 fn py_subscribe_quotes(
904 &mut self,
905 instrument_id: InstrumentId,
906 client_id: Option<ClientId>,
907 params: Option<IndexMap<String, String>>,
908 ) -> PyResult<()> {
909 self.subscribe_quotes(instrument_id, client_id, params);
910 Ok(())
911 }
912
913 #[pyo3(name = "subscribe_trades")]
914 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
915 fn py_subscribe_trades(
916 &mut self,
917 instrument_id: InstrumentId,
918 client_id: Option<ClientId>,
919 params: Option<IndexMap<String, String>>,
920 ) -> PyResult<()> {
921 self.subscribe_trades(instrument_id, client_id, params);
922 Ok(())
923 }
924
925 #[pyo3(name = "subscribe_bars")]
926 #[pyo3(signature = (bar_type, client_id=None, await_partial=false, params=None))]
927 fn py_subscribe_bars(
928 &mut self,
929 bar_type: BarType,
930 client_id: Option<ClientId>,
931 await_partial: bool,
932 params: Option<IndexMap<String, String>>,
933 ) -> PyResult<()> {
934 self.subscribe_bars(bar_type, client_id, await_partial, params);
935 Ok(())
936 }
937
938 #[pyo3(name = "subscribe_mark_prices")]
939 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
940 fn py_subscribe_mark_prices(
941 &mut self,
942 instrument_id: InstrumentId,
943 client_id: Option<ClientId>,
944 params: Option<IndexMap<String, String>>,
945 ) -> PyResult<()> {
946 self.subscribe_mark_prices(instrument_id, client_id, params);
947 Ok(())
948 }
949
950 #[pyo3(name = "subscribe_index_prices")]
951 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
952 fn py_subscribe_index_prices(
953 &mut self,
954 instrument_id: InstrumentId,
955 client_id: Option<ClientId>,
956 params: Option<IndexMap<String, String>>,
957 ) -> PyResult<()> {
958 self.subscribe_index_prices(instrument_id, client_id, params);
959 Ok(())
960 }
961
962 #[pyo3(name = "subscribe_instrument_status")]
963 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
964 fn py_subscribe_instrument_status(
965 &mut self,
966 instrument_id: InstrumentId,
967 client_id: Option<ClientId>,
968 params: Option<IndexMap<String, String>>,
969 ) -> PyResult<()> {
970 self.subscribe_instrument_status(instrument_id, client_id, params);
971 Ok(())
972 }
973
974 #[pyo3(name = "subscribe_instrument_close")]
975 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
976 fn py_subscribe_instrument_close(
977 &mut self,
978 instrument_id: InstrumentId,
979 client_id: Option<ClientId>,
980 params: Option<IndexMap<String, String>>,
981 ) -> PyResult<()> {
982 self.subscribe_instrument_close(instrument_id, client_id, params);
983 Ok(())
984 }
985
986 #[cfg(feature = "defi")]
987 #[pyo3(name = "subscribe_blocks")]
988 #[pyo3(signature = (chain, client_id=None, params=None))]
989 fn py_subscribe_blocks(
990 &mut self,
991 chain: Blockchain,
992 client_id: Option<ClientId>,
993 params: Option<IndexMap<String, String>>,
994 ) -> PyResult<()> {
995 self.subscribe_blocks(chain, client_id, params);
996 Ok(())
997 }
998
999 #[cfg(feature = "defi")]
1000 #[pyo3(name = "subscribe_pool")]
1001 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1002 fn py_subscribe_pool(
1003 &mut self,
1004 instrument_id: InstrumentId,
1005 client_id: Option<ClientId>,
1006 params: Option<IndexMap<String, String>>,
1007 ) -> PyResult<()> {
1008 self.subscribe_pool(instrument_id, client_id, params);
1009 Ok(())
1010 }
1011
1012 #[cfg(feature = "defi")]
1013 #[pyo3(name = "subscribe_pool_swaps")]
1014 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1015 fn py_subscribe_pool_swaps(
1016 &mut self,
1017 instrument_id: InstrumentId,
1018 client_id: Option<ClientId>,
1019 params: Option<IndexMap<String, String>>,
1020 ) -> PyResult<()> {
1021 self.subscribe_pool_swaps(instrument_id, client_id, params);
1022 Ok(())
1023 }
1024
1025 #[cfg(feature = "defi")]
1026 #[pyo3(name = "subscribe_pool_liquidity_updates")]
1027 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1028 fn py_subscribe_pool_liquidity_updates(
1029 &mut self,
1030 instrument_id: InstrumentId,
1031 client_id: Option<ClientId>,
1032 params: Option<IndexMap<String, String>>,
1033 ) -> PyResult<()> {
1034 self.subscribe_pool_liquidity_updates(instrument_id, client_id, params);
1035 Ok(())
1036 }
1037
1038 #[pyo3(name = "request_data")]
1039 #[pyo3(signature = (data_type, client_id, start=None, end=None, limit=None, params=None))]
1040 fn py_request_data(
1041 &mut self,
1042 data_type: DataType,
1043 client_id: ClientId,
1044 start: Option<u64>,
1045 end: Option<u64>,
1046 limit: Option<usize>,
1047 params: Option<IndexMap<String, String>>,
1048 ) -> PyResult<String> {
1049 let limit = limit.and_then(NonZeroUsize::new);
1050 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1051 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1052
1053 let request_id = self
1054 .request_data(data_type, client_id, start, end, limit, params)
1055 .map_err(to_pyvalue_err)?;
1056 Ok(request_id.to_string())
1057 }
1058
1059 #[pyo3(name = "request_instrument")]
1060 #[pyo3(signature = (instrument_id, start=None, end=None, client_id=None, params=None))]
1061 fn py_request_instrument(
1062 &mut self,
1063 instrument_id: InstrumentId,
1064 start: Option<u64>,
1065 end: Option<u64>,
1066 client_id: Option<ClientId>,
1067 params: Option<IndexMap<String, String>>,
1068 ) -> PyResult<String> {
1069 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1070 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1071
1072 let request_id = self
1073 .request_instrument(instrument_id, start, end, client_id, params)
1074 .map_err(to_pyvalue_err)?;
1075 Ok(request_id.to_string())
1076 }
1077
1078 #[pyo3(name = "request_instruments")]
1079 #[pyo3(signature = (venue=None, start=None, end=None, client_id=None, params=None))]
1080 fn py_request_instruments(
1081 &mut self,
1082 venue: Option<Venue>,
1083 start: Option<u64>,
1084 end: Option<u64>,
1085 client_id: Option<ClientId>,
1086 params: Option<IndexMap<String, String>>,
1087 ) -> PyResult<String> {
1088 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1089 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1090
1091 let request_id = self
1092 .request_instruments(venue, start, end, client_id, params)
1093 .map_err(to_pyvalue_err)?;
1094 Ok(request_id.to_string())
1095 }
1096
1097 #[pyo3(name = "request_book_snapshot")]
1098 #[pyo3(signature = (instrument_id, depth=None, client_id=None, params=None))]
1099 fn py_request_book_snapshot(
1100 &mut self,
1101 instrument_id: InstrumentId,
1102 depth: Option<usize>,
1103 client_id: Option<ClientId>,
1104 params: Option<IndexMap<String, String>>,
1105 ) -> PyResult<String> {
1106 let depth = depth.and_then(NonZeroUsize::new);
1107
1108 let request_id = self
1109 .request_book_snapshot(instrument_id, depth, client_id, params)
1110 .map_err(to_pyvalue_err)?;
1111 Ok(request_id.to_string())
1112 }
1113
1114 #[pyo3(name = "request_quotes")]
1115 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
1116 fn py_request_quotes(
1117 &mut self,
1118 instrument_id: InstrumentId,
1119 start: Option<u64>,
1120 end: Option<u64>,
1121 limit: Option<usize>,
1122 client_id: Option<ClientId>,
1123 params: Option<IndexMap<String, String>>,
1124 ) -> PyResult<String> {
1125 let limit = limit.and_then(NonZeroUsize::new);
1126 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1127 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1128
1129 let request_id = self
1130 .request_quotes(instrument_id, start, end, limit, client_id, params)
1131 .map_err(to_pyvalue_err)?;
1132 Ok(request_id.to_string())
1133 }
1134
1135 #[pyo3(name = "request_trades")]
1136 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None, client_id=None, params=None))]
1137 fn py_request_trades(
1138 &mut self,
1139 instrument_id: InstrumentId,
1140 start: Option<u64>,
1141 end: Option<u64>,
1142 limit: Option<usize>,
1143 client_id: Option<ClientId>,
1144 params: Option<IndexMap<String, String>>,
1145 ) -> PyResult<String> {
1146 let limit = limit.and_then(NonZeroUsize::new);
1147 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1148 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1149
1150 let request_id = self
1151 .request_trades(instrument_id, start, end, limit, client_id, params)
1152 .map_err(to_pyvalue_err)?;
1153 Ok(request_id.to_string())
1154 }
1155
1156 #[pyo3(name = "request_bars")]
1157 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, client_id=None, params=None))]
1158 fn py_request_bars(
1159 &mut self,
1160 bar_type: BarType,
1161 start: Option<u64>,
1162 end: Option<u64>,
1163 limit: Option<usize>,
1164 client_id: Option<ClientId>,
1165 params: Option<IndexMap<String, String>>,
1166 ) -> PyResult<String> {
1167 let limit = limit.and_then(NonZeroUsize::new);
1168 let start = start.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1169 let end = end.map(|ts| UnixNanos::from(ts).to_datetime_utc());
1170
1171 let request_id = self
1172 .request_bars(bar_type, start, end, limit, client_id, params)
1173 .map_err(to_pyvalue_err)?;
1174 Ok(request_id.to_string())
1175 }
1176
1177 #[pyo3(name = "unsubscribe_data")]
1178 #[pyo3(signature = (data_type, client_id=None, params=None))]
1179 fn py_unsubscribe_data(
1180 &mut self,
1181 data_type: DataType,
1182 client_id: Option<ClientId>,
1183 params: Option<IndexMap<String, String>>,
1184 ) -> PyResult<()> {
1185 self.unsubscribe_data(data_type, client_id, params);
1186 Ok(())
1187 }
1188
1189 #[pyo3(name = "unsubscribe_instruments")]
1190 #[pyo3(signature = (venue, client_id=None, params=None))]
1191 fn py_unsubscribe_instruments(
1192 &mut self,
1193 venue: Venue,
1194 client_id: Option<ClientId>,
1195 params: Option<IndexMap<String, String>>,
1196 ) -> PyResult<()> {
1197 self.unsubscribe_instruments(venue, client_id, params);
1198 Ok(())
1199 }
1200
1201 #[pyo3(name = "unsubscribe_instrument")]
1202 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1203 fn py_unsubscribe_instrument(
1204 &mut self,
1205 instrument_id: InstrumentId,
1206 client_id: Option<ClientId>,
1207 params: Option<IndexMap<String, String>>,
1208 ) -> PyResult<()> {
1209 self.unsubscribe_instrument(instrument_id, client_id, params);
1210 Ok(())
1211 }
1212
1213 #[pyo3(name = "unsubscribe_book_deltas")]
1214 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1215 fn py_unsubscribe_book_deltas(
1216 &mut self,
1217 instrument_id: InstrumentId,
1218 client_id: Option<ClientId>,
1219 params: Option<IndexMap<String, String>>,
1220 ) -> PyResult<()> {
1221 self.unsubscribe_book_deltas(instrument_id, client_id, params);
1222 Ok(())
1223 }
1224
1225 #[pyo3(name = "unsubscribe_book_at_interval")]
1226 #[pyo3(signature = (instrument_id, interval_ms, client_id=None, params=None))]
1227 fn py_unsubscribe_book_at_interval(
1228 &mut self,
1229 instrument_id: InstrumentId,
1230 interval_ms: usize,
1231 client_id: Option<ClientId>,
1232 params: Option<IndexMap<String, String>>,
1233 ) -> PyResult<()> {
1234 let interval_ms = NonZeroUsize::new(interval_ms)
1235 .ok_or_else(|| PyErr::new::<PyValueError, _>("interval_ms must be > 0"))?;
1236
1237 self.unsubscribe_book_at_interval(instrument_id, interval_ms, client_id, params);
1238 Ok(())
1239 }
1240
1241 #[pyo3(name = "unsubscribe_quotes")]
1242 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1243 fn py_unsubscribe_quotes(
1244 &mut self,
1245 instrument_id: InstrumentId,
1246 client_id: Option<ClientId>,
1247 params: Option<IndexMap<String, String>>,
1248 ) -> PyResult<()> {
1249 self.unsubscribe_quotes(instrument_id, client_id, params);
1250 Ok(())
1251 }
1252
1253 #[pyo3(name = "unsubscribe_trades")]
1254 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1255 fn py_unsubscribe_trades(
1256 &mut self,
1257 instrument_id: InstrumentId,
1258 client_id: Option<ClientId>,
1259 params: Option<IndexMap<String, String>>,
1260 ) -> PyResult<()> {
1261 self.unsubscribe_trades(instrument_id, client_id, params);
1262 Ok(())
1263 }
1264
1265 #[pyo3(name = "unsubscribe_bars")]
1266 #[pyo3(signature = (bar_type, client_id=None, params=None))]
1267 fn py_unsubscribe_bars(
1268 &mut self,
1269 bar_type: BarType,
1270 client_id: Option<ClientId>,
1271 params: Option<IndexMap<String, String>>,
1272 ) -> PyResult<()> {
1273 self.unsubscribe_bars(bar_type, client_id, params);
1274 Ok(())
1275 }
1276
1277 #[pyo3(name = "unsubscribe_mark_prices")]
1278 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1279 fn py_unsubscribe_mark_prices(
1280 &mut self,
1281 instrument_id: InstrumentId,
1282 client_id: Option<ClientId>,
1283 params: Option<IndexMap<String, String>>,
1284 ) -> PyResult<()> {
1285 self.unsubscribe_mark_prices(instrument_id, client_id, params);
1286 Ok(())
1287 }
1288
1289 #[pyo3(name = "unsubscribe_index_prices")]
1290 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1291 fn py_unsubscribe_index_prices(
1292 &mut self,
1293 instrument_id: InstrumentId,
1294 client_id: Option<ClientId>,
1295 params: Option<IndexMap<String, String>>,
1296 ) -> PyResult<()> {
1297 self.unsubscribe_index_prices(instrument_id, client_id, params);
1298 Ok(())
1299 }
1300
1301 #[pyo3(name = "unsubscribe_instrument_status")]
1302 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1303 fn py_unsubscribe_instrument_status(
1304 &mut self,
1305 instrument_id: InstrumentId,
1306 client_id: Option<ClientId>,
1307 params: Option<IndexMap<String, String>>,
1308 ) -> PyResult<()> {
1309 self.unsubscribe_instrument_status(instrument_id, client_id, params);
1310 Ok(())
1311 }
1312
1313 #[pyo3(name = "unsubscribe_instrument_close")]
1314 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1315 fn py_unsubscribe_instrument_close(
1316 &mut self,
1317 instrument_id: InstrumentId,
1318 client_id: Option<ClientId>,
1319 params: Option<IndexMap<String, String>>,
1320 ) -> PyResult<()> {
1321 self.unsubscribe_instrument_close(instrument_id, client_id, params);
1322 Ok(())
1323 }
1324
1325 #[cfg(feature = "defi")]
1326 #[pyo3(name = "unsubscribe_blocks")]
1327 #[pyo3(signature = (chain, client_id=None, params=None))]
1328 fn py_unsubscribe_blocks(
1329 &mut self,
1330 chain: Blockchain,
1331 client_id: Option<ClientId>,
1332 params: Option<IndexMap<String, String>>,
1333 ) -> PyResult<()> {
1334 self.unsubscribe_blocks(chain, client_id, params);
1335 Ok(())
1336 }
1337
1338 #[cfg(feature = "defi")]
1339 #[pyo3(name = "unsubscribe_pool")]
1340 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1341 fn py_unsubscribe_pool(
1342 &mut self,
1343 instrument_id: InstrumentId,
1344 client_id: Option<ClientId>,
1345 params: Option<IndexMap<String, String>>,
1346 ) -> PyResult<()> {
1347 self.unsubscribe_pool(instrument_id, client_id, params);
1348 Ok(())
1349 }
1350
1351 #[cfg(feature = "defi")]
1352 #[pyo3(name = "unsubscribe_pool_swaps")]
1353 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1354 fn py_unsubscribe_pool_swaps(
1355 &mut self,
1356 instrument_id: InstrumentId,
1357 client_id: Option<ClientId>,
1358 params: Option<IndexMap<String, String>>,
1359 ) -> PyResult<()> {
1360 self.unsubscribe_pool_swaps(instrument_id, client_id, params);
1361 Ok(())
1362 }
1363
1364 #[cfg(feature = "defi")]
1365 #[pyo3(name = "unsubscribe_pool_liquidity_updates")]
1366 #[pyo3(signature = (instrument_id, client_id=None, params=None))]
1367 fn py_unsubscribe_pool_liquidity_updates(
1368 &mut self,
1369 instrument_id: InstrumentId,
1370 client_id: Option<ClientId>,
1371 params: Option<IndexMap<String, String>>,
1372 ) -> PyResult<()> {
1373 self.unsubscribe_pool_liquidity_updates(instrument_id, client_id, params);
1374 Ok(())
1375 }
1376
1377 #[allow(unused_variables)]
1378 #[pyo3(name = "on_historical_data")]
1379 fn py_on_historical_data(&mut self, data: Py<PyAny>) -> PyResult<()> {
1380 Ok(())
1382 }
1383
1384 #[allow(unused_variables)]
1385 #[pyo3(name = "on_historical_quotes")]
1386 fn py_on_historical_quotes(&mut self, quotes: Vec<QuoteTick>) -> PyResult<()> {
1387 Ok(())
1389 }
1390
1391 #[allow(unused_variables)]
1392 #[pyo3(name = "on_historical_trades")]
1393 fn py_on_historical_trades(&mut self, trades: Vec<TradeTick>) -> PyResult<()> {
1394 Ok(())
1396 }
1397
1398 #[allow(unused_variables)]
1399 #[pyo3(name = "on_historical_bars")]
1400 fn py_on_historical_bars(&mut self, bars: Vec<Bar>) -> PyResult<()> {
1401 Ok(())
1403 }
1404
1405 #[allow(unused_variables)]
1406 #[pyo3(name = "on_historical_mark_prices")]
1407 fn py_on_historical_mark_prices(&mut self, mark_prices: Vec<MarkPriceUpdate>) -> PyResult<()> {
1408 Ok(())
1410 }
1411
1412 #[allow(unused_variables)]
1413 #[pyo3(name = "on_historical_index_prices")]
1414 fn py_on_historical_index_prices(
1415 &mut self,
1416 index_prices: Vec<IndexPriceUpdate>,
1417 ) -> PyResult<()> {
1418 Ok(())
1420 }
1421}
1422
1423#[cfg(test)]
1427mod tests {
1428 use std::{
1429 any::Any,
1430 cell::RefCell,
1431 collections::HashMap,
1432 ops::{Deref, DerefMut},
1433 rc::Rc,
1434 str::FromStr,
1435 sync::{Arc, Mutex},
1436 };
1437
1438 use nautilus_core::{UUID4, UnixNanos};
1439 #[cfg(feature = "defi")]
1440 use nautilus_model::defi::{
1441 AmmType, Block, Blockchain, Chain, Dex, DexType, Pool, PoolLiquidityUpdate, PoolSwap, Token,
1442 };
1443 use nautilus_model::{
1444 data::{
1445 Bar, BarType, DataType, IndexPriceUpdate, InstrumentStatus, MarkPriceUpdate,
1446 OrderBookDelta, OrderBookDeltas, QuoteTick, TradeTick, close::InstrumentClose,
1447 },
1448 enums::BookType,
1449 identifiers::{ClientId, TraderId, Venue},
1450 instruments::{CurrencyPair, InstrumentAny, stubs::audusd_sim},
1451 orderbook::OrderBook,
1452 types::{Price, Quantity},
1453 };
1454 use rstest::{fixture, rstest};
1455 use ustr::Ustr;
1456
1457 use super::PyDataActor;
1458 use crate::{
1459 actor::{DataActor, data_actor::DataActorCore},
1460 cache::Cache,
1461 clock::TestClock,
1462 component::Component,
1463 enums::ComponentState,
1464 runner::{SyncDataCommandSender, set_data_cmd_sender},
1465 signal::Signal,
1466 timer::TimeEvent,
1467 };
1468
1469 #[fixture]
1470 fn clock() -> Rc<RefCell<TestClock>> {
1471 Rc::new(RefCell::new(TestClock::new()))
1472 }
1473
1474 #[fixture]
1475 fn cache() -> Rc<RefCell<Cache>> {
1476 Rc::new(RefCell::new(Cache::new(None, None)))
1477 }
1478
1479 #[fixture]
1480 fn trader_id() -> TraderId {
1481 TraderId::from("TRADER-001")
1482 }
1483
1484 #[fixture]
1485 fn client_id() -> ClientId {
1486 ClientId::new("TestClient")
1487 }
1488
1489 #[fixture]
1490 fn venue() -> Venue {
1491 Venue::from("SIM")
1492 }
1493
1494 #[fixture]
1495 fn data_type() -> DataType {
1496 DataType::new("TestData", None)
1497 }
1498
1499 #[fixture]
1500 fn bar_type(audusd_sim: CurrencyPair) -> BarType {
1501 BarType::from_str(&format!("{}-1-MINUTE-LAST-INTERNAL", audusd_sim.id)).unwrap()
1502 }
1503
1504 fn create_unregistered_actor() -> PyDataActor {
1505 PyDataActor::new(None)
1506 }
1507
1508 fn create_registered_actor(
1509 clock: Rc<RefCell<TestClock>>,
1510 cache: Rc<RefCell<Cache>>,
1511 trader_id: TraderId,
1512 ) -> PyDataActor {
1513 let sender = SyncDataCommandSender;
1515 set_data_cmd_sender(Arc::new(sender));
1516
1517 let mut actor = PyDataActor::new(None);
1518 actor.register(trader_id, clock, cache).unwrap();
1519 actor
1520 }
1521
1522 #[rstest]
1523 fn test_new_actor_creation() {
1524 let actor = PyDataActor::new(None);
1525 assert!(actor.trader_id().is_none());
1526 }
1527
1528 #[rstest]
1529 fn test_clock_access_before_registration_raises_error() {
1530 let actor = PyDataActor::new(None);
1531
1532 let result = actor.py_clock();
1534 assert!(result.is_err());
1535
1536 let error = result.unwrap_err();
1537 pyo3::Python::initialize();
1538 pyo3::Python::attach(|py| {
1539 assert!(error.is_instance_of::<pyo3::exceptions::PyRuntimeError>(py));
1540 });
1541
1542 let error_msg = error.to_string();
1543 assert!(
1544 error_msg.contains("Actor must be registered with a trader before accessing clock")
1545 );
1546 }
1547
1548 #[rstest]
1549 fn test_unregistered_actor_methods_work() {
1550 let actor = create_unregistered_actor();
1551
1552 assert!(!actor.py_is_ready());
1553 assert!(!actor.py_is_running());
1554 assert!(!actor.py_is_stopped());
1555 assert!(!actor.py_is_disposed());
1556 assert!(!actor.py_is_degraded());
1557 assert!(!actor.py_is_faulted());
1558
1559 assert_eq!(actor.trader_id(), None);
1561 }
1562
1563 #[rstest]
1564 fn test_registration_success(
1565 clock: Rc<RefCell<TestClock>>,
1566 cache: Rc<RefCell<Cache>>,
1567 trader_id: TraderId,
1568 ) {
1569 let mut actor = create_unregistered_actor();
1570 actor.register(trader_id, clock, cache).unwrap();
1571 assert!(actor.trader_id().is_some());
1572 assert_eq!(actor.trader_id().unwrap(), trader_id);
1573 }
1574
1575 #[rstest]
1576 fn test_registered_actor_basic_properties(
1577 clock: Rc<RefCell<TestClock>>,
1578 cache: Rc<RefCell<Cache>>,
1579 trader_id: TraderId,
1580 ) {
1581 let actor = create_registered_actor(clock, cache, trader_id);
1582
1583 assert_eq!(actor.state(), ComponentState::Ready);
1584 assert_eq!(actor.trader_id(), Some(TraderId::from("TRADER-001")));
1585 assert!(actor.py_is_ready());
1586 assert!(!actor.py_is_running());
1587 assert!(!actor.py_is_stopped());
1588 assert!(!actor.py_is_disposed());
1589 assert!(!actor.py_is_degraded());
1590 assert!(!actor.py_is_faulted());
1591 }
1592
1593 #[rstest]
1594 fn test_basic_subscription_methods_compile(
1595 clock: Rc<RefCell<TestClock>>,
1596 cache: Rc<RefCell<Cache>>,
1597 trader_id: TraderId,
1598 data_type: DataType,
1599 client_id: ClientId,
1600 audusd_sim: CurrencyPair,
1601 ) {
1602 let mut actor = create_registered_actor(clock, cache, trader_id);
1603
1604 let _ = actor.py_subscribe_data(data_type.clone(), Some(client_id), None);
1605 let _ = actor.py_subscribe_quotes(audusd_sim.id, Some(client_id), None);
1606 let _ = actor.py_unsubscribe_data(data_type, Some(client_id), None);
1607 let _ = actor.py_unsubscribe_quotes(audusd_sim.id, Some(client_id), None);
1608 }
1609
1610 #[ignore] #[rstest]
1612 fn test_lifecycle_methods_pass_through(
1613 clock: Rc<RefCell<TestClock>>,
1614 cache: Rc<RefCell<Cache>>,
1615 trader_id: TraderId,
1616 ) {
1617 let mut actor = create_registered_actor(clock, cache, trader_id);
1618
1619 assert!(actor.py_start().is_ok());
1620 assert!(actor.py_stop().is_ok());
1621 assert!(actor.py_dispose().is_ok());
1622 }
1623
1624 #[rstest]
1625 fn test_shutdown_system_passes_through(
1626 clock: Rc<RefCell<TestClock>>,
1627 cache: Rc<RefCell<Cache>>,
1628 trader_id: TraderId,
1629 ) {
1630 let actor = create_registered_actor(clock, cache, trader_id);
1631
1632 assert!(
1633 actor
1634 .py_shutdown_system(Some("Test shutdown".to_string()))
1635 .is_ok()
1636 );
1637 assert!(actor.py_shutdown_system(None).is_ok());
1638 }
1639
1640 #[rstest]
1641 fn test_book_at_interval_invalid_interval_ms(
1642 clock: Rc<RefCell<TestClock>>,
1643 cache: Rc<RefCell<Cache>>,
1644 trader_id: TraderId,
1645 audusd_sim: CurrencyPair,
1646 ) {
1647 pyo3::Python::initialize();
1648 let mut actor = create_registered_actor(clock, cache, trader_id);
1649
1650 let result = actor.py_subscribe_book_at_interval(
1651 audusd_sim.id,
1652 BookType::L2_MBP,
1653 0,
1654 None,
1655 None,
1656 None,
1657 );
1658 assert!(result.is_err());
1659 assert_eq!(
1660 result.unwrap_err().to_string(),
1661 "ValueError: interval_ms must be > 0"
1662 );
1663
1664 let result = actor.py_unsubscribe_book_at_interval(audusd_sim.id, 0, None, None);
1665 assert!(result.is_err());
1666 assert_eq!(
1667 result.unwrap_err().to_string(),
1668 "ValueError: interval_ms must be > 0"
1669 );
1670 }
1671
1672 #[rstest]
1673 fn test_request_methods_signatures_exist() {
1674 let actor = create_unregistered_actor();
1675 assert!(actor.trader_id().is_none());
1676 }
1677
1678 #[rstest]
1679 fn test_data_actor_trait_implementation(
1680 clock: Rc<RefCell<TestClock>>,
1681 cache: Rc<RefCell<Cache>>,
1682 trader_id: TraderId,
1683 ) {
1684 let actor = create_registered_actor(clock, cache, trader_id);
1685 let state = actor.state();
1686 assert_eq!(state, ComponentState::Ready);
1687 }
1688
1689 static CALL_TRACKER: std::sync::LazyLock<Arc<Mutex<HashMap<String, i32>>>> =
1693 std::sync::LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
1694
1695 #[derive(Debug)]
1697 struct TestDataActor {
1698 inner: PyDataActor,
1699 }
1700
1701 impl TestDataActor {
1702 fn new() -> Self {
1703 Self {
1704 inner: PyDataActor::new(None),
1705 }
1706 }
1707
1708 fn track_call(&self, handler_name: &str) {
1709 let mut tracker = CALL_TRACKER.lock().unwrap();
1710 *tracker.entry(handler_name.to_string()).or_insert(0) += 1;
1711 }
1712
1713 fn get_call_count(&self, handler_name: &str) -> i32 {
1714 let tracker = CALL_TRACKER.lock().unwrap();
1715 tracker.get(handler_name).copied().unwrap_or(0)
1716 }
1717
1718 fn reset_tracker(&self) {
1719 let mut tracker = CALL_TRACKER.lock().unwrap();
1720 tracker.clear();
1721 }
1722 }
1723
1724 impl Deref for TestDataActor {
1725 type Target = DataActorCore;
1726 fn deref(&self) -> &Self::Target {
1727 &self.inner.core
1728 }
1729 }
1730
1731 impl DerefMut for TestDataActor {
1732 fn deref_mut(&mut self) -> &mut Self::Target {
1733 &mut self.inner.core
1734 }
1735 }
1736
1737 impl DataActor for TestDataActor {
1738 fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
1739 self.track_call("on_time_event");
1740 self.inner.on_time_event(event)
1741 }
1742
1743 fn on_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
1744 self.track_call("on_data");
1745 self.inner.on_data(data)
1746 }
1747
1748 fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
1749 self.track_call("on_signal");
1750 self.inner.on_signal(signal)
1751 }
1752
1753 fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
1754 self.track_call("on_instrument");
1755 self.inner.on_instrument(instrument)
1756 }
1757
1758 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
1759 self.track_call("on_quote");
1760 self.inner.on_quote(quote)
1761 }
1762
1763 fn on_trade(&mut self, trade: &TradeTick) -> anyhow::Result<()> {
1764 self.track_call("on_trade");
1765 self.inner.on_trade(trade)
1766 }
1767
1768 fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
1769 self.track_call("on_bar");
1770 self.inner.on_bar(bar)
1771 }
1772
1773 fn on_book(&mut self, book: &OrderBook) -> anyhow::Result<()> {
1774 self.track_call("on_book");
1775 self.inner.on_book(book)
1776 }
1777
1778 fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
1779 self.track_call("on_book_deltas");
1780 self.inner.on_book_deltas(deltas)
1781 }
1782
1783 fn on_mark_price(&mut self, update: &MarkPriceUpdate) -> anyhow::Result<()> {
1784 self.track_call("on_mark_price");
1785 self.inner.on_mark_price(update)
1786 }
1787
1788 fn on_index_price(&mut self, update: &IndexPriceUpdate) -> anyhow::Result<()> {
1789 self.track_call("on_index_price");
1790 self.inner.on_index_price(update)
1791 }
1792
1793 fn on_instrument_status(&mut self, update: &InstrumentStatus) -> anyhow::Result<()> {
1794 self.track_call("on_instrument_status");
1795 self.inner.on_instrument_status(update)
1796 }
1797
1798 fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
1799 self.track_call("on_instrument_close");
1800 self.inner.on_instrument_close(update)
1801 }
1802
1803 #[cfg(feature = "defi")]
1804 fn on_block(&mut self, block: &Block) -> anyhow::Result<()> {
1805 self.track_call("on_block");
1806 self.inner.on_block(block)
1807 }
1808
1809 #[cfg(feature = "defi")]
1810 fn on_pool(&mut self, pool: &Pool) -> anyhow::Result<()> {
1811 self.track_call("on_pool");
1812 self.inner.on_pool(pool)
1813 }
1814
1815 #[cfg(feature = "defi")]
1816 fn on_pool_swap(&mut self, swap: &PoolSwap) -> anyhow::Result<()> {
1817 self.track_call("on_pool_swap");
1818 self.inner.on_pool_swap(swap)
1819 }
1820
1821 #[cfg(feature = "defi")]
1822 fn on_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) -> anyhow::Result<()> {
1823 self.track_call("on_pool_liquidity_update");
1824 self.inner.on_pool_liquidity_update(update)
1825 }
1826 }
1827
1828 #[rstest]
1829 fn test_python_on_signal_handler(
1830 clock: Rc<RefCell<TestClock>>,
1831 cache: Rc<RefCell<Cache>>,
1832 trader_id: TraderId,
1833 ) {
1834 pyo3::Python::initialize();
1835 let mut test_actor = TestDataActor::new();
1836 test_actor.reset_tracker();
1837 test_actor
1838 .register(trader_id, clock.clone(), cache.clone())
1839 .unwrap();
1840
1841 let signal = Signal::new(
1842 Ustr::from("test_signal"),
1843 "1.0".to_string(),
1844 UnixNanos::default(),
1845 UnixNanos::default(),
1846 );
1847
1848 assert!(test_actor.on_signal(&signal).is_ok());
1849 assert_eq!(test_actor.get_call_count("on_signal"), 1);
1850 }
1851
1852 #[rstest]
1853 fn test_python_on_data_handler(
1854 clock: Rc<RefCell<TestClock>>,
1855 cache: Rc<RefCell<Cache>>,
1856 trader_id: TraderId,
1857 ) {
1858 pyo3::Python::initialize();
1859 let mut test_actor = TestDataActor::new();
1860 test_actor.reset_tracker();
1861 test_actor
1862 .register(trader_id, clock.clone(), cache.clone())
1863 .unwrap();
1864
1865 assert!(test_actor.on_data(&()).is_ok());
1866 assert_eq!(test_actor.get_call_count("on_data"), 1);
1867 }
1868
1869 #[rstest]
1870 fn test_python_on_time_event_handler(
1871 clock: Rc<RefCell<TestClock>>,
1872 cache: Rc<RefCell<Cache>>,
1873 trader_id: TraderId,
1874 ) {
1875 pyo3::Python::initialize();
1876 let mut test_actor = TestDataActor::new();
1877 test_actor.reset_tracker();
1878 test_actor
1879 .register(trader_id, clock.clone(), cache.clone())
1880 .unwrap();
1881
1882 let time_event = TimeEvent::new(
1883 Ustr::from("test_timer"),
1884 UUID4::new(),
1885 UnixNanos::default(),
1886 UnixNanos::default(),
1887 );
1888
1889 assert!(test_actor.on_time_event(&time_event).is_ok());
1890 assert_eq!(test_actor.get_call_count("on_time_event"), 1);
1891 }
1892
1893 #[rstest]
1894 fn test_python_on_instrument_handler(
1895 clock: Rc<RefCell<TestClock>>,
1896 cache: Rc<RefCell<Cache>>,
1897 trader_id: TraderId,
1898 audusd_sim: CurrencyPair,
1899 ) {
1900 pyo3::Python::initialize();
1901 let mut rust_actor = PyDataActor::new(None);
1902 rust_actor
1903 .register(trader_id, clock.clone(), cache.clone())
1904 .unwrap();
1905
1906 let instrument = InstrumentAny::CurrencyPair(audusd_sim);
1907
1908 assert!(rust_actor.on_instrument(&instrument).is_ok());
1909 }
1910
1911 #[rstest]
1912 fn test_python_on_quote_handler(
1913 clock: Rc<RefCell<TestClock>>,
1914 cache: Rc<RefCell<Cache>>,
1915 trader_id: TraderId,
1916 audusd_sim: CurrencyPair,
1917 ) {
1918 pyo3::Python::initialize();
1919 let mut rust_actor = PyDataActor::new(None);
1920 rust_actor
1921 .register(trader_id, clock.clone(), cache.clone())
1922 .unwrap();
1923
1924 let quote = QuoteTick::new(
1925 audusd_sim.id,
1926 Price::from("1.0000"),
1927 Price::from("1.0001"),
1928 Quantity::from("100000"),
1929 Quantity::from("100000"),
1930 UnixNanos::default(),
1931 UnixNanos::default(),
1932 );
1933
1934 assert!(rust_actor.on_quote("e).is_ok());
1935 }
1936
1937 #[rstest]
1938 fn test_python_on_trade_handler(
1939 clock: Rc<RefCell<TestClock>>,
1940 cache: Rc<RefCell<Cache>>,
1941 trader_id: TraderId,
1942 audusd_sim: CurrencyPair,
1943 ) {
1944 pyo3::Python::initialize();
1945 let mut rust_actor = PyDataActor::new(None);
1946 rust_actor
1947 .register(trader_id, clock.clone(), cache.clone())
1948 .unwrap();
1949
1950 let trade = TradeTick::new(
1951 audusd_sim.id,
1952 Price::from("1.0000"),
1953 Quantity::from("100000"),
1954 nautilus_model::enums::AggressorSide::Buyer,
1955 "T123".to_string().into(),
1956 UnixNanos::default(),
1957 UnixNanos::default(),
1958 );
1959
1960 assert!(rust_actor.on_trade(&trade).is_ok());
1961 }
1962
1963 #[rstest]
1964 fn test_python_on_bar_handler(
1965 clock: Rc<RefCell<TestClock>>,
1966 cache: Rc<RefCell<Cache>>,
1967 trader_id: TraderId,
1968 audusd_sim: CurrencyPair,
1969 ) {
1970 pyo3::Python::initialize();
1971 let mut rust_actor = PyDataActor::new(None);
1972 rust_actor
1973 .register(trader_id, clock.clone(), cache.clone())
1974 .unwrap();
1975
1976 let bar_type =
1977 BarType::from_str(&format!("{}-1-MINUTE-LAST-INTERNAL", audusd_sim.id)).unwrap();
1978 let bar = Bar::new(
1979 bar_type,
1980 Price::from("1.0000"),
1981 Price::from("1.0001"),
1982 Price::from("0.9999"),
1983 Price::from("1.0000"),
1984 Quantity::from("100000"),
1985 UnixNanos::default(),
1986 UnixNanos::default(),
1987 );
1988
1989 assert!(rust_actor.on_bar(&bar).is_ok());
1990 }
1991
1992 #[rstest]
1993 fn test_python_on_book_handler(
1994 clock: Rc<RefCell<TestClock>>,
1995 cache: Rc<RefCell<Cache>>,
1996 trader_id: TraderId,
1997 audusd_sim: CurrencyPair,
1998 ) {
1999 pyo3::Python::initialize();
2000 let mut rust_actor = PyDataActor::new(None);
2001 rust_actor
2002 .register(trader_id, clock.clone(), cache.clone())
2003 .unwrap();
2004
2005 let book = OrderBook::new(audusd_sim.id, BookType::L2_MBP);
2006 assert!(rust_actor.on_book(&book).is_ok());
2007 }
2008
2009 #[rstest]
2010 fn test_python_on_book_deltas_handler(
2011 clock: Rc<RefCell<TestClock>>,
2012 cache: Rc<RefCell<Cache>>,
2013 trader_id: TraderId,
2014 audusd_sim: CurrencyPair,
2015 ) {
2016 pyo3::Python::initialize();
2017 let mut rust_actor = PyDataActor::new(None);
2018 rust_actor
2019 .register(trader_id, clock.clone(), cache.clone())
2020 .unwrap();
2021
2022 let delta =
2023 OrderBookDelta::clear(audusd_sim.id, 0, UnixNanos::default(), UnixNanos::default());
2024 let deltas = OrderBookDeltas::new(audusd_sim.id, vec![delta]);
2025
2026 assert!(rust_actor.on_book_deltas(&deltas).is_ok());
2027 }
2028
2029 #[rstest]
2030 fn test_python_on_mark_price_handler(
2031 clock: Rc<RefCell<TestClock>>,
2032 cache: Rc<RefCell<Cache>>,
2033 trader_id: TraderId,
2034 audusd_sim: CurrencyPair,
2035 ) {
2036 pyo3::Python::initialize();
2037 let mut rust_actor = PyDataActor::new(None);
2038 rust_actor
2039 .register(trader_id, clock.clone(), cache.clone())
2040 .unwrap();
2041
2042 let mark_price = MarkPriceUpdate::new(
2043 audusd_sim.id,
2044 Price::from("1.0000"),
2045 UnixNanos::default(),
2046 UnixNanos::default(),
2047 );
2048
2049 assert!(rust_actor.on_mark_price(&mark_price).is_ok());
2050 }
2051
2052 #[rstest]
2053 fn test_python_on_index_price_handler(
2054 clock: Rc<RefCell<TestClock>>,
2055 cache: Rc<RefCell<Cache>>,
2056 trader_id: TraderId,
2057 audusd_sim: CurrencyPair,
2058 ) {
2059 pyo3::Python::initialize();
2060 let mut rust_actor = PyDataActor::new(None);
2061 rust_actor
2062 .register(trader_id, clock.clone(), cache.clone())
2063 .unwrap();
2064
2065 let index_price = IndexPriceUpdate::new(
2066 audusd_sim.id,
2067 Price::from("1.0000"),
2068 UnixNanos::default(),
2069 UnixNanos::default(),
2070 );
2071
2072 assert!(rust_actor.on_index_price(&index_price).is_ok());
2073 }
2074
2075 #[rstest]
2076 fn test_python_on_instrument_status_handler(
2077 clock: Rc<RefCell<TestClock>>,
2078 cache: Rc<RefCell<Cache>>,
2079 trader_id: TraderId,
2080 audusd_sim: CurrencyPair,
2081 ) {
2082 pyo3::Python::initialize();
2083 let mut rust_actor = PyDataActor::new(None);
2084 rust_actor
2085 .register(trader_id, clock.clone(), cache.clone())
2086 .unwrap();
2087
2088 let status = InstrumentStatus::new(
2089 audusd_sim.id,
2090 nautilus_model::enums::MarketStatusAction::Trading,
2091 UnixNanos::default(),
2092 UnixNanos::default(),
2093 None,
2094 None,
2095 None,
2096 None,
2097 None,
2098 );
2099
2100 assert!(rust_actor.on_instrument_status(&status).is_ok());
2101 }
2102
2103 #[rstest]
2104 fn test_python_on_instrument_close_handler(
2105 clock: Rc<RefCell<TestClock>>,
2106 cache: Rc<RefCell<Cache>>,
2107 trader_id: TraderId,
2108 audusd_sim: CurrencyPair,
2109 ) {
2110 pyo3::Python::initialize();
2111 let mut rust_actor = PyDataActor::new(None);
2112 rust_actor
2113 .register(trader_id, clock.clone(), cache.clone())
2114 .unwrap();
2115
2116 let close = InstrumentClose::new(
2117 audusd_sim.id,
2118 Price::from("1.0000"),
2119 nautilus_model::enums::InstrumentCloseType::EndOfSession,
2120 UnixNanos::default(),
2121 UnixNanos::default(),
2122 );
2123
2124 assert!(rust_actor.on_instrument_close(&close).is_ok());
2125 }
2126
2127 #[cfg(feature = "defi")]
2128 #[rstest]
2129 fn test_python_on_block_handler(
2130 clock: Rc<RefCell<TestClock>>,
2131 cache: Rc<RefCell<Cache>>,
2132 trader_id: TraderId,
2133 ) {
2134 pyo3::Python::initialize();
2135 let mut test_actor = TestDataActor::new();
2136 test_actor.reset_tracker();
2137 test_actor
2138 .register(trader_id, clock.clone(), cache.clone())
2139 .unwrap();
2140
2141 let block = Block::new(
2142 "0x1234567890abcdef".to_string(),
2143 "0xabcdef1234567890".to_string(),
2144 12345,
2145 "0x742E4422b21FB8B4dF463F28689AC98bD56c39e0".into(),
2146 21000,
2147 20000,
2148 UnixNanos::default(),
2149 Some(Blockchain::Ethereum),
2150 );
2151
2152 assert!(test_actor.on_block(&block).is_ok());
2153 assert_eq!(test_actor.get_call_count("on_block"), 1);
2154 }
2155
2156 #[cfg(feature = "defi")]
2157 #[rstest]
2158 fn test_python_on_pool_swap_handler(
2159 clock: Rc<RefCell<TestClock>>,
2160 cache: Rc<RefCell<Cache>>,
2161 trader_id: TraderId,
2162 ) {
2163 pyo3::Python::initialize();
2164 let mut rust_actor = PyDataActor::new(None);
2165 rust_actor
2166 .register(trader_id, clock.clone(), cache.clone())
2167 .unwrap();
2168
2169 let chain = Arc::new(Chain::new(Blockchain::Ethereum, 1));
2170 let dex = Arc::new(Dex::new(
2171 Chain::new(Blockchain::Ethereum, 1),
2172 DexType::UniswapV3,
2173 "0x1F98431c8aD98523631AE4a59f267346ea31F984",
2174 0,
2175 AmmType::CLAMM,
2176 "PoolCreated",
2177 "Swap",
2178 "Mint",
2179 "Burn",
2180 "Collect",
2181 ));
2182 let token0 = Token::new(
2183 chain.clone(),
2184 "0xa0b86a33e6441c8c06dd7b111a8c4e82e2b2a5e1"
2185 .parse()
2186 .unwrap(),
2187 "USDC".into(),
2188 "USD Coin".into(),
2189 6,
2190 );
2191 let token1 = Token::new(
2192 chain.clone(),
2193 "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
2194 .parse()
2195 .unwrap(),
2196 "WETH".into(),
2197 "Wrapped Ether".into(),
2198 18,
2199 );
2200 let pool = Arc::new(Pool::new(
2201 chain.clone(),
2202 dex.clone(),
2203 "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"
2204 .parse()
2205 .unwrap(),
2206 12345,
2207 token0,
2208 token1,
2209 Some(500),
2210 Some(10),
2211 UnixNanos::default(),
2212 ));
2213
2214 let swap = PoolSwap::new(
2215 chain.clone(),
2216 dex.clone(),
2217 pool.instrument_id,
2218 pool.address,
2219 12345,
2220 "0xabc123".to_string(),
2221 0,
2222 0,
2223 None,
2224 "0x742E4422b21FB8B4dF463F28689AC98bD56c39e0"
2225 .parse()
2226 .unwrap(),
2227 nautilus_model::enums::OrderSide::Buy,
2228 Quantity::from("1000"),
2229 Price::from("1.0"),
2230 );
2231
2232 assert!(rust_actor.on_pool_swap(&swap).is_ok());
2233 }
2234
2235 #[cfg(feature = "defi")]
2236 #[rstest]
2237 fn test_python_on_pool_liquidity_update_handler(
2238 clock: Rc<RefCell<TestClock>>,
2239 cache: Rc<RefCell<Cache>>,
2240 trader_id: TraderId,
2241 ) {
2242 pyo3::Python::initialize();
2243 let mut rust_actor = PyDataActor::new(None);
2244 rust_actor
2245 .register(trader_id, clock.clone(), cache.clone())
2246 .unwrap();
2247
2248 let block = Block::new(
2249 "0x1234567890abcdef".to_string(),
2250 "0xabcdef1234567890".to_string(),
2251 12345,
2252 "0x742E4422b21FB8B4dF463F28689AC98bD56c39e0".into(),
2253 21000,
2254 20000,
2255 UnixNanos::default(),
2256 Some(Blockchain::Ethereum),
2257 );
2258
2259 assert!(rust_actor.on_block(&block).is_ok());
2263 }
2264}