1#![warn(clippy::clone_on_ref_ptr)]
17
18use std::{cell::RefCell, rc::Rc};
19
20use jiff::{SignedDuration, Timestamp};
21use nautilus_core::{UnixNanos, datetime::try_datetime_to_unix_nanos, python::to_pyvalue_err};
22use pyo3::prelude::*;
23
24use crate::{
25 clock::{Clock, TestClock},
26 live::clock::LiveClock,
27 timer::TimeEventCallback,
28};
29
30#[allow(non_camel_case_types)]
40#[pyo3::pyclass(
41 module = "nautilus_trader.common",
42 name = "Clock",
43 unsendable,
44 from_py_object
45)]
46#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
47#[derive(Debug, Clone)]
48pub struct PyClock(Rc<RefCell<dyn Clock>>);
49
50#[pymethods]
51#[pyo3_stub_gen::derive::gen_stub_pymethods]
52impl PyClock {
53 #[staticmethod]
54 #[pyo3(name = "new_test")]
55 fn py_new_test() -> Self {
56 Self(Rc::new(RefCell::new(TestClock::default())))
57 }
58
59 #[pyo3(name = "timestamp_ns")]
61 fn py_timestamp_ns(&self) -> u64 {
62 self.0.borrow().timestamp_ns().as_u64()
63 }
64
65 #[pyo3(name = "timestamp_us")]
67 fn py_timestamp_us(&self) -> u64 {
68 self.0.borrow().timestamp_us()
69 }
70
71 #[pyo3(name = "timestamp_ms")]
73 fn py_timestamp_ms(&self) -> u64 {
74 self.0.borrow().timestamp_ms()
75 }
76
77 #[pyo3(name = "timestamp")]
79 fn py_timestamp(&self) -> f64 {
80 self.0.borrow().timestamp()
81 }
82
83 #[pyo3(name = "utc_now")]
85 fn py_utc_now(&self) -> Timestamp {
86 self.0.borrow().utc_now()
87 }
88
89 #[pyo3(name = "set_time")]
90 fn py_set_time(&mut self, to_time_ns: u64) -> PyResult<()> {
91 let mut clock = self.0.borrow_mut();
92 let Some(test_clock) = clock.as_any_mut().downcast_mut::<TestClock>() else {
93 return Err(to_pyvalue_err("set_time is only supported by test clocks"));
94 };
95
96 test_clock.set_time(to_time_ns.into());
97 Ok(())
98 }
99
100 #[pyo3(name = "timer_names")]
102 fn py_timer_names(&self) -> Vec<String> {
103 self.0
104 .borrow()
105 .timer_names()
106 .into_iter()
107 .map(String::from)
108 .collect()
109 }
110
111 #[pyo3(name = "timer_count")]
113 fn py_timer_count(&self) -> usize {
114 self.0.borrow().timer_count()
115 }
116
117 #[pyo3(name = "register_default_handler")]
118 fn py_register_default_handler(&mut self, callback: Py<PyAny>) {
119 self.0
120 .borrow_mut()
121 .register_default_handler(TimeEventCallback::from(callback));
122 }
123
124 #[pyo3(name = "cancel_default_handler")]
125 fn py_cancel_default_handler(&mut self) {
126 self.0.borrow_mut().cancel_default_handler();
127 }
128
129 #[pyo3(name = "cancel_callbacks")]
130 fn py_cancel_callbacks(&mut self) {
131 self.0.borrow_mut().cancel_callbacks();
132 }
133
134 #[pyo3(
135 name = "set_time_alert",
136 signature = (name, alert_time, callback=None, allow_past=None)
137 )]
138 fn py_set_time_alert(
139 &mut self,
140 name: &str,
141 alert_time: Timestamp,
142 callback: Option<Py<PyAny>>,
143 allow_past: Option<bool>,
144 ) -> PyResult<()> {
145 self.0
146 .borrow_mut()
147 .set_time_alert(
148 name,
149 alert_time,
150 callback.map(TimeEventCallback::from),
151 allow_past,
152 )
153 .map_err(to_pyvalue_err)
154 }
155
156 #[pyo3(
157 name = "set_time_alert_ns",
158 signature = (name, alert_time_ns, callback=None, allow_past=None)
159 )]
160 fn py_set_time_alert_ns(
161 &mut self,
162 name: &str,
163 alert_time_ns: u64,
164 callback: Option<Py<PyAny>>,
165 allow_past: Option<bool>,
166 ) -> PyResult<()> {
167 self.0
168 .borrow_mut()
169 .set_time_alert_ns(
170 name,
171 alert_time_ns.into(),
172 callback.map(TimeEventCallback::from),
173 allow_past,
174 )
175 .map_err(to_pyvalue_err)
176 }
177
178 #[expect(clippy::too_many_arguments)]
179 #[pyo3(
180 name = "set_timer",
181 signature = (name, interval, start_time=None, stop_time=None, callback=None, allow_past=None, fire_immediately=None)
182 )]
183 fn py_set_timer(
184 &mut self,
185 name: &str,
186 interval: SignedDuration,
187 start_time: Option<Timestamp>,
188 stop_time: Option<Timestamp>,
189 callback: Option<Py<PyAny>>,
190 allow_past: Option<bool>,
191 fire_immediately: Option<bool>,
192 ) -> PyResult<()> {
193 let interval_ns = interval.as_nanos();
194
195 if interval_ns <= 0 {
196 return Err(to_pyvalue_err("Interval must be positive"));
197 }
198 let interval_ns =
199 u64::try_from(interval_ns).map_err(|_| to_pyvalue_err("Interval too large"))?;
200
201 let start_time_ns = start_time
202 .map(try_datetime_to_unix_nanos)
203 .transpose()
204 .map_err(to_pyvalue_err)?;
205 let stop_time_ns = stop_time
206 .map(try_datetime_to_unix_nanos)
207 .transpose()
208 .map_err(to_pyvalue_err)?;
209
210 self.0
211 .borrow_mut()
212 .set_timer_ns(
213 name,
214 interval_ns,
215 start_time_ns,
216 stop_time_ns,
217 callback.map(TimeEventCallback::from),
218 allow_past,
219 fire_immediately,
220 )
221 .map_err(to_pyvalue_err)
222 }
223
224 #[expect(clippy::too_many_arguments)]
225 #[pyo3(
226 name = "set_timer_ns",
227 signature = (name, interval_ns, start_time_ns=None, stop_time_ns=None, callback=None, allow_past=None, fire_immediately=None)
228 )]
229 fn py_set_timer_ns(
230 &mut self,
231 name: &str,
232 interval_ns: u64,
233 start_time_ns: Option<u64>,
234 stop_time_ns: Option<u64>,
235 callback: Option<Py<PyAny>>,
236 allow_past: Option<bool>,
237 fire_immediately: Option<bool>,
238 ) -> PyResult<()> {
239 self.0
240 .borrow_mut()
241 .set_timer_ns(
242 name,
243 interval_ns,
244 start_time_ns.map(UnixNanos::from),
245 stop_time_ns.map(UnixNanos::from),
246 callback.map(TimeEventCallback::from),
247 allow_past,
248 fire_immediately,
249 )
250 .map_err(to_pyvalue_err)
251 }
252
253 #[pyo3(name = "next_time_ns")]
254 fn py_next_time_ns(&self, name: &str) -> Option<u64> {
255 self.0.borrow().next_time_ns(name).map(|t| t.as_u64())
256 }
257
258 #[pyo3(name = "cancel_timer")]
259 fn py_cancel_timer(&mut self, name: &str) {
260 self.0.borrow_mut().cancel_timer(name);
261 }
262
263 #[pyo3(name = "cancel_timers")]
264 fn py_cancel_timers(&mut self) {
265 self.0.borrow_mut().cancel_timers();
266 }
267}
268
269impl PyClock {
270 #[must_use]
272 pub fn from_rc(rc: Rc<RefCell<dyn Clock>>) -> Self {
273 Self(rc)
274 }
275
276 #[must_use]
278 pub fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
279 Rc::clone(&self.0)
280 }
281
282 #[must_use]
284 pub fn new_test() -> Self {
285 Self(Rc::new(RefCell::new(TestClock::default())))
286 }
287
288 #[must_use]
290 pub fn new_live() -> Self {
291 Self(Rc::new(RefCell::new(LiveClock::default())))
292 }
293
294 #[must_use]
296 pub fn inner(&self) -> std::cell::Ref<'_, dyn Clock> {
297 self.0.borrow()
298 }
299
300 #[must_use]
302 pub fn inner_mut(&mut self) -> std::cell::RefMut<'_, dyn Clock> {
303 self.0.borrow_mut()
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use std::sync::Arc;
310
311 use jiff::{SignedDuration, Timestamp};
312 use nautilus_core::{UnixNanos, python::IntoPyObjectNautilusExt};
313 use pyo3::{prelude::*, types::PyList};
314 use rstest::*;
315
316 use crate::{
317 clock::{Clock, TestClock},
318 python::clock::PyClock,
319 runner::{TimeEventMessage, TimeEventSender, set_time_event_sender},
320 timer::TimeEventCallback,
321 };
322
323 fn ensure_sender() {
324 if crate::runner::try_get_time_event_sender().is_none() {
325 set_time_event_sender(Arc::new(DummySender));
326 }
327 }
328
329 #[derive(Debug)]
331 struct DummySender;
332
333 impl TimeEventSender for DummySender {
334 fn send(&self, _message: TimeEventMessage) {}
335 }
336
337 #[fixture]
338 pub fn test_clock() -> TestClock {
339 TestClock::new()
340 }
341
342 pub(super) fn test_callback() -> TimeEventCallback {
343 Python::initialize();
344 Python::attach(|py| {
345 let py_list = PyList::empty(py);
346 let py_append = Py::from(py_list.getattr("append").unwrap());
347 let py_append = py_append.into_py_any_unwrap(py);
348 TimeEventCallback::from(py_append)
349 })
350 }
351
352 pub(super) fn test_py_callback() -> Py<PyAny> {
353 Python::initialize();
354 Python::attach(|py| {
355 let py_list = PyList::empty(py);
356 let py_append = Py::from(py_list.getattr("append").unwrap());
357 py_append.into_py_any_unwrap(py)
358 })
359 }
360
361 #[rstest]
366 fn test_test_clock_py_set_time_alert() {
367 Python::initialize();
368 Python::attach(|_py| {
369 let mut py_clock = PyClock::new_test();
370 let callback = test_py_callback();
371 py_clock.py_register_default_handler(callback);
372 let dt = Timestamp::now() + SignedDuration::from_secs(1);
373 py_clock
374 .py_set_time_alert("ALERT1", dt, None, None)
375 .expect("set_time_alert failed");
376 });
377 }
378
379 #[rstest]
380 fn test_test_clock_py_set_time() {
381 Python::initialize();
382 Python::attach(|_py| {
383 let mut py_clock = PyClock::new_test();
384
385 py_clock.py_set_time(1_700_000_000_000_000_000).unwrap();
386
387 assert_eq!(py_clock.py_timestamp_ns(), 1_700_000_000_000_000_000);
388 });
389 }
390
391 #[rstest]
392 fn test_test_clock_py_set_timer() {
393 Python::initialize();
394 Python::attach(|_py| {
395 let mut py_clock = PyClock::new_test();
396 let callback = test_py_callback();
397 py_clock.py_register_default_handler(callback);
398 let interval = SignedDuration::from_secs(2);
399 py_clock
400 .py_set_timer("TIMER1", interval, None, None, None, None, None)
401 .expect("set_timer failed");
402 });
403 }
404
405 #[rstest]
406 fn test_test_clock_py_set_timer_rejects_unconvertible_datetime() {
407 Python::initialize();
408 Python::attach(|_py| {
409 let mut py_clock = PyClock::new_test();
410 let callback = test_py_callback();
411 py_clock.py_register_default_handler(callback);
412 let interval = SignedDuration::from_secs(2);
413 let pre_epoch = Timestamp::from_nanosecond(-1).unwrap();
414
415 let err = py_clock
416 .py_set_timer(
417 "PRE_EPOCH_START",
418 interval,
419 Some(pre_epoch),
420 None,
421 None,
422 None,
423 None,
424 )
425 .expect_err("set_timer should reject a pre-epoch start time");
426 assert!(
427 err.to_string().contains("cannot be negative"),
428 "unexpected error: {err}"
429 );
430
431 let err = py_clock
432 .py_set_timer(
433 "PRE_EPOCH_STOP",
434 interval,
435 None,
436 Some(pre_epoch),
437 None,
438 None,
439 None,
440 )
441 .expect_err("set_timer should reject a pre-epoch stop time");
442 assert!(
443 err.to_string().contains("cannot be negative"),
444 "unexpected error: {err}"
445 );
446
447 assert_eq!(py_clock.py_timer_count(), 0);
448 });
449 }
450
451 #[rstest]
452 fn test_test_clock_py_set_time_alert_ns() {
453 Python::initialize();
454 Python::attach(|_py| {
455 let mut py_clock = PyClock::new_test();
456 let callback = test_py_callback();
457 py_clock.py_register_default_handler(callback);
458 let ts_ns = (Timestamp::now() + SignedDuration::from_secs(1)).as_nanosecond();
459 let ts_ns = u64::try_from(ts_ns).unwrap();
460 py_clock
461 .py_set_time_alert_ns("ALERT_NS", ts_ns, None, None)
462 .expect("set_time_alert_ns failed");
463 });
464 }
465
466 #[rstest]
467 fn test_test_clock_py_set_timer_ns() {
468 Python::initialize();
469 Python::attach(|_py| {
470 let mut py_clock = PyClock::new_test();
471 let callback = test_py_callback();
472 py_clock.py_register_default_handler(callback);
473 py_clock
474 .py_set_timer_ns("TIMER_NS", 1_000_000, None, None, None, None, None)
475 .expect("set_timer_ns failed");
476 });
477 }
478
479 #[rstest]
480 fn test_test_clock_raw_set_timer_ns(mut test_clock: TestClock) {
481 Python::initialize();
482 Python::attach(|_py| {
483 let callback = test_callback();
484 test_clock.register_default_handler(callback);
485
486 let timer_name = "TEST_TIME1";
487 test_clock
488 .set_timer_ns(timer_name, 10, None, None, None, None, None)
489 .unwrap();
490
491 assert_eq!(test_clock.timer_names(), [timer_name]);
492 assert_eq!(test_clock.timer_count(), 1);
493 });
494 }
495
496 #[rstest]
497 fn test_test_clock_cancel_timer(mut test_clock: TestClock) {
498 Python::initialize();
499 Python::attach(|_py| {
500 let callback = test_callback();
501 test_clock.register_default_handler(callback);
502
503 let timer_name = "TEST_TIME1";
504 test_clock
505 .set_timer_ns(timer_name, 10, None, None, None, None, None)
506 .unwrap();
507 test_clock.cancel_timer(timer_name);
508
509 assert!(test_clock.timer_names().is_empty());
510 assert_eq!(test_clock.timer_count(), 0);
511 });
512 }
513
514 #[rstest]
515 fn test_test_clock_cancel_timers(mut test_clock: TestClock) {
516 Python::initialize();
517 Python::attach(|_py| {
518 let callback = test_callback();
519 test_clock.register_default_handler(callback);
520
521 let timer_name = "TEST_TIME1";
522 test_clock
523 .set_timer_ns(timer_name, 10, None, None, None, None, None)
524 .unwrap();
525 test_clock.cancel_timers();
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_advance_within_stop_time_py(mut test_clock: TestClock) {
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 1,
544 Some(UnixNanos::from(1)),
545 Some(UnixNanos::from(3)),
546 None,
547 None,
548 None,
549 )
550 .unwrap();
551 test_clock.advance_time(2.into(), true);
552
553 assert_eq!(test_clock.timer_names(), [timer_name]);
554 assert_eq!(test_clock.timer_count(), 1);
555 });
556 }
557
558 #[rstest]
559 fn test_test_clock_advance_time_to_stop_time_with_set_time_true(mut test_clock: TestClock) {
560 Python::initialize();
561 Python::attach(|_py| {
562 let callback = test_callback();
563 test_clock.register_default_handler(callback);
564
565 test_clock
566 .set_timer_ns(
567 "TEST_TIME1",
568 2,
569 None,
570 Some(UnixNanos::from(3)),
571 None,
572 None,
573 None,
574 )
575 .unwrap();
576 test_clock.advance_time(3.into(), true);
577
578 assert_eq!(test_clock.timer_names().len(), 1);
579 assert_eq!(test_clock.timer_count(), 1);
580 assert_eq!(test_clock.get_time_ns(), 3);
581 });
582 }
583
584 #[rstest]
585 fn test_test_clock_advance_time_to_stop_time_with_set_time_false(mut test_clock: TestClock) {
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 2,
595 None,
596 Some(UnixNanos::from(3)),
597 None,
598 None,
599 None,
600 )
601 .unwrap();
602 test_clock.advance_time(3.into(), false);
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(), 0);
607 });
608 }
609
610 #[rstest]
615 fn test_live_clock_py_set_time_alert() {
616 ensure_sender();
617
618 Python::initialize();
619 Python::attach(|_py| {
620 let mut py_clock = PyClock::new_live();
621 let callback = test_py_callback();
622 py_clock.py_register_default_handler(callback);
623 let dt = Timestamp::now() + SignedDuration::from_secs(1);
624
625 py_clock
626 .py_set_time_alert("ALERT1", dt, None, None)
627 .expect("live set_time_alert failed");
628 });
629 }
630
631 #[rstest]
632 fn test_live_clock_py_set_time_returns_error() {
633 Python::initialize();
634 Python::attach(|_py| {
635 let mut py_clock = PyClock::new_live();
636
637 let result = py_clock.py_set_time(1_700_000_000_000_000_000);
638
639 assert_eq!(
640 result.unwrap_err().to_string(),
641 "ValueError: set_time is only supported by test clocks",
642 );
643 });
644 }
645
646 #[rstest]
647 fn test_live_clock_py_set_timer() {
648 ensure_sender();
649
650 Python::initialize();
651 Python::attach(|_py| {
652 let mut py_clock = PyClock::new_live();
653 let callback = test_py_callback();
654 py_clock.py_register_default_handler(callback);
655 let interval = SignedDuration::from_secs(3);
656
657 py_clock
658 .py_set_timer("TIMER1", interval, None, None, None, None, None)
659 .expect("live set_timer failed");
660 });
661 }
662
663 #[rstest]
664 fn test_live_clock_py_set_time_alert_ns() {
665 ensure_sender();
666
667 Python::initialize();
668 Python::attach(|_py| {
669 let mut py_clock = PyClock::new_live();
670 let callback = test_py_callback();
671 py_clock.py_register_default_handler(callback);
672 let dt_ns = (Timestamp::now() + SignedDuration::from_secs(1)).as_nanosecond();
673 let dt_ns = u64::try_from(dt_ns).unwrap();
674
675 py_clock
676 .py_set_time_alert_ns("ALERT_NS", dt_ns, None, None)
677 .expect("live set_time_alert_ns failed");
678 });
679 }
680
681 #[rstest]
682 fn test_live_clock_py_set_timer_ns() {
683 ensure_sender();
684
685 Python::initialize();
686 Python::attach(|_py| {
687 let mut py_clock = PyClock::new_live();
688 let callback = test_py_callback();
689 py_clock.py_register_default_handler(callback);
690 let interval_ns = 1_000_000_000_u64; py_clock
693 .py_set_timer_ns("TIMER_NS", interval_ns, None, None, None, None, None)
694 .expect("live set_timer_ns failed");
695 });
696 }
697}