Phase 24 — Risk-sizing primitives¶
Summary¶
Four DSL surface additions that close long-standing gaps in the language:
SIZING <N> PCT RISK— risk-percent sizing as a first-class form (sugar overSIZING RISK N/100).WARMUP <N> BARSonSYMBOLSlines — per-stream gate that suppresses rule firing until N closed candles arrive.<expr> IS NULL/IS NOT NULL— explicit test for the internalValue.Undefinedsentinel.FLATTEN— keyword alias forCLOSE_ALL, reads better in session-end / risk-off rules.
Three of the four (PCT RISK, IS NULL, FLATTEN) compile to existing engine paths — they're parser-surface improvements. Only WARMUP adds a new runtime component (WarmupGate).
What's new¶
SIZING N PCT RISK¶
Constant-folded at parse time to SizeRiskFrac(NumLit(N/100)). Rejects non-literal N and N <= 0 at parse time. Requires a BRACKET STOP_LOSS — without one the engine has no stop distance to size against.
WARMUP N BARS¶
A rule fires only when every stream alias it references has observed N closed candles. A rule that references two streams waits for both to be warm; a stream with no WARMUP declared is warm from tick zero.
Updated by Phase 25B: live deploys now auto-prefetch the requested history from the broker so WARMUP rarely produces operator-visible wait time. See phase 25B.
IS NULL / IS NOT NULL¶
Tests whether an expression evaluates to Value.Undefined. Always returns a boolean — never propagates undefined itself — so it composes safely with AND / OR. Binds at comparison precedence, tighter than AND.
FLATTEN¶
WHEN NOW.hour_utc = 21 THEN FLATTEN -- session close
WHEN ACCOUNT.realized_pnl < -1000 THEN FLATTEN -- daily loss kill switch
Same compiled action as CLOSE_ALL. Pick whichever reads more naturally.
Migration from previous phase¶
Pure additions — every existing strategy continues to parse and run unchanged. The SIZING N PCT RISK form replaces the manual LET stopDist / LET riskUsd / LET riskQty workaround documented under Computing risk-based size manually, but the workaround still works for cases where you need a sizing expression PCT RISK doesn't model (e.g. correlation across positions).
File map¶
| Concern | Files |
|---|---|
Lexer tokens (WARMUP, BARS, FLATTEN, IS, NULL) |
src/main/kotlin/com/qkt/dsl/parse/TokenKind.kt |
| Parser surface | src/main/kotlin/com/qkt/dsl/parse/Parser.kt |
| AST | src/main/kotlin/com/qkt/dsl/ast/ExprAst.kt (IsNull), StrategyAst.kt (StreamDecl.warmupBars) |
| Compile / runtime | src/main/kotlin/com/qkt/dsl/compile/{ExprCompiler,RuleAliasScan,WarmupGate,AstCompiler,CompiledRule}.kt |
Visitor passes patched for IsNull |
MetaRef.kt, LetResolver.kt, SnapshotPlan.kt, IterVarSubstitution.kt, DefaultsMerge.kt |
| Editor | editor/textmate/qkt.tmLanguage.json |
| Docs | docs/reference/dsl/{sizing,streams,expressions,actions}.md, docs/reference/dsl-grammar.md |
Common gotchas¶
SIZING 1 PCT RISKwithout a stop. Rejected at order submission. Risk sizing needs a stop distance; provide one viaBRACKET STOP_LOSS.WARMUPdoesn't backfill. Counter starts at zero per engine restart. Use it as a guard against not-yet-warm indicators, not as a substitute for historical prefetch.IS NULLprecedence. Binds tighter thanAND, looser than arithmetic.a + b IS NULLparses as(a + b) IS NULL.a IS NULL AND b > 0parses as(a IS NULL) AND (b > 0).FLATTENvsCLOSE <stream>.FLATTENcloses every position across every stream the strategy holds;CLOSE <stream>only closes that one stream.