Appearance

Personalize your experience

Mode
Accent Color
Direction
MarketplacestrategiesVolatility-Targeted Trend
QuaScript

Volatility-Targeted Trend

Free

Kalman trend that steps aside when volatility is unreliable

0.0(0)
0 installs 0 0 comments
Q
159 listings

The QuaTick team's QuaScript analyses, run by the native C++ engine.

View Profile
quascriptstrategytrendkalmanvolatility
Free
Backtest this strategy Open in QuaTick IDE Edit in IDE
Version
Updated
Compatible

Live engine preview

QuaScriptQuaScript

The QuaScript engine's own output on the chart the terminal uses. Change the settings or edit the code and press Run to recompute it.

Volatility-Targeted Trendengine result
Loading the engine result…
quatick-charts · QuaScript engine on synthetic 1-minute bars · drag to pan · scroll to zoom · Run executes the script on the engine (⌘/Ctrl+Enter while editing)

About

A trend follower that steps aside when volatility is unreliable. The direction comes from a pair of Kalman-filtered averages; a position is held only while the Yang-Zhang volatility is below its long-run percentile cap and the five OHLC estimators agree, which removes the gap- and crash-driven periods where trend systems give back most of their gains. Entry: fast Kalman line above (below) the slow one, volatility percentile below the cap, and estimator disagreement below the limit. Exit: direction flip, a volatility spike, or a 4 ATR stop. Risk: 95 % of equity while invested, 30 % drawdown halt. Method: Kalman (1960); Yang and Zhang (2000) volatility.


Settings

Fast Kalman noise ratio: 0.01

Slow Kalman noise ratio: 0.0006

Volatility window: 30

Percentile history: 500

Volatility cap (percentile): 80

Maximum disagreement: 0.45

Stop (ATR): 4


Measured backtests

Run by the backtest engine on public candles, 2025-11-01 to 2026-11-01, with the script's own commission and slippage, on BTCUSDT 1h (Binance) and ETHUSDT 1h (Binance). The figures per market are in the Measured backtest panel below. Past results do not predict future returns.


Backtest it yourself

Open it on the backtesting page to run it on any symbol, interval and date range, then use Monte Carlo, optimisation, walk-forward and robustness tests.

Strategy Rules

Entry Conditions

  • ▸ fast Kalman line above (below) the slow one, volatility percentile below the cap, and estimator disagreement below the limit

Exit Conditions

  • ▸ direction flip, a volatility spike, or a 4 ATR stop

Measured backtest

BTCUSDT 1h (Binance), 2025-11-01 to 2026-11-01 · backtest engine, script costs included

Backtest this strategy

-30.3%

Return

-2.34

Sharpe

-30.4%

Max DD

15.4%

Win Rate

26

Trades

MarketTradesReturnSharpeMax DDWin rateProfit factor
BTCUSDT 1h26-30.3%-2.34-30.4%15.4%0.19
ETHUSDT 1h18-29.9%-1.84-30.3%22.2%0.24

Past results on historical data do not predict future returns.

Source Code

Edit in IDE
quascript
1strategy "Volatility-Targeted Trend" {
2 overlay = true
3 capital = 100000
4 commission = 0.05
5 slippage = 1
6 sizing = percent_equity
7 size = 95
8 pyramiding = 1
9}
10/// A trend follower that steps aside when volatility is unreliable. The direction comes from a
11/// pair of Kalman-filtered averages; a position is held only while the Yang-Zhang volatility is
12/// below its long-run percentile cap and the five OHLC estimators agree, which removes the gap- and
13/// crash-driven periods where trend systems give back most of their gains.
14/// Entry: fast Kalman line above (below) the slow one, volatility percentile below the cap, and
15/// estimator disagreement below the limit. Exit: direction flip, a volatility spike, or a 4 ATR stop.
16/// Risk: 95 % of equity while invested, 30 % drawdown halt.
17/// Method: Kalman (1960); Yang and Zhang (2000) volatility.
18use "viz/premium" as pr
19fast_q = param.float(0.01, title = "Fast Kalman noise ratio", min = 0.00001, max = 1, step = 0.0005, group = "Trend")
20slow_q = param.float(0.0006, title = "Slow Kalman noise ratio", min = 0.000001, max = 0.5, step = 0.0001, group = "Trend")
21vol_len = param.int(30, title = "Volatility window", min = 5, max = 500, group = "Volatility")
22rank_len = param.int(500, title = "Percentile history", min = 50, max = 5000, group = "Volatility")
23cap = param.float(80, title = "Volatility cap (percentile)", min = 30, max = 100, group = "Volatility")
24dis_max = param.float(0.45, title = "Maximum disagreement", min = 0.05, max = 2, step = 0.05, group = "Volatility")
25stop_k = param.float(4, title = "Stop (ATR)", min = 0.5, max = 10, step = 0.1, group = "Risk")
26// process / measurement noise ratio sets the responsiveness (about EMA 20 and EMA 80 here)
27f = ta.kalman(close, fast_q, 1)
28s = ta.kalman(close, slow_q, 1)
29yz = hft.yang_zhang(vol_len, 252)
30pk = hft.parkinson(vol_len, 252)
31gk = hft.garman_klass(vol_len, 252)
32rs = hft.rogers_satchell(vol_len, 252)
33cc = hft.realized_vol(close, vol_len, 252)
34mean_v = (yz + pk + gk + rs + cc) / 5
35dis = (math.max(math.max(math.max(yz, pk), math.max(gk, rs)), cc) - math.min(math.min(math.min(yz, pk), math.min(gk, rs)), cc)) / math.max(mean_v, 1e-12)
36pct = stat.percent_rank(yz, rank_len)
37// hysteresis: enter below the cap, leave only well above it, so the filter does not flicker
38persist calm = false
39if pct < cap and dis < dis_max { calm := true }
40if pct > math.min(cap + 12, 100) or dis > dis_max * 1.4 { calm := false }
41a = ta.atr(20)
42// direction changes only once the lines separate by half an ATR
43persist dir = 0
44if f - s > 0.5 * a { dir := 1 }
45if s - f > 0.5 * a { dir := -1 }
46want = calm ? dir : 0
47// one trade per direction signal: after a stop, wait for the next flip instead of re-entering
48persist used = 0
49if dir != dir[1] { used := 0 }
50if want == 1 and position.side <= 0 and used != 1 {
51 order.market("L", side.long)
52 exit.bracket("L exit", entry = "L", stop = close - stop_k * a)
53 used := 1
54}
55if want == -1 and position.side >= 0 and used != -1 {
56 order.market("S", side.short)
57 exit.bracket("S exit", entry = "S", stop = close + stop_k * a)
58 used := -1
59}
60if want == 0 and position.size != 0 { exit.close_all() }
61risk.max_drawdown(30)
62pf = render.line(f, "Fast Kalman", color = calm ? (f > s ? color.emerald.300 : color.rose.300) : color.slate.500, width = 2)
63ps = render.line(s, "Slow Kalman", color = color.translucent(color.slate.300, 40), width = 1.5)
64render.band(pf, ps, color = color.translucent(f > s ? color.emerald.500 : color.rose.500, calm ? 85 : 97), opacity = 1, title = "Trend ribbon")
65persist t = draw.table.create(anchor.top_right, 5, 2)
66if bar.is_last {
67 pr.heading(t, 2, "Volatility-Targeted Trend", "Strategy · Kalman trend, YZ volatility")
68 pr.row(t, 2, "Volatility percentile", str.format("{:.0f}", pct), pct < cap ? pr.BULL : pr.WARN)
69 pr.row(t, 3, "Estimator disagreement", str.format("{:.0%}", dis), dis < dis_max ? pr.BULL : pr.WARN)
70 pr.row(t, 4, "Stance", want > 0 ? "Long" : want < 0 ? "Short" : "Stand aside", color.white)
71}
72 
Read-only preview. Open in the QuaTick IDE to edit and deploy.

Reviews

No reviews yet. Be the first to review this listing.

Discussion

Sign in to join the discussion. Your posts and replies show up on your profile.