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