Skip to main content

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