nautechsystems/nautilus_trader · error · ValueError

BacktestNode state is unavailable when…

Error message

BacktestNode state is unavailable when dispose_on_completion=True; set dispose_on_completion=False before running the backtest

What it means

The tearsheet reads chart data from the BacktestNode's internal engine state after a run. If the run config had dispose_on_completion=True, the engine is torn down and its state discarded once the backtest finishes, so the library refuses to proceed rather than fail later with missing data.

Solutions

  1. Set dispose_on_completion=False on the BacktestRunConfig before running the backtest, then re-run.
  2. If the run already completed with disposal, re-execute the backtest with disposal disabled before generating the tearsheet.
  3. Generate tearsheets without node-dependent charts (remove 'bars_with_fills' from chart_names) and pass node=None.

Example fix

// before
config = BacktestRunConfig(...)
node = BacktestNode(configs=[config])
node.run()
// after
config = BacktestRunConfig(..., dispose_on_completion=False)
node = BacktestNode(configs=[config])
node.run()
Defensive patterns

Strategy: validation

Validate before calling

if node is not None:
    for rc in node.configs:
        if rc.dispose_on_completion:
            raise ValueError(f'Run config {rc.id} has dispose_on_completion=True; tearsheet needs engine state')

Try / catch

try:
    path = create_tearsheet(result, config=config, node=node, run_config_id=rid)
except ValueError as e:
    if 'dispose_on_completion' in str(e):
        config.dispose_on_completion = False
        node.run()  # re-run with state retained
        path = create_tearsheet(result, config=config, node=node, run_config_id=rid)
    else:
        raise

Prevention

When it happens

Trigger: Running a backtest via BacktestNode whose BacktestRunConfig has dispose_on_completion=True (the teardown/default in some wrappers), then calling create_tearsheet with that node and the matching run_config_id for charts that need engine data (e.g. bars_with_fills).

Common situations: Reusing a node from a distributed/parallel run where dispose_on_completion defaults to True; configuring the node for memory efficiency and later adding visualization; following older examples written before tearsheets consumed node state.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/30cf4a2a6230a3e4. Report an issue: GitHub.

Appendix: source

Thrown at python/nautilus_trader/analysis/tearsheet.py:555

        stats_pnls=_filter_stats_pnls(result.stats_pnls, currency),
        stats_returns=result.stats_returns,
        stats_general=result.stats_general,
        returns=returns,
        output_path=output_path,
        title=title,
        config=config,
        benchmark_returns=benchmark_returns,
        benchmark_name=benchmark_name,
        engine=engine_view,
    )


def _validate_result_node_state(node: BacktestNode, run_config_id: str) -> None:
    for run_config in node.configs:
        if run_config.id != run_config_id:
            continue
        if run_config.dispose_on_completion:
            raise ValueError(
                "BacktestNode state is unavailable when dispose_on_completion=True; "
                "set dispose_on_completion=False before running the backtest",
            )
        return


def _filter_stats_pnls(
    stats_pnls: Mapping[str, Any],
    currency: Currency | None,
) -> dict[str, Any]:
    stats_pnls = dict(stats_pnls)
    if currency is None:
        return stats_pnls

    currency_code = getattr(currency, "code", str(currency))
    return {currency_code: stats_pnls[currency_code]} if currency_code in stats_pnls else {}

View on GitHub (pinned to 18893faf8b)