nautilus_trading/algorithm/
core.rs1use std::{
19 cell::RefCell,
20 fmt::Debug,
21 ops::{Deref, DerefMut},
22 rc::Rc,
23};
24
25use ahash::{AHashMap, AHashSet};
26use nautilus_common::{
27 actor::{DataActorConfig, DataActorCore},
28 cache::Cache,
29 clock::Clock,
30};
31use nautilus_model::identifiers::{ActorId, ClientOrderId, ExecAlgorithmId, StrategyId, TraderId};
32
33use super::config::ExecutionAlgorithmConfig;
34
35pub struct ExecutionAlgorithmCore {
44 pub actor: DataActorCore,
46 pub config: ExecutionAlgorithmConfig,
48 pub exec_algorithm_id: ExecAlgorithmId,
50 exec_spawn_ids: AHashMap<ClientOrderId, u32>,
52 subscribed_strategies: AHashSet<StrategyId>,
54}
55
56impl Debug for ExecutionAlgorithmCore {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct(stringify!(ExecutionAlgorithmCore))
59 .field("actor", &self.actor)
60 .field("config", &self.config)
61 .field("exec_algorithm_id", &self.exec_algorithm_id)
62 .field("exec_spawn_ids", &self.exec_spawn_ids.len())
63 .field("subscribed_strategies", &self.subscribed_strategies.len())
64 .finish()
65 }
66}
67
68impl ExecutionAlgorithmCore {
69 #[must_use]
75 pub fn new(config: ExecutionAlgorithmConfig) -> Self {
76 let exec_algorithm_id = config
77 .exec_algorithm_id
78 .expect("ExecutionAlgorithmConfig must have exec_algorithm_id set");
79
80 let actor_config = DataActorConfig {
81 actor_id: Some(ActorId::from(exec_algorithm_id.inner().as_str())),
82 log_events: config.log_events,
83 log_commands: config.log_commands,
84 };
85
86 Self {
87 actor: DataActorCore::new(actor_config),
88 config,
89 exec_algorithm_id,
90 exec_spawn_ids: AHashMap::new(),
91 subscribed_strategies: AHashSet::new(),
92 }
93 }
94
95 pub fn register(
101 &mut self,
102 trader_id: TraderId,
103 clock: Rc<RefCell<dyn Clock>>,
104 cache: Rc<RefCell<Cache>>,
105 ) -> anyhow::Result<()> {
106 self.actor.register(trader_id, clock, cache)
107 }
108
109 #[must_use]
111 pub fn id(&self) -> ExecAlgorithmId {
112 self.exec_algorithm_id
113 }
114
115 #[must_use]
119 pub fn spawn_client_order_id(&mut self, primary_id: &ClientOrderId) -> ClientOrderId {
120 let sequence = self
121 .exec_spawn_ids
122 .entry(*primary_id)
123 .and_modify(|s| *s += 1)
124 .or_insert(1);
125
126 ClientOrderId::new(format!("{primary_id}-E{sequence}"))
127 }
128
129 #[must_use]
131 pub fn spawn_sequence(&self, primary_id: &ClientOrderId) -> Option<u32> {
132 self.exec_spawn_ids.get(primary_id).copied()
133 }
134
135 #[must_use]
137 pub fn is_strategy_subscribed(&self, strategy_id: &StrategyId) -> bool {
138 self.subscribed_strategies.contains(strategy_id)
139 }
140
141 pub fn add_subscribed_strategy(&mut self, strategy_id: StrategyId) {
143 self.subscribed_strategies.insert(strategy_id);
144 }
145
146 pub fn clear_spawn_ids(&mut self) {
148 self.exec_spawn_ids.clear();
149 }
150
151 pub fn clear_subscribed_strategies(&mut self) {
153 self.subscribed_strategies.clear();
154 }
155
156 pub fn reset(&mut self) {
158 self.exec_spawn_ids.clear();
159 self.subscribed_strategies.clear();
160 }
161}
162
163impl Deref for ExecutionAlgorithmCore {
164 type Target = DataActorCore;
165 fn deref(&self) -> &Self::Target {
166 &self.actor
167 }
168}
169
170impl DerefMut for ExecutionAlgorithmCore {
171 fn deref_mut(&mut self) -> &mut Self::Target {
172 &mut self.actor
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use rstest::rstest;
179
180 use super::*;
181
182 fn create_test_config() -> ExecutionAlgorithmConfig {
183 ExecutionAlgorithmConfig {
184 exec_algorithm_id: Some(ExecAlgorithmId::new("TWAP")),
185 ..Default::default()
186 }
187 }
188
189 #[rstest]
190 fn test_core_new() {
191 let config = create_test_config();
192 let core = ExecutionAlgorithmCore::new(config.clone());
193
194 assert_eq!(core.exec_algorithm_id, ExecAlgorithmId::new("TWAP"));
195 assert_eq!(core.config.log_events, config.log_events);
196 assert!(core.exec_spawn_ids.is_empty());
197 assert!(core.subscribed_strategies.is_empty());
198 }
199
200 #[rstest]
201 fn test_spawn_client_order_id_sequence() {
202 let config = create_test_config();
203 let mut core = ExecutionAlgorithmCore::new(config);
204
205 let primary_id = ClientOrderId::new("O-001");
206
207 let spawn1 = core.spawn_client_order_id(&primary_id);
208 assert_eq!(spawn1.as_str(), "O-001-E1");
209
210 let spawn2 = core.spawn_client_order_id(&primary_id);
211 assert_eq!(spawn2.as_str(), "O-001-E2");
212
213 let spawn3 = core.spawn_client_order_id(&primary_id);
214 assert_eq!(spawn3.as_str(), "O-001-E3");
215 }
216
217 #[rstest]
218 fn test_spawn_client_order_id_different_primaries() {
219 let config = create_test_config();
220 let mut core = ExecutionAlgorithmCore::new(config);
221
222 let primary1 = ClientOrderId::new("O-001");
223 let primary2 = ClientOrderId::new("O-002");
224
225 let spawn1_1 = core.spawn_client_order_id(&primary1);
226 let spawn2_1 = core.spawn_client_order_id(&primary2);
227 let spawn1_2 = core.spawn_client_order_id(&primary1);
228
229 assert_eq!(spawn1_1.as_str(), "O-001-E1");
230 assert_eq!(spawn2_1.as_str(), "O-002-E1");
231 assert_eq!(spawn1_2.as_str(), "O-001-E2");
232 }
233
234 #[rstest]
235 fn test_spawn_sequence() {
236 let config = create_test_config();
237 let mut core = ExecutionAlgorithmCore::new(config);
238
239 let primary_id = ClientOrderId::new("O-001");
240
241 assert_eq!(core.spawn_sequence(&primary_id), None);
242
243 let _ = core.spawn_client_order_id(&primary_id);
244 assert_eq!(core.spawn_sequence(&primary_id), Some(1));
245
246 let _ = core.spawn_client_order_id(&primary_id);
247 assert_eq!(core.spawn_sequence(&primary_id), Some(2));
248 }
249
250 #[rstest]
251 fn test_strategy_subscription_tracking() {
252 let config = create_test_config();
253 let mut core = ExecutionAlgorithmCore::new(config);
254
255 let strategy_id = StrategyId::new("TEST-001");
256
257 assert!(!core.is_strategy_subscribed(&strategy_id));
258
259 core.add_subscribed_strategy(strategy_id);
260 assert!(core.is_strategy_subscribed(&strategy_id));
261 }
262
263 #[rstest]
264 fn test_clear_spawn_ids() {
265 let config = create_test_config();
266 let mut core = ExecutionAlgorithmCore::new(config);
267
268 let primary_id = ClientOrderId::new("O-001");
269 let _ = core.spawn_client_order_id(&primary_id);
270
271 assert!(core.spawn_sequence(&primary_id).is_some());
272
273 core.clear_spawn_ids();
274 assert!(core.spawn_sequence(&primary_id).is_none());
275 }
276
277 #[rstest]
278 fn test_reset() {
279 let config = create_test_config();
280 let mut core = ExecutionAlgorithmCore::new(config);
281
282 let primary_id = ClientOrderId::new("O-001");
283 let strategy_id = StrategyId::new("TEST-001");
284
285 let _ = core.spawn_client_order_id(&primary_id);
286 core.add_subscribed_strategy(strategy_id);
287
288 core.reset();
289
290 assert!(core.spawn_sequence(&primary_id).is_none());
291 assert!(!core.is_strategy_subscribed(&strategy_id));
292 }
293
294 #[rstest]
295 fn test_deref_to_data_actor_core() {
296 let config = create_test_config();
297 let core = ExecutionAlgorithmCore::new(config);
298
299 assert!(core.trader_id().is_none());
301 }
302}