use std::{collections::HashMap, time::Duration};
use nautilus_common::{
cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
msgbus::database::MessageBusConfig,
};
use nautilus_core::UUID4;
use nautilus_data::engine::config::DataEngineConfig;
use nautilus_execution::engine::config::ExecutionEngineConfig;
use nautilus_model::identifiers::TraderId;
use nautilus_portfolio::config::PortfolioConfig;
use nautilus_risk::engine::config::RiskEngineConfig;
use nautilus_system::config::{NautilusKernelConfig, StreamingConfig};
use serde::{Deserialize, Serialize};
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
pub struct LiveDataEngineConfig {
#[builder(default = 100_000)]
pub qsize: u32,
}
impl Default for LiveDataEngineConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl From<LiveDataEngineConfig> for DataEngineConfig {
fn from(_config: LiveDataEngineConfig) -> Self {
Self::default()
}
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
pub struct LiveRiskEngineConfig {
#[builder(default = 100_000)]
pub qsize: u32,
}
impl Default for LiveRiskEngineConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl From<LiveRiskEngineConfig> for RiskEngineConfig {
fn from(_config: LiveRiskEngineConfig) -> Self {
Self::default()
}
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, bon::Builder)]
pub struct LiveExecEngineConfig {
#[builder(default = true)]
pub reconciliation: bool,
#[builder(default = 10.0)]
pub reconciliation_startup_delay_secs: f64,
pub reconciliation_lookback_mins: Option<u32>,
pub reconciliation_instrument_ids: Option<Vec<String>>,
#[builder(default)]
pub filter_unclaimed_external_orders: bool,
#[builder(default)]
pub filter_position_reports: bool,
pub filtered_client_order_ids: Option<Vec<String>>,
#[builder(default = true)]
pub generate_missing_orders: bool,
#[builder(default = 2_000)]
pub inflight_check_interval_ms: u32,
#[builder(default = 5_000)]
pub inflight_check_threshold_ms: u32,
#[builder(default = 5)]
pub inflight_check_retries: u32,
pub open_check_interval_secs: Option<f64>,
pub open_check_lookback_mins: Option<u32>,
#[builder(default = 5_000)]
pub open_check_threshold_ms: u32,
#[builder(default = 5)]
pub open_check_missing_retries: u32,
#[builder(default = true)]
pub open_check_open_only: bool,
#[builder(default = 5)]
pub max_single_order_queries_per_cycle: u32,
#[builder(default = 100)]
pub single_order_query_delay_ms: u32,
pub position_check_interval_secs: Option<f64>,
#[builder(default = 60)]
pub position_check_lookback_mins: u32,
#[builder(default = 60_000)]
pub position_check_threshold_ms: u32,
#[builder(default = 3)]
pub position_check_retries: u32,
pub purge_closed_orders_interval_mins: Option<u32>,
pub purge_closed_orders_buffer_mins: Option<u32>,
pub purge_closed_positions_interval_mins: Option<u32>,
pub purge_closed_positions_buffer_mins: Option<u32>,
pub purge_account_events_interval_mins: Option<u32>,
pub purge_account_events_lookback_mins: Option<u32>,
#[builder(default)]
pub purge_from_database: bool,
pub own_books_audit_interval_secs: Option<f64>,
#[builder(default)]
pub graceful_shutdown_on_error: bool,
#[builder(default = 100_000)]
pub qsize: u32,
}
impl Default for LiveExecEngineConfig {
fn default() -> Self {
Self {
open_check_lookback_mins: Some(60),
..Self::builder().build()
}
}
}
impl From<LiveExecEngineConfig> for ExecutionEngineConfig {
fn from(config: LiveExecEngineConfig) -> Self {
Self {
purge_closed_orders_interval_mins: config.purge_closed_orders_interval_mins,
purge_closed_orders_buffer_mins: config.purge_closed_orders_buffer_mins,
purge_closed_positions_interval_mins: config.purge_closed_positions_interval_mins,
purge_closed_positions_buffer_mins: config.purge_closed_positions_buffer_mins,
purge_account_events_interval_mins: config.purge_account_events_interval_mins,
purge_account_events_lookback_mins: config.purge_account_events_lookback_mins,
purge_from_database: config.purge_from_database,
..Self::default()
}
}
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
pub struct RoutingConfig {
#[builder(default)]
pub default: bool,
pub venues: Option<Vec<String>>,
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
pub struct InstrumentProviderConfig {
#[builder(default)]
pub load_all: bool,
#[builder(default = true)]
pub load_ids: bool,
#[builder(default)]
pub filters: HashMap<String, String>,
}
impl Default for InstrumentProviderConfig {
fn default() -> Self {
Self::builder().build()
}
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
pub struct LiveDataClientConfig {
#[builder(default)]
pub handle_revised_bars: bool,
#[builder(default)]
pub instrument_provider: InstrumentProviderConfig,
#[builder(default)]
pub routing: RoutingConfig,
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
pub struct LiveExecClientConfig {
#[builder(default)]
pub instrument_provider: InstrumentProviderConfig,
#[builder(default)]
pub routing: RoutingConfig,
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
)]
#[derive(Debug, Clone, bon::Builder)]
pub struct LiveNodeConfig {
#[builder(default = Environment::Live)]
pub environment: Environment,
#[builder(default = TraderId::from("TRADER-001"))]
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 portfolio: Option<PortfolioConfig>,
pub streaming: Option<StreamingConfig>,
#[builder(default)]
pub data_engine: LiveDataEngineConfig,
#[builder(default)]
pub risk_engine: LiveRiskEngineConfig,
#[builder(default)]
pub exec_engine: LiveExecEngineConfig,
#[builder(default)]
pub data_clients: HashMap<String, LiveDataClientConfig>,
#[builder(default)]
pub exec_clients: HashMap<String, LiveExecClientConfig>,
}
impl Default for LiveNodeConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl NautilusKernelConfig for LiveNodeConfig {
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> {
Some(self.data_engine.clone().into())
}
fn risk_engine(&self) -> Option<RiskEngineConfig> {
Some(self.risk_engine.clone().into())
}
fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
Some(self.exec_engine.clone().into())
}
fn portfolio(&self) -> Option<PortfolioConfig> {
self.portfolio
}
fn streaming(&self) -> Option<StreamingConfig> {
self.streaming.clone()
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_trading_node_config_default() {
let config = LiveNodeConfig::default();
assert_eq!(config.environment, Environment::Live);
assert_eq!(config.trader_id, TraderId::from("TRADER-001"));
assert_eq!(config.data_engine.qsize, 100_000);
assert_eq!(config.risk_engine.qsize, 100_000);
assert_eq!(config.exec_engine.qsize, 100_000);
assert!(config.exec_engine.reconciliation);
assert!(!config.exec_engine.filter_unclaimed_external_orders);
assert!(config.data_clients.is_empty());
assert!(config.exec_clients.is_empty());
}
#[rstest]
fn test_trading_node_config_as_kernel_config() {
let config = LiveNodeConfig::default();
assert_eq!(config.environment(), Environment::Live);
assert_eq!(config.trader_id(), TraderId::from("TRADER-001"));
assert!(config.data_engine().is_some());
assert!(config.risk_engine().is_some());
assert!(config.exec_engine().is_some());
assert!(!config.load_state());
assert!(!config.save_state());
}
#[rstest]
fn test_live_exec_engine_config_defaults() {
let config = LiveExecEngineConfig::default();
assert!(config.reconciliation);
assert_eq!(config.reconciliation_startup_delay_secs, 10.0);
assert_eq!(config.reconciliation_lookback_mins, None);
assert_eq!(config.reconciliation_instrument_ids, None);
assert_eq!(config.filtered_client_order_ids, None);
assert!(!config.filter_unclaimed_external_orders);
assert!(!config.filter_position_reports);
assert!(config.generate_missing_orders);
assert_eq!(config.inflight_check_interval_ms, 2_000);
assert_eq!(config.inflight_check_threshold_ms, 5_000);
assert_eq!(config.inflight_check_retries, 5);
assert_eq!(config.open_check_threshold_ms, 5_000);
assert_eq!(config.open_check_lookback_mins, Some(60));
assert_eq!(config.open_check_missing_retries, 5);
assert!(config.open_check_open_only);
assert_eq!(config.position_check_retries, 3);
assert!(!config.purge_from_database);
assert!(!config.graceful_shutdown_on_error);
assert_eq!(config.qsize, 100_000);
assert_eq!(config.reconciliation_startup_delay_secs, 10.0);
}
#[rstest]
fn test_routing_config_default() {
let config = RoutingConfig::default();
assert!(!config.default);
assert_eq!(config.venues, None);
}
#[rstest]
fn test_live_data_client_config_default() {
let config = LiveDataClientConfig::default();
assert!(!config.handle_revised_bars);
assert!(!config.instrument_provider.load_all);
assert!(config.instrument_provider.load_ids);
assert!(!config.routing.default);
}
}