nautilus_trading/algorithm/
core.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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//! Core component for execution algorithms.
17
18use 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
35/// The core component of an [`ExecutionAlgorithm`](super::ExecutionAlgorithm).
36///
37/// This struct manages the internal state for execution algorithms including
38/// spawn ID tracking and strategy subscriptions. It wraps a [`DataActorCore`]
39/// to provide data actor capabilities.
40///
41/// User algorithms should hold this as a member and implement `Deref`/`DerefMut`
42/// to satisfy the trait bounds of [`ExecutionAlgorithm`](super::ExecutionAlgorithm).
43pub struct ExecutionAlgorithmCore {
44    /// The underlying data actor core.
45    pub actor: DataActorCore,
46    /// The execution algorithm configuration.
47    pub config: ExecutionAlgorithmConfig,
48    /// The execution algorithm ID.
49    pub exec_algorithm_id: ExecAlgorithmId,
50    /// Maps primary order client IDs to their spawn sequence counter.
51    exec_spawn_ids: AHashMap<ClientOrderId, u32>,
52    /// Tracks strategies that have been subscribed to for events.
53    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    /// Creates a new [`ExecutionAlgorithmCore`] instance.
70    ///
71    /// # Panics
72    ///
73    /// Panics if `config.exec_algorithm_id` is `None`.
74    #[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    /// Registers the execution algorithm with the trading engine components.
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if registration with the actor core fails.
100    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    /// Returns the execution algorithm ID.
110    #[must_use]
111    pub fn id(&self) -> ExecAlgorithmId {
112        self.exec_algorithm_id
113    }
114
115    /// Generates the next spawn client order ID for a primary order.
116    ///
117    /// The generated ID follows the pattern: `{primary_id}-E{sequence}`.
118    #[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    /// Returns the current spawn sequence for a primary order, if any.
130    #[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    /// Checks if a strategy has been subscribed to for events.
136    #[must_use]
137    pub fn is_strategy_subscribed(&self, strategy_id: &StrategyId) -> bool {
138        self.subscribed_strategies.contains(strategy_id)
139    }
140
141    /// Marks a strategy as subscribed for events.
142    pub fn add_subscribed_strategy(&mut self, strategy_id: StrategyId) {
143        self.subscribed_strategies.insert(strategy_id);
144    }
145
146    /// Clears all spawn tracking state.
147    pub fn clear_spawn_ids(&mut self) {
148        self.exec_spawn_ids.clear();
149    }
150
151    /// Clears all strategy subscriptions.
152    pub fn clear_subscribed_strategies(&mut self) {
153        self.subscribed_strategies.clear();
154    }
155
156    /// Resets the core to its initial state.
157    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        // Should be able to access DataActorCore methods via Deref
300        assert!(core.trader_id().is_none());
301    }
302}