Skip to main content

nautilus_trading/strategy/
config.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
16use nautilus_core::serialization::{default_false, default_true};
17use nautilus_model::{
18    enums::{OmsType, TimeInForce},
19    identifiers::{InstrumentId, StrategyId},
20};
21use serde::{Deserialize, Serialize};
22
23/// The base model for all trading strategy configurations.
24#[derive(Clone, Debug, Deserialize, Serialize)]
25#[cfg_attr(
26    feature = "python",
27    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.trading")
28)]
29pub struct StrategyConfig {
30    /// The unique ID for the strategy. Will become the strategy ID if not None.
31    pub strategy_id: Option<StrategyId>,
32    /// The unique order ID tag for the strategy. Must be unique
33    /// amongst all running strategies for a particular trader ID.
34    pub order_id_tag: Option<String>,
35    /// If UUID4's should be used for client order ID values.
36    #[serde(default = "default_false")]
37    pub use_uuid_client_order_ids: bool,
38    /// If hyphens should be used in generated client order ID values.
39    #[serde(default = "default_true")]
40    pub use_hyphens_in_client_order_ids: bool,
41    /// The order management system type for the strategy. This will determine
42    /// how the `ExecutionEngine` handles position IDs.
43    pub oms_type: Option<OmsType>,
44    /// The external order claim instrument IDs.
45    /// External orders for matching instrument IDs will be associated with (claimed by) the strategy.
46    pub external_order_claims: Option<Vec<InstrumentId>>,
47    /// If OTO, OCO, and OUO **open** contingent orders should be managed automatically by the strategy.
48    /// Any emulated orders which are active local will be managed by the `OrderEmulator` instead.
49    #[serde(default = "default_false")]
50    pub manage_contingent_orders: bool,
51    /// If all order GTD time in force expirations should be managed by the strategy.
52    /// If True, then will ensure open orders have their GTD timers re-activated on start.
53    #[serde(default = "default_false")]
54    pub manage_gtd_expiry: bool,
55    /// If the strategy should automatically perform a market exit when stopped.
56    /// If true, calling stop() will first cancel all orders and close all positions
57    /// before the strategy transitions to the STOPPED state.
58    #[serde(default = "default_false")]
59    pub manage_stop: bool,
60    /// The interval in milliseconds to check for in-flight orders and open positions
61    /// during a market exit.
62    #[serde(default = "default_market_exit_interval_ms")]
63    pub market_exit_interval_ms: u64,
64    /// The maximum number of attempts to wait for orders and positions to close
65    /// during a market exit before completing. Defaults to 100 attempts
66    /// (10 seconds at 100ms intervals).
67    #[serde(default = "default_market_exit_max_attempts")]
68    pub market_exit_max_attempts: u64,
69    /// The time in force for closing market orders during a market exit.
70    #[serde(default = "default_market_exit_time_in_force")]
71    pub market_exit_time_in_force: TimeInForce,
72    /// If closing market orders during a market exit should be reduce only.
73    #[serde(default = "default_true")]
74    pub market_exit_reduce_only: bool,
75    /// If events should be logged by the strategy.
76    /// If False, then only warning events and above are logged.
77    #[serde(default = "default_true")]
78    pub log_events: bool,
79    /// If commands should be logged by the strategy.
80    #[serde(default = "default_true")]
81    pub log_commands: bool,
82    /// If order rejected events where `due_post_only` is True should be logged as warnings.
83    #[serde(default = "default_true")]
84    pub log_rejected_due_post_only_as_warning: bool,
85}
86
87const fn default_market_exit_interval_ms() -> u64 {
88    100
89}
90
91const fn default_market_exit_max_attempts() -> u64 {
92    100
93}
94
95const fn default_market_exit_time_in_force() -> TimeInForce {
96    TimeInForce::Gtc
97}
98
99impl Default for StrategyConfig {
100    fn default() -> Self {
101        Self {
102            strategy_id: None,
103            order_id_tag: None,
104            use_uuid_client_order_ids: false,
105            use_hyphens_in_client_order_ids: true,
106            oms_type: None,
107            external_order_claims: None,
108            manage_contingent_orders: false,
109            manage_gtd_expiry: false,
110            manage_stop: false,
111            market_exit_interval_ms: default_market_exit_interval_ms(),
112            market_exit_max_attempts: default_market_exit_max_attempts(),
113            market_exit_time_in_force: TimeInForce::Gtc,
114            market_exit_reduce_only: true,
115            log_events: true,
116            log_commands: true,
117            log_rejected_due_post_only_as_warning: true,
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use rstest::rstest;
125
126    use super::*;
127
128    #[rstest]
129    fn test_strategy_config_default() {
130        let config = StrategyConfig::default();
131
132        assert!(config.strategy_id.is_none());
133        assert!(config.order_id_tag.is_none());
134        assert!(!config.use_uuid_client_order_ids);
135        assert!(config.use_hyphens_in_client_order_ids);
136        assert!(config.oms_type.is_none());
137        assert!(config.external_order_claims.is_none());
138        assert!(!config.manage_contingent_orders);
139        assert!(!config.manage_gtd_expiry);
140        assert!(!config.manage_stop);
141        assert_eq!(config.market_exit_interval_ms, 100);
142        assert_eq!(config.market_exit_max_attempts, 100);
143        assert_eq!(config.market_exit_time_in_force, TimeInForce::Gtc);
144        assert!(config.market_exit_reduce_only);
145        assert!(config.log_events);
146        assert!(config.log_commands);
147        assert!(config.log_rejected_due_post_only_as_warning);
148    }
149
150    #[rstest]
151    fn test_strategy_config_with_strategy_id() {
152        let strategy_id = StrategyId::from("TEST-001");
153        let config = StrategyConfig {
154            strategy_id: Some(strategy_id),
155            ..Default::default()
156        };
157
158        assert_eq!(config.strategy_id, Some(strategy_id));
159    }
160
161    #[rstest]
162    fn test_strategy_config_serialization() {
163        let config = StrategyConfig {
164            strategy_id: Some(StrategyId::from("TEST-001")),
165            order_id_tag: Some("TAG1".to_string()),
166            use_uuid_client_order_ids: true,
167            ..Default::default()
168        };
169
170        let json = serde_json::to_string(&config).unwrap();
171        let deserialized: StrategyConfig = serde_json::from_str(&json).unwrap();
172
173        assert_eq!(config.strategy_id, deserialized.strategy_id);
174        assert_eq!(config.order_id_tag, deserialized.order_id_tag);
175        assert_eq!(
176            config.use_uuid_client_order_ids,
177            deserialized.use_uuid_client_order_ids
178        );
179    }
180}