1#![warn(clippy::clone_on_ref_ptr)]
17
18use std::{cell::RefCell, rc::Rc};
19
20use jiff::{SignedDuration, Timestamp};
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, 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 #[pyo3(name = "next_time_ns")]
256 fn py_next_time_ns(&self, name: &str) -> Option<u64> {
257 self.0.borrow().next_time_ns(name).map(|t| t.as_u64())
258 }
259
260 #[pyo3(name = "cancel_timer")]
261 fn py_cancel_timer(&mut self, name: &str) {
262 self.0.borrow_mut().cancel_timer(name);
263 }
264
265 #[pyo3(name = "cancel_timers")]
266 fn py_cancel_timers(&mut self) {
267 self.0.borrow_mut().cancel_timers();
268 }
269}
270
271impl PyClock {
272 #[must_use]
274 pub fn from_rc(rc: Rc<RefCell<dyn Clock>>) -> Self {
275 Self(rc)
276 }
277
278 #[must_use]
280 pub fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
281 Rc::clone(&self.0)
282 }
283
284 #[must_use]
286 pub fn new_test() -> Self {
287 Self(Rc::new(RefCell::new(VirtualClock::default())))
288 }
289
290 #[must_use]
292 pub fn new_live() -> Self {
293 Self(Rc::new(RefCell::new(LiveClock::default())))
294 }
295
296 #[must_use]
298 pub fn inner(&self) -> std::cell::Ref<'_, dyn Clock> {
299 self.0.borrow()
300 }
301
302 #[must_use]
304 pub fn inner_mut(&mut self) -> std::cell::RefMut<'_, dyn Clock> {
305 self.0.borrow_mut()
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use std::sync::Arc;
312
313 use jiff::{SignedDuration, Timestamp};
314 use nautilus_core::{DurationNanos, UnixNanos, python::IntoPyObjectNautilusExt};
315 use pyo3::{prelude::*, types::PyList};
316 use rstest::*;
317
318 use crate::{
319 clock::{Clock, VirtualClock},
320 python::clock::PyClock,
321 runner::{TimeEventMessage, TimeEventSender, set_time_event_sender},
322 timer::TimeEventCallback,
323 };
324
325 fn ensure_sender() {
326 if crate::runner::try_get_time_event_sender().is_none() {
327 set_time_event_sender(Arc::new(DummySender));
328 }
329 }
330
331 #[derive(Debug)]
333 struct DummySender;
334
335 impl TimeEventSender for DummySender {
336 fn send(&self, _message: TimeEventMessage) {}
337 }
338
339 #[fixture]
340 pub fn test_clock() -> VirtualClock {
341 VirtualClock::new()
342 }
343
344 pub(super) fn test_callback() -> TimeEventCallback {
345 Python::initialize();
346 Python::attach(|py| {
347 let py_list = PyList::empty(py);
348 let py_append = Py::from(py_list.getattr("append").unwrap());
349 let py_append = py_append.into_py_any_unwrap(py);
350 TimeEventCallback::from(py_append)
351 })
352 }
353
354 pub(super) fn test_py_callback() -> Py<PyAny> {
355 Python::initialize();
356 Python::attach(|py| {
357 let py_list = PyList::empty(py);
358 let py_append = Py::from(py_list.getattr("append").unwrap());
359 py_append.into_py_any_unwrap(py)
360 })
361 }
362
363 #[rstest]
368 fn test_test_clock_py_set_time_alert() {
369 Python::initialize();
370 Python::attach(|_py| {
371 let mut py_clock = PyClock::new_test();
372 let callback = test_py_callback();
373 py_clock.py_register_default_handler(callback);
374 let dt = Timestamp::now() + SignedDuration::from_secs(1);
375 py_clock
376 .py_set_time_alert("ALERT1", dt, None, None)
377 .expect("set_time_alert failed");
378 });
379 }
380
381 #[rstest]
382 fn test_test_clock_py_set_time() {
383 Python::initialize();
384 Python::attach(|_py| {
385 let mut py_clock = PyClock::new_test();
386
387 py_clock.py_set_time(1_700_000_000_000_000_000).unwrap();
388
389 assert_eq!(py_clock.py_timestamp_ns(), 1_700_000_000_000_000_000);
390 });
391 }
392
393 #[rstest]
394 fn test_test_clock_py_set_timer() {
395 Python::initialize();
396 Python::attach(|_py| {
397 let mut py_clock = PyClock::new_test();
398 let callback = test_py_callback();
399 py_clock.py_register_default_handler(callback);
400 let interval = SignedDuration::from_secs(2);
401 py_clock
402 .py_set_timer("TIMER1", interval, None, None, None, None, None)
403 .expect("set_timer failed");
404 });
405 }
406
407 #[rstest]
408 fn test_test_clock_py_set_timer_rejects_unconvertible_datetime() {
409 Python::initialize();
410 Python::attach(|_py| {
411 let mut py_clock = PyClock::new_test();
412 let callback = test_py_callback();
413 py_clock.py_register_default_handler(callback);
414 let interval = SignedDuration::from_secs(2);
415 let pre_epoch = Timestamp::from_nanosecond(-1).unwrap();
416
417 let err = py_clock
418 .py_set_timer(
419 "PRE_EPOCH_START",
420 interval,
421 Some(pre_epoch),
422 None,
423 None,
424 None,
425 None,
426 )
427 .expect_err("set_timer should reject a pre-epoch start time");
428 assert!(
429 err.to_string().contains("cannot be negative"),
430 "unexpected error: {err}"
431 );
432
433 let err = py_clock
434 .py_set_timer(
435 "PRE_EPOCH_STOP",
436 interval,
437 None,
438 Some(pre_epoch),
439 None,
440 None,
441 None,
442 )
443 .expect_err("set_timer should reject a pre-epoch stop time");
444 assert!(
445 err.to_string().contains("cannot be negative"),
446 "unexpected error: {err}"
447 );
448
449 assert_eq!(py_clock.py_timer_count(), 0);
450 });
451 }
452
453 #[rstest]
454 fn test_test_clock_py_set_time_alert_ns() {
455 Python::initialize();
456 Python::attach(|_py| {
457 let mut py_clock = PyClock::new_test();
458 let callback = test_py_callback();
459 py_clock.py_register_default_handler(callback);
460 let ts_ns = (Timestamp::now() + SignedDuration::from_secs(1)).as_nanosecond();
461 let ts_ns = u64::try_from(ts_ns).unwrap();
462 py_clock
463 .py_set_time_alert_ns("ALERT_NS", ts_ns, None, None)
464 .expect("set_time_alert_ns failed");
465 });
466 }
467
468 #[rstest]
469 fn test_test_clock_py_set_timer_ns() {
470 Python::initialize();
471 Python::attach(|_py| {
472 let mut py_clock = PyClock::new_test();
473 let callback = test_py_callback();
474 py_clock.py_register_default_handler(callback);
475 py_clock
476 .py_set_timer_ns("TIMER_NS", 1_000_000, None, None, None, None, None)
477 .expect("set_timer_ns failed");
478 });
479 }
480
481 #[rstest]
482 fn test_test_clock_raw_set_timer_ns(mut test_clock: VirtualClock) {
483 Python::initialize();
484 Python::attach(|_py| {
485 let callback = test_callback();
486 test_clock.register_default_handler(callback);
487
488 let timer_name = "TEST_TIME1";
489 test_clock
490 .set_timer_ns(
491 timer_name,
492 DurationNanos::new(10),
493 None,
494 None,
495 None,
496 None,
497 None,
498 )
499 .unwrap();
500
501 assert_eq!(test_clock.timer_names(), [timer_name]);
502 assert_eq!(test_clock.timer_count(), 1);
503 });
504 }
505
506 #[rstest]
507 fn test_test_clock_cancel_timer(mut test_clock: VirtualClock) {
508 Python::initialize();
509 Python::attach(|_py| {
510 let callback = test_callback();
511 test_clock.register_default_handler(callback);
512
513 let timer_name = "TEST_TIME1";
514 test_clock
515 .set_timer_ns(
516 timer_name,
517 DurationNanos::new(10),
518 None,
519 None,
520 None,
521 None,
522 None,
523 )
524 .unwrap();
525 test_clock.cancel_timer(timer_name);
526
527 assert!(test_clock.timer_names().is_empty());
528 assert_eq!(test_clock.timer_count(), 0);
529 });
530 }
531
532 #[rstest]
533 fn test_test_clock_cancel_timers(mut test_clock: VirtualClock) {
534 Python::initialize();
535 Python::attach(|_py| {
536 let callback = test_callback();
537 test_clock.register_default_handler(callback);
538
539 let timer_name = "TEST_TIME1";
540 test_clock
541 .set_timer_ns(
542 timer_name,
543 DurationNanos::new(10),
544 None,
545 None,
546 None,
547 None,
548 None,
549 )
550 .unwrap();
551 test_clock.cancel_timers();
552
553 assert!(test_clock.timer_names().is_empty());
554 assert_eq!(test_clock.timer_count(), 0);
555 });
556 }
557
558 #[rstest]
559 fn test_test_clock_advance_within_stop_time_py(mut test_clock: VirtualClock) {
560 Python::initialize();
561 Python::attach(|_py| {
562 let callback = test_callback();
563 test_clock.register_default_handler(callback);
564
565 let timer_name = "TEST_TIME1";
566 test_clock
567 .set_timer_ns(
568 timer_name,
569 DurationNanos::new(1),
570 Some(UnixNanos::from(1)),
571 Some(UnixNanos::from(3)),
572 None,
573 None,
574 None,
575 )
576 .unwrap();
577 test_clock.advance_time(2.into(), true);
578
579 assert_eq!(test_clock.timer_names(), [timer_name]);
580 assert_eq!(test_clock.timer_count(), 1);
581 });
582 }
583
584 #[rstest]
585 fn test_test_clock_advance_time_to_stop_time_with_set_time_true(mut test_clock: VirtualClock) {
586 Python::initialize();
587 Python::attach(|_py| {
588 let callback = test_callback();
589 test_clock.register_default_handler(callback);
590
591 test_clock
592 .set_timer_ns(
593 "TEST_TIME1",
594 DurationNanos::new(2),
595 None,
596 Some(UnixNanos::from(3)),
597 None,
598 None,
599 None,
600 )
601 .unwrap();
602 test_clock.advance_time(3.into(), true);
603
604 assert_eq!(test_clock.timer_names().len(), 1);
605 assert_eq!(test_clock.timer_count(), 1);
606 assert_eq!(test_clock.get_time_ns(), 3);
607 });
608 }
609
610 #[rstest]
611 fn test_test_clock_advance_time_to_stop_time_with_set_time_false(mut test_clock: VirtualClock) {
612 Python::initialize();
613 Python::attach(|_py| {
614 let callback = test_callback();
615 test_clock.register_default_handler(callback);
616
617 test_clock
618 .set_timer_ns(
619 "TEST_TIME1",
620 DurationNanos::new(2),
621 None,
622 Some(UnixNanos::from(3)),
623 None,
624 None,
625 None,
626 )
627 .unwrap();
628 test_clock.advance_time(3.into(), false);
629
630 assert_eq!(test_clock.timer_names().len(), 1);
631 assert_eq!(test_clock.timer_count(), 1);
632 assert_eq!(test_clock.get_time_ns(), 0);
633 });
634 }
635
636 #[rstest]
641 fn test_live_clock_py_set_time_alert() {
642 ensure_sender();
643
644 Python::initialize();
645 Python::attach(|_py| {
646 let mut py_clock = PyClock::new_live();
647 let callback = test_py_callback();
648 py_clock.py_register_default_handler(callback);
649 let dt = Timestamp::now() + SignedDuration::from_secs(1);
650
651 py_clock
652 .py_set_time_alert("ALERT1", dt, None, None)
653 .expect("live set_time_alert failed");
654 });
655 }
656
657 #[rstest]
658 fn test_live_clock_py_set_time_returns_error() {
659 Python::initialize();
660 Python::attach(|_py| {
661 let mut py_clock = PyClock::new_live();
662
663 let result = py_clock.py_set_time(1_700_000_000_000_000_000);
664
665 assert_eq!(
666 result.unwrap_err().to_string(),
667 "ValueError: set_time is only supported by virtual clocks",
668 );
669 });
670 }
671
672 #[rstest]
673 fn test_live_clock_py_set_timer() {
674 ensure_sender();
675
676 Python::initialize();
677 Python::attach(|_py| {
678 let mut py_clock = PyClock::new_live();
679 let callback = test_py_callback();
680 py_clock.py_register_default_handler(callback);
681 let interval = SignedDuration::from_secs(3);
682
683 py_clock
684 .py_set_timer("TIMER1", interval, None, None, None, None, None)
685 .expect("live set_timer failed");
686 });
687 }
688
689 #[rstest]
690 fn test_live_clock_py_set_time_alert_ns() {
691 ensure_sender();
692
693 Python::initialize();
694 Python::attach(|_py| {
695 let mut py_clock = PyClock::new_live();
696 let callback = test_py_callback();
697 py_clock.py_register_default_handler(callback);
698 let dt_ns = (Timestamp::now() + SignedDuration::from_secs(1)).as_nanosecond();
699 let dt_ns = u64::try_from(dt_ns).unwrap();
700
701 py_clock
702 .py_set_time_alert_ns("ALERT_NS", dt_ns, None, None)
703 .expect("live set_time_alert_ns failed");
704 });
705 }
706
707 #[rstest]
708 fn test_live_clock_py_set_timer_ns() {
709 ensure_sender();
710
711 Python::initialize();
712 Python::attach(|_py| {
713 let mut py_clock = PyClock::new_live();
714 let callback = test_py_callback();
715 py_clock.py_register_default_handler(callback);
716 let interval_ns = 1_000_000_000_u64; py_clock
719 .py_set_timer_ns("TIMER_NS", interval_ns, None, None, None, None, None)
720 .expect("live set_timer_ns failed");
721 });
722 }
723}