1use nautilus_core::{UnixNanos, python::to_pyvalue_err};
17use pyo3::prelude::*;
18
19use super::timer::TimeEventHandler_Py;
20use crate::{
21 clock::{Clock, LiveClock, TestClock},
22 timer::{TimeEvent, TimeEventCallback},
23};
24
25#[allow(non_camel_case_types)]
34#[pyo3::pyclass(
35 module = "posei_trader.core.nautilus_pyo3.common",
36 name = "TestClock",
37 unsendable
38)]
39#[derive(Debug)]
40pub struct TestClock_Py(Box<TestClock>);
41
42#[pymethods]
43impl TestClock_Py {
44 #[new]
45 fn py_new() -> Self {
46 Self(Box::default())
47 }
48
49 fn advance_time(&mut self, to_time_ns: u64, set_time: bool) -> Vec<TimeEvent> {
50 self.0.advance_time(to_time_ns.into(), set_time)
51 }
52
53 fn match_handlers(&self, events: Vec<TimeEvent>) -> Vec<TimeEventHandler_Py> {
54 self.0
55 .match_handlers(events)
56 .into_iter()
57 .map(Into::into)
58 .collect()
59 }
60
61 fn register_default_handler(&mut self, callback: PyObject) {
62 self.0
63 .register_default_handler(TimeEventCallback::from(callback));
64 }
65
66 #[pyo3(signature = (name, alert_time_ns, callback=None, allow_past=None))]
67 fn set_time_alert_ns(
68 &mut self,
69 name: &str,
70 alert_time_ns: u64,
71 callback: Option<PyObject>,
72 allow_past: Option<bool>,
73 ) -> PyResult<()> {
74 self.0
75 .set_time_alert_ns(
76 name,
77 alert_time_ns.into(),
78 callback.map(TimeEventCallback::from),
79 allow_past,
80 )
81 .map_err(to_pyvalue_err)
82 }
83
84 #[allow(clippy::too_many_arguments)]
85 #[pyo3(signature = (name, interval_ns, start_time_ns, stop_time_ns=None, callback=None, allow_past=None, fire_immediately=None))]
86 fn set_timer_ns(
87 &mut self,
88 name: &str,
89 interval_ns: u64,
90 start_time_ns: Option<u64>,
91 stop_time_ns: Option<u64>,
92 callback: Option<PyObject>,
93 allow_past: Option<bool>,
94 fire_immediately: Option<bool>,
95 ) -> PyResult<()> {
96 self.0
97 .set_timer_ns(
98 name,
99 interval_ns,
100 start_time_ns.map(UnixNanos::from),
101 stop_time_ns.map(UnixNanos::from),
102 callback.map(TimeEventCallback::from),
103 allow_past,
104 fire_immediately,
105 )
106 .map_err(to_pyvalue_err)
107 }
108
109 fn next_time_ns(&self, name: &str) -> Option<u64> {
110 self.0.next_time_ns(name).map(|n| n.as_u64())
111 }
112
113 fn cancel_timer(&mut self, name: &str) {
114 self.0.cancel_timer(name);
115 }
116
117 fn cancel_timers(&mut self) {
118 self.0.cancel_timers();
119 }
120}
121
122#[allow(non_camel_case_types)]
131#[pyo3::pyclass(
132 module = "posei_trader.core.nautilus_pyo3.common",
133 name = "LiveClock",
134 unsendable
135)]
136#[derive(Debug)]
137pub struct LiveClock_Py(Box<LiveClock>);
138
139#[pymethods]
140impl LiveClock_Py {
141 #[new]
142 fn py_new() -> Self {
143 Self(Box::default())
144 }
145
146 fn register_default_handler(&mut self, callback: PyObject) {
147 self.0
148 .register_default_handler(TimeEventCallback::from(callback));
149 }
150
151 #[pyo3(signature = (name, alert_time_ns, callback=None, allow_past=None))]
152 fn set_time_alert_ns(
153 &mut self,
154 name: &str,
155 alert_time_ns: u64,
156 callback: Option<PyObject>,
157 allow_past: Option<bool>,
158 ) -> PyResult<()> {
159 self.0
160 .set_time_alert_ns(
161 name,
162 alert_time_ns.into(),
163 callback.map(TimeEventCallback::from),
164 allow_past,
165 )
166 .map_err(to_pyvalue_err)
167 }
168
169 #[allow(clippy::too_many_arguments)]
170 #[pyo3(signature = (name, interval_ns, start_time_ns, stop_time_ns=None, callback=None, allow_past=None, fire_immediately=None))]
171 fn set_timer_ns(
172 &mut self,
173 name: &str,
174 interval_ns: u64,
175 start_time_ns: Option<u64>,
176 stop_time_ns: Option<u64>,
177 callback: Option<PyObject>,
178 allow_past: Option<bool>,
179 fire_immediately: Option<bool>,
180 ) -> PyResult<()> {
181 self.0
182 .set_timer_ns(
183 name,
184 interval_ns,
185 start_time_ns.map(UnixNanos::from),
186 stop_time_ns.map(UnixNanos::from),
187 callback.map(TimeEventCallback::from),
188 allow_past,
189 fire_immediately,
190 )
191 .map_err(to_pyvalue_err)
192 }
193
194 fn next_time_ns(&self, name: &str) -> Option<u64> {
195 self.0.next_time_ns(name).map(|t| t.as_u64())
196 }
197
198 fn cancel_timer(&mut self, name: &str) {
199 self.0.cancel_timer(name);
200 }
201
202 fn cancel_timers(&mut self) {
203 self.0.cancel_timers();
204 }
205}
206
207#[cfg(test)]
211mod tests {
212 use nautilus_core::{UnixNanos, python::IntoPyObjectPoseiExt};
213 use pyo3::{prelude::*, types::PyList};
214 use rstest::*;
215
216 use crate::{
217 clock::{Clock, TestClock},
218 timer::TimeEventCallback,
219 };
220
221 #[fixture]
222 pub fn test_clock() -> TestClock {
223 TestClock::new()
224 }
225
226 pub fn test_callback() -> TimeEventCallback {
227 Python::with_gil(|py| {
228 let py_list = PyList::empty(py);
229 let py_append = Py::from(py_list.getattr("append").unwrap());
230 let py_append = py_append.into_py_any_unwrap(py);
231 TimeEventCallback::from(py_append)
232 })
233 }
234
235 #[rstest]
236 fn test_set_timer_ns_py(mut test_clock: TestClock) {
237 pyo3::prepare_freethreaded_python();
238
239 Python::with_gil(|_py| {
240 let callback = test_callback();
241 test_clock.register_default_handler(callback);
242
243 let timer_name = "TEST_TIME1";
244 test_clock
245 .set_timer_ns(timer_name, 10, None, None, None, None, None)
246 .unwrap();
247
248 assert_eq!(test_clock.timer_names(), [timer_name]);
249 assert_eq!(test_clock.timer_count(), 1);
250 });
251 }
252
253 #[rstest]
254 fn test_cancel_timer(mut test_clock: TestClock) {
255 pyo3::prepare_freethreaded_python();
256
257 Python::with_gil(|_py| {
258 let callback = test_callback();
259 test_clock.register_default_handler(callback);
260
261 let timer_name = "TEST_TIME1";
262 test_clock
263 .set_timer_ns(timer_name, 10, None, None, None, None, None)
264 .unwrap();
265 test_clock.cancel_timer(timer_name);
266
267 assert!(test_clock.timer_names().is_empty());
268 assert_eq!(test_clock.timer_count(), 0);
269 });
270 }
271
272 #[rstest]
273 fn test_cancel_timers(mut test_clock: TestClock) {
274 pyo3::prepare_freethreaded_python();
275
276 Python::with_gil(|_py| {
277 let callback = test_callback();
278 test_clock.register_default_handler(callback);
279
280 let timer_name = "TEST_TIME1";
281 test_clock
282 .set_timer_ns(timer_name, 10, None, None, None, None, None)
283 .unwrap();
284 test_clock.cancel_timers();
285
286 assert!(test_clock.timer_names().is_empty());
287 assert_eq!(test_clock.timer_count(), 0);
288 });
289 }
290
291 #[rstest]
292 fn test_advance_within_stop_time_py(mut test_clock: TestClock) {
293 pyo3::prepare_freethreaded_python();
294
295 Python::with_gil(|_py| {
296 let callback = test_callback();
297 test_clock.register_default_handler(callback);
298
299 let timer_name = "TEST_TIME1";
300 test_clock
301 .set_timer_ns(
302 timer_name,
303 1,
304 Some(UnixNanos::from(1)),
305 Some(UnixNanos::from(3)),
306 None,
307 None,
308 None,
309 )
310 .unwrap();
311 test_clock.advance_time(2.into(), true);
312
313 assert_eq!(test_clock.timer_names(), [timer_name]);
314 assert_eq!(test_clock.timer_count(), 1);
315 });
316 }
317
318 #[rstest]
319 fn test_advance_time_to_stop_time_with_set_time_true(mut test_clock: TestClock) {
320 pyo3::prepare_freethreaded_python();
321
322 Python::with_gil(|_py| {
323 let callback = test_callback();
324 test_clock.register_default_handler(callback);
325
326 test_clock
327 .set_timer_ns(
328 "TEST_TIME1",
329 2,
330 None,
331 Some(UnixNanos::from(3)),
332 None,
333 None,
334 None,
335 )
336 .unwrap();
337 test_clock.advance_time(3.into(), true);
338
339 assert_eq!(test_clock.timer_names().len(), 1);
340 assert_eq!(test_clock.timer_count(), 1);
341 assert_eq!(test_clock.get_time_ns(), 3);
342 });
343 }
344
345 #[rstest]
346 fn test_advance_time_to_stop_time_with_set_time_false(mut test_clock: TestClock) {
347 pyo3::prepare_freethreaded_python();
348
349 Python::with_gil(|_py| {
350 let callback = test_callback();
351 test_clock.register_default_handler(callback);
352
353 test_clock
354 .set_timer_ns(
355 "TEST_TIME1",
356 2,
357 None,
358 Some(UnixNanos::from(3)),
359 None,
360 None,
361 None,
362 )
363 .unwrap();
364 test_clock.advance_time(3.into(), false);
365
366 assert_eq!(test_clock.timer_names().len(), 1);
367 assert_eq!(test_clock.timer_count(), 1);
368 assert_eq!(test_clock.get_time_ns(), 0);
369 });
370 }
371}