nautilus_backtest/
config.rs

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