An ICT/SMC model, ported to Pine and checked bar by bar
Seven confluence modules feeding an ordered sweep → market-structure-shift → break-of-structure chain, with premium/discount gating and a daily-led higher-timeframe bias. Written twice, on purpose, so the two versions could be made to argue.
- Built as
- Reference implementation and public dashboard
- Written in
- Pine Script v6 and JavaScript
- Verified over
- 18,048 fifteen-minute bars
- Status
- Live, with one open disagreement
The brief this answers
ICT and smart-money briefs arrive the same way: a chart with liquidity sweeps circled, a structure break marked, and a note saying "only take the entry when all of this lines up." The words are shared vocabulary. The definitions are not.
Your swing high is not the textbook swing high. Your sweep has to take out a level that mattered, not merely a wick from Tuesday. Whether a session counts before it has closed is a real decision with a real effect on the signal count, and nobody writes it down.
So the first deliverable is never code. It is the set of definitions, written out, that the code will be held to.
Fifteen-minute evaluations on BTC, 1 February to 8 August 2026 — the sample the model was measured against.
Reached ENTRY READY, all seven modules green. 0.017% of evaluations. A confluence filter that fires often is not a confluence filter.
The part that goes wrong quietly
A break of structure needs a reference level to break. The obvious implementation grabs the most recent opposing pivot and checks whether price has closed beyond it.
That version fires almost every time. The pivot it picks has usually already been closed through before the structure shift even happened, so the break condition is satisfied on the very next bar, trivially. The chart fills with confirmations. The backtest looks tremendous. The rule has stopped meaning anything.
The fix is a staleness rule: the reference must be the most recent opposing pivot at or before the structure shift with no bar after it closing beyond it. If every candidate is stale, the break waits rather than firing. Seven bars in the replay flipped from a pass to a wait — and those seven were the tell that the original was wrong.
// reference for a break of structure ref = most recent opposing pivot p where p.time <= mss.time and no bar after p closes beyond p and no bar at-or-before mss.time closes beyond p // if every candidate is stale then break waits. it does not fire. // staleness and scan windows both // partition at the structure-shift cutoff, // not at the current bar.
Stated as a rule rather than as source, because this is the artefact that mattered: both implementations were written against this text, separately.
Writing it twice
The Pine indicator is one implementation. A pure JavaScript engine — seven modules, closed candles only, ATR-scaled thresholds, no chart state — is the other. Neither was derived from the other; both were written from the definitions above.
A replay harness runs the engine over historical candles and dumps a per-bar status CSV. Pine logs the same columns from the chart. Then the two files get diffed by timestamp, and every row where they disagree has to be explained before the work is done.
This is the part clients rarely get, and it is the only reason I can tell you what the script does rather than what it was intended to do.
time,overall,direction,bias,liquidity, mss,bos,fvg_ob,volume,four_h overall 0-3 direction 1 / -1 / 0 modules 1 / 0 / -1 # diff by timestamp_ms, no shift. # canonical parity timeframe is 5m — # a 15m chart export silently feeds # 15m bars into the 5m leg.
What the diff said
Two forced-direction runs over a five-minute chart. The higher-timeframe leg agreed on every single bar in both runs, which is the leg most ports get wrong.
The residual disagreements are not noise I waved through. The largest group traces to a one-bar boundary definition — a Pine row at time T includes the five-minute bar opening at T, while an engine row keyed T is closed through T — so both sides fire on the same break and land on adjacent keys.
One disagreement is still open: twelve bars where the chains match exactly but the zone layer disagrees. It is named, bounded, and on the list. I would rather show you that than a table of green ticks.
| Run | Bar-exact | Overall + direction | 4H leg |
|---|---|---|---|
| Forced short | 94.3% | 98.2% | 0 divergent |
| Forced long | 92.4% | 96.6% | 0 divergent |
| Open item | 12 bars | zone layer only | chains agree |
Timeframe legs evaluated per run: 17 daily, 97 four-hour, 1,537 fifteen-minute, 4,609 five-minute.
What it looks like running
The dashboard is public and keyless. It scans a live symbol list, and most of the time every row it shows you is a refusal — which is the honest resting state of a confluence tool and the thing a screenshot of an all-green table would hide.
At any given moment roughly half a symbol across the whole list sits at SETUP FORMING.
BCHUSDT at SETUP FORMING. Five checks pass, volume fails on weak breakout participation, and the 4H framework waits because price is in the premium — so nothing fires. See it live, or watch a setup read end to end.