Skip to content

Commit c228c5c

Browse files
authored
Port backtest engine and kernel to Rust (#2449)
- move time event accumulator to its own file - add missing fields for data engine config with descriptions - implement `StreamingConfig` - create nautilus system crate with config and kernel - add `Environment` enum
1 parent beeee93 commit c228c5c

File tree

17 files changed

+1008
-95
lines changed

17 files changed

+1008
-95
lines changed

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ members = [
2020
"crates/pyo3",
2121
"crates/risk",
2222
"crates/serialization",
23+
"crates/system",
2324
"crates/test_kit",
2425
"crates/trading",
2526
]

crates/backtest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ nautilus-core = { path = "../core" }
4141
nautilus-data = { path = "../data" }
4242
nautilus-execution = { path = "../execution" }
4343
nautilus-model = { path = "../model" , features = ["stubs"]}
44+
nautilus-system = { path = "../system" }
4445
anyhow = { workspace = true }
4546
chrono = { workspace = true }
4647
log = { workspace = true }

crates/backtest/src/accumulator.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3+
// https://nautechsystems.io
4+
//
5+
// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6+
// You may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
// -------------------------------------------------------------------------------------------------
15+
16+
use nautilus_common::{clock::TestClock, timer::TimeEventHandlerV2};
17+
use nautilus_core::UnixNanos;
18+
19+
/// Provides a means of accumulating and draining time event handlers.
20+
pub struct TimeEventAccumulator {
21+
event_handlers: Vec<TimeEventHandlerV2>,
22+
}
23+
24+
impl TimeEventAccumulator {
25+
/// Creates a new [`TimeEventAccumulator`] instance.
26+
#[must_use]
27+
pub const fn new() -> Self {
28+
Self {
29+
event_handlers: Vec::new(),
30+
}
31+
}
32+
33+
/// Advance the given clock to the `to_time_ns`.
34+
pub fn advance_clock(&mut self, clock: &mut TestClock, to_time_ns: UnixNanos, set_time: bool) {
35+
let events = clock.advance_time(to_time_ns, set_time);
36+
let handlers = clock.match_handlers(events);
37+
self.event_handlers.extend(handlers);
38+
}
39+
40+
/// Drain the accumulated time event handlers in sorted order (by the events `ts_event`).
41+
pub fn drain(&mut self) -> Vec<TimeEventHandlerV2> {
42+
// stable sort is not necessary since there is no relation between
43+
// events of the same clock. Only time based ordering is needed.
44+
self.event_handlers
45+
.sort_unstable_by_key(|v| v.event.ts_event);
46+
self.event_handlers.drain(..).collect()
47+
}
48+
}
49+
50+
impl Default for TimeEventAccumulator {
51+
/// Creates a new default [`TimeEventAccumulator`] instance.
52+
fn default() -> Self {
53+
Self::new()
54+
}
55+
}
56+
57+
////////////////////////////////////////////////////////////////////////////////
58+
// Tests
59+
////////////////////////////////////////////////////////////////////////////////
60+
#[cfg(all(test, feature = "python"))]
61+
mod tests {
62+
use nautilus_common::timer::{TimeEvent, TimeEventCallback};
63+
use nautilus_core::UUID4;
64+
use pyo3::{Py, Python, prelude::*, types::PyList};
65+
use rstest::*;
66+
use ustr::Ustr;
67+
68+
use super::*;
69+
70+
#[rstest]
71+
fn test_accumulator_drain_sorted() {
72+
pyo3::prepare_freethreaded_python();
73+
74+
Python::with_gil(|py| {
75+
let py_list = PyList::empty(py);
76+
let py_append = Py::from(py_list.getattr("append").unwrap());
77+
78+
let mut accumulator = TimeEventAccumulator::new();
79+
80+
let time_event1 = TimeEvent::new(
81+
Ustr::from("TEST_EVENT_1"),
82+
UUID4::new(),
83+
100.into(),
84+
100.into(),
85+
);
86+
let time_event2 = TimeEvent::new(
87+
Ustr::from("TEST_EVENT_2"),
88+
UUID4::new(),
89+
300.into(),
90+
300.into(),
91+
);
92+
let time_event3 = TimeEvent::new(
93+
Ustr::from("TEST_EVENT_3"),
94+
UUID4::new(),
95+
200.into(),
96+
200.into(),
97+
);
98+
99+
// Note: as_ptr returns a borrowed pointer. It is valid as long
100+
// as the object is in scope. In this case `callback_ptr` is valid
101+
// as long as `py_append` is in scope.
102+
let callback = TimeEventCallback::from(py_append.into_any());
103+
104+
let handler1 = TimeEventHandlerV2::new(time_event1.clone(), callback.clone());
105+
let handler2 = TimeEventHandlerV2::new(time_event2.clone(), callback.clone());
106+
let handler3 = TimeEventHandlerV2::new(time_event3.clone(), callback);
107+
108+
accumulator.event_handlers.push(handler1);
109+
accumulator.event_handlers.push(handler2);
110+
accumulator.event_handlers.push(handler3);
111+
112+
let drained_handlers = accumulator.drain();
113+
114+
assert_eq!(drained_handlers.len(), 3);
115+
assert_eq!(drained_handlers[0].event.ts_event, time_event1.ts_event);
116+
assert_eq!(drained_handlers[1].event.ts_event, time_event3.ts_event);
117+
assert_eq!(drained_handlers[2].event.ts_event, time_event2.ts_event);
118+
});
119+
}
120+
}

0 commit comments

Comments
 (0)