use std::{fmt::Display, str::FromStr, time::Duration};
use ahash::AHashMap;
use nautilus_common::{
cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
msgbus::database::MessageBusConfig,
};
use nautilus_core::{UUID4, UnixNanos};
use nautilus_data::engine::config::DataEngineConfig;
use nautilus_execution::engine::config::ExecutionEngineConfig;
use nautilus_model::{
data::{BarSpecification, BarType},
enums::{AccountType, BookType, OmsType, OtoTriggerMode},
identifiers::{ClientId, InstrumentId, TraderId},
types::Currency,
};
use nautilus_portfolio::config::PortfolioConfig;
use nautilus_risk::engine::config::RiskEngineConfig;
use nautilus_system::config::{NautilusKernelConfig, StreamingConfig};
use ustr::Ustr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NautilusDataType {
QuoteTick,
TradeTick,
Bar,
OrderBookDelta,
OrderBookDepth10,
MarkPriceUpdate,
IndexPriceUpdate,
InstrumentClose,
}
impl Display for NautilusDataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl FromStr for NautilusDataType {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
match s {
stringify!(QuoteTick) => Ok(Self::QuoteTick),
stringify!(TradeTick) => Ok(Self::TradeTick),
stringify!(Bar) => Ok(Self::Bar),
stringify!(OrderBookDelta) => Ok(Self::OrderBookDelta),
stringify!(OrderBookDepth10) => Ok(Self::OrderBookDepth10),
stringify!(MarkPriceUpdate) => Ok(Self::MarkPriceUpdate),
stringify!(IndexPriceUpdate) => Ok(Self::IndexPriceUpdate),
stringify!(InstrumentClose) => Ok(Self::InstrumentClose),
_ => anyhow::bail!("Invalid `NautilusDataType`: '{s}'"),
}
}
}
#[derive(Debug, Clone, bon::Builder)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
)]
pub struct BacktestEngineConfig {
#[builder(default = Environment::Backtest)]
pub environment: Environment,
#[builder(default)]
pub trader_id: TraderId,
#[builder(default)]
pub load_state: bool,
#[builder(default)]
pub save_state: bool,
#[builder(default)]
pub logging: LoggerConfig,
pub instance_id: Option<UUID4>,
#[builder(default = Duration::from_secs(60))]
pub timeout_connection: Duration,
#[builder(default = Duration::from_secs(30))]
pub timeout_reconciliation: Duration,
#[builder(default = Duration::from_secs(10))]
pub timeout_portfolio: Duration,
#[builder(default = Duration::from_secs(10))]
pub timeout_disconnection: Duration,
#[builder(default = Duration::from_secs(10))]
pub delay_post_stop: Duration,
#[builder(default = Duration::from_secs(5))]
pub timeout_shutdown: Duration,
pub cache: Option<CacheConfig>,
pub msgbus: Option<MessageBusConfig>,
pub data_engine: Option<DataEngineConfig>,
pub risk_engine: Option<RiskEngineConfig>,
pub exec_engine: Option<ExecutionEngineConfig>,
pub portfolio: Option<PortfolioConfig>,
pub streaming: Option<StreamingConfig>,
#[builder(default)]
pub bypass_logging: bool,
#[builder(default = true)]
pub run_analysis: bool,
}
impl NautilusKernelConfig for BacktestEngineConfig {
fn environment(&self) -> Environment {
self.environment
}
fn trader_id(&self) -> TraderId {
self.trader_id
}
fn load_state(&self) -> bool {
self.load_state
}
fn save_state(&self) -> bool {
self.save_state
}
fn logging(&self) -> LoggerConfig {
self.logging.clone()
}
fn instance_id(&self) -> Option<UUID4> {
self.instance_id
}
fn timeout_connection(&self) -> Duration {
self.timeout_connection
}
fn timeout_reconciliation(&self) -> Duration {
self.timeout_reconciliation
}
fn timeout_portfolio(&self) -> Duration {
self.timeout_portfolio
}
fn timeout_disconnection(&self) -> Duration {
self.timeout_disconnection
}
fn delay_post_stop(&self) -> Duration {
self.delay_post_stop
}
fn timeout_shutdown(&self) -> Duration {
self.timeout_shutdown
}
fn cache(&self) -> Option<CacheConfig> {
self.cache.clone()
}
fn msgbus(&self) -> Option<MessageBusConfig> {
self.msgbus.clone()
}
fn data_engine(&self) -> Option<DataEngineConfig> {
self.data_engine.clone()
}
fn risk_engine(&self) -> Option<RiskEngineConfig> {
self.risk_engine.clone()
}
fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
self.exec_engine.clone()
}
fn portfolio(&self) -> Option<PortfolioConfig> {
self.portfolio
}
fn streaming(&self) -> Option<StreamingConfig> {
self.streaming.clone()
}
}
impl Default for BacktestEngineConfig {
fn default() -> Self {
Self::builder().build()
}
}
#[derive(Debug, Clone, bon::Builder)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
)]
pub struct BacktestVenueConfig {
name: Ustr,
oms_type: OmsType,
account_type: AccountType,
book_type: BookType,
#[builder(default)]
starting_balances: Vec<String>,
#[builder(default)]
routing: bool,
#[builder(default)]
frozen_account: bool,
#[builder(default = true)]
reject_stop_orders: bool,
#[builder(default = true)]
support_gtd_orders: bool,
#[builder(default = true)]
support_contingent_orders: bool,
#[builder(default = true)]
use_position_ids: bool,
#[builder(default)]
use_random_ids: bool,
#[builder(default = true)]
use_reduce_only: bool,
#[builder(default = true)]
bar_execution: bool,
#[builder(default)]
bar_adaptive_high_low_ordering: bool,
#[builder(default = true)]
trade_execution: bool,
#[builder(default)]
use_market_order_acks: bool,
#[builder(default)]
liquidity_consumption: bool,
#[builder(default)]
allow_cash_borrowing: bool,
#[builder(default)]
queue_position: bool,
#[builder(default)]
oto_trigger_mode: OtoTriggerMode,
base_currency: Option<Currency>,
default_leverage: Option<f64>,
leverages: Option<AHashMap<InstrumentId, f64>>,
#[builder(default)]
price_protection_points: u32,
}
impl BacktestVenueConfig {
#[must_use]
pub fn name(&self) -> Ustr {
self.name
}
#[must_use]
pub fn oms_type(&self) -> OmsType {
self.oms_type
}
#[must_use]
pub fn account_type(&self) -> AccountType {
self.account_type
}
#[must_use]
pub fn book_type(&self) -> BookType {
self.book_type
}
#[must_use]
pub fn starting_balances(&self) -> &[String] {
&self.starting_balances
}
#[must_use]
pub fn routing(&self) -> bool {
self.routing
}
#[must_use]
pub fn frozen_account(&self) -> bool {
self.frozen_account
}
#[must_use]
pub fn reject_stop_orders(&self) -> bool {
self.reject_stop_orders
}
#[must_use]
pub fn support_gtd_orders(&self) -> bool {
self.support_gtd_orders
}
#[must_use]
pub fn support_contingent_orders(&self) -> bool {
self.support_contingent_orders
}
#[must_use]
pub fn use_position_ids(&self) -> bool {
self.use_position_ids
}
#[must_use]
pub fn use_random_ids(&self) -> bool {
self.use_random_ids
}
#[must_use]
pub fn use_reduce_only(&self) -> bool {
self.use_reduce_only
}
#[must_use]
pub fn bar_execution(&self) -> bool {
self.bar_execution
}
#[must_use]
pub fn bar_adaptive_high_low_ordering(&self) -> bool {
self.bar_adaptive_high_low_ordering
}
#[must_use]
pub fn trade_execution(&self) -> bool {
self.trade_execution
}
#[must_use]
pub fn use_market_order_acks(&self) -> bool {
self.use_market_order_acks
}
#[must_use]
pub fn liquidity_consumption(&self) -> bool {
self.liquidity_consumption
}
#[must_use]
pub fn allow_cash_borrowing(&self) -> bool {
self.allow_cash_borrowing
}
#[must_use]
pub fn queue_position(&self) -> bool {
self.queue_position
}
#[must_use]
pub fn oto_trigger_mode(&self) -> OtoTriggerMode {
self.oto_trigger_mode
}
#[must_use]
pub fn base_currency(&self) -> Option<Currency> {
self.base_currency
}
#[must_use]
pub fn default_leverage(&self) -> Option<f64> {
self.default_leverage
}
#[must_use]
pub fn leverages(&self) -> Option<&AHashMap<InstrumentId, f64>> {
self.leverages.as_ref()
}
#[must_use]
pub fn price_protection_points(&self) -> u32 {
self.price_protection_points
}
}
#[derive(Debug, Clone, bon::Builder)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
)]
pub struct BacktestDataConfig {
data_type: NautilusDataType,
catalog_path: String,
catalog_fs_protocol: Option<String>,
catalog_fs_storage_options: Option<AHashMap<String, String>>,
instrument_id: Option<InstrumentId>,
instrument_ids: Option<Vec<InstrumentId>>,
start_time: Option<UnixNanos>,
end_time: Option<UnixNanos>,
filter_expr: Option<String>,
client_id: Option<ClientId>,
#[allow(dead_code)]
metadata: Option<AHashMap<String, String>>,
bar_spec: Option<BarSpecification>,
bar_types: Option<Vec<String>>,
#[builder(default)]
optimize_file_loading: bool,
}
impl BacktestDataConfig {
#[must_use]
pub const fn data_type(&self) -> NautilusDataType {
self.data_type
}
#[must_use]
pub fn catalog_path(&self) -> &str {
&self.catalog_path
}
#[must_use]
pub fn catalog_fs_protocol(&self) -> Option<&str> {
self.catalog_fs_protocol.as_deref()
}
#[must_use]
pub fn catalog_fs_storage_options(&self) -> Option<&AHashMap<String, String>> {
self.catalog_fs_storage_options.as_ref()
}
#[must_use]
pub fn instrument_id(&self) -> Option<InstrumentId> {
self.instrument_id
}
#[must_use]
pub fn instrument_ids(&self) -> Option<&[InstrumentId]> {
self.instrument_ids.as_deref()
}
#[must_use]
pub fn start_time(&self) -> Option<UnixNanos> {
self.start_time
}
#[must_use]
pub fn end_time(&self) -> Option<UnixNanos> {
self.end_time
}
#[must_use]
pub fn filter_expr(&self) -> Option<&str> {
self.filter_expr.as_deref()
}
#[must_use]
pub fn client_id(&self) -> Option<ClientId> {
self.client_id
}
#[must_use]
pub fn bar_spec(&self) -> Option<BarSpecification> {
self.bar_spec
}
#[must_use]
pub fn bar_types(&self) -> Option<&[String]> {
self.bar_types.as_deref()
}
#[must_use]
pub fn optimize_file_loading(&self) -> bool {
self.optimize_file_loading
}
#[must_use]
pub fn query_identifiers(&self) -> Option<Vec<String>> {
if self.data_type == NautilusDataType::Bar {
if let Some(bar_types) = &self.bar_types
&& !bar_types.is_empty()
{
return Some(bar_types.clone());
}
if let Some(bar_spec) = &self.bar_spec {
if let Some(id) = self.instrument_id {
return Some(vec![format!("{id}-{bar_spec}-EXTERNAL")]);
}
if let Some(ids) = &self.instrument_ids {
let bar_types: Vec<String> = ids
.iter()
.map(|id| format!("{id}-{bar_spec}-EXTERNAL"))
.collect();
if !bar_types.is_empty() {
return Some(bar_types);
}
}
}
}
if let Some(id) = self.instrument_id {
return Some(vec![id.to_string()]);
}
if let Some(ids) = &self.instrument_ids {
let strs: Vec<String> = ids.iter().map(ToString::to_string).collect();
if !strs.is_empty() {
return Some(strs);
}
}
None
}
pub fn get_instrument_ids(&self) -> anyhow::Result<Vec<InstrumentId>> {
if let Some(id) = self.instrument_id {
return Ok(vec![id]);
}
if let Some(ids) = &self.instrument_ids {
return Ok(ids.clone());
}
if let Some(bar_types) = &self.bar_types {
let ids = bar_types
.iter()
.map(|bt| {
bt.parse::<BarType>()
.map(|b| b.instrument_id())
.map_err(|_| anyhow::anyhow!("Invalid bar type string: '{bt}'"))
})
.collect::<anyhow::Result<Vec<_>>>()?;
return Ok(ids);
}
Ok(Vec::new())
}
}
#[derive(Debug, Clone, bon::Builder)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
)]
pub struct BacktestRunConfig {
#[builder(default = UUID4::new().to_string())]
id: String,
venues: Vec<BacktestVenueConfig>,
data: Vec<BacktestDataConfig>,
#[builder(default)]
engine: BacktestEngineConfig,
chunk_size: Option<usize>,
#[builder(default = true)]
dispose_on_completion: bool,
start: Option<UnixNanos>,
end: Option<UnixNanos>,
}
impl BacktestRunConfig {
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn venues(&self) -> &[BacktestVenueConfig] {
&self.venues
}
#[must_use]
pub fn data(&self) -> &[BacktestDataConfig] {
&self.data
}
#[must_use]
pub fn engine(&self) -> &BacktestEngineConfig {
&self.engine
}
#[must_use]
pub fn chunk_size(&self) -> Option<usize> {
self.chunk_size
}
#[must_use]
pub fn dispose_on_completion(&self) -> bool {
self.dispose_on_completion
}
#[must_use]
pub fn start(&self) -> Option<UnixNanos> {
self.start
}
#[must_use]
pub fn end(&self) -> Option<UnixNanos> {
self.end
}
}