nautilus_trading/strategy/
core.rs

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
16use std::{
17    cell::RefCell,
18    fmt::Debug,
19    ops::{Deref, DerefMut},
20    rc::Rc,
21};
22
23use ahash::AHashMap;
24use nautilus_common::{
25    actor::{DataActorConfig, DataActorCore},
26    cache::Cache,
27    clock::Clock,
28    factories::OrderFactory,
29};
30use nautilus_core::time::get_atomic_clock_static;
31use nautilus_execution::order_manager::manager::OrderManager;
32use nautilus_model::identifiers::{ActorId, ClientOrderId, StrategyId, TraderId};
33use nautilus_portfolio::portfolio::Portfolio;
34use ustr::Ustr;
35
36use super::config::StrategyConfig;
37
38/// The core component of a [`Strategy`](super::Strategy), managing data, orders, and state.
39///
40/// This struct is intended to be held as a member within a user's custom strategy struct.
41/// The user's struct should then `Deref` and `DerefMut` to this `StrategyCore` instance
42/// to satisfy the trait bounds of [`Strategy`](super::Strategy) and
43/// [`DataActor`](nautilus_common::actor::data_actor::DataActor).
44pub struct StrategyCore {
45    /// The underlying data actor core.
46    pub actor: DataActorCore,
47    /// The strategy configuration.
48    pub config: StrategyConfig,
49    /// The order manager.
50    pub order_manager: Option<OrderManager>,
51    /// The order factory.
52    pub order_factory: Option<OrderFactory>,
53    /// The portfolio.
54    pub portfolio: Option<Rc<RefCell<Portfolio>>>,
55    /// Maps client order IDs to GTD expiry timer names.
56    pub gtd_timers: AHashMap<ClientOrderId, Ustr>,
57}
58
59impl Debug for StrategyCore {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        f.debug_struct("StrategyCore")
62            .field("actor", &self.actor)
63            .field("config", &self.config)
64            .field("order_manager", &self.order_manager)
65            .field("order_factory", &self.order_factory)
66            .finish()
67    }
68}
69
70impl StrategyCore {
71    /// Creates a new [`StrategyCore`] instance.
72    pub fn new(config: StrategyConfig) -> Self {
73        let actor_config = DataActorConfig {
74            actor_id: config
75                .strategy_id
76                .map(|id| ActorId::from(id.inner().as_str())),
77            log_events: config.log_events,
78            log_commands: config.log_commands,
79        };
80
81        Self {
82            actor: DataActorCore::new(actor_config),
83            config,
84            order_manager: None,
85            order_factory: None,
86            portfolio: None,
87            gtd_timers: AHashMap::new(),
88        }
89    }
90
91    /// Registers the strategy with the trading engine components.
92    ///
93    /// This is typically called by the framework when the strategy is added to an engine.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if registration with the actor core fails.
98    pub fn register(
99        &mut self,
100        trader_id: TraderId,
101        clock: Rc<RefCell<dyn Clock>>,
102        cache: Rc<RefCell<Cache>>,
103        portfolio: Rc<RefCell<Portfolio>>,
104    ) -> anyhow::Result<()> {
105        self.actor
106            .register(trader_id, clock.clone(), cache.clone())?;
107
108        let strategy_id = StrategyId::from(self.actor.actor_id.inner().as_str());
109
110        self.order_factory = Some(OrderFactory::new(
111            trader_id,
112            strategy_id,
113            None,
114            None,
115            get_atomic_clock_static(),
116            self.config.use_uuid_client_order_ids,
117            self.config.use_hyphens_in_client_order_ids,
118        ));
119
120        self.order_manager = Some(OrderManager::new(
121            clock, cache, false, // active_local
122        ));
123
124        self.portfolio = Some(portfolio);
125
126        Ok(())
127    }
128}
129
130impl Deref for StrategyCore {
131    type Target = DataActorCore;
132    fn deref(&self) -> &Self::Target {
133        &self.actor
134    }
135}
136
137impl DerefMut for StrategyCore {
138    fn deref_mut(&mut self) -> &mut Self::Target {
139        &mut self.actor
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use std::{cell::RefCell, rc::Rc};
146
147    use nautilus_common::{cache::Cache, clock::TestClock};
148    use nautilus_model::identifiers::{StrategyId, TraderId};
149    use nautilus_portfolio::portfolio::Portfolio;
150    use rstest::rstest;
151
152    use super::*;
153
154    fn create_test_config() -> StrategyConfig {
155        StrategyConfig {
156            strategy_id: Some(StrategyId::from("TEST-001")),
157            order_id_tag: Some("001".to_string()),
158            ..Default::default()
159        }
160    }
161
162    #[rstest]
163    fn test_strategy_core_new() {
164        let config = create_test_config();
165        let core = StrategyCore::new(config.clone());
166
167        assert_eq!(core.config.strategy_id, config.strategy_id);
168        assert_eq!(core.config.order_id_tag, config.order_id_tag);
169        assert!(core.order_manager.is_none());
170        assert!(core.order_factory.is_none());
171        assert!(core.portfolio.is_none());
172    }
173
174    #[rstest]
175    fn test_strategy_core_register() {
176        let config = create_test_config();
177        let mut core = StrategyCore::new(config);
178
179        let trader_id = TraderId::from("TRADER-001");
180        let clock = Rc::new(RefCell::new(TestClock::new()));
181        let cache = Rc::new(RefCell::new(Cache::default()));
182        let portfolio = Rc::new(RefCell::new(Portfolio::new(
183            cache.clone(),
184            clock.clone(),
185            None,
186        )));
187
188        let result = core.register(trader_id, clock, cache, portfolio);
189        assert!(result.is_ok());
190
191        assert!(core.order_manager.is_some());
192        assert!(core.order_factory.is_some());
193        assert!(core.portfolio.is_some());
194        assert_eq!(core.trader_id(), Some(trader_id));
195    }
196
197    #[rstest]
198    fn test_strategy_core_deref() {
199        let config = create_test_config();
200        let core = StrategyCore::new(config);
201
202        assert!(core.trader_id().is_none());
203    }
204
205    #[rstest]
206    fn test_strategy_core_debug() {
207        let config = create_test_config();
208        let core = StrategyCore::new(config);
209
210        let debug_str = format!("{core:?}");
211        assert!(debug_str.contains("StrategyCore"));
212    }
213}