I've been backtesting an ICT-style Order Block + Fair Value Gap strategy on crypto and forex. After 50 iterations and a lot of dead variants, here's what survived. Posting the Pine Script v5 below in case it saves someone the same week of debugging.
The setup
ICT (Inner Circle Trader) methodology relies on two ideas:
- Order Block (OB) — the last opposite candle right before a strong displacement. Bull OB = last bearish candle before a sharp bullish push.
- Fair Value Gap (FVG) — a 3-bar imbalance where price moved so fast it left a gap (low > high[2]).
Entry = price retraces back into the OB, ideally inside an FVG, with trend confirmation.
Why this version finally worked
Earlier tries took every OB. Win rate dropped to 35%. Two filters fixed it:
-
Displacement filter: the move out of the OB must exceed
2 × ATR(14). Weak moves create weak OBs. They don't hold. - Trend filter: only longs above EMA(200) trending up. Short below, trending down. Sounds basic, but cut my losing trades by 40%.
Stop loss = OB low minus 0.3 × ATR. TP = 3R fixed. Anything tighter and noise eats you. Anything wider and you give back too much.
The full Pine Script v5
//@version=5
strategy("ICT_OB_SEED11", overlay=true, initial_capital=3000, commission_type=strategy.commission.percent, commission_value=0.05, slippage=2, default_qty_type=strategy.cash, default_qty_value=300, process_orders_on_close=true)
displ_atr_mult = input.float(2.0, "Displacement ATR mult")
ob_lookback = input.int(20, "OB lookback")
atr = ta.atr(14)
displ_up = close - close[1] > atr * displ_atr_mult
displ_dn = close[1] - close > atr * displ_atr_mult
var float obUpHi = na, var float obUpLo = na
var float obDnHi = na, var float obDnLo = na
if displ_up and close[1] < open[1]
obUpHi := high[1]
obUpLo := low[1]
if displ_dn and close[1] > open[1]
obDnHi := high[1]
obDnLo := low[1]
bullFVG = low > high[2]
bearFVG = high < low[2]
ema200 = ta.ema(close, 200)
upTrend = close > ema200 and ema200 > ema200[20]
dnTrend = close < ema200 and ema200 < ema200[20]
retestLong = upTrend and not na(obUpHi) and low <= obUpHi and high >= obUpLo and close > open
retestShort = dnTrend and not na(obDnLo) and high >= obDnLo and low <= obDnHi and close < open
if retestLong and barstate.isconfirmed and strategy.position_size == 0
sl = obUpLo - atr * 0.3
tp = close + (close - sl) * 3
strategy.entry("L", strategy.long)
strategy.exit("XL", "L", stop=sl, limit=tp)
obUpHi := na
obUpLo := na
if retestShort and barstate.isconfirmed and strategy.position_size == 0
sl = obDnHi + atr * 0.3
tp = close - (sl - close) * 3
strategy.entry("S", strategy.short)
strategy.exit("XS", "S", stop=sl, limit=tp)
obDnHi := na
obDnLo := na
Results across pairs (1H, 2 years backtest)
- BTCUSDT: PF 1.42, MaxDD -8.2%, 312 trades
- EURUSD: PF 1.18, MaxDD -6.1%, 184 trades
- XAUUSD: PF 1.61, MaxDD -11.4%, 247 trades
- SP500 1H: PF 1.09, MaxDD -7.8%, 142 trades
PF below 1.1 = skip. Above 1.4 = keep monitoring forward. Forward test always trails backtest by 15-25%.
What I tried that didn't work
- ATR(7) instead of ATR(14): too noisy, false displacements
- Tighter SL (0.1 ATR): stopped out 70% of valid setups
- 5R TP: too greedy, only 12% hit it before reversal
- No trend filter: half the OBs bounced once then died
Next iterations
- Add session filter (London/NY only, skip Asia)
- Multi-timeframe OB confirmation (4H OB on 1H entry)
- Volume profile overlay for OB validation
About me
I run a small portfolio of niche tools and content sites across AI, SEO, trading, and B2B. Some of them:
AI & automation:
- vocalis.pro — AI-powered SEO content automation
- vocalis.blog — case studies and field notes
- vocalis-ai.org — voice AI agents for sales calls
- agents-ia.pro — agentic AI playbook
- agentic-whatsup.com — agentic AI for WhatsApp business
- ai-due.com — AI for SMB operations
SEO & sales:
- seo-true.com — SEO automation toolkit
- master-seller.fr — sales enablement (FR)
- lead-gene.com — B2B lead generation tools
Trust, security, finance:
- trustly-ai.com — trust signals automation
- trust-vault.com — encrypted vault tools
- factureimpayee.fr — French unpaid invoice recovery
Niche content:
- tesla-mag.ch — Tesla coverage (CH)
- iapmesuisse.ch — AI for Swiss SMBs
- cbdeuropa.com — CBD market analysis EU
If you trade and want to chat about backtesting frameworks or automation in general, drop a comment. Anyone running ICT on lower timeframes (5m/15m)? Curious what your filter stack looks like.
Top comments (0)