nautilus_trading/strategy/
core.rs1use 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_execution::order_manager::manager::OrderManager;
31use nautilus_model::identifiers::{ActorId, ClientOrderId, StrategyId, TraderId};
32use nautilus_portfolio::portfolio::Portfolio;
33use ustr::Ustr;
34
35use super::config::StrategyConfig;
36
37pub struct StrategyCore {
44 pub(crate) actor: DataActorCore,
45 pub config: StrategyConfig,
47 pub(crate) order_manager: Option<OrderManager>,
48 pub(crate) order_factory: Option<OrderFactory>,
49 pub(crate) portfolio: Option<Rc<RefCell<Portfolio>>>,
50 pub(crate) gtd_timers: AHashMap<ClientOrderId, Ustr>,
51 pub(crate) is_exiting: bool,
52 pub(crate) pending_stop: bool,
53 pub(crate) market_exit_attempts: u64,
54 pub(crate) market_exit_timer_name: Ustr,
55 pub(crate) market_exit_tag: Ustr,
56}
57
58impl Debug for StrategyCore {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct(stringify!(StrategyCore))
61 .field("actor", &self.actor)
62 .field("config", &self.config)
63 .field("order_manager", &self.order_manager)
64 .field("order_factory", &self.order_factory)
65 .field("is_exiting", &self.is_exiting)
66 .field("pending_stop", &self.pending_stop)
67 .field("market_exit_attempts", &self.market_exit_attempts)
68 .finish()
69 }
70}
71
72impl StrategyCore {
73 pub fn new(config: StrategyConfig) -> Self {
75 let actor_config = DataActorConfig {
76 actor_id: config
77 .strategy_id
78 .map(|id| ActorId::from(id.inner().as_str())),
79 log_events: config.log_events,
80 log_commands: config.log_commands,
81 };
82
83 let strategy_id = config
84 .strategy_id
85 .map(|id| id.inner().to_string())
86 .unwrap_or_default();
87 let market_exit_timer_name = Ustr::from(&format!("MARKET_EXIT_CHECK:{strategy_id}"));
88
89 Self {
90 actor: DataActorCore::new(actor_config),
91 config,
92 order_manager: None,
93 order_factory: None,
94 portfolio: None,
95 gtd_timers: AHashMap::new(),
96 is_exiting: false,
97 pending_stop: false,
98 market_exit_attempts: 0,
99 market_exit_timer_name,
100 market_exit_tag: Ustr::from("MARKET_EXIT"),
101 }
102 }
103
104 pub fn register(
112 &mut self,
113 trader_id: TraderId,
114 clock: Rc<RefCell<dyn Clock>>,
115 cache: Rc<RefCell<Cache>>,
116 portfolio: Rc<RefCell<Portfolio>>,
117 ) -> anyhow::Result<()> {
118 self.actor
119 .register(trader_id, clock.clone(), cache.clone())?;
120
121 let strategy_id = StrategyId::from(self.actor.actor_id.inner().as_str());
122
123 self.market_exit_timer_name = Ustr::from(&format!("MARKET_EXIT_CHECK:{strategy_id}"));
125
126 self.order_factory = Some(OrderFactory::new(
127 trader_id,
128 strategy_id,
129 None,
130 None,
131 clock.clone(),
132 self.config.use_uuid_client_order_ids,
133 self.config.use_hyphens_in_client_order_ids,
134 ));
135
136 self.order_manager = Some(OrderManager::new(clock, cache, false, None, None, None));
137
138 self.portfolio = Some(portfolio);
139
140 Ok(())
141 }
142
143 pub fn order_factory(&mut self) -> &mut OrderFactory {
149 self.order_factory
150 .as_mut()
151 .expect("Strategy not registered: OrderFactory not initialized")
152 }
153
154 pub fn order_manager(&mut self) -> &mut OrderManager {
160 self.order_manager
161 .as_mut()
162 .expect("Strategy not registered: OrderManager not initialized")
163 }
164
165 pub fn portfolio(&self) -> &Rc<RefCell<Portfolio>> {
171 self.portfolio
172 .as_ref()
173 .expect("Strategy not registered: Portfolio not initialized")
174 }
175
176 pub fn reset_market_exit_state(&mut self) {
178 self.is_exiting = false;
179 self.pending_stop = false;
180 self.market_exit_attempts = 0;
181 }
182}
183
184impl Deref for StrategyCore {
185 type Target = DataActorCore;
186 fn deref(&self) -> &Self::Target {
187 &self.actor
188 }
189}
190
191impl DerefMut for StrategyCore {
192 fn deref_mut(&mut self) -> &mut Self::Target {
193 &mut self.actor
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use std::{cell::RefCell, rc::Rc};
200
201 use nautilus_common::{cache::Cache, clock::TestClock};
202 use nautilus_model::identifiers::{StrategyId, TraderId};
203 use nautilus_portfolio::portfolio::Portfolio;
204 use rstest::rstest;
205
206 use super::*;
207
208 fn create_test_config() -> StrategyConfig {
209 StrategyConfig {
210 strategy_id: Some(StrategyId::from("TEST-001")),
211 order_id_tag: Some("001".to_string()),
212 ..Default::default()
213 }
214 }
215
216 #[rstest]
217 fn test_strategy_core_new() {
218 let config = create_test_config();
219 let core = StrategyCore::new(config.clone());
220
221 assert_eq!(core.config.strategy_id, config.strategy_id);
222 assert_eq!(core.config.order_id_tag, config.order_id_tag);
223 assert!(core.order_manager.is_none());
224 assert!(core.order_factory.is_none());
225 assert!(core.portfolio.is_none());
226 assert!(!core.is_exiting);
227 assert!(!core.pending_stop);
228 assert_eq!(core.market_exit_attempts, 0);
229 }
230
231 #[rstest]
232 fn test_strategy_core_register() {
233 let config = create_test_config();
234 let mut core = StrategyCore::new(config);
235
236 let trader_id = TraderId::from("TRADER-001");
237 let clock = Rc::new(RefCell::new(TestClock::new()));
238 let cache = Rc::new(RefCell::new(Cache::default()));
239 let portfolio = Rc::new(RefCell::new(Portfolio::new(
240 cache.clone(),
241 clock.clone(),
242 None,
243 )));
244
245 let result = core.register(trader_id, clock, cache, portfolio);
246 assert!(result.is_ok());
247
248 assert!(core.order_manager.is_some());
249 assert!(core.order_factory.is_some());
250 assert!(core.portfolio.is_some());
251 assert_eq!(core.trader_id(), Some(trader_id));
252 }
253
254 #[rstest]
255 fn test_strategy_core_deref() {
256 let config = create_test_config();
257 let core = StrategyCore::new(config);
258
259 assert!(core.trader_id().is_none());
260 }
261
262 #[rstest]
263 fn test_strategy_core_debug() {
264 let config = create_test_config();
265 let core = StrategyCore::new(config);
266
267 let debug_str = format!("{core:?}");
268 assert!(debug_str.contains("StrategyCore"));
269 }
270}