nautilus_backtest/
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
16// Under development
17#![allow(dead_code)]
18#![allow(unused_variables)]
19
20use std::{collections::HashMap, time::Duration};
21
22use nautilus_common::{
23    cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
24    msgbus::database::MessageBusConfig,
25};
26use nautilus_core::{UUID4, UnixNanos};
27use nautilus_data::engine::config::DataEngineConfig;
28use nautilus_execution::engine::config::ExecutionEngineConfig;
29use nautilus_model::{
30    data::BarSpecification,
31    enums::{AccountType, BookType, OmsType},
32    identifiers::{ClientId, InstrumentId, TraderId},
33    types::Currency,
34};
35use nautilus_portfolio::config::PortfolioConfig;
36use nautilus_risk::engine::config::RiskEngineConfig;
37use nautilus_system::config::{NautilusKernelConfig, StreamingConfig};
38use ustr::Ustr;
39
40/// Configuration for ``BacktestEngine`` instances.
41#[derive(Debug, Clone)]
42pub struct BacktestEngineConfig {
43    /// The kernel environment context.
44    pub environment: Environment,
45    /// The trader ID for the node.
46    pub trader_id: TraderId,
47    /// If trading strategy state should be loaded from the database on start.
48    pub load_state: bool,
49    /// If trading strategy state should be saved to the database on stop.
50    pub save_state: bool,
51    /// The logging configuration for the kernel.
52    pub logging: LoggerConfig,
53    /// The unique instance identifier for the kernel.
54    pub instance_id: Option<UUID4>,
55    /// The timeout for all clients to connect and initialize.
56    pub timeout_connection: Duration,
57    /// The timeout for execution state to reconcile.
58    pub timeout_reconciliation: Duration,
59    /// The timeout for portfolio to initialize margins and unrealized pnls.
60    pub timeout_portfolio: Duration,
61    /// The timeout for all engine clients to disconnect.
62    pub timeout_disconnection: Duration,
63    /// The delay after stopping the node to await residual events before final shutdown.
64    pub delay_post_stop: Duration,
65    /// The timeout to await pending tasks cancellation during shutdown.
66    pub timeout_shutdown: Duration,
67    /// The cache configuration.
68    pub cache: Option<CacheConfig>,
69    /// The message bus configuration.
70    pub msgbus: Option<MessageBusConfig>,
71    /// The data engine configuration.
72    pub data_engine: Option<DataEngineConfig>,
73    /// The risk engine configuration.
74    pub risk_engine: Option<RiskEngineConfig>,
75    /// The execution engine configuration.
76    pub exec_engine: Option<ExecutionEngineConfig>,
77    /// The portfolio configuration.
78    pub portfolio: Option<PortfolioConfig>,
79    /// The configuration for streaming to feather files.
80    pub streaming: Option<StreamingConfig>,
81    /// If logging should be bypassed.
82    pub bypass_logging: bool,
83    /// If post backtest performance analysis should be run.
84    pub run_analysis: bool,
85}
86
87impl BacktestEngineConfig {
88    #[must_use]
89    #[allow(clippy::too_many_arguments)]
90    pub fn new(
91        environment: Environment,
92        trader_id: TraderId,
93        load_state: Option<bool>,
94        save_state: Option<bool>,
95        bypass_logging: Option<bool>,
96        run_analysis: Option<bool>,
97        timeout_connection: Option<u64>,
98        timeout_reconciliation: Option<u64>,
99        timeout_portfolio: Option<u64>,
100        timeout_disconnection: Option<u64>,
101        delay_post_stop: Option<u64>,
102        timeout_shutdown: Option<u64>,
103        logging: Option<LoggerConfig>,
104        instance_id: Option<UUID4>,
105        cache: Option<CacheConfig>,
106        msgbus: Option<MessageBusConfig>,
107        data_engine: Option<DataEngineConfig>,
108        risk_engine: Option<RiskEngineConfig>,
109        exec_engine: Option<ExecutionEngineConfig>,
110        portfolio: Option<PortfolioConfig>,
111        streaming: Option<StreamingConfig>,
112    ) -> Self {
113        Self {
114            environment,
115            trader_id,
116            load_state: load_state.unwrap_or(false),
117            save_state: save_state.unwrap_or(false),
118            logging: logging.unwrap_or_default(),
119            instance_id,
120            timeout_connection: Duration::from_secs(timeout_connection.unwrap_or(60)),
121            timeout_reconciliation: Duration::from_secs(timeout_reconciliation.unwrap_or(30)),
122            timeout_portfolio: Duration::from_secs(timeout_portfolio.unwrap_or(10)),
123            timeout_disconnection: Duration::from_secs(timeout_disconnection.unwrap_or(10)),
124            delay_post_stop: Duration::from_secs(delay_post_stop.unwrap_or(10)),
125            timeout_shutdown: Duration::from_secs(timeout_shutdown.unwrap_or(5)),
126            cache,
127            msgbus,
128            data_engine,
129            risk_engine,
130            exec_engine,
131            portfolio,
132            streaming,
133            bypass_logging: bypass_logging.unwrap_or(false),
134            run_analysis: run_analysis.unwrap_or(true),
135        }
136    }
137}
138
139impl NautilusKernelConfig for BacktestEngineConfig {
140    fn environment(&self) -> Environment {
141        self.environment
142    }
143
144    fn trader_id(&self) -> TraderId {
145        self.trader_id
146    }
147
148    fn load_state(&self) -> bool {
149        self.load_state
150    }
151
152    fn save_state(&self) -> bool {
153        self.save_state
154    }
155
156    fn logging(&self) -> LoggerConfig {
157        self.logging.clone()
158    }
159
160    fn instance_id(&self) -> Option<UUID4> {
161        self.instance_id
162    }
163
164    fn timeout_connection(&self) -> Duration {
165        self.timeout_connection
166    }
167
168    fn timeout_reconciliation(&self) -> Duration {
169        self.timeout_reconciliation
170    }
171
172    fn timeout_portfolio(&self) -> Duration {
173        self.timeout_portfolio
174    }
175
176    fn timeout_disconnection(&self) -> Duration {
177        self.timeout_disconnection
178    }
179
180    fn delay_post_stop(&self) -> Duration {
181        self.delay_post_stop
182    }
183
184    fn timeout_shutdown(&self) -> Duration {
185        self.timeout_shutdown
186    }
187
188    fn cache(&self) -> Option<CacheConfig> {
189        self.cache.clone()
190    }
191
192    fn msgbus(&self) -> Option<MessageBusConfig> {
193        self.msgbus.clone()
194    }
195
196    fn data_engine(&self) -> Option<DataEngineConfig> {
197        self.data_engine.clone()
198    }
199
200    fn risk_engine(&self) -> Option<RiskEngineConfig> {
201        self.risk_engine.clone()
202    }
203
204    fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
205        self.exec_engine.clone()
206    }
207
208    fn portfolio(&self) -> Option<PortfolioConfig> {
209        self.portfolio.clone()
210    }
211
212    fn streaming(&self) -> Option<StreamingConfig> {
213        self.streaming.clone()
214    }
215}
216
217impl Default for BacktestEngineConfig {
218    fn default() -> Self {
219        Self {
220            environment: Environment::Backtest,
221            trader_id: TraderId::default(),
222            load_state: false,
223            save_state: false,
224            logging: LoggerConfig::default(),
225            instance_id: None,
226            timeout_connection: Duration::from_secs(60),
227            timeout_reconciliation: Duration::from_secs(30),
228            timeout_portfolio: Duration::from_secs(10),
229            timeout_disconnection: Duration::from_secs(10),
230            delay_post_stop: Duration::from_secs(10),
231            timeout_shutdown: Duration::from_secs(5),
232            cache: None,
233            msgbus: None,
234            data_engine: None,
235            risk_engine: None,
236            exec_engine: None,
237            portfolio: None,
238            streaming: None,
239            bypass_logging: false,
240            run_analysis: true,
241        }
242    }
243}
244
245/// Represents a venue configuration for one specific backtest engine.
246#[derive(Debug, Clone)]
247pub struct BacktestVenueConfig {
248    /// The name of the venue.
249    name: Ustr,
250    /// The order management system type for the exchange. If ``HEDGING`` will generate new position IDs.
251    oms_type: OmsType,
252    /// The account type for the exchange.
253    account_type: AccountType,
254    /// The default order book type.
255    book_type: BookType,
256    /// The starting account balances (specify one for a single asset account).
257    starting_balances: Vec<String>,
258    /// If multi-venue routing should be enabled for the execution client.
259    routing: bool,
260    /// If the account for this exchange is frozen (balances will not change).
261    frozen_account: bool,
262    /// If stop orders are rejected on submission if trigger price is in the market.
263    reject_stop_orders: bool,
264    /// If orders with GTD time in force will be supported by the venue.
265    support_gtd_orders: bool,
266    /// If contingent orders will be supported/respected by the venue.
267    /// If False, then it's expected the strategy will be managing any contingent orders.
268    support_contingent_orders: bool,
269    /// If venue position IDs will be generated on order fills.
270    use_position_ids: bool,
271    /// If all venue generated identifiers will be random UUID4's.
272    use_random_ids: bool,
273    /// If the `reduce_only` execution instruction on orders will be honored.
274    use_reduce_only: bool,
275    /// If bars should be processed by the matching engine(s) (and move the market).
276    bar_execution: bool,
277    /// Determines whether the processing order of bar prices is adaptive based on a heuristic.
278    /// This setting is only relevant when `bar_execution` is True.
279    /// If False, bar prices are always processed in the fixed order: Open, High, Low, Close.
280    /// If True, the processing order adapts with the heuristic:
281    /// - If High is closer to Open than Low then the processing order is Open, High, Low, Close.
282    /// - If Low is closer to Open than High then the processing order is Open, Low, High, Close.
283    bar_adaptive_high_low_ordering: bool,
284    /// If trades should be processed by the matching engine(s) (and move the market).
285    trade_execution: bool,
286    /// The account base currency for the exchange. Use `None` for multi-currency accounts.
287    base_currency: Option<Currency>,
288    /// The account default leverage (for margin accounts).
289    default_leverage: Option<f64>,
290    /// The instrument specific leverage configuration (for margin accounts).
291    leverages: Option<HashMap<Currency, f64>>,
292    /// Defines an exchange-calculated price boundary to prevent a market order from being
293    /// filled at an extremely aggressive price.
294    price_protection_points: u32,
295}
296
297impl BacktestVenueConfig {
298    #[allow(clippy::too_many_arguments)]
299    #[must_use]
300    pub fn new(
301        name: Ustr,
302        oms_type: OmsType,
303        account_type: AccountType,
304        book_type: BookType,
305        routing: Option<bool>,
306        frozen_account: Option<bool>,
307        reject_stop_orders: Option<bool>,
308        support_gtd_orders: Option<bool>,
309        support_contingent_orders: Option<bool>,
310        use_position_ids: Option<bool>,
311        use_random_ids: Option<bool>,
312        use_reduce_only: Option<bool>,
313        bar_execution: Option<bool>,
314        bar_adaptive_high_low_ordering: Option<bool>,
315        trade_execution: Option<bool>,
316        starting_balances: Vec<String>,
317        base_currency: Option<Currency>,
318        default_leverage: Option<f64>,
319        leverages: Option<HashMap<Currency, f64>>,
320        price_protection_points: Option<u32>,
321    ) -> Self {
322        Self {
323            name,
324            oms_type,
325            account_type,
326            book_type,
327            routing: routing.unwrap_or(false),
328            frozen_account: frozen_account.unwrap_or(false),
329            reject_stop_orders: reject_stop_orders.unwrap_or(true),
330            support_gtd_orders: support_gtd_orders.unwrap_or(true),
331            support_contingent_orders: support_contingent_orders.unwrap_or(true),
332            use_position_ids: use_position_ids.unwrap_or(true),
333            use_random_ids: use_random_ids.unwrap_or(false),
334            use_reduce_only: use_reduce_only.unwrap_or(true),
335            bar_execution: bar_execution.unwrap_or(true),
336            bar_adaptive_high_low_ordering: bar_adaptive_high_low_ordering.unwrap_or(false),
337            trade_execution: trade_execution.unwrap_or(false),
338            starting_balances,
339            base_currency,
340            default_leverage,
341            leverages,
342            price_protection_points: price_protection_points.unwrap_or(0),
343        }
344    }
345}
346
347#[derive(Debug, Clone)]
348/// Represents the data configuration for one specific backtest run.
349pub struct BacktestDataConfig {
350    /// The path to the data catalog.
351    catalog_path: String,
352    /// The `fsspec` filesystem protocol for the catalog.
353    catalog_fs_protocol: Option<String>,
354    /// The instrument ID for the data configuration.
355    instrument_id: Option<InstrumentId>,
356    /// The start time for the data configuration.
357    start_time: Option<UnixNanos>,
358    /// The end time for the data configuration.
359    end_time: Option<UnixNanos>,
360    /// The additional filter expressions for the data catalog query.
361    filter_expr: Option<String>,
362    /// The client ID for the data configuration.
363    client_id: Option<ClientId>,
364    /// The metadata for the data catalog query.
365    metadata: Option<HashMap<String, String>>,
366    /// The bar specification for the data catalog query.
367    bar_spec: Option<BarSpecification>,
368}
369
370impl BacktestDataConfig {
371    #[allow(clippy::too_many_arguments)]
372    #[must_use]
373    pub const fn new(
374        catalog_path: String,
375        catalog_fs_protocol: Option<String>,
376        instrument_id: Option<InstrumentId>,
377        start_time: Option<UnixNanos>,
378        end_time: Option<UnixNanos>,
379        filter_expr: Option<String>,
380        client_id: Option<ClientId>,
381        metadata: Option<HashMap<String, String>>,
382        bar_spec: Option<BarSpecification>,
383    ) -> Self {
384        Self {
385            catalog_path,
386            catalog_fs_protocol,
387            instrument_id,
388            start_time,
389            end_time,
390            filter_expr,
391            client_id,
392            metadata,
393            bar_spec,
394        }
395    }
396}
397
398/// Represents the configuration for one specific backtest run.
399/// This includes a backtest engine with its actors and strategies, with the external inputs of venues and data.
400#[derive(Debug, Clone)]
401pub struct BacktestRunConfig {
402    /// The venue configurations for the backtest run.
403    venues: Vec<BacktestVenueConfig>,
404    /// The data configurations for the backtest run.
405    data: Vec<BacktestDataConfig>,
406    /// The backtest engine configuration (the core system kernel).
407    engine: BacktestEngineConfig,
408    /// The number of data points to process in each chunk during streaming mode.
409    /// If `None`, the backtest will run without streaming, loading all data at once.
410    chunk_size: Option<usize>,
411    /// If the backtest engine should be disposed on completion of the run.
412    /// If `True`, then will drop data and all state.
413    /// If `False`, then will *only* drop data.
414    dispose_on_completion: bool,
415    /// The start datetime (UTC) for the backtest run.
416    /// If `None` engine runs from the start of the data.
417    start: Option<UnixNanos>,
418    /// The end datetime (UTC) for the backtest run.
419    /// If `None` engine runs to the end of the data.
420    end: Option<UnixNanos>,
421}
422
423impl BacktestRunConfig {
424    #[must_use]
425    pub fn new(
426        venues: Vec<BacktestVenueConfig>,
427        data: Vec<BacktestDataConfig>,
428        engine: BacktestEngineConfig,
429        chunk_size: Option<usize>,
430        dispose_on_completion: Option<bool>,
431        start: Option<UnixNanos>,
432        end: Option<UnixNanos>,
433    ) -> Self {
434        Self {
435            venues,
436            data,
437            engine,
438            chunk_size,
439            dispose_on_completion: dispose_on_completion.unwrap_or(true),
440            start,
441            end,
442        }
443    }
444}