nautechsystems/nautilus_trader · critical
Python on_start failed: {e}
Error message
Python on_start failed: {e} What it means
This error wraps any Python exception raised by the strategy's user-implemented `on_start()` callback. The Rust `DataActor::on_start` first runs the native `Strategy::on_start` state transition, then dispatches to the Python instance via `py_self.call_method0(py, "on_start")` (crates/trading/src/python/strategy.rs:335-340). If that Python call returns a `PyErr`, it is re-wrapped with `anyhow::anyhow!("Python on_start failed: {e}")` and propagated, aborting the strategy's startup sequence.
Source
Thrown at crates/trading/src/python/strategy.rs:1061
fn on_position_event(&mut self, event: PositionEvent) {
let _ = self.dispatch_on_position_event(event);
}
fn on_position_changed(&mut self, event: PositionChanged) {
let _ = self.dispatch_on_position_changed(event);
}
fn on_position_closed(&mut self, event: PositionClosed) {
let _ = self.dispatch_on_position_closed(event);
}
}
impl DataActor for PyStrategyInner {
fn on_start(&mut self) -> anyhow::Result<()> {
Strategy::on_start(self)?;
self.dispatch_on_start()
.map_err(|e| anyhow::anyhow!("Python on_start failed: {e}"))
}
fn on_stop(&mut self) -> anyhow::Result<()> {
self.dispatch_on_stop()
.map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
}
fn on_resume(&mut self) -> anyhow::Result<()> {
self.dispatch_on_resume()
.map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
}
fn on_reset(&mut self) -> anyhow::Result<()> {
self.dispatch_on_reset()
.map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
}
fn on_dispose(&mut self) -> anyhow::Result<()> {View on GitHub (pinned to 18893faf8b)
Solutions
- Read the chained Python traceback in the error message; it names the exact line in your strategy's `on_start` that raised.
- Wrap risky startup logic (subscriptions, config access) in `try/except` inside `on_start` and log/handle, or fail fast with a clear message.
- Validate strategy config fields (instrument IDs, client IDs, bar/quote types) before starting the trader node.
- Verify all adapters/clients the strategy subscribes through are connected before `trader.start()`.
Example fix
// before (strategy.py)
def on_start(self):
self.subscribe_bars(self.config.bar_type) # raises if bar_type is None
// after
def on_start(self):
if self.config.bar_type is None:
self.log.error("bar_type not configured; not subscribing")
return
self.subscribe_bars(BarType.from_str(self.config.bar_type)) Defensive patterns
Strategy: try-catch
Validate before calling
# before starting the trader
assert strategy.config.bar_type is not None, "bar_type missing"
assert strategy.config.instrument_id is not None, "instrument_id missing"
for cid in [strategy.config.client_id]:
assert trader is not None, "trader not built"
# optionally dry-run the subscription targets through the cache
assert strategy.cache.instrument(strategy.config.instrument_id) is not None Type guard
def ensure_bar_type(config) -> BarType:
bt = getattr(config, "bar_type", None)
if not isinstance(bt, (str, BarType)) or (isinstance(bt, str) and not bt):
raise ValueError(f"config.bar_type invalid: {bt!r}")
return BarType.from_str(bt) if isinstance(bt, str) else bt Try / catch
def on_start(self):
try:
self.subscribe_bars(self.bar_type)
except Exception as e:
self.log.exception(f"on_start subscription failed: {e}")
# decide: return (degrade) or re-raise to stop startup deliberately Prevention
- Validate the strategy config (instrument IDs, bar types, client IDs) before constructing/starting the trader.
- Wrap external calls in `on_start` (subscribe, cache reads) in try/except with logging.
- Ensure adapters/clients are connected before calling trader.start().
- Exercise `on_start` in an integration test against a sandbox/backtest engine before live use.
When it happens
Trigger: Calling `strategy.start()` (or the trader/engine's start path) when the Python subclass's `on_start()` raises: e.g. subscribing to an invalid instrument ID, `self.subscribe_...` on a disconnected client, accessing config fields that are None, an unregistered clock timer name, or any unhandled exception in user code inside `on_start`.
Common situations: Config mistakes (missing/typo'd instrument IDs or client IDs in strategy config), calling subscription or order APIs before connections are ready, referencing cache entries that don't exist yet, or a Python-side bug such as a NameError/AttributeError introduced in a strategy edit.
Related errors
- Python on_start failed: {e}
- Python on_start failed: {e}
- Python on_stop failed: {e}
- Python on_resume failed: {e}
- Python on_reset failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/72d9817f30ae0ded.
Report an issue: GitHub.