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