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