1#![warn(clippy::clone_on_ref_ptr)]
17
18use std::{cell::RefCell, rc::Rc};
19
20use jiff::{SignedDuration, Span, Timestamp, Zoned};
21use nautilus_core::{
22 DurationNanos, UnixNanos, datetime::try_datetime_to_unix_nanos, python::to_pyvalue_err,
23};
24use pyo3::prelude::*;
25
26use crate::{
27 clock::{Clock, ClockApi, VirtualClock},
28 live::clock::LiveClock,
29 timer::TimeEventCallback,
30};
31
32#[allow(non_camel_case_types)]
42#[pyo3::pyclass(
43 module = "nautilus_trader.common",
44 name = "Clock",
45 unsendable,
46 from_py_object
47)]
48#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
49#[derive(Debug, Clone)]
50pub struct PyClock(Rc<RefCell<dyn Clock>>);
51
52#[pymethods]
53#[pyo3_stub_gen::derive::gen_stub_pymethods]
54impl PyClock {
55 #[staticmethod]
56 #[pyo3(name = "new_test")]
57 fn py_new_test() -> Self {
58 Self(Rc::new(RefCell::new(VirtualClock::default())))
59 }
60
61 #[pyo3(name = "timestamp_ns")]
63 fn py_timestamp_ns(&self) -> u64 {
64 self.0.borrow().timestamp_ns().as_u64()
65 }
66
67 #[pyo3(name = "timestamp_us")]
69 fn py_timestamp_us(&self) -> u64 {
70 self.0.borrow().timestamp_us()
71 }
72
73 #[pyo3(name = "timestamp_ms")]
75 fn py_timestamp_ms(&self) -> u64 {
76 self.0.borrow().timestamp_ms()
77 }
78
79 #[pyo3(name = "timestamp")]
81 fn py_timestamp(&self) -> f64 {
82 self.0.borrow().timestamp()
83 }
84
85 #[pyo3(name = "utc_now")]
87 fn py_utc_now(&self) -> Timestamp {
88 self.0.borrow().utc_now()
89 }
90
91 #[pyo3(name = "set_time")]
92 fn py_set_time(&mut self, to_time_ns: u64) -> PyResult<()> {
93 let mut clock = self.0.borrow_mut();
94 let Some(test_clock) = clock.as_any_mut().downcast_mut::<VirtualClock>() else {
95 return Err(to_pyvalue_err(
96 "set_time is only supported by virtual clocks",
97 ));
98 };
99
100 test_clock.set_time(to_time_ns.into());
101 Ok(())
102 }
103
104 #[pyo3(name = "timer_names")]
106 fn py_timer_names(&self) -> Vec<String> {
107 self.0
108 .borrow()
109 .timer_names()
110 .into_iter()
111 .map(String::from)
112 .collect()
113 }
114
115 #[pyo3(name = "timer_count")]
117 fn py_timer_count(&self) -> usize {
118 self.0.borrow().timer_count()
119 }
120
121 #[pyo3(name = "register_default_handler")]
122 fn py_register_default_handler(&mut self, callback: Py<PyAny>) {
123 self.0
124 .borrow_mut()
125 .register_default_handler(TimeEventCallback::from(callback));
126 }
127
128 #[pyo3(name = "cancel_default_handler")]
129 fn py_cancel_default_handler(&mut self) {
130 self.0.borrow_mut().cancel_default_handler();
131 }
132
133 #[pyo3(name = "cancel_callbacks")]
134 fn py_cancel_callbacks(&mut self) {
135 self.0.borrow_mut().cancel_callbacks();
136 }
137
138 #[pyo3(
139 name = "set_time_alert",
140 signature = (name, alert_time, callback=None, allow_past=None)
141 )]
142 fn py_set_time_alert(
143 &mut self,
144 name: &str,
145 alert_time: Timestamp,
146 callback: Option<Py<PyAny>>,
147 allow_past: Option<bool>,
148 ) -> PyResult<()> {
149 self.0
150 .borrow_mut()
151 .set_time_alert(
152 name,
153 alert_time,
154 callback.map(TimeEventCallback::from),
155 allow_past,
156 )
157 .map_err(to_pyvalue_err)
158 }
159
160 #[pyo3(
161 name = "set_time_alert_ns",
162 signature = (name, alert_time_ns, callback=None, allow_past=None)
163 )]
164 fn py_set_time_alert_ns(
165 &mut self,
166 name: &str,
167 alert_time_ns: u64,
168 callback: Option<Py<PyAny>>,
169 allow_past: Option<bool>,
170 ) -> PyResult<()> {
171 self.0
172 .borrow_mut()
173 .set_time_alert_ns(
174 name,
175 alert_time_ns.into(),
176 callback.map(TimeEventCallback::from),
177 allow_past,
178 )
179 .map_err(to_pyvalue_err)
180 }
181
182 #[expect(clippy::too_many_arguments)]
183 #[pyo3(
184 name = "set_timer",
185 signature = (name, interval, start_time=None, stop_time=None, callback=None, allow_past=None, fire_immediately=None)
186 )]
187 fn py_set_timer(
188 &mut self,
189 name: &str,
190 interval: SignedDuration,
191 start_time: Option<Timestamp>,
192 stop_time: Option<Timestamp>,
193 callback: Option<Py<PyAny>>,
194 allow_past: Option<bool>,
195 fire_immediately: Option<bool>,
196 ) -> PyResult<()> {
197 if interval <= SignedDuration::ZERO {
198 return Err(to_pyvalue_err("Interval must be positive"));
199 }
200 let interval_ns =
201 DurationNanos::try_from(interval).map_err(|_| to_pyvalue_err("Interval too large"))?;
202
203 let start_time_ns = start_time
204 .map(try_datetime_to_unix_nanos)
205 .transpose()
206 .map_err(to_pyvalue_err)?;
207 let stop_time_ns = stop_time
208 .map(try_datetime_to_unix_nanos)
209 .transpose()
210 .map_err(to_pyvalue_err)?;
211
212 self.0
213 .borrow_mut()
214 .set_timer_ns(
215 name,
216 interval_ns,
217 start_time_ns,
218 stop_time_ns,
219 callback.map(TimeEventCallback::from),
220 allow_past,
221 fire_immediately,
222 )
223 .map_err(to_pyvalue_err)
224 }
225
226 #[expect(clippy::too_many_arguments)]
227 #[pyo3(
228 name = "set_timer_ns",
229 signature = (name, interval_ns, start_time_ns=None, stop_time_ns=None, callback=None, allow_past=None, fire_immediately=None)
230 )]
231 fn py_set_timer_ns(
232 &mut self,
233 name: &str,
234 interval_ns: u64,
235 start_time_ns: Option<u64>,
236 stop_time_ns: Option<u64>,
237 callback: Option<Py<PyAny>>,
238 allow_past: Option<bool>,
239 fire_immediately: Option<bool>,
240 ) -> PyResult<()> {
241 self.0
242 .borrow_mut()
243 .set_timer_ns(
244 name,
245 DurationNanos::new(interval_ns),
246 start_time_ns.map(UnixNanos::from),
247 stop_time_ns.map(UnixNanos::from),
248 callback.map(TimeEventCallback::from),
249 allow_past,
250 fire_immediately,
251 )
252 .map_err(to_pyvalue_err)
253 }
254
255 #[expect(
297 clippy::doc_markdown,
298 reason = "Python API docstrings use NumPy parameter and exception sections"
299 )]
300 #[expect(
301 clippy::too_many_arguments,
302 clippy::needless_pass_by_value,
303 reason = "PyO3 extracts the aware datetime as an owned Zoned"
304 )]
305 #[pyo3(
306 name = "set_timer_zoned",
307 signature = (name, interval, start_time, stop_time=None, callback=None, allow_past=None, fire_immediately=None)
308 )]
309 fn py_set_timer_zoned(
310 &mut self,
311 name: &str,
312 interval: &str,
313 start_time: Zoned,
314 stop_time: Option<Timestamp>,
315 callback: Option<Py<PyAny>>,
316 allow_past: Option<bool>,
317 fire_immediately: Option<bool>,
318 ) -> PyResult<()> {
319 let interval = interval.parse::<Span>().map_err(to_pyvalue_err)?;
320
321 ClockApi::new(self.0.as_ref())
322 .set_timer_zoned(
323 name,
324 interval,
325 &start_time,
326 stop_time,
327 callback.map(TimeEventCallback::from),
328 allow_past,
329 fire_immediately,
330 )
331 .map_err(to_pyvalue_err)
332 }
333
334 #[pyo3(name = "next_time_ns")]
335 fn py_next_time_ns(&self, name: &str) -> Option<u64> {
336 self.0.borrow().next_time_ns(name).map(|t| t.as_u64())
337 }
338
339 #[pyo3(name = "cancel_timer")]
340 fn py_cancel_timer(&mut self, name: &str) {
341 self.0.borrow_mut().cancel_timer(name);
342 }
343
344 #[pyo3(name = "cancel_timers")]
345 fn py_cancel_timers(&mut self) {
346 self.0.borrow_mut().cancel_timers();
347 }
348}
349
350impl PyClock {
351 #[must_use]
353 pub fn from_rc(rc: Rc<RefCell<dyn Clock>>) -> Self {
354 Self(rc)
355 }
356
357 #[must_use]
359 pub fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
360 Rc::clone(&self.0)
361 }
362
363 #[must_use]
365 pub fn new_test() -> Self {
366 Self(Rc::new(RefCell::new(VirtualClock::default())))
367 }
368
369 #[must_use]
371 pub fn new_live() -> Self {
372 Self(Rc::new(RefCell::new(LiveClock::default())))
373 }
374
375 #[must_use]
377 pub fn inner(&self) -> std::cell::Ref<'_, dyn Clock> {
378 self.0.borrow()
379 }
380
381 #[must_use]
383 pub fn inner_mut(&mut self) -> std::cell::RefMut<'_, dyn Clock> {
384 self.0.borrow_mut()
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use std::sync::Arc;
391
392 use jiff::{SignedDuration, Timestamp};
393 use nautilus_core::{DurationNanos, UnixNanos, python::IntoPyObjectNautilusExt};
394 use pyo3::{prelude::*, types::PyList};
395 use rstest::*;
396
397 use crate::{
398 clock::{Clock, VirtualClock},
399 python::clock::PyClock,
400 runner::{TimeEventMessage, TimeEventSender, set_time_event_sender},
401 timer::TimeEventCallback,
402 };
403
404 fn ensure_sender() {
405 if crate::runner::try_get_time_event_sender().is_none() {
406 set_time_event_sender(Arc::new(DummySender));
407 }
408 }
409
410 #[derive(Debug)]
412 struct DummySender;
413
414 impl TimeEventSender for DummySender {
415 fn send(&self, _message: TimeEventMessage) {}
416 }
417
418 #[fixture]
419 pub fn test_clock() -> VirtualClock {
420 VirtualClock::new()
421 }
422
423 pub(super) fn test_callback() -> TimeEventCallback {
424 Python::initialize();
425 Python::attach(|py| {
426 let py_list = PyList::empty(py);
427 let py_append = Py::from(py_list.getattr("append").unwrap());
428 let py_append = py_append.into_py_any_unwrap(py);
429 TimeEventCallback::from(py_append)
430 })
431 }
432
433 pub(super) fn test_py_callback() -> Py<PyAny> {
434 Python::initialize();
435 Python::attach(|py| {
436 let py_list = PyList::empty(py);
437 let py_append = Py::from(py_list.getattr("append").unwrap());
438 py_append.into_py_any_unwrap(py)
439 })
440 }
441
442 #[rstest]
447 fn test_test_clock_py_set_time_alert() {
448 Python::initialize();
449 Python::attach(|_py| {
450 let mut py_clock = PyClock::new_test();
451 let callback = test_py_callback();
452 py_clock.py_register_default_handler(callback);
453 let dt = Timestamp::now() + SignedDuration::from_secs(1);
454 py_clock
455 .py_set_time_alert("ALERT1", dt, None, None)
456 .expect("set_time_alert failed");
457 });
458 }
459
460 #[rstest]
461 fn test_test_clock_py_set_time() {
462 Python::initialize();
463 Python::attach(|_py| {
464 let mut py_clock = PyClock::new_test();
465
466 py_clock.py_set_time(1_700_000_000_000_000_000).unwrap();
467
468 assert_eq!(py_clock.py_timestamp_ns(), 1_700_000_000_000_000_000);
469 });
470 }
471
472 #[rstest]
473 fn test_test_clock_py_set_timer() {
474 Python::initialize();
475 Python::attach(|_py| {
476 let mut py_clock = PyClock::new_test();
477 let callback = test_py_callback();
478 py_clock.py_register_default_handler(callback);
479 let interval = SignedDuration::from_secs(2);
480 py_clock
481 .py_set_timer("TIMER1", interval, None, None, None, None, None)
482 .expect("set_timer failed");
483 });
484 }
485
486 #[rstest]
487 fn test_test_clock_py_set_timer_rejects_unconvertible_datetime() {
488 Python::initialize();
489 Python::attach(|_py| {
490 let mut py_clock = PyClock::new_test();
491 let callback = test_py_callback();
492 py_clock.py_register_default_handler(callback);
493 let interval = SignedDuration::from_secs(2);
494 let pre_epoch = Timestamp::from_nanosecond(-1).unwrap();
495
496 let err = py_clock
497 .py_set_timer(
498 "PRE_EPOCH_START",
499 interval,
500 Some(pre_epoch),
501 None,
502 None,
503 None,
504 None,
505 )
506 .expect_err("set_timer should reject a pre-epoch start time");
507 assert!(
508 err.to_string().contains("cannot be negative"),
509 "unexpected error: {err}"
510 );
511
512 let err = py_clock
513 .py_set_timer(
514 "PRE_EPOCH_STOP",
515 interval,
516 None,
517 Some(pre_epoch),
518 None,
519 None,
520 None,
521 )
522 .expect_err("set_timer should reject a pre-epoch stop time");
523 assert!(
524 err.to_string().contains("cannot be negative"),
525 "unexpected error: {err}"
526 );
527
528 assert_eq!(py_clock.py_timer_count(), 0);
529 });
530 }
531
532 #[rstest]
533 fn test_test_clock_py_set_time_alert_ns() {
534 Python::initialize();
535 Python::attach(|_py| {
536 let mut py_clock = PyClock::new_test();
537 let callback = test_py_callback();
538 py_clock.py_register_default_handler(callback);
539 let ts_ns = (Timestamp::now() + SignedDuration::from_secs(1)).as_nanosecond();
540 let ts_ns = u64::try_from(ts_ns).unwrap();
541 py_clock
542 .py_set_time_alert_ns("ALERT_NS", ts_ns, None, None)
543 .expect("set_time_alert_ns failed");
544 });
545 }
546
547 #[rstest]
548 fn test_test_clock_py_set_timer_ns() {
549 Python::initialize();
550 Python::attach(|_py| {
551 let mut py_clock = PyClock::new_test();
552 let callback = test_py_callback();
553 py_clock.py_register_default_handler(callback);
554 py_clock
555 .py_set_timer_ns("TIMER_NS", 1_000_000, None, None, None, None, None)
556 .expect("set_timer_ns failed");
557 });
558 }
559
560 #[rstest]
561 fn test_test_clock_raw_set_timer_ns(mut test_clock: VirtualClock) {
562 Python::initialize();
563 Python::attach(|_py| {
564 let callback = test_callback();
565 test_clock.register_default_handler(callback);
566
567 let timer_name = "TEST_TIME1";
568 test_clock
569 .set_timer_ns(
570 timer_name,
571 DurationNanos::new(10),
572 None,
573 None,
574 None,
575 None,
576 None,
577 )
578 .unwrap();
579
580 assert_eq!(test_clock.timer_names(), [timer_name]);
581 assert_eq!(test_clock.timer_count(), 1);
582 });
583 }
584
585 #[rstest]
586 fn test_test_clock_cancel_timer(mut test_clock: VirtualClock) {
587 Python::initialize();
588 Python::attach(|_py| {
589 let callback = test_callback();
590 test_clock.register_default_handler(callback);
591
592 let timer_name = "TEST_TIME1";
593 test_clock
594 .set_timer_ns(
595 timer_name,
596 DurationNanos::new(10),
597 None,
598 None,
599 None,
600 None,
601 None,
602 )
603 .unwrap();
604 test_clock.cancel_timer(timer_name);
605
606 assert!(test_clock.timer_names().is_empty());
607 assert_eq!(test_clock.timer_count(), 0);
608 });
609 }
610
611 #[rstest]
612 fn test_test_clock_cancel_timers(mut test_clock: VirtualClock) {
613 Python::initialize();
614 Python::attach(|_py| {
615 let callback = test_callback();
616 test_clock.register_default_handler(callback);
617
618 let timer_name = "TEST_TIME1";
619 test_clock
620 .set_timer_ns(
621 timer_name,
622 DurationNanos::new(10),
623 None,
624 None,
625 None,
626 None,
627 None,
628 )
629 .unwrap();
630 test_clock.cancel_timers();
631
632 assert!(test_clock.timer_names().is_empty());
633 assert_eq!(test_clock.timer_count(), 0);
634 });
635 }
636
637 #[rstest]
638 fn test_test_clock_advance_within_stop_time_py(mut test_clock: VirtualClock) {
639 Python::initialize();
640 Python::attach(|_py| {
641 let callback = test_callback();
642 test_clock.register_default_handler(callback);
643
644 let timer_name = "TEST_TIME1";
645 test_clock
646 .set_timer_ns(
647 timer_name,
648 DurationNanos::new(1),
649 Some(UnixNanos::from(1)),
650 Some(UnixNanos::from(3)),
651 None,
652 None,
653 None,
654 )
655 .unwrap();
656 test_clock.advance_time(2.into(), true);
657
658 assert_eq!(test_clock.timer_names(), [timer_name]);
659 assert_eq!(test_clock.timer_count(), 1);
660 });
661 }
662
663 #[rstest]
664 fn test_test_clock_advance_time_to_stop_time_with_set_time_true(mut test_clock: VirtualClock) {
665 Python::initialize();
666 Python::attach(|_py| {
667 let callback = test_callback();
668 test_clock.register_default_handler(callback);
669
670 test_clock
671 .set_timer_ns(
672 "TEST_TIME1",
673 DurationNanos::new(2),
674 None,
675 Some(UnixNanos::from(3)),
676 None,
677 None,
678 None,
679 )
680 .unwrap();
681 test_clock.advance_time(3.into(), true);
682
683 assert_eq!(test_clock.timer_names().len(), 1);
684 assert_eq!(test_clock.timer_count(), 1);
685 assert_eq!(test_clock.get_time_ns(), 3);
686 });
687 }
688
689 #[rstest]
690 fn test_test_clock_advance_time_to_stop_time_with_set_time_false(mut test_clock: VirtualClock) {
691 Python::initialize();
692 Python::attach(|_py| {
693 let callback = test_callback();
694 test_clock.register_default_handler(callback);
695
696 test_clock
697 .set_timer_ns(
698 "TEST_TIME1",
699 DurationNanos::new(2),
700 None,
701 Some(UnixNanos::from(3)),
702 None,
703 None,
704 None,
705 )
706 .unwrap();
707 test_clock.advance_time(3.into(), false);
708
709 assert_eq!(test_clock.timer_names().len(), 1);
710 assert_eq!(test_clock.timer_count(), 1);
711 assert_eq!(test_clock.get_time_ns(), 0);
712 });
713 }
714
715 #[rstest]
720 fn test_live_clock_py_set_time_alert() {
721 ensure_sender();
722
723 Python::initialize();
724 Python::attach(|_py| {
725 let mut py_clock = PyClock::new_live();
726 let callback = test_py_callback();
727 py_clock.py_register_default_handler(callback);
728 let dt = Timestamp::now() + SignedDuration::from_secs(1);
729
730 py_clock
731 .py_set_time_alert("ALERT1", dt, None, None)
732 .expect("live set_time_alert failed");
733 });
734 }
735
736 #[rstest]
737 fn test_live_clock_py_set_time_returns_error() {
738 Python::initialize();
739 Python::attach(|_py| {
740 let mut py_clock = PyClock::new_live();
741
742 let result = py_clock.py_set_time(1_700_000_000_000_000_000);
743
744 assert_eq!(
745 result.unwrap_err().to_string(),
746 "ValueError: set_time is only supported by virtual clocks",
747 );
748 });
749 }
750
751 #[rstest]
752 fn test_live_clock_py_set_timer() {
753 ensure_sender();
754
755 Python::initialize();
756 Python::attach(|_py| {
757 let mut py_clock = PyClock::new_live();
758 let callback = test_py_callback();
759 py_clock.py_register_default_handler(callback);
760 let interval = SignedDuration::from_secs(3);
761
762 py_clock
763 .py_set_timer("TIMER1", interval, None, None, None, None, None)
764 .expect("live set_timer failed");
765 });
766 }
767
768 #[rstest]
769 fn test_live_clock_py_set_time_alert_ns() {
770 ensure_sender();
771
772 Python::initialize();
773 Python::attach(|_py| {
774 let mut py_clock = PyClock::new_live();
775 let callback = test_py_callback();
776 py_clock.py_register_default_handler(callback);
777 let dt_ns = (Timestamp::now() + SignedDuration::from_secs(1)).as_nanosecond();
778 let dt_ns = u64::try_from(dt_ns).unwrap();
779
780 py_clock
781 .py_set_time_alert_ns("ALERT_NS", dt_ns, None, None)
782 .expect("live set_time_alert_ns failed");
783 });
784 }
785
786 #[rstest]
787 fn test_live_clock_py_set_timer_ns() {
788 ensure_sender();
789
790 Python::initialize();
791 Python::attach(|_py| {
792 let mut py_clock = PyClock::new_live();
793 let callback = test_py_callback();
794 py_clock.py_register_default_handler(callback);
795 let interval_ns = 1_000_000_000_u64; py_clock
798 .py_set_timer_ns("TIMER_NS", interval_ns, None, None, None, None, None)
799 .expect("live set_timer_ns failed");
800 });
801 }
802}