Lesson 12 turned all 18 strategies into Python. This lesson ports the same 8 primitives and 18 strategies to TradingView Pine Script v6 — one self-contained strategy() script you can paste directly into the Pine Editor, pick a strategy from a dropdown, and actually backtest on the Strategy Tester tab.
Why Pine, Not Just Python
Three of the 8 primitives are arguably more natural in Pine than in the Python version from Lesson 12:
| find_swings | in_killzone | order block / FVG zones | |
|---|---|---|---|
| Python | Manual fractal loop over a bars list | Hand-written string-split on a "HH:MM" timestamp | Returns coordinates only, nothing to look at |
| Pine | Native `ta.pivothigh()` / `ta.pivotlow()` — built for exactly this | `hour(time, "America/New_York")` — timezone-aware, no parsing | `box.new()` draws the actual zone on the live chart |
The real cost of porting isn't syntax — it's the paradigm. Python's version operates on the whole bars list with slicing and comprehensions; Pine runs once per bar and needs state carried forward with var variables. label_structure's trend-tracking, for example, becomes a persistent var string trendState updated each bar instead of a loop over a list — same logic, different shape.
The Script
Paste this whole block into a new Pine Editor script. It's one file — all 8 primitives, all 18 strategies, a dropdown to pick which one is live, and real strategy.entry() calls so the Strategy Tester tab shows actual trades.
// ═══════════════════════════════════════════════════════════════════════
// SMC & ICT Strategy Compendium — Oyamori (Pine Script v6)
// Ported from the Python reference: smc_ict_strategies.py
//
// Learning reference, NOT a proven trading system. Every function below is
// a direct translation of the rule described in Oyamori's SMC & ICT
// Fundamentals course (Lessons 1-11) — nothing has been added, and nothing
// here has been backtested or optimized. Pick one strategy from the
// dropdown, review its entries on the Strategy Tester tab yourself.
// ═══════════════════════════════════════════════════════════════════════
//@version=6
strategy("SMC & ICT Strategy Compendium — Oyamori", overlay=true,
max_boxes_count=100, max_lines_count=100, max_labels_count=100,
default_qty_type=strategy.percent_of_equity, default_qty_value=10)
// ── Inputs ─────────────────────────────────────────────────────────────
strategySelect = input.string("OTE + Order Block Confluence", "Strategy", options=[
"OTE + Order Block Confluence", "Break-and-Retest", "Turtle Soup", "Silver Bullet", "2022 Model",
"Judas Swing", "Unicorn Model", "Breaker Block Reversal", "Mitigation Block Entry",
"Inversion FVG Reversal", "NY AM Session Reversal", "Liquidity Void Fill",
"Market Maker Buy Model", "Venom Model", "Smart Money Reversal", "Power of Three Daily Bias",
"Weekly Profile", "Quarterly Theory Reversal"])
swingLeft = input.int(2, "Swing Lookback (left/right bars)", minval=1)
eqTolPct = input.float(0.1, "Liquidity Pool Tolerance (%)", minval=0.0) / 100
kzStartH = input.int(10, "Killzone Start Hour (NY time)", minval=0, maxval=23)
kzEndH = input.int(11, "Killzone End Hour (NY time)", minval=0, maxval=23)
showZones = input.bool(true, "Draw order block / FVG zones")
// ── Types (mirrors Python's Swing / Signal dataclasses) ─────────────────
type Signal
string direction
float entry
float stop
float target
string note
// ── Persistent swing history (Python's find_swings, via native pivots) ──
var array<float> swHighPrice = array.new_float()
var array<float> swLowPrice = array.new_float()
ph = ta.pivothigh(high, swingLeft, swingLeft)
pl = ta.pivotlow(low, swingLeft, swingLeft)
if not na(ph)
array.push(swHighPrice, ph)
if not na(pl)
array.push(swLowPrice, pl)
// ── Structure labeling — HH/HL/LH/LL + BOS/CHoCH (Python label_structure) ─
var float lastHigh = na
var float lastLow = na
var string trendState = na
var string lastBreak = na // "BOS" or "CHoCH", set the bar a swing confirms
lastBreak := na
if not na(ph)
label1 = na(lastHigh) ? "H" : (ph > lastHigh ? "HH" : "LH")
if trendState == "up" and label1 == "LH"
lastBreak := "CHoCH"
trendState := "down"
else if trendState == "up" and label1 == "HH"
lastBreak := "BOS"
else if na(trendState) and label1 == "HH"
trendState := "up"
lastHigh := ph
if not na(pl)
label2 = na(lastLow) ? "L" : (pl > lastLow ? "HL" : "LL")
if trendState == "down" and label2 == "HL"
lastBreak := "CHoCH"
trendState := "up"
else if trendState == "down" and label2 == "LL"
lastBreak := "BOS"
else if na(trendState) and label2 == "LL"
trendState := "down"
lastLow := pl
// ── Liquidity pools — cluster swings within eqTolPct (Python find_liquidity_pools) ─
poolPrice(array<float> prices, float tol) =>
float result = na
n = array.size(prices)
if n > 0
float p0 = array.get(prices, n - 1)
float sum = p0
int cnt = 1
if n >= 2
for i = n - 2 to 0
float pi = array.get(prices, i)
if math.abs(pi - p0) / p0 <= tol
sum += pi
cnt += 1
else
break
result := sum / cnt
result
lowPoolPrice = poolPrice(swLowPrice, eqTolPct)
highPoolPrice = poolPrice(swHighPrice, eqTolPct)
// ── Sweep detection — trades through the pool then closes back (Python detect_sweep) ─
sweptLow = not na(lowPoolPrice) and low < lowPoolPrice and close > lowPoolPrice
sweptHigh = not na(highPoolPrice) and high > highPoolPrice and close < highPoolPrice
// ── Order block — last opposite candle before a break (Python find_order_block) ─
bullishOB(int lookback) =>
float obLow = na
float obHigh = na
for i = 1 to lookback
if close[i] < open[i]
obLow := low[i]
obHigh := high[i]
break
[obLow, obHigh]
[obLowBull, obHighBull] = bullishOB(20)
// This port only wires up the bullish/long side of each strategy for clarity —
// a bearishOB() mirror is the same 6-line function with close[i] > open[i], and every
// short-side condition below (Breaker, Inversion FVG) is built directly from it inline.
// ── Fair Value Gap — 3-candle wick gap (Python find_fvg; native Pine idiom) ─
// Bullish-side only, matching the long-only simplification noted above —
// a bearish FVG is the mirror condition: high < low[2].
bullFVG = low > high[2]
bullFVGLow = high[2]
bullFVGHigh = low
// ── Premium/Discount + OTE band (Python premium_discount_zone) ───────────
dealingHigh = array.size(swHighPrice) > 0 ? array.max(swHighPrice) : na
dealingLow = array.size(swLowPrice) > 0 ? array.min(swLowPrice) : na
midpoint = (dealingHigh + dealingLow) / 2
oteHigh = dealingHigh - (dealingHigh - dealingLow) * 0.62
oteLow = dealingHigh - (dealingHigh - dealingLow) * 0.79
// ── Killzone — native time functions beat the Python string-parsing hack ─
nyHour = hour(time, "America/New_York")
inKillzone = nyHour >= kzStartH and nyHour < kzEndH
// ═══════════════════════════════════════════════════════════════════════
// Tier 1 — Highly Mechanical
// ═══════════════════════════════════════════════════════════════════════
// OTE + Order Block Confluence: bullish OB overlaps the 62-79% OTE band
oteObOverlap = not na(obLowBull) and not na(oteHigh) and
math.max(obLowBull, oteLow) <= math.min(obHighBull, oteHigh)
oteObSignal = oteObOverlap and low <= obHighBull and close > obLowBull
// Break-and-Retest: BOS confirmed, then price retests the broken level
bosLevel = lastBreak == "BOS" ? (trendState == "up" ? lastHigh : lastLow) : na
breakRetestSignal = not na(bosLevel) and low <= bosLevel and high >= bosLevel and close > bosLevel
// Turtle Soup: false breakout of a prior low that reverses same/next bar
turtleSoupSignal = low < ta.lowest(low, 20)[2] and close > ta.lowest(low, 20)[2]
// Silver Bullet: FVG forms inside the killzone
silverBulletSignal = bullFVG and inKillzone
// 2022 Model: sweep of old low -> CHoCH -> entry on the retracement FVG
model2022Signal = sweptLow and lastBreak == "CHoCH" and bullFVG
// ═══════════════════════════════════════════════════════════════════════
// Tier 2 — Moderately Precise
// ═══════════════════════════════════════════════════════════════════════
// Judas Swing: early false move at session open sweeps liquidity, reverses
judasSwingSignal = sweptLow and close > open[2]
// Unicorn Model: breaker block + FVG overlap
unicornSignal = lastBreak == "CHoCH" and not na(obLowBull) and bullFVG and
math.max(obLowBull, bullFVGLow) <= math.min(obHighBull, bullFVGHigh)
// Breaker Block Reversal: failed bullish OB retested from above, rejects
obFailed = not na(obLowBull) and close < obLowBull
breakerRevSignal = obFailed[1] and high >= obLowBull[1] and high <= obHighBull[1] and close < obLowBull[1]
// Mitigation Block Entry: narrow pre-launch consolidation retest
launchCandle = (close - open) > 2 * (high[1] - low[1])
mitigationSignal = launchCandle[1] and low <= high[2] and low >= low[2] and close > low[2]
// Inversion FVG Reversal: bullish FVG closed through completely, then flips
fvgInverted = bullFVG[3] and close < bullFVGLow[3]
inversionFvgSignal = fvgInverted and high >= bullFVGLow[3] and high <= bullFVGHigh[3] and close < bullFVGLow[3]
// NY AM Session Reversal: sweep-and-reverse, filtered to the killzone only
nyAmSignal = sweptLow and inKillzone
// Liquidity Void Fill: thin/fast candle's range gets traded through quickly
voidRange = high[2] - low[2] > 0 ? high[2] - low[2] : 1e-9
voidBodyPct = math.abs(close[2] - open[2]) / voidRange
voidCandle = voidBodyPct >= 1 / 2.5 // mostly-body candle, little wick either side
voidFillSignal = voidCandle and low <= math.max(open[2], close[2]) and low >= math.min(open[2], close[2])
// ═══════════════════════════════════════════════════════════════════════
// Tier 3 — Conceptual / Discretionary
// (these compute a BIAS, not a hard signal — matching Lesson 11's honesty)
// ═══════════════════════════════════════════════════════════════════════
// Market Maker Buy Model: accumulation range swept, then reclaimed (AMD)
accHigh15 = ta.highest(high, 5)[10]
accLow15 = ta.lowest(low, 5)[10]
mmbmSwept = ta.lowest(low, 5)[5] < accLow15
mmbmSignal = mmbmSwept and close > accHigh15
// Venom Model: liquidity run + FVG entry, no CHoCH required (looser by design)
venomSignal = (ta.lowest(low, 5) < ta.lowest(low, 5)[5]) and bullFVG and
low <= bullFVGHigh and low >= bullFVGLow
// Smart Money Reversal: sweep + ANY structure shift, no confluence at all
smrSignal = (sweptLow or sweptHigh) and (lastBreak == "BOS" or lastBreak == "CHoCH")
// Power of Three Daily Bias: bias only, not a trigger
poThreeAccHigh = ta.highest(high, 8)[16]
poThreeAccLow = ta.lowest(low, 8)[16]
poThreeSweptLow = ta.lowest(low, 8)[8] < poThreeAccLow
poThreeBias = poThreeSweptLow and close > poThreeAccHigh ? "bullish" : (not poThreeSweptLow ? "bearish" : "unclear")
// Weekly Profile: this week's range vs prior week's high/low
priorWeekHigh = request.security(syminfo.tickerid, "W", high[1], lookahead=barmerge.lookahead_off)
priorWeekLow = request.security(syminfo.tickerid, "W", low[1], lookahead=barmerge.lookahead_off)
weeklySweptLow = low < priorWeekLow
weeklyBrokeHigh = high > priorWeekHigh
weeklyProfileBias = weeklySweptLow and weeklyBrokeHigh ? "bullish" : (weeklyBrokeHigh ? "bearish" : "mixed")
// Quarterly Theory Reversal: reversal near a Q4 (final-quarter) boundary
qSize = 20 // bars per quarter — adjust per your own convention, not standardized
q1Range = high[qSize * 4 - 1] - low[qSize * 4 - 1]
quarterlyReversalSignal = (close - open) > q1Range and close > open and bar_index % (qSize * 4) >= qSize * 3
// ═══════════════════════════════════════════════════════════════════════
// Selector — route the chosen strategy to entries + zone drawing
// ═══════════════════════════════════════════════════════════════════════
bool longCondition = switch strategySelect
"OTE + Order Block Confluence" => oteObSignal
"Break-and-Retest" => breakRetestSignal
"Turtle Soup" => turtleSoupSignal
"Silver Bullet" => silverBulletSignal
"2022 Model" => model2022Signal
"Judas Swing" => judasSwingSignal
"Unicorn Model" => unicornSignal
"Mitigation Block Entry" => mitigationSignal
"NY AM Session Reversal" => nyAmSignal
"Liquidity Void Fill" => voidFillSignal
"Market Maker Buy Model" => mmbmSignal
"Venom Model" => venomSignal
"Smart Money Reversal" => smrSignal
"Quarterly Theory Reversal" => quarterlyReversalSignal
=> false // Breaker/InversionFVG are short setups; Power of Three/Weekly Profile are bias-only — see notes below
bool shortCondition = switch strategySelect
"Breaker Block Reversal" => breakerRevSignal
"Inversion FVG Reversal" => inversionFvgSignal
=> false
if longCondition
strategy.entry("Long", strategy.long)
sigLong = Signal.new("long", close, low[1], na, strategySelect)
label.new(bar_index, low, "▲ " + sigLong.direction + " @ " + str.tostring(sigLong.entry, format.mintick) +
"\nstop " + str.tostring(sigLong.stop, format.mintick), style=label.style_label_up,
color=color.new(color.green, 0), textcolor=color.white, size=size.small)
if shortCondition
strategy.entry("Short", strategy.short)
sigShort = Signal.new("short", close, high[1], na, strategySelect)
label.new(bar_index, high, "▼ " + sigShort.direction + " @ " + str.tostring(sigShort.entry, format.mintick) +
"\nstop " + str.tostring(sigShort.stop, format.mintick), style=label.style_label_down,
color=color.new(color.red, 0), textcolor=color.white, size=size.small)
// Power of Three and Weekly Profile intentionally have no strategy.entry()
// call — they're bias frameworks, not triggers, exactly as Lesson 11 ranks
// them (Quarterly Theory Reversal, by contrast, does define a hard entry in
// both the Python and Pine ports — see the switch above). Plot the two
// bias-only frameworks as labels instead of faking a signal for them:
if strategySelect == "Power of Three Daily Bias" and barstate.islast
label.new(bar_index, high, "PO3 bias: " + poThreeBias, style=label.style_label_down, color=color.new(color.orange, 0))
if strategySelect == "Weekly Profile" and barstate.islast
label.new(bar_index, high, "Weekly bias: " + weeklyProfileBias, style=label.style_label_down, color=color.new(color.gray, 0))
// ── Zone drawing (order block / FVG) for the currently selected strategy ─
if showZones and not na(obLowBull) and (strategySelect == "OTE + Order Block Confluence" or
strategySelect == "Unicorn Model" or strategySelect == "Breaker Block Reversal")
box.new(bar_index - 20, obHighBull, bar_index, obLowBull, border_color=color.blue, bgcolor=color.new(color.blue, 85))
if showZones and bullFVG
box.new(bar_index - 2, bullFVGHigh, bar_index, bullFVGLow, border_color=color.purple, bgcolor=color.new(color.purple, 88))
plotshape(longCondition, title="Long Entry", style=shape.triangleup, location=location.belowbar, color=color.new(color.green, 0), size=size.small)
plotshape(shortCondition, title="Short Entry", style=shape.triangledown, location=location.abovebar, color=color.new(color.red, 0), size=size.small)
What Changed From the Python Port
Long-only simplification. Every strategy in this port checks the bullish/long case — Breaker Block Reversal and Inversion FVG Reversal are the two natural short setups from Lesson 11, so those keep their short-side logic, but the rest (which could theoretically fire short too) are wired long-only here. A bearish mirror of any function is the same handful of lines with the comparison direction flipped — close[i] > open[i] instead of <, high < low[2] instead of low > high[2] — left as an exercise rather than doubling the script's length.
Quarterly boundaries aren't calendar-aligned. qSize = 20 bars per quarter is a fixed bar count, not an actual calendar split — exactly the "Q1 start convention is not standardized" caveat from both Lesson 11 and the Python version, just made concrete as a number you'd need to adjust for your own convention.
request.security() for Weekly Profile. Pulling the prior week's high/low needs a higher-timeframe request in Pine — there's no Python equivalent needed since that version just took bars_by_week as a pre-split argument. This is a case where Pine requires more, not less, code than the Python original.
Why does the Python version get smoke-tested but not this one?
The Python file could be imported and run directly in this environment — a few lines of random-generated bars and 18 function calls, all with zero setup cost. Pine Script can only be compiled inside the TradingView Pine Editor, which isn't available as a tool here. The honest position is: manually reviewed for syntax, not compiler-verified — paste it in and check the console before trusting it, exactly as the danger callout at the top says.
Can I actually backtest this in TradingView's Strategy Tester?
Yes — that's the whole point of using strategy() instead of indicator(). Pick a strategy from the dropdown, apply it to a chart, and the Strategy Tester tab will show real trades, equity curve, and performance metrics based on the strategy.entry() calls. Just remember: it's still testing the SAME unverified logic as the Python version, now with a real backtester attached — a backtest result is only as good as the strategy logic feeding it.
Why one script with a dropdown instead of 18 separate scripts?
A learner comparing strategies benefits from one consistent chart, one consistent set of inputs, and instant switching — publishing 18 separate scripts (with the friction of finding, adding, and configuring each one on TradingView) would work against the compendium's whole purpose: seeing how these relate to each other, not just running one in isolation.
Download the complete file: smc_ict_strategies.pine