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