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