Skip to main content

nautilus_testkit/testers/exec/
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::num::NonZeroUsize;
17
18use nautilus_core::Params;
19use nautilus_model::{
20    enums::{BookType, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
21    identifiers::{ClientId, InstrumentId, StrategyId},
22    types::Quantity,
23};
24use nautilus_trading::strategy::StrategyConfig;
25use rust_decimal::Decimal;
26use serde::{Deserialize, Serialize};
27
28/// Configuration for the execution tester strategy.
29#[derive(Debug, Clone, Deserialize, Serialize, bon::Builder)]
30#[serde(default, deny_unknown_fields)]
31pub struct ExecTesterConfig {
32    /// Base strategy configuration.
33    #[builder(default)]
34    pub base: StrategyConfig,
35    /// Instrument ID to test.
36    #[builder(default = InstrumentId::from("BTCUSDT-PERP.BINANCE"))]
37    pub instrument_id: InstrumentId,
38    /// Order quantity.
39    #[builder(default = Quantity::from("0.001"))]
40    pub order_qty: Quantity,
41    /// Display quantity for iceberg orders (None for full display, Some(0) for hidden).
42    pub order_display_qty: Option<Quantity>,
43    /// Minutes until GTD orders expire (None for GTC).
44    pub order_expire_time_delta_mins: Option<u64>,
45    /// Adapter-specific order parameters.
46    pub order_params: Option<Params>,
47    /// Client ID to use for orders and subscriptions.
48    pub client_id: Option<ClientId>,
49    /// Whether to subscribe to order book.
50    #[builder(default = false)]
51    pub subscribe_book: bool,
52    /// Whether to subscribe to quotes.
53    #[builder(default = true)]
54    pub subscribe_quotes: bool,
55    /// Whether to subscribe to trades.
56    #[builder(default = true)]
57    pub subscribe_trades: bool,
58    /// Book type for order book subscriptions.
59    #[builder(default = BookType::L2_MBP)]
60    pub book_type: BookType,
61    /// Order book depth for subscriptions.
62    pub book_depth: Option<NonZeroUsize>,
63    /// Order book interval in milliseconds.
64    #[builder(default = NonZeroUsize::new(1000).unwrap())]
65    pub book_interval_ms: NonZeroUsize,
66    /// Number of order book levels to print when logging.
67    #[builder(default = 10)]
68    pub book_levels_to_print: usize,
69    /// Quantity to open position on start (positive for buy, negative for sell).
70    pub open_position_on_start_qty: Option<Decimal>,
71    /// Time in force for opening position order.
72    #[builder(default = TimeInForce::Gtc)]
73    pub open_position_time_in_force: TimeInForce,
74    /// Enable limit buy orders.
75    #[builder(default = true)]
76    pub enable_limit_buys: bool,
77    /// Enable limit sell orders.
78    #[builder(default = true)]
79    pub enable_limit_sells: bool,
80    /// Enable stop buy orders.
81    #[builder(default = false)]
82    pub enable_stop_buys: bool,
83    /// Enable stop sell orders.
84    #[builder(default = false)]
85    pub enable_stop_sells: bool,
86    /// Offset from TOB in price ticks for limit orders.
87    #[builder(default = 500)]
88    pub tob_offset_ticks: u64,
89    /// Override time in force for limit orders (None uses GTC/GTD logic).
90    pub limit_time_in_force: Option<TimeInForce>,
91    /// Type of stop order (STOP_MARKET, STOP_LIMIT, MARKET_IF_TOUCHED, LIMIT_IF_TOUCHED).
92    #[builder(default = OrderType::StopMarket)]
93    pub stop_order_type: OrderType,
94    /// Offset from market in price ticks for stop trigger.
95    #[builder(default = 100)]
96    pub stop_offset_ticks: u64,
97    /// Offset from trigger price in ticks for stop limit price.
98    pub stop_limit_offset_ticks: Option<u64>,
99    /// Trigger type for stop orders.
100    #[builder(default = TriggerType::Default)]
101    pub stop_trigger_type: TriggerType,
102    /// Override time in force for stop orders (None uses GTC/GTD logic).
103    pub stop_time_in_force: Option<TimeInForce>,
104    /// Trailing offset for TRAILING_STOP_MARKET orders.
105    pub trailing_offset: Option<Decimal>,
106    /// Trailing offset type (BasisPoints or Price).
107    #[builder(default = TrailingOffsetType::BasisPoints)]
108    pub trailing_offset_type: TrailingOffsetType,
109    /// Enable bracket orders (entry with TP/SL).
110    #[builder(default = false)]
111    pub enable_brackets: bool,
112    /// Submit limit buy and sell as an order list instead of individual orders.
113    #[builder(default = false)]
114    pub batch_submit_limit_pair: bool,
115    /// Entry order type for bracket orders.
116    #[builder(default = OrderType::Limit)]
117    pub bracket_entry_order_type: OrderType,
118    /// Offset in ticks for bracket TP/SL from entry price.
119    #[builder(default = 500)]
120    pub bracket_offset_ticks: u64,
121    /// Modify limit orders to maintain TOB offset.
122    #[builder(default = false)]
123    pub modify_orders_to_maintain_tob_offset: bool,
124    /// Modify stop orders to maintain offset.
125    #[builder(default = false)]
126    pub modify_stop_orders_to_maintain_offset: bool,
127    /// Cancel and replace limit orders to maintain TOB offset.
128    #[builder(default = false)]
129    pub cancel_replace_orders_to_maintain_tob_offset: bool,
130    /// Cancel and replace stop orders to maintain offset.
131    #[builder(default = false)]
132    pub cancel_replace_stop_orders_to_maintain_offset: bool,
133    /// Use post-only for limit orders.
134    #[builder(default = false)]
135    pub use_post_only: bool,
136    /// Use quote quantity for orders.
137    #[builder(default = false)]
138    pub use_quote_quantity: bool,
139    /// Emulation trigger type for orders.
140    pub emulation_trigger: Option<TriggerType>,
141    /// Cancel all orders on stop.
142    #[builder(default = true)]
143    pub cancel_orders_on_stop: bool,
144    /// Close all positions on stop.
145    #[builder(default = true)]
146    pub close_positions_on_stop: bool,
147    /// Time in force for closing positions (None defaults to GTC).
148    pub close_positions_time_in_force: Option<TimeInForce>,
149    /// Use reduce_only when closing positions.
150    #[builder(default = true)]
151    pub reduce_only_on_stop: bool,
152    /// Use individual cancel commands instead of cancel_all.
153    #[builder(default = false)]
154    pub use_individual_cancels_on_stop: bool,
155    /// Use batch cancel command when stopping.
156    #[builder(default = false)]
157    pub use_batch_cancel_on_stop: bool,
158    /// Dry run mode (no order submission).
159    #[builder(default = false)]
160    pub dry_run: bool,
161    /// Log received data.
162    #[builder(default = true)]
163    pub log_data: bool,
164    /// Test post-only rejection by placing orders on wrong side of spread.
165    #[builder(default = false)]
166    pub test_reject_post_only: bool,
167    /// Test reduce-only rejection by setting reduce_only on open position order.
168    #[builder(default = false)]
169    pub test_reject_reduce_only: bool,
170    /// Whether unsubscribe is supported on stop.
171    #[builder(default = true)]
172    pub can_unsubscribe: bool,
173}
174
175impl ExecTesterConfig {
176    /// Creates a new [`ExecTesterConfig`] with minimal settings.
177    ///
178    /// # Panics
179    ///
180    /// Panics if `NonZeroUsize::new(1000)` fails (which should never happen).
181    #[must_use]
182    pub fn new(
183        strategy_id: StrategyId,
184        instrument_id: InstrumentId,
185        client_id: ClientId,
186        order_qty: Quantity,
187    ) -> Self {
188        Self {
189            base: StrategyConfig {
190                strategy_id: Some(strategy_id),
191                order_id_tag: None,
192                ..Default::default()
193            },
194            instrument_id,
195            order_qty,
196            order_display_qty: None,
197            order_expire_time_delta_mins: None,
198            order_params: None,
199            client_id: Some(client_id),
200            subscribe_quotes: true,
201            subscribe_trades: true,
202            subscribe_book: false,
203            book_type: BookType::L2_MBP,
204            book_depth: None,
205            book_interval_ms: NonZeroUsize::new(1000).unwrap(),
206            book_levels_to_print: 10,
207            open_position_on_start_qty: None,
208            open_position_time_in_force: TimeInForce::Gtc,
209            enable_limit_buys: true,
210            enable_limit_sells: true,
211            enable_stop_buys: false,
212            enable_stop_sells: false,
213            tob_offset_ticks: 500,
214            limit_time_in_force: None,
215            stop_order_type: OrderType::StopMarket,
216            stop_offset_ticks: 100,
217            stop_limit_offset_ticks: None,
218            stop_trigger_type: TriggerType::Default,
219            stop_time_in_force: None,
220            trailing_offset: None,
221            trailing_offset_type: TrailingOffsetType::BasisPoints,
222            enable_brackets: false,
223            batch_submit_limit_pair: false,
224            bracket_entry_order_type: OrderType::Limit,
225            bracket_offset_ticks: 500,
226            modify_orders_to_maintain_tob_offset: false,
227            modify_stop_orders_to_maintain_offset: false,
228            cancel_replace_orders_to_maintain_tob_offset: false,
229            cancel_replace_stop_orders_to_maintain_offset: false,
230            use_post_only: false,
231            use_quote_quantity: false,
232            emulation_trigger: None,
233            cancel_orders_on_stop: true,
234            close_positions_on_stop: true,
235            close_positions_time_in_force: None,
236            reduce_only_on_stop: true,
237            use_individual_cancels_on_stop: false,
238            use_batch_cancel_on_stop: false,
239            dry_run: false,
240            log_data: true,
241            test_reject_post_only: false,
242            test_reject_reduce_only: false,
243            can_unsubscribe: true,
244        }
245    }
246}
247
248impl Default for ExecTesterConfig {
249    fn default() -> Self {
250        Self::builder().build()
251    }
252}