DSL grammar¶
The qkt DSL is a small declarative language for trading strategies. This page is the one-page sketch of every accepted shape; the DSL reference explains each construct.
Living reference
Every ``qkt block on this page is compiled byDslReferenceCodeBlocksTest, exactly asqkt parsewould. Grammar sketches with
File-level shapes¶
Every .qkt file is one of:
STRATEGY— a single strategy, the most common casePORTFOLIO— a composition of strategies with regime-gated activation
STRATEGY¶
STRATEGY <name> VERSION <int>
[ DEFAULTS { <KEY> = <value> ... } ]
SYMBOLS
<alias> = <BROKER>:<symbol> EVERY <timeframe>
[ ... more streams ... ]
[ PARAM <name> = <literal> ]
[ ... more PARAMs ... ]
[ LET <name> = <expression> [, <name> = <expression> ...] ]
[ ... more LETs ... ]
[ SCHEDULE ... ]
[ SEQUENCE ... ]
[ RULES
WHEN <condition>
THEN <action> [ ; <action> ... ]
[ ... more rules ... ]
[ FOR EACH <ident> IN [ <alias>, ... ] DO WHEN ... THEN ... ]
]
The blocks come in this fixed order: DEFAULTS, SYMBOLS, PARAM, LET, SCHEDULE,
SEQUENCE, RULES. A section keyword after RULES is a located parse error
(LET must come before RULES). RULES is optional to the parser, but SYMBOLS is required
to compile: a rule that names a stream the file never declared fails with
Unknown stream alias. FOR EACH lives inside RULES and can interleave with plain WHEN
rules; names (the strategy, aliases, LETs, PARAMs) may start with a letter or _.
STRATEGY _grammar_tour VERSION 1
DEFAULTS {
SIZING = 0.1
TIF = GTC
}
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h
gold = BACKTEST:XAUUSD EVERY 1h
PARAM fast = 9
PARAM slow = 21
LET fastMa = ema(btc.close, fast), slowMa = ema(btc.close, slow)
RULES
WHEN fastMa CROSSES ABOVE slowMa AND POSITION.btc = 0
THEN BUY btc ; LOG "long btc"
FOR EACH s IN [btc, gold] DO
WHEN s.close < s.open AND POSITION.s > 0
THEN CLOSE s
WHEN gold.close > gold.open AND POSITION.gold = 0
THEN BUY gold SIZING 0.2
PARAM declares an overridable scalar (number, boolean, or string) with a default. Use the name
in conditions and actions. A portfolio can override it via RUN <alias> OVERRIDE { key = value }
— e.g. PARAM riskPct = 0.01 in the child becomes 0.008 in an aggressive portfolio slot.
PORTFOLIO¶
PORTFOLIO <name> VERSION <int> [ CAPITAL <number> ]
[ SYMBOLS ... ]
IMPORT '<path>' AS <alias> [ HOLD ]
[ ... more imports ... ]
[ REGIMES ... ]
[ ALLOCATE METHOD regime_weighted [ REBALANCE EVERY <duration> ] ... ]
RULES
[ WHEN <condition> ] RUN <alias> [ WEIGHT <number> ] [ OVERRIDE { <key> = <literal>, ... } ]
[ ... more rules ... ]
HOLD keeps a child's positions when the supervisor deactivates it. Without HOLD, deactivation
flattens. OVERRIDE retunes a child's PARAM values for this portfolio deployment without
editing the child file. Keys must match PARAM names declared in the child strategy; types must
match. WEIGHT needs CAPITAL on the header and the weights must sum to at most 1.0; a
portfolio has no LET or DEFAULTS block. See PORTFOLIO files.
PORTFOLIO book VERSION 1 CAPITAL 10000
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h
IMPORT 'trend.qkt' AS trend
IMPORT 'meanrev.qkt' AS meanrev HOLD
RULES
RUN trend WEIGHT 0.6 OVERRIDE { riskPct = 0.008 }
WHEN adx(btc, 14) < 20 RUN meanrev WEIGHT 0.4
Stream declaration¶
<BROKER>resolves against the broker registry:BACKTESTplus anybrokers:entry inqkt.config.yaml, upper-cased (e.g.EXNESS,BYBIT_LINEAR,DERIBIT). The parser accepts the prefix in any case; the registry lookup is what decides whether it exists.<symbol>is the canonical symbol qkt sees (EURUSD,BTCUSDT). Per-broker translation (suffix, alias) happens at the broker boundary — see broker integration.<timeframe>is1m,5m,15m,1h,1d, etc. — unitss,m,h,donly, there is no1w. Drives the candle aggregator.WARMUP <N> BARS(optional) gates every rule that touches this stream until N closed candles arrive — see streams.
Actions¶
| Action | Effect |
|---|---|
BUY <stream> SIZING ... [ORDER_TYPE = ...] [BRACKET ...] [OCO ...] [STACK ...] [TIF ...] [TIMES <n>] [EXIT AFTER <duration>] |
Long entry |
SELL <stream> ... (same clauses) |
Short entry |
CLOSE <stream> |
Flatten position on the stream's symbol |
CLOSE_ALL / FLATTEN |
Flatten every open position (aliases) |
CANCEL <stream> |
Cancel pending orders on the stream's symbol |
CANCEL_ALL |
Cancel all pending orders |
RESIZE <stream> TO <sizing> |
Set the position to a target size |
OCO_ENTRY { <entry>, <entry> } |
Two pending entries, one cancels the other |
LATCH <stream> OFFSET <d> ARM <duration> { ENTER ... } |
Directional trip-wire entry |
LOG [WARN\|ERROR\|DEBUG] "<msg>" [field=expr ...] |
Emit a log line — see logging |
Actions in one rule are separated by ; only; a newline does not separate them. BUY/SELL
need a SIZING, on the action or in DEFAULTS:
STRATEGY no_sizing VERSION 1
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h
RULES
WHEN btc.close > 0 THEN BUY btc
-- compile error: BUY/SELL requires SIZING
Sizing¶
SIZING <quantity>
SIZING <pct> PCT OF (EQUITY | BALANCE)
SIZING <N> PCT RISK -- sugar over SIZING RISK N/100; N is a numeric literal;
-- requires a BRACKET STOP_LOSS
SIZING <N> PCT RISK OF BOOK -- same, but risks N% of the portfolio book (CAPITAL + realized
-- PnL of every child) instead of this strategy's own equity;
-- only valid for PORTFOLIO children, rejected at deploy otherwise
SIZING RISK $ <expr> -- account-currency risk budget
SIZING <usd> USD
SIZING POSITION.<alias> -- full current position
BUY btc SIZING 0.1
BUY btc SIZING 5 PCT OF EQUITY
BUY btc SIZING 1000 USD
SELL btc SIZING POSITION.btc
Bracket¶
BRACKET { STOP_LOSS BY <pct> PCT, TAKE_PROFIT BY <pct> PCT }
BRACKET { STOP_LOSS AT <price-expr>, TAKE_PROFIT AT <price-expr> }
BRACKET { STOP_LOSS BY <distance>, TAKE_PROFIT RR <ratio> }
BUY btc SIZING 0.1 BRACKET { STOP_LOSS BY 1 PCT, TAKE_PROFIT BY 2 PCT }
BUY btc SIZING 0.5 PCT RISK BRACKET { STOP_LOSS BY atr(btc, 14) * 2, TAKE_PROFIT RR 3 }
The compiler routes BRACKET to native broker support if the broker has the BRACKET
capability (MT5, PaperBroker), else falls back to engine-managed SL/TP via separate orders. Both
legs are required; a bare STOP_LOSS outside a BRACKET is a parse error.
Order types¶
ORDER_TYPE = MARKET
ORDER_TYPE = LIMIT AT <price-expr>
ORDER_TYPE = STOP AT <price-expr>
ORDER_TYPE = STOP AT <price-expr> LIMIT AT <price-expr>
ORDER_TYPE = TRAILING BY <distance> | TRAILING PCT <percent>
TIF GTC | IOC | FOK | DAY | GTD [UNTIL] <epoch-ms-expr>
Stack¶
BUY btc SIZING 0.1 STACK 3 SPACING 100 ABOVE WITHIN 1h
BUY btc STACK [ 0.1, 0.2 AT entry + 100, 0.3 LIMIT AT entry + 200 ]
Pyramiding — one signal becomes N price-triggered entries. See STACK.
Timed exit¶
Closes the entry's own leg at market once it has been open for the duration, timed from the
fill and checked every tick. Also applies to its STACK_AT legs. See
EXIT AFTER.
Expressions¶
Literals¶
- Number:
100,1.5,0.001,1e-3 - Boolean:
TRUE,FALSE - String:
'BUY',"BUY"; single-line, escapes\',\",\\,\n,\t; supports exact, case-sensitive equality and inequality
Stream fields¶
<stream>.close(alias.price),.open,.high,.low,.volume,.bid,.ask,.spread,.timestamp;.close[n]is the valuenbars ago- Instrument meta:
.tick_size,.contract_size,.volume_step,.volume_min,.swap_long_points,.swap_short_points,.tick_value,.multiplier - Futures contract:
.contract(string),.dte,.days_to_roll; Undefined on non-futures streams - Mark and index:
.mark,.index, the contract's venue mark and index price; refused at start on a feed that serves none (see streams) - Trade flow, one bar back or more:
.buy_volume[n],.sell_volume[n](aggressor volume),.long_liq_volume[n],.short_liq_volume[n](liquidated volume); the bar closing (n= 0) is refused (see streams) - Option contract:
.iv(mark implied volatility),.delta,.gamma,.vega,.theta(per contract); refused at start on any other stream or on a feed without option marks (see streams) - Order-book depth:
.bid_depth,.ask_depth(quantity on the ten best levels a side),.book_imbalance(−1 to 1); a venue stream only, refused at start on a feed whose gateway does not declaredepth(see streams)
Indicators¶
ema(<value>, <period>),sma(...),rsi(...),atr(<stream>, <period>),vwap(<stream>.tick, <ticks>), ... — the full catalog with arities and warmup is in Indicators.
Operators¶
- Arithmetic:
+ - * /and unary-; there is no%— usemod(<a>, <b>) - Comparison: numeric
< <= > >= = == != <>; strings support=and!= - Boolean:
AND OR NOT - Null test:
<expr> IS NULL,<expr> IS NOT NULL— binds at comparison precedence; always yields a boolean - Crosses:
<a> CROSSES ABOVE <b>,CROSSES BELOW - Ranges:
<x> BETWEEN <lo> AND <hi>,<x> IN [<a>, <b>, <c>] - Conditional:
CASE WHEN <cond> THEN <expr> [WHEN ...] ELSE <expr> END—ELSEis required
LET size = CASE WHEN atr(btc, 14) > 200 THEN 0.05 ELSE 0.1 END
LET rem = mod(btc.close, 10)
RULES
WHEN rem < 1 AND ema(btc.close, 50) IS NOT NULL AND btc.close BETWEEN 100 AND 200
THEN BUY btc SIZING size
Account / position references¶
ACCOUNT.equity,.balance,.realized_pnl,.unrealized_pnl,.total_pnl,.dd_pct,.equity_peak,.open_positions_count,.trades_today,.wins_today,.losses_today, ...POSITION.<stream>— current quantity (signed);POSITION.<stream>.qtyis the same valuePOSITION.<stream>.entry_price(avg_price,avg_entry_price, orPOSITION_AVG_PRICE.<stream>),.pnl,.unrealized_pnl,.realized_pnl,.holding_duration,.mfe,.mae,.count,.longs,.shorts,.gross,.trades_today,.last_trade_atPOSITION.<structure>and.pnl,.credit,.max_loss,.pnl_pct,.dte,.delta,.gamma,.vega,.thetaon an option structure alias (Option structures)
See Expressions for the full list.
Defaults¶
DEFAULTS keys are the clause keywords SIZING, STOP_LOSS, TAKE_PROFIT, TIF,
ORDER_TYPE and TRAILING; a stop or target takes a child-price form (AT, BY, PCT, RR)
and SYMBOL stands for the acting rule's stream.
Apply to every action that doesn't override. An unknown key is a parse error:
LET clauses¶
Reusable expression aliases, inlined at compile time and evaluated per tick. LETs are resolved
by name, so one may reference another declared later; a LET that references itself is a
compile error.
FOR EACH¶
RULES
FOR EACH s IN [btc, gold, aapl] DO
WHEN s.close > s.open
AND POSITION.s = 0
THEN BUY s SIZING 0.1
Iterates over streams; the loop variable substitutes textually into the rule body, so this is one
rule per stream. The stream list needs its square brackets: FOR EACH s IN btc, gold DO is a parse
error (expected '[' to open stream alias list).
See also¶
- Architecture — what happens when a rule fires
- Backtest model — what the engine guarantees
- API reference — every parser AST node, every action class (built by CI)