Indicators¶
The technical-analysis functions you can call in conditions and expressions. qkt ships ~10 hand-implemented indicators — the most common ones, written from scratch with hand-computed tests for correctness.
Function call shape¶
Every indicator is a function call:
The first argument is what to compute on. For most indicators that's <stream>.close (close price). For ATR it's the stream itself (uses high/low/close). For VWAP it's the stream too (uses ticks + volume).
Catalog¶
Moving averages¶
ema(<value>, <period>) -- exponential moving average
sma(<value>, <period>) -- simple moving average
wma(<value>, <period>) -- weighted moving average
<value> is typically stream.close but can be any price expression. <period> is the lookback length in bars (not ticks).
ema(btc.close, 9) -- 9-bar EMA of close
sma(btc.close, 200) -- 200-bar SMA of close
sma((btc.high + btc.low) / 2, 20) -- 20-bar SMA of midpoint
When to use which:
ema— reacts faster to recent prices. Most common for short-term signal generation.sma— equal weighting; smoother but slower. Used for long-term filters (50, 100, 200 period).wma— linearly weighted (recent bars count more). Less common; sometimes useful when you want EMA-like responsiveness with a discrete window.
Oscillators¶
RSI uses Wilder's smoothing. Bounded [0, 100]. Below 30 = oversold, above 70 = overbought are the conventional thresholds.
Volatility¶
ATR is Wilder's smoothed average of true range. Uses high/low/close — so you pass the stream, not stream.close. Used heavily in stop-loss sizing (STOP_LOSS BY atr(btc, 14) * 2).
MACD¶
Default Connors-style values: (12, 26, 9). Returns the MACD line:
To compare against the signal line, qkt provides macd_signal and macd_hist:
WHEN macd(btc.close, 12, 26, 9) CROSSES ABOVE macd_signal(btc.close, 12, 26, 9)
THEN BUY btc SIZING 0.1
The three values share the same internal computation — the parser deduplicates.
Bollinger Bands¶
bollinger_upper(<value>, <period>, <stddev>)
bollinger_middle(<value>, <period>, <stddev>) -- = SMA
bollinger_lower(<value>, <period>, <stddev>)
<stddev> is the band width in standard deviations; the typical value is 2.0.
VWAP¶
Takes the stream because it needs both price and volume. The period is in ticks, not bars — VWAP is a tick-level indicator.
If a tick has no volume, that tick contributes 0 (doesn't pollute the average).
Donchian (rolling extremes)¶
WHEN btc.close > highest(btc.close, 20) -- breakout above 20-bar high
THEN BUY btc SIZING 0.1
WHEN btc.close < lowest(btc.close, 10) -- breakdown below 10-bar low
AND POSITION.btc > 0
THEN CLOSE btc
highest(close, N) excludes the current bar. It looks at the last N prior closes. This matters for breakout strategies — otherwise close > highest(close, N) could never fire (the current bar can't exceed itself).
Math helpers¶
Available alongside indicators:
abs(<expr>) -- absolute value
max(<a>, <b>) -- maximum of two values
min(<a>, <b>) -- minimum
sqrt(<expr>) -- square root
log(<expr>) -- natural log
exp(<expr>) -- e^x
floor(<expr>) -- floor
ceil(<expr>) -- ceiling
round(<expr>) -- round half-even
pow(<base>, <exp>) -- exponentiation
Annualized 20-bar realized volatility from log returns. Composes the helpers and a 20-bar sum aggregate.
Aggregates¶
sum(<expr>, <period>) -- rolling sum
avg(<expr>, <period>) -- rolling mean (same as sma)
count(<predicate>, <period>) -- rolling count of bars where predicate is true
LET upDays = count(btc.close > btc.close[1], 20)
WHEN upDays >= 15 THEN LOG INFO "trend confirmed: 15 of last 20 bars were up"
Warmup¶
Every indicator has a warmup period — bars needed before it produces a meaningful value.
| Indicator | Warmup |
|---|---|
sma(value, N) |
N bars |
ema(value, N) |
N bars (seeds with SMA of first N) |
wma(value, N) |
N bars |
rsi(value, N) |
N+1 bars |
atr(stream, N) |
N bars |
macd(value, F, S, sig) |
S + sig bars |
bollinger_*(value, N, k) |
N bars |
vwap(stream, N) |
N ticks |
highest/lowest(value, N) |
N bars |
During warmup the indicator returns null. Comparisons with null are false — your rule won't fire, but it won't crash either.
The DSL compiler automatically infers warmup requirements from your indicator calls and tells the engine to discard pre-warmup signals. To declare a custom warmup window explicitly:
Useful when you want to ensure long-period indicators (200-bar SMA) are warm even on short backtest windows.
Composing indicators¶
Indicators return numbers; numbers compose freely. Common patterns:
Difference between indicators¶
LET emaSpread = ema(btc.close, 9) - ema(btc.close, 21)
WHEN emaSpread > 100 THEN LOG INFO "strong trend"
Ratio¶
LET ratio = ema(btc.close, 9) / ema(btc.close, 21)
WHEN ratio > 1.02 THEN LOG INFO "2% above slow MA"
Multiply ATR for stops¶
Combine across streams¶
When the compiler complains¶
- "Unknown indicator" — typo, or you used a name not in the catalog. Check this page.
- "Indicator requires Stream, got Number" — passing
btc.closewherebtcwas expected (ATR, VWAP). Pass the stream, not the field. - "Indicator requires Number, got Stream" — opposite — passing
btcwherebtc.closewas expected. - "Period must be positive integer" — you wrote
ema(btc.close, -9)orema(btc.close, 0).
Common gotchas¶
atrandvwaptake a stream, notstream.close. They need OHLC / volume, not just close.- Periods are in bars, not ticks — except VWAP, which is ticks.
- Indicators are recomputed per bar. Long-period indicators are computationally cheap (linear in period); don't worry.
- No mutable state across rules. Each rule's indicator references compile to independent indicator objects (deduplicated where the period + value source match). State is internal — you can't read "the previous value" via
[N]on an indicator call (ema(...)[1]is not valid). Use aLETto capture the current value, then on the next bar yourLETevaluation is for the new bar. - VWAP needs volume. A tick feed with
volume=nullproduces a VWAP ofnull. Most CSV/MT5 feeds have volume; some Bybit endpoints don't.
What this composes with¶
- Conditions — every indicator can appear in
WHENclauses - Expressions — arithmetic on indicator values
- SIZING — ATR is the canonical risk-sizing input
- BRACKET — ATR-based stops and targets
- LET — name reusable indicator combinations