Phase 25B — Live auto-warmup on deploy¶
Summary¶
Closes the live-side gap left by Phase 24's WARMUP N BARS: deploying a DSL strategy with EMA(close, 50) no longer makes the operator wait for 50 live closed candles before the first signal can fire. On every qkt deploy (cold-boot or hot-add against a running engine), the engine now:
- Derives per-stream bars-needed from
WARMUP N BARS+ indicator periods, taking the max per stream. - Fetches that history from the broker's historical bar API (MT5 + Bybit).
- Seeds the shared
CandleHubso lookback (btc.close[N]) works from tick zero. - Replays the same bars through the existing
IndicatorWarmerso indicators warm. - Credits Phase 24's
WarmupGateto match — rules fire on the very next live closed candle.
Backtest is untouched; it already gets warmup implicitly via sequential candle replay.
What's new¶
DSL CompiledStrategy now implements PerStreamWarmable¶
SYMBOLS
gold = EXNESS:XAUUSD EVERY 5m WARMUP 50 BARS,
spx = BACKTEST:SPX500 EVERY 1h
RULES
WHEN ema(spx.close, 24) > gold.close THEN ...
On deploy, the engine fetches:
- 50 bars of 5m XAUUSD from EXNESS (max of explicit 50 and no implicit need).
- 24 bars of 1h SPX500 from BACKTEST (implicit from ema(..., 24)).
Different timeframes warm independently, including two aliases for the same symbol. PerStreamWarmable
keys requirements by WarmupStream(symbol, window) so neither declaration can overwrite the other.
WarmupRequirements.compute(ast)¶
Inspects the parsed AST and returns per-alias bars-needed: max of explicit WARMUP N BARS and indicator periods walked from rule conditions and LET expressions. Nested indicators report the outer period only; set explicit WARMUP N BARS to override when the true warmup exceeds it.
CandleHub.seed(key, candles)¶
Prepend-only bulk load — places historical candles older than the existing oldest in the ring without firing onClosed callbacks. Safe to call before strategies bind their callbacks, and safe in hot-add: existing strategies on the daemon are never disturbed because the seed only prepends.
WarmupGate.recordBars(alias, count)¶
Pre-credits the Phase 24 gate so it doesn't sit cold after the hub has already been seeded. Called from CompiledStrategy.bindToHub using hub.historySize(key).
IndicatorWarmer.warmup(perStream, now)¶
Per-stream form of the existing single-spec method. Each (symbol, window) stream gets its own
WarmupSpec. Legacy single-spec callers continue to work via a thin wrapper.
WarmupFailedException¶
Thrown when the broker historical API errors during deploy. Operator sees:
qkt: failed to fetch warmup history for stream 'gold' (EXNESS:XAUUSD) — broker historical API returned: rate limit exceeded. Deploy aborted. Retry after fixing the broker connection, or remove WARMUP / reduce indicator periods to deploy without prefetch.
Deploy aborts before any rule fires. Half-warm strategies are worse than not-deployed strategies.
Migration from Phase 24¶
DSL syntax is unchanged. Custom PerStreamWarmable implementations must replace string keys with
WarmupStream(symbol, window) keys. This prevents same-symbol multi-timeframe declarations from
colliding during live deploy.
WARMUP 50 BARS means "prefetch 50 bars and fire on the first live bar." A strategy with
EMA(close, 200) but no explicit WARMUP also gets 200 bars pre-fetched.
The legacy single-spec Warmable interface still works for non-DSL strategies — LiveSession.start() falls back to it when PerStreamWarmable isn't implemented.
File map¶
| Concern | Files |
|---|---|
| Interface | src/main/kotlin/com/qkt/strategy/PerStreamWarmable.kt |
| Requirements derivation | src/main/kotlin/com/qkt/dsl/compile/WarmupRequirements.kt |
| Hub seed API | src/main/kotlin/com/qkt/dsl/compile/CandleHub.kt (seed) |
| Gate seed API | src/main/kotlin/com/qkt/dsl/compile/WarmupGate.kt (recordBars) |
| Per-stream warmer | src/main/kotlin/com/qkt/app/IndicatorWarmer.kt |
| Deploy-time wiring | src/main/kotlin/com/qkt/app/LiveSession.kt (seedHubFromHistory + per-stream branch) |
| Error type | src/main/kotlin/com/qkt/app/WarmupFailedException.kt |
| Strategy integration | src/main/kotlin/com/qkt/dsl/compile/AstCompiler.kt (CompiledStrategy : PerStreamWarmable) |
Common gotchas¶
- Broker rate limits. A heavy strategy spanning many streams + deep indicators issues many
bars(...)calls at deploy. If the broker rate-limits, deploy aborts withWarmupFailedException. Workaround: stagger deploys, or use a broker with higher historical-API quotas. - Nested indicators undershoot warmup.
EMA(EMA(close, 9), 21)reports only 21. The true warmup is closer to 29 (inner warms first, outer needs 21 inner-warm samples). Set explicitWARMUP 30 BARSif you use this pattern. - Lookback indices (
btc.close[20]) not yet derived. Set explicitWARMUP 21 BARSto cover them. Will be added as a follow-up. - Hot-add doesn't backfill
WarmupGatefor existing strategies. Phase 25B is forward-only: when strategy B deploys, only B gets warmed. Strategy A (already running) is not disturbed.