Module 14: Configuration SystemHow NautilusTrader keeps trading systems reproducible, explicit, and easy to control
1. Why the Configuration System Matters
❌ Hard-coded values in strategy code: every parameter change requires a code edit, makes version history noisy, and increases the chance that your backtest settings drift away from your live settings.
✅ A dedicated configuration system: keeps parameters in one serializable, versionable contract. NautilusConfig is built on msgspec, and configuration objects are frozen (immutable) so they cannot be changed accidentally at runtime.
2. Configuration Hierarchy Architecture
NautilusTrader uses a tree-shaped configuration model in which a root config owns the configs for each subsystem:
flowchart TD
root["🔧 TradingNodeConfig / BacktestEngineConfig"]
kernel["NautilusKernelConfig"]
cache["CacheConfig"]
msgbus["MessageBusConfig"]
data["DataEngineConfig"]
exec["ExecEngineConfig"]
risk["RiskEngineConfig"]
log["LoggingConfig"]
port["PortfolioConfig"]
emu["OrderEmulatorConfig"]
stream["StreamingConfig"]
ctrl["ControllerConfig"]
root --> kernel
kernel --> cache
kernel --> msgbus
kernel --> data
kernel --> exec
kernel --> risk
kernel --> log
kernel --> port
kernel --> emu
kernel --> stream
kernel --> ctrl
style root fill:#0f1419,stroke:#4fc3f7,stroke-width:2px
style kernel fill:#0f1419,stroke:#ab47bc,stroke-width:2px
All config classes are declared with frozen=True. Once created, they cannot be changed. This is deliberate: it keeps system behavior deterministic and prevents accidental mutation while the node is running. If you want different settings, create a new config and restart the process.
3. NautilusKernelConfig — The Core Configuration
This is the fundamental core configuration from which all TradingNodeConfig and BacktestEngineConfig inherit.
| Parameter | Type | Default | Description |
|---|---|---|---|
environment | Environment | — | Execution environment: BACKTEST / SANDBOX / LIVE. |
trader_id | TraderId | “TRADER-001” | Trader ID; must follow the NAME-TAG format. |
instance_id | UUID4 | None (Auto) | Instance UUID, used to distinguish multiple parallel instances. |
load_state | bool | False | Whether to load strategy state from the database at startup. |
save_state | bool | False | Whether to persist strategy state to the database at shutdown. |
loop_debug | bool | False | Whether to enable debug mode for the asyncio event loop. |
Timeout Parameters (Critical for Live Trading)
| Parameter | Default Value | Description |
|---|---|---|
timeout_connection | 120s | Timeout waiting for all clients to connect and initialize. |
timeout_reconciliation | 30s | Timeout waiting for execution state reconciliation (crucial during live startup). |
timeout_portfolio | 10s | Timeout waiting for the Portfolio to initialize margins and unrealized PnL. |
timeout_disconnection | 10s | Timeout waiting for all clients to cleanly disconnect. |
timeout_post_stop | 10s | Timeout waiting for residual events after stopping. |
timeout_shutdown | 5s | Timeout waiting for pending tasks to be canceled during final shutdown. |
4. Subsystem Configuration Details
4.1 CacheConfig
from nautilus_trader.cache.config import CacheConfig
cache_config = CacheConfig(
database=DatabaseConfig(
type="redis",
host="localhost",
port=6379,
),
timestamps_as_iso8601=True, # Timestamp format
buffer_interval_ms=100, # Batch write interval
)❌ Without external caching: After a process crash, all state is lost. After restarting, the strategy is unaware of its current positions and open orders.
✅ Using Redis: Enables state persistence and recovery after a crash — setting load_state=True automatically loads the previous state from Redis.
4.2 MessageBusConfig
from nautilus_trader.common.config import MessageBusConfig
msgbus_config = MessageBusConfig(
database=DatabaseConfig(
type="redis",
host="localhost",
port=6379,
),
stream_per_topic=True, # One Redis Stream per topic
autotrim_size=10000, # Maximum messages retained per Stream
types_filter=[ # Only persist specific event types
"OrderFilled",
"PositionChanged",
],
)4.3 LoggingConfig
from nautilus_trader.common.config import LoggingConfig
logging_config = LoggingConfig(
log_level="INFO", # Console log level
log_level_file="DEBUG", # File log level (more detailed)
log_directory="./logs", # Log file directory
log_file_name="nautilus", # Log file name
log_file_format=None, # Log format (None = default)
log_colors=True, # Console colorized output
log_component_levels={
# Set distinct log levels for specific components
"RiskEngine": "WARNING", # RiskEngine only logs WARNING and above
"DataEngine": "INFO", # DataEngine logs all INFO
},
bypass_logging=False, # Bypass logging for performance-sensitive runs
)NautilusTrader’s logging system is implemented in Rust at its core, which is critical for operating in high-frequency scenarios. Python’s native logging module significantly bottlenecks the system when processing tens of thousands of logs per second, whereas the Rust logger introduces almost no performance overhead.
4.4 LiveDataEngineConfig / LiveExecEngineConfig
from nautilus_trader.live.config import (
LiveDataEngineConfig,
LiveExecEngineConfig,
LiveRiskEngineConfig,
)
data_engine = LiveDataEngineConfig(
qos=True, # Quality of Service (checks for duplicates and ordering)
)
exec_engine = LiveExecEngineConfig(
reconciliation=True, # Enable order reconciliation (highly recommended)
inflight_check_interval_ms=2000, # Interval to check "in-flight" orders
inflight_check_threshold_ms=5000, # Alert if order remains unacknowledged beyond this threshold
)
risk_engine = LiveRiskEngineConfig(
bypass=False, # Whether to bypass risk checks (keep False in production)
max_order_submit_rate="100/00:00:01", # Max 100 order submissions per second
max_order_modify_rate="100/00:00:01", # Max 100 order modifications per second
)5. Comprehensive Live Configuration Example
"""Comprehensive TradingNode Configuration — Production Grade"""
from nautilus_trader.live.node import TradingNode
from nautilus_trader.live.config import (
TradingNodeConfig,
LiveDataEngineConfig,
LiveExecEngineConfig,
LiveRiskEngineConfig,
)
from nautilus_trader.cache.config import CacheConfig
from nautilus_trader.common.config import (
DatabaseConfig,
LoggingConfig,
MessageBusConfig,
)
from nautilus_trader.adapters.binance.config import (
BinanceLiveDataClientConfig,
BinanceLiveExecClientConfig,
)
config = TradingNodeConfig(
trader_id="LIVE-001",
# === Cache ===
cache=CacheConfig(
database=DatabaseConfig(type="redis", host="localhost", port=6379),
),
# === Message Bus ===
message_bus=MessageBusConfig(
database=DatabaseConfig(type="redis", host="localhost", port=6379),
types_filter=["OrderFilled", "PositionChanged"],
),
# === Logging ===
logging=LoggingConfig(
log_level="INFO",
log_level_file="DEBUG",
log_directory="./logs",
),
# === Engine Configuration ===
data_engine=LiveDataEngineConfig(qos=True),
exec_engine=LiveExecEngineConfig(
reconciliation=True,
inflight_check_interval_ms=2000,
),
risk_engine=LiveRiskEngineConfig(
max_order_submit_rate="50/00:00:01",
),
# === Adapters ===
data_clients={
"BINANCE": BinanceLiveDataClientConfig(
api_key="your-key",
api_secret="your-secret",
account_type="USDT_FUTURE",
),
},
exec_clients={
"BINANCE": BinanceLiveExecClientConfig(
api_key="your-key",
api_secret="your-secret",
account_type="USDT_FUTURE",
),
},
# === Timeouts ===
timeout_connection=60.0,
timeout_reconciliation=30.0,
# === State Persistence ===
load_state=True,
save_state=True,
)
node = TradingNode(config=config)
node.trader.add_strategy(my_strategy)
node.run()6. Backtest vs Live Configuration Comparison
| Component | BacktestEngineConfig | TradingNodeConfig |
|---|---|---|
| Environment | BACKTEST | LIVE / SANDBOX |
| Data Source | BacktestDataConfig (Catalog) | LiveDataClientConfig (Adapter) |
| Venue | BacktestVenueConfig (Simulated) | LiveExecClientConfig (Real) |
| Engines | DataEngineConfig | LiveDataEngineConfig (Includes QoS, etc.) |
| Redis | Usually not required | Highly recommended (State Persistence) |
| Reconciliation | Not required | reconciliation=True |
| Strategy Code | ✅ Same strategy class | |
7. StreamingConfig — Streaming Data Export
from nautilus_trader.common.config import StreamingConfig
streaming = StreamingConfig(
catalog_path="./catalog", # Automatically save to DataCatalog
fs_protocol="file", # File system protocol
include_types=[ # Only persist these data types
"OrderFilled",
"PositionChanged",
"Bar",
],
)
# Usage in TradingNodeConfig
config = TradingNodeConfig(
streaming=streaming,
# ... other configurations
)This ensures that designated events and data are automatically written to Parquet during live execution, enabling seamless offline post-trade analysis via the DataCatalog later.
8. Interactive Exercises
Your strategy connects to the Binance exchange, but frequently times out during startup. Which timeout parameter should you adjust?
Adjust timeout_connection (Default: 120s)
If 120s is insufficient, possible reasons include:
- High network latency → Increase to 180s or 300s
- Too many subscribed instruments → The adapter requires more time to initialize
- Binance API rate limits → Reduce initial subscription volume
Also check:
timeout_reconciliation(Reconciliation timeout)timeout_portfolio(Portfolio initialization timeout)
If timeouts occur only occasionally, prioritize increasing timeout_connection.
If they happen frequently, investigate network latency and subscription logic.
For the following scenarios, do you need to configure Redis? Why?
- Running historical backtests only.
- Running a strategy live, with manual restarts during the day.
- Running multiple strategies in a 24/7 automated environment.
- Backtests only → ❌ Not required
Backtesting is a single-pass execution; state persistence is unnecessary. - Manual daytime restarts → ⚠️ Recommended
After restart, the system must recognize open orders and existing positions.
Setload_state=Trueandsave_state=True. - 24/7 Automated execution → ✅ Mandatory
- Requires
CacheConfigto persist state via Redis. - Requires
MessageBusConfigto persist event streams via Redis. - Enables automatic state recovery after a crash.
- Supports multi-instance analysis of historical events.
- Requires
Identify the 3 critical risks in the following production configuration:
config = TradingNodeConfig(
trader_id="LIVE-001",
risk_engine=LiveRiskEngineConfig(
bypass=True, # (1)
),
exec_engine=LiveExecEngineConfig(
reconciliation=False, # (2)
),
logging=LoggingConfig(
bypass_logging=True, # (3)
),
load_state=False,
save_state=False,
)Three critical risks:
bypass=True→ 🔴 Bypasses risk checks!
Risk checks should remain enabled in production; a logic bug can otherwise result in an uncontrolled order loop.
✅ Fix:bypass=Falsereconciliation=False→ 🔴 No reconciliation!
After a restart, local order states will be out of sync with the exchange truth.
This could result in duplicate orders or untracked positions.
✅ Fix:reconciliation=Truebypass_logging=True→ 🟠 Zero logging!
Post-mortem diagnostics are impossible when issues arise.
✅ Fix:bypass_logging=False, combined with an appropriatelog_level.
9. Module Summary
- Tree Structure:
TradingNodeConfigencapsulates all subsystem configurations (Cache, MessageBus, Engines, Logging). - Frozen Immutability: All Configs are frozen, preventing parameter mutation during runtime.
- Redis Persistence: Cache and MessageBus can be configured with Redis to enable state recovery and event stream persistence.
- Rust Logging: The underlying logger is implemented in Rust, supporting component-level log granularity without performance bottlenecks.
- Timeout Parameters: Six timeout parameters govern the system’s startup and shutdown lifecycle.
- StreamingConfig: Live execution data is automatically written to Parquet for offline post-trade analysis.