Skip to main content

nautilus_testkit/
components.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//! Stateful actor and strategy test components.
17
18use indexmap::IndexMap;
19use nautilus_common::{
20    actor::{DataActor, DataActorCore, data_actor::DataActorConfig},
21    nautilus_actor,
22};
23use nautilus_model::identifiers::{ActorId, StrategyId};
24use nautilus_trading::{
25    nautilus_strategy,
26    strategy::{config::StrategyConfig, core::StrategyCore},
27};
28
29use crate::cache::TestCacheDatabaseControl;
30
31/// Actor with observable state lifecycle callbacks.
32#[derive(Debug)]
33pub struct StateActor {
34    core: DataActorCore,
35    control: TestCacheDatabaseControl,
36    state_load: Option<IndexMap<String, Vec<u8>>>,
37    state_save: IndexMap<String, Vec<u8>>,
38    fail_load: bool,
39    fail_save: bool,
40    fail_start: bool,
41}
42
43impl StateActor {
44    /// Creates a stateful actor.
45    #[must_use]
46    pub fn new(
47        actor_id: ActorId,
48        control: TestCacheDatabaseControl,
49        state_save: IndexMap<String, Vec<u8>>,
50    ) -> Self {
51        Self {
52            core: DataActorCore::new(DataActorConfig {
53                actor_id: Some(actor_id),
54                ..Default::default()
55            }),
56            control,
57            state_load: None,
58            state_save,
59            fail_load: false,
60            fail_save: false,
61            fail_start: false,
62        }
63    }
64
65    /// Configures the actor load callback to fail.
66    #[must_use]
67    pub const fn with_fail_load(mut self) -> Self {
68        self.fail_load = true;
69        self
70    }
71
72    /// Configures the actor save callback to fail.
73    #[must_use]
74    pub const fn with_fail_save(mut self) -> Self {
75        self.fail_save = true;
76        self
77    }
78
79    /// Configures the actor start callback to fail.
80    #[must_use]
81    pub const fn with_fail_start(mut self) -> Self {
82        self.fail_start = true;
83        self
84    }
85
86    /// Returns the state received by the load callback.
87    #[must_use]
88    pub const fn state_load(&self) -> Option<&IndexMap<String, Vec<u8>>> {
89        self.state_load.as_ref()
90    }
91}
92
93impl DataActor for StateActor {
94    fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {
95        self.control.record("actor.on_load");
96        if self.fail_load {
97            anyhow::bail!("test actor on_load failure");
98        }
99        self.state_load = Some(state);
100        Ok(())
101    }
102
103    fn on_start(&mut self) -> anyhow::Result<()> {
104        self.control.record("actor.on_start");
105        if self.fail_start {
106            anyhow::bail!("test actor on_start failure");
107        }
108        Ok(())
109    }
110
111    fn on_stop(&mut self) -> anyhow::Result<()> {
112        self.control.record("actor.on_stop");
113        Ok(())
114    }
115
116    fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {
117        self.control.record("actor.on_save");
118        if self.fail_save {
119            anyhow::bail!("test actor on_save failure");
120        }
121        Ok(self.state_save.clone())
122    }
123}
124
125nautilus_actor!(StateActor);
126
127/// Strategy with observable state lifecycle callbacks.
128#[derive(Debug)]
129pub struct StateStrategy {
130    core: StrategyCore,
131    control: TestCacheDatabaseControl,
132    state_load: Option<IndexMap<String, Vec<u8>>>,
133    state_save: IndexMap<String, Vec<u8>>,
134    fail_load: bool,
135    fail_save: bool,
136    fail_start: bool,
137}
138
139impl StateStrategy {
140    /// Creates a stateful strategy.
141    #[must_use]
142    pub fn new(
143        strategy_id: StrategyId,
144        control: TestCacheDatabaseControl,
145        state_save: IndexMap<String, Vec<u8>>,
146    ) -> Self {
147        Self {
148            core: StrategyCore::new(StrategyConfig {
149                strategy_id: Some(strategy_id),
150                ..Default::default()
151            }),
152            control,
153            state_load: None,
154            state_save,
155            fail_load: false,
156            fail_save: false,
157            fail_start: false,
158        }
159    }
160
161    /// Configures the strategy load callback to fail.
162    #[must_use]
163    pub const fn with_fail_load(mut self) -> Self {
164        self.fail_load = true;
165        self
166    }
167
168    /// Configures the strategy save callback to fail.
169    #[must_use]
170    pub const fn with_fail_save(mut self) -> Self {
171        self.fail_save = true;
172        self
173    }
174
175    /// Configures the strategy start callback to fail.
176    #[must_use]
177    pub const fn with_fail_start(mut self) -> Self {
178        self.fail_start = true;
179        self
180    }
181
182    /// Returns the state received by the load callback.
183    #[must_use]
184    pub const fn state_load(&self) -> Option<&IndexMap<String, Vec<u8>>> {
185        self.state_load.as_ref()
186    }
187}
188
189impl DataActor for StateStrategy {
190    fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {
191        self.control.record("strategy.on_load");
192        if self.fail_load {
193            anyhow::bail!("test strategy on_load failure");
194        }
195        self.state_load = Some(state);
196        Ok(())
197    }
198
199    fn on_start(&mut self) -> anyhow::Result<()> {
200        self.control.record("strategy.on_start");
201        if self.fail_start {
202            anyhow::bail!("test strategy on_start failure");
203        }
204        Ok(())
205    }
206
207    fn on_stop(&mut self) -> anyhow::Result<()> {
208        self.control.record("strategy.on_stop");
209        Ok(())
210    }
211
212    fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {
213        self.control.record("strategy.on_save");
214        if self.fail_save {
215            anyhow::bail!("test strategy on_save failure");
216        }
217        Ok(self.state_save.clone())
218    }
219}
220
221nautilus_strategy!(StateStrategy);