Skip to content

Warm Start Guide

AKQuant provides a powerful Warm Start workflow that lets you save current backtest state (snapshot) and resume later. This is useful for long-horizon segmented backtests, rolling workflows, and production-like continuation runs.

1. What Is Warm Start?

In a traditional event-driven backtest, each run is a cold start, and all state (positions, cash, indicator history) is rebuilt from scratch.

With warm start, you can:

  1. Save (Snapshot): Serialize in-memory engine and strategy state (positions, open orders, strategy attributes, indicator state) to disk.
  2. Resume: Load the snapshot and continue with new data as if execution was not interrupted.

2. Basic Usage

2.1 Save Snapshot (Phase 1)

At the end of phase 1 (or at any strategy checkpoint), save state with save_snapshot.

from akquant.checkpoint import save_snapshot

# Run phase 1
result1 = run_backtest(data=data_phase1, strategy=MyStrategy, ...)

# Save snapshot file
checkpoint_file = "checkpoint_phase1.pkl"
save_snapshot(result1.engine, result1.strategy, checkpoint_file)
print(f"Snapshot saved to {checkpoint_file}")

2.2 Resume and Continue (Phase 2)

Use run_warm_start with the snapshot path and phase-2 data.

Important: snapshots only store dynamic runtime state (Portfolio, Orders, strategy attributes). Static configuration (Instrument, MarketModel) is not persisted and must be reconfigured on resume.

import akquant as aq

# Prepare phase-2 data
data_phase2 = ...

config = aq.BacktestConfig(
    strategy_config=aq.StrategyConfig(
        strategy_id="alpha",
        strategies_by_slot={"beta": BetaStrategy},
        strategy_max_order_size={"alpha": 10, "beta": 20},
    )
)

# Resume from snapshot and continue
result2 = aq.run_warm_start(
    checkpoint_path="checkpoint_phase1.pkl",
    data=data_phase2,
    symbols="AAPL",
    commission_rate=0.0003,
    stamp_tax_rate=0.001,
    transfer_fee_rate=0.00001,
    config=config,
    t_plus_one=True,
)

3. Strategy Adaptation

To support warm start correctly, strategy code should separate initialization from restoration behavior.

3.1 Lifecycle Hooks

AKQuant provides two startup hooks:

  • on_start(): Called for both cold start and warm start.
  • on_resume(): Called only in warm start, before on_start().

3.2 Avoid Overwriting Restored State

A common mistake is unconditional indicator reinitialization in on_start, which overwrites restored indicator state.

Incorrect:

def on_start(self):
    self.sma = SMA(30)
    self.subscribe(self.symbol)

Correct:

def on_start(self):
    if not self.is_restored:
        self.sma = SMA(30)
    else:
        self.log("Resumed from snapshot. Indicators retained.")

    self.register_indicator("sma", self.sma)
    self.subscribe(self.symbol)

3.3 Indicator Serialization

Built-in indicators (SMA, EMA, etc.) support pickle serialization. For custom indicators or third-party objects (for example TA-Lib wrappers), ensure they are pickle-compatible or implement state handling explicitly.

4. Notes

  1. Instrument re-registration: run_warm_start auto-registers default instrument info for symbols in new data. If your strategy depends on custom lot_size or multiplier, verify and override in on_start.
  2. MarketModel reset: Fee settings and trading rules (for example T+1) are not persisted in snapshots. Re-pass them via explicit args or config.strategy_config on resume.
  3. Initial cash display: result2.metrics.initial_cash is adjusted to resumed-phase starting cash, so phase-2 return metrics remain interpretable.
  4. Data continuity: Keep phase-1 end and phase-2 start continuous to avoid indicator jumps.
  5. Runtime config injection: Use strategy_runtime_config in run_warm_start to override runtime behavior at resume.
  6. Strategy-level risk state continuity: Strategy limits, strategy cashflow, daily-loss baseline, drawdown peak, and reduce-only activation state are persisted and restored.
  7. Default timezone: If timezone is not explicitly provided to run_warm_start, default is Asia/Shanghai.

5. Full Example

See 21_warm_start_demo.py for a complete runnable example.

class MyStrategy(Strategy):
    def on_start(self):
        if not self.is_restored:
            self.sma = SMA(10)
        self.register_indicator("sma", self.sma)

# ... run phase 1 ...
save_snapshot(engine, strategy, "checkpoint.pkl")

# ... run phase 2 ...
run_warm_start("checkpoint.pkl", data_new, ...)

6. Further Reading