1use nautilus_core::{python::to_pyvalue_err, UnixNanos};
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;
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 TimeEventCallback::from(py_append.into_py(py))
213 })
214 }
215
216 #[rstest]
217 fn test_set_timer_ns_py(mut test_clock: TestClock) {
218 pyo3::prepare_freethreaded_python();
219
220 Python::with_gil(|_py| {
221 let callback = test_callback();
222 test_clock.register_default_handler(callback);
223
224 let timer_name = "TEST_TIME1";
225 test_clock
226 .set_timer_ns(timer_name, 10, 0.into(), None, None)
227 .unwrap();
228
229 assert_eq!(test_clock.timer_names(), [timer_name]);
230 assert_eq!(test_clock.timer_count(), 1);
231 });
232 }
233
234 #[rstest]
235 fn test_cancel_timer(mut test_clock: TestClock) {
236 pyo3::prepare_freethreaded_python();
237
238 Python::with_gil(|_py| {
239 let callback = test_callback();
240 test_clock.register_default_handler(callback);
241
242 let timer_name = "TEST_TIME1";
243 test_clock
244 .set_timer_ns(timer_name, 10, 0.into(), None, None)
245 .unwrap();
246 test_clock.cancel_timer(timer_name);
247
248 assert!(test_clock.timer_names().is_empty());
249 assert_eq!(test_clock.timer_count(), 0);
250 });
251 }
252
253 #[rstest]
254 fn test_cancel_timers(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, 0.into(), None, None)
264 .unwrap();
265 test_clock.cancel_timers();
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_advance_within_stop_time_py(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, 1, 1.into(), Some(UnixNanos::from(3)), None)
283 .unwrap();
284 test_clock.advance_time(2.into(), true);
285
286 assert_eq!(test_clock.timer_names(), [timer_name]);
287 assert_eq!(test_clock.timer_count(), 1);
288 });
289 }
290
291 #[rstest]
292 fn test_advance_time_to_stop_time_with_set_time_true(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 test_clock
300 .set_timer_ns("TEST_TIME1", 2, 0.into(), Some(UnixNanos::from(3)), None)
301 .unwrap();
302 test_clock.advance_time(3.into(), true);
303
304 assert_eq!(test_clock.timer_names().len(), 1);
305 assert_eq!(test_clock.timer_count(), 1);
306 assert_eq!(test_clock.get_time_ns(), 3);
307 });
308 }
309
310 #[rstest]
311 fn test_advance_time_to_stop_time_with_set_time_false(mut test_clock: TestClock) {
312 pyo3::prepare_freethreaded_python();
313
314 Python::with_gil(|_py| {
315 let callback = test_callback();
316 test_clock.register_default_handler(callback);
317
318 test_clock
319 .set_timer_ns("TEST_TIME1", 2, 0.into(), Some(UnixNanos::from(3)), None)
320 .unwrap();
321 test_clock.advance_time(3.into(), false);
322
323 assert_eq!(test_clock.timer_names().len(), 1);
324 assert_eq!(test_clock.timer_count(), 1);
325 assert_eq!(test_clock.get_time_ns(), 0);
326 });
327 }
328}