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:
- Save (Snapshot): Serialize in-memory engine and strategy state (positions, open orders, strategy attributes, indicator state) to disk.
- 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_checkpoint.
from akquant.checkpoint import save_checkpoint
# Run phase 1
result1 = run_backtest(data=data_phase1, strategy=MyStrategy, ...)
# Save snapshot file
checkpoint_file = "checkpoint_phase1.pkl"
save_checkpoint(result1.engine, result1.strategy, checkpoint_file)
print(f"Snapshot saved to {checkpoint_file}")
2.2 Resume and Continue (Phase 2)¶
Use run_from_checkpoint 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_from_checkpoint(
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, beforeon_start().
3.2 Avoid Overwriting Restored State¶
A common mistake is unconditional indicator reinitialization in on_start, which overwrites restored indicator state.
Incorrect:
Correct:
def on_start(self):
if not self.is_restored:
self.sma = SMA(30)
else:
self.log("Resumed from snapshot. Indicators retained.")
self.register_precomputed_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. For the full custom-indicator workflow, see the Custom Indicator Guide.
4. Notes¶
- Instrument re-registration:
run_from_checkpointauto-registers default instrument info for symbols in new data. If your strategy depends on customlot_sizeormultiplier, verify and override inon_start. - 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_configon resume, includingcommission_policy/commission_rate, taxes, and transfer-fee settings. - Initial cash display:
result2.metrics.initial_cashis adjusted to resumed-phase starting cash, so phase-2 return metrics remain interpretable. - Data continuity: Keep phase-1 end and phase-2 start continuous to avoid indicator jumps.
get_history()continuity: New snapshots also persist the history buffer, soget_history()andget_history_map()resume with the phase-1 rolling window intact. In the normal warm-start path you no longer need to manually prepend extra lookback bars.- Runtime config injection: Use
strategy_runtime_configinrun_from_checkpointto override runtime behavior at resume. - Strategy-level risk state continuity: Strategy limits, strategy cashflow, daily-loss baseline, drawdown peak, and reduce-only activation state are persisted and restored.
- Default timezone: If
timezoneis not explicitly provided torun_from_checkpoint, default isAsia/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_precomputed_indicator("sma", self.sma)
# ... run phase 1 ...
save_checkpoint(engine, strategy, "checkpoint.pkl")
# ... run phase 2 ...
run_from_checkpoint("checkpoint.pkl", data_new, ...)
5.x Merging Multi-Phase Results (merge_results)¶
Each segment's BacktestResult only covers its own window. Use merge_results
to stitch multiple segments into one continuous result instead of hand-rolling a
concatenation loop:
import akquant as aq
r1 = aq.run_backtest(data=phase1, strategy=MyStrategy, symbols="X", initial_cash=1e6)
aq.save_checkpoint(r1.engine, r1.strategy, "ckpt.pkl")
r2 = aq.run_from_checkpoint("ckpt.pkl", data=phase2, symbols="X")
merged = aq.merge_results(r1, r2)
print(merged.equity_curve) # full curve across both segments
print(merged.metrics.total_return_pct)
returns = merged.to_quantstats() # feed quantstats for reporting
Notes:
- Segments must be time-ordered and non-overlapping (gaps allowed);
overlapping segments raise
ValueError. dedupe_boundary=True(default) drops duplicated boundary timestamps between adjacent segments.drop_expired_instruments=True(default) removes expired-instrument position rows using each segment's instrument-snapshotexpiry_date, preventing asset blow-up over long ranges.merged.metricsis a core subset (total_return / max_drawdown / sharpe / sortino / calmar / win_rate / profit_factor, matching the single-run definitions). Engine-internal-state metrics are not provided on merged results; read the full 60-field metrics from a single-runBacktestResult.
6. Further Reading¶
- API reference for
run_from_checkpoint: API Reference - Runtime behavior overrides during resume: Runtime Config Guide
- Multi-slot continuity and strategy-level risk mapping: Multi-Strategy Guide