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