1use std::{cell::RefCell, rc::Rc};
17
18use chrono::{DateTime, Duration, Utc};
19use nautilus_core::{UnixNanos, python::to_pyvalue_err};
20use pyo3::prelude::*;
21
22use crate::{
23 clock::{Clock, TestClock},
24 live::clock::LiveClock,
25 timer::TimeEventCallback,
26};
27
28#[allow(non_camel_case_types)]
38#[pyo3::pyclass(
39 module = "nautilus_trader.core.nautilus_pyo3.common",
40 name = "Clock",
41 unsendable,
42 from_py_object
43)]
44#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
45#[derive(Debug, Clone)]
46pub struct PyClock(Rc<RefCell<dyn Clock>>);
47
48#[pymethods]
49#[pyo3_stub_gen::derive::gen_stub_pymethods]
50impl PyClock {
51 #[staticmethod]
52 #[pyo3(name = "new_test")]
53 fn py_new_test() -> Self {
54 Self(Rc::new(RefCell::new(TestClock::default())))
55 }
56
57 #[pyo3(name = "timestamp_ns")]
59 fn py_timestamp_ns(&self) -> u64 {
60 self.0.borrow().timestamp_ns().as_u64()
61 }
62
63 #[pyo3(name = "timestamp_us")]
65 fn py_timestamp_us(&self) -> u64 {
66 self.0.borrow().timestamp_us()
67 }
68
69 #[pyo3(name = "timestamp_ms")]
71 fn py_timestamp_ms(&self) -> u64 {
72 self.0.borrow().timestamp_ms()
73 }
74
75 #[pyo3(name = "timestamp")]
77 fn py_timestamp(&self) -> f64 {
78 self.0.borrow().timestamp()
79 }
80
81 #[pyo3(name = "utc_now")]
83 fn py_utc_now(&self) -> DateTime<Utc> {
84 self.0.borrow().utc_now()
85 }
86
87 #[pyo3(name = "timer_names")]
89 fn py_timer_names(&self) -> Vec<String> {
90 self.0
91 .borrow()
92 .timer_names()
93 .into_iter()
94 .map(String::from)
95 .collect()
96 }
97
98 #[pyo3(name = "timer_count")]
100 fn py_timer_count(&self) -> usize {
101 self.0.borrow().timer_count()
102 }
103
104 #[pyo3(name = "register_default_handler")]
105 fn py_register_default_handler(&mut self, callback: Py<PyAny>) {
106 self.0
107 .borrow_mut()
108 .register_default_handler(TimeEventCallback::from(callback));
109 }
110
111 #[pyo3(
112 name = "set_time_alert",
113 signature = (name, alert_time, callback=None, allow_past=None)
114 )]
115 fn py_set_time_alert(
116 &mut self,
117 name: &str,
118 alert_time: DateTime<Utc>,
119 callback: Option<Py<PyAny>>,
120 allow_past: Option<bool>,
121 ) -> PyResult<()> {
122 self.0
123 .borrow_mut()
124 .set_time_alert(
125 name,
126 alert_time,
127 callback.map(TimeEventCallback::from),
128 allow_past,
129 )
130 .map_err(to_pyvalue_err)
131 }
132
133 #[pyo3(
134 name = "set_time_alert_ns",
135 signature = (name, alert_time_ns, callback=None, allow_past=None)
136 )]
137 fn py_set_time_alert_ns(
138 &mut self,
139 name: &str,
140 alert_time_ns: u64,
141 callback: Option<Py<PyAny>>,
142 allow_past: Option<bool>,
143 ) -> PyResult<()> {
144 self.0
145 .borrow_mut()
146 .set_time_alert_ns(
147 name,
148 alert_time_ns.into(),
149 callback.map(TimeEventCallback::from),
150 allow_past,
151 )
152 .map_err(to_pyvalue_err)
153 }
154
155 #[expect(clippy::too_many_arguments)]
156 #[pyo3(
157 name = "set_timer",
158 signature = (name, interval, start_time=None, stop_time=None, callback=None, allow_past=None, fire_immediately=None)
159 )]
160 fn py_set_timer(
161 &mut self,
162 name: &str,
163 interval: Duration,
164 start_time: Option<DateTime<Utc>>,
165 stop_time: Option<DateTime<Utc>>,
166 callback: Option<Py<PyAny>>,
167 allow_past: Option<bool>,
168 fire_immediately: Option<bool>,
169 ) -> PyResult<()> {
170 let interval_ns_i64 = interval
171 .num_nanoseconds()
172 .ok_or_else(|| to_pyvalue_err("Interval too large"))?;
173
174 if interval_ns_i64 <= 0 {
175 return Err(to_pyvalue_err("Interval must be positive"));
176 }
177 let interval_ns = interval_ns_i64 as u64;
178
179 self.0
180 .borrow_mut()
181 .set_timer_ns(
182 name,
183 interval_ns,
184 start_time.map(UnixNanos::from),
185 stop_time.map(UnixNanos::from),
186 callback.map(TimeEventCallback::from),
187 allow_past,
188 fire_immediately,
189 )
190 .map_err(to_pyvalue_err)
191 }
192
193 #[expect(clippy::too_many_arguments)]
194 #[pyo3(
195 name = "set_timer_ns",
196 signature = (name, interval_ns, start_time_ns=None, stop_time_ns=None, callback=None, allow_past=None, fire_immediately=None)
197 )]
198 fn py_set_timer_ns(
199 &mut self,
200 name: &str,
201 interval_ns: u64,
202 start_time_ns: Option<u64>,
203 stop_time_ns: Option<u64>,
204 callback: Option<Py<PyAny>>,
205 allow_past: Option<bool>,
206 fire_immediately: Option<bool>,
207 ) -> PyResult<()> {
208 self.0
209 .borrow_mut()
210 .set_timer_ns(
211 name,
212 interval_ns,
213 start_time_ns.map(UnixNanos::from),
214 stop_time_ns.map(UnixNanos::from),
215 callback.map(TimeEventCallback::from),
216 allow_past,
217 fire_immediately,
218 )
219 .map_err(to_pyvalue_err)
220 }
221
222 #[pyo3(name = "next_time_ns")]
223 fn py_next_time_ns(&self, name: &str) -> Option<u64> {
224 self.0.borrow().next_time_ns(name).map(|t| t.as_u64())
225 }
226
227 #[pyo3(name = "cancel_timer")]
228 fn py_cancel_timer(&mut self, name: &str) {
229 self.0.borrow_mut().cancel_timer(name);
230 }
231
232 #[pyo3(name = "cancel_timers")]
233 fn py_cancel_timers(&mut self) {
234 self.0.borrow_mut().cancel_timers();
235 }
236}
237
238impl PyClock {
239 #[must_use]
241 pub fn from_rc(rc: Rc<RefCell<dyn Clock>>) -> Self {
242 Self(rc)
243 }
244
245 #[must_use]
247 pub fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
248 self.0.clone()
249 }
250
251 #[must_use]
253 pub fn new_test() -> Self {
254 Self(Rc::new(RefCell::new(TestClock::default())))
255 }
256
257 #[must_use]
259 pub fn new_live() -> Self {
260 Self(Rc::new(RefCell::new(LiveClock::default())))
261 }
262
263 #[must_use]
265 pub fn inner(&self) -> std::cell::Ref<'_, dyn Clock> {
266 self.0.borrow()
267 }
268
269 #[must_use]
271 pub fn inner_mut(&mut self) -> std::cell::RefMut<'_, dyn Clock> {
272 self.0.borrow_mut()
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use std::sync::Arc;
279
280 use chrono::{Duration, Utc};
281 use nautilus_core::{UnixNanos, python::IntoPyObjectNautilusExt};
282 use pyo3::{prelude::*, types::PyList};
283 use rstest::*;
284
285 use crate::{
286 clock::{Clock, TestClock},
287 python::clock::PyClock,
288 runner::{TimeEventSender, set_time_event_sender},
289 timer::{TimeEventCallback, TimeEventHandler},
290 };
291
292 fn ensure_sender() {
293 if crate::runner::try_get_time_event_sender().is_none() {
294 set_time_event_sender(Arc::new(DummySender));
295 }
296 }
297
298 #[derive(Debug)]
300 struct DummySender;
301
302 impl TimeEventSender for DummySender {
303 fn send(&self, _handler: TimeEventHandler) {}
304 }
305
306 #[fixture]
307 pub fn test_clock() -> TestClock {
308 TestClock::new()
309 }
310
311 pub fn test_callback() -> TimeEventCallback {
312 Python::initialize();
313 Python::attach(|py| {
314 let py_list = PyList::empty(py);
315 let py_append = Py::from(py_list.getattr("append").unwrap());
316 let py_append = py_append.into_py_any_unwrap(py);
317 TimeEventCallback::from(py_append)
318 })
319 }
320
321 pub fn test_py_callback() -> Py<PyAny> {
322 Python::initialize();
323 Python::attach(|py| {
324 let py_list = PyList::empty(py);
325 let py_append = Py::from(py_list.getattr("append").unwrap());
326 py_append.into_py_any_unwrap(py)
327 })
328 }
329
330 #[rstest]
335 fn test_test_clock_py_set_time_alert() {
336 Python::initialize();
337 Python::attach(|_py| {
338 let mut py_clock = PyClock::new_test();
339 let callback = test_py_callback();
340 py_clock.py_register_default_handler(callback);
341 let dt = Utc::now() + Duration::seconds(1);
342 py_clock
343 .py_set_time_alert("ALERT1", dt, None, None)
344 .expect("set_time_alert failed");
345 });
346 }
347
348 #[rstest]
349 fn test_test_clock_py_set_timer() {
350 Python::initialize();
351 Python::attach(|_py| {
352 let mut py_clock = PyClock::new_test();
353 let callback = test_py_callback();
354 py_clock.py_register_default_handler(callback);
355 let interval = Duration::seconds(2);
356 py_clock
357 .py_set_timer("TIMER1", interval, None, None, None, None, None)
358 .expect("set_timer failed");
359 });
360 }
361
362 #[rstest]
363 fn test_test_clock_py_set_time_alert_ns() {
364 Python::initialize();
365 Python::attach(|_py| {
366 let mut py_clock = PyClock::new_test();
367 let callback = test_py_callback();
368 py_clock.py_register_default_handler(callback);
369 let ts_ns = (Utc::now() + Duration::seconds(1))
370 .timestamp_nanos_opt()
371 .unwrap() as u64;
372 py_clock
373 .py_set_time_alert_ns("ALERT_NS", ts_ns, None, None)
374 .expect("set_time_alert_ns failed");
375 });
376 }
377
378 #[rstest]
379 fn test_test_clock_py_set_timer_ns() {
380 Python::initialize();
381 Python::attach(|_py| {
382 let mut py_clock = PyClock::new_test();
383 let callback = test_py_callback();
384 py_clock.py_register_default_handler(callback);
385 py_clock
386 .py_set_timer_ns("TIMER_NS", 1_000_000, None, None, None, None, None)
387 .expect("set_timer_ns failed");
388 });
389 }
390
391 #[rstest]
392 fn test_test_clock_raw_set_timer_ns(mut test_clock: TestClock) {
393 Python::initialize();
394 Python::attach(|_py| {
395 let callback = test_callback();
396 test_clock.register_default_handler(callback);
397
398 let timer_name = "TEST_TIME1";
399 test_clock
400 .set_timer_ns(timer_name, 10, None, None, None, None, None)
401 .unwrap();
402
403 assert_eq!(test_clock.timer_names(), [timer_name]);
404 assert_eq!(test_clock.timer_count(), 1);
405 });
406 }
407
408 #[rstest]
409 fn test_test_clock_cancel_timer(mut test_clock: TestClock) {
410 Python::initialize();
411 Python::attach(|_py| {
412 let callback = test_callback();
413 test_clock.register_default_handler(callback);
414
415 let timer_name = "TEST_TIME1";
416 test_clock
417 .set_timer_ns(timer_name, 10, None, None, None, None, None)
418 .unwrap();
419 test_clock.cancel_timer(timer_name);
420
421 assert!(test_clock.timer_names().is_empty());
422 assert_eq!(test_clock.timer_count(), 0);
423 });
424 }
425
426 #[rstest]
427 fn test_test_clock_cancel_timers(mut test_clock: TestClock) {
428 Python::initialize();
429 Python::attach(|_py| {
430 let callback = test_callback();
431 test_clock.register_default_handler(callback);
432
433 let timer_name = "TEST_TIME1";
434 test_clock
435 .set_timer_ns(timer_name, 10, None, None, None, None, None)
436 .unwrap();
437 test_clock.cancel_timers();
438
439 assert!(test_clock.timer_names().is_empty());
440 assert_eq!(test_clock.timer_count(), 0);
441 });
442 }
443
444 #[rstest]
445 fn test_test_clock_advance_within_stop_time_py(mut test_clock: TestClock) {
446 Python::initialize();
447 Python::attach(|_py| {
448 let callback = test_callback();
449 test_clock.register_default_handler(callback);
450
451 let timer_name = "TEST_TIME1";
452 test_clock
453 .set_timer_ns(
454 timer_name,
455 1,
456 Some(UnixNanos::from(1)),
457 Some(UnixNanos::from(3)),
458 None,
459 None,
460 None,
461 )
462 .unwrap();
463 test_clock.advance_time(2.into(), true);
464
465 assert_eq!(test_clock.timer_names(), [timer_name]);
466 assert_eq!(test_clock.timer_count(), 1);
467 });
468 }
469
470 #[rstest]
471 fn test_test_clock_advance_time_to_stop_time_with_set_time_true(mut test_clock: TestClock) {
472 Python::initialize();
473 Python::attach(|_py| {
474 let callback = test_callback();
475 test_clock.register_default_handler(callback);
476
477 test_clock
478 .set_timer_ns(
479 "TEST_TIME1",
480 2,
481 None,
482 Some(UnixNanos::from(3)),
483 None,
484 None,
485 None,
486 )
487 .unwrap();
488 test_clock.advance_time(3.into(), true);
489
490 assert_eq!(test_clock.timer_names().len(), 1);
491 assert_eq!(test_clock.timer_count(), 1);
492 assert_eq!(test_clock.get_time_ns(), 3);
493 });
494 }
495
496 #[rstest]
497 fn test_test_clock_advance_time_to_stop_time_with_set_time_false(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 test_clock
504 .set_timer_ns(
505 "TEST_TIME1",
506 2,
507 None,
508 Some(UnixNanos::from(3)),
509 None,
510 None,
511 None,
512 )
513 .unwrap();
514 test_clock.advance_time(3.into(), false);
515
516 assert_eq!(test_clock.timer_names().len(), 1);
517 assert_eq!(test_clock.timer_count(), 1);
518 assert_eq!(test_clock.get_time_ns(), 0);
519 });
520 }
521
522 #[rstest]
527 fn test_live_clock_py_set_time_alert() {
528 ensure_sender();
529
530 Python::initialize();
531 Python::attach(|_py| {
532 let mut py_clock = PyClock::new_live();
533 let callback = test_py_callback();
534 py_clock.py_register_default_handler(callback);
535 let dt = Utc::now() + Duration::seconds(1);
536
537 py_clock
538 .py_set_time_alert("ALERT1", dt, None, None)
539 .expect("live set_time_alert failed");
540 });
541 }
542
543 #[rstest]
544 fn test_live_clock_py_set_timer() {
545 ensure_sender();
546
547 Python::initialize();
548 Python::attach(|_py| {
549 let mut py_clock = PyClock::new_live();
550 let callback = test_py_callback();
551 py_clock.py_register_default_handler(callback);
552 let interval = Duration::seconds(3);
553
554 py_clock
555 .py_set_timer("TIMER1", interval, None, None, None, None, None)
556 .expect("live set_timer failed");
557 });
558 }
559
560 #[rstest]
561 fn test_live_clock_py_set_time_alert_ns() {
562 ensure_sender();
563
564 Python::initialize();
565 Python::attach(|_py| {
566 let mut py_clock = PyClock::new_live();
567 let callback = test_py_callback();
568 py_clock.py_register_default_handler(callback);
569 let dt_ns = (Utc::now() + Duration::seconds(1))
570 .timestamp_nanos_opt()
571 .unwrap() as u64;
572
573 py_clock
574 .py_set_time_alert_ns("ALERT_NS", dt_ns, None, None)
575 .expect("live set_time_alert_ns failed");
576 });
577 }
578
579 #[rstest]
580 fn test_live_clock_py_set_timer_ns() {
581 ensure_sender();
582
583 Python::initialize();
584 Python::attach(|_py| {
585 let mut py_clock = PyClock::new_live();
586 let callback = test_py_callback();
587 py_clock.py_register_default_handler(callback);
588 let interval_ns = 1_000_000_000_u64; py_clock
591 .py_set_timer_ns("TIMER_NS", interval_ns, None, None, None, None, None)
592 .expect("live set_timer_ns failed");
593 });
594 }
595}