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