บทเรียนที่ 12 แปลกลยุทธ์ทั้ง 18 กลยุทธ์เป็นภาษา Python บทเรียนนี้จะพอร์ตค่าใช้จ่ายพื้นฐาน 8 ค่า และกลยุทธ์ 18 กลยุทธ์ไปยัง Pine Script v6 ของ TradingView — สคริปต์ strategy() แบบเรียบง่ายที่คุณสามารถวางลงใน Pine Editor ได้โดยตรง เลือกกลยุทธ์จากรายการแบบเลื่อน และทำการทดสอบย้อนหลังจริงบนแท็บ Strategy Tester
ทำไมต้อง Pine ไม่ใช่แค่ Python
ค่าใช้จ่าย 3 ค่าจาก 8 ค่าใช้จ่ายนั้นอาจเป็น ธรรมชาติ มากกว่าใน Pine เมื่อเทียบกับเวอร์ชัน Python จากบทเรียนที่ 12:
| ค้นหาสวิง | ในเขตสังหาร | order block / FVG โซน | |
|---|---|---|---|
| Python | การวนลูปแบบแมนนวลผ่านรายการแท่งเทียน | การแยกสตริงแบบเขียนเองโดยใช้ "HH:MM" เป็นตัวคั่นเวลา | คืนค่าพิกัดเท่านั้น, ไม่มีอะไรให้ดู |
| Pine | `ta.pivothigh()` / `ta.pivotlow()` ดั้งเดิม — สร้างขึ้นมาเพื่อสิ่งนี้โดยเฉพาะ | `hour(time, "America/New_York")` — รองรับเขตเวลา, ไม่ต้องมีการแยกวิเคราะห์ | `box.new()` วาดพื้นที่จริงบนแผนภูมิสด |
ต้นทุนที่แท้จริงของการพอร์ตไม่ใช่ไวยากรณ์ แต่เป็น paradigm เวอร์ชัน Python ทำงานกับรายการ bars ทั้งหมดด้วยการ slicing และ comprehensions; Pine ทำงานครั้งละหนึ่งแท่ง และต้องการสถานะที่ส่งต่อด้วยตัวแปร var ตัวอย่างเช่น การติดตามแนวโน้มของ label_structure จะกลายเป็น var string trendState ที่คงอยู่อัปเดตแต่ละแท่ง แทนที่จะวนซ้ำรายการ — ตรรกะเดียวกัน รูปแบบที่แตกต่างกัน
สคริปต์
วางบล็อกทั้งหมดนี้ลงในสคริปต์ Pine Editor ใหม่ เป็นไฟล์เดียว — ค่าใช้จ่ายพื้นฐานทั้ง 8 ค่า กลยุทธ์ทั้ง 18 กลยุทธ์ รายการแบบเลื่อนเพื่อเลือกกลยุทธ์ที่จะใช้งานจริง และการเรียก strategy.entry() จริงเพื่อให้แท็บ Strategy Tester แสดงการซื้อขายจริง
// ═══════════════════════════════════════════════════════════════════════
// 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)
อะไรที่เปลี่ยนแปลงไปจากพอร์ต Python
การลดความซับซ้อนให้เป็นแบบ Long-only. กลยุทธ์ทั้งหมดใน port นี้จะตรวจสอบกรณีที่เป็น Bullish/Long — Breaker Block Reversal และ Inversion FVG Reversal เป็นการตั้งค่า Short สองแบบที่เป็นธรรมชาติจากบทเรียนที่ 11 ดังนั้นจึงยังคงตรรกะด้าน Short ไว้ แต่ที่เหลือ (ซึ่งอาจจะถูกเรียกใช้ Short ได้ตามทฤษฎี) จะถูกตั้งค่าให้เป็น Long-only ที่นี่ การทำกระจกแบบ Bearish ของฟังก์ชันใดๆ คือชุดบรรทัดไม่กี่บรรทัดด้วยทิศทางของการเปรียบเทียบที่กลับด้านกัน — close[i] > open[i] แทนที่จะเป็น <, high < low[2] แทนที่จะเป็น low > high[2] — ไว้เป็นแบบฝึกหัดแทนที่จะเพิ่มความยาวของสคริปต์เป็นสองเท่า
ขอบเขตรายไตรมาสไม่ได้เรียงตามปฏิทิน. qSize = 20 bars ต่อไตรมาสเป็นจำนวน bars ที่กำหนด ไม่ใช่การแบ่งปฏิทินจริง — ตรงกับข้อควรระวังว่า "Q1 start convention ไม่ได้มาตรฐาน" จากทั้งบทเรียนที่ 11 และเวอร์ชัน Python เพียงแต่ทำให้เป็นรูปธรรมในฐานะตัวเลขที่คุณจะต้องปรับเปลี่ยนตามแบบแผนของคุณเอง
request.security() สำหรับ Weekly Profile. การดึง High/Low ของสัปดาห์ก่อนหน้าต้องใช้คำขอใน Timeframe ที่สูงขึ้นใน Pine — ไม่จำเป็นต้องมีส่วนประกอบเทียบเท่าใน Python เนื่องจากเวอร์ชันนั้นเพียงแค่รับ bars_by_week เป็นอาร์กิวเมนต์ที่แบ่งไว้ล่วงหน้า นี่เป็นกรณีที่ Pine ต้องการโค้ดมากขึ้น ไม่ใช่ น้อยกว่า เวอร์ชัน Python ดั้งเดิม
ทำไมเวอร์ชัน Python ถึงถูกทดสอบแบบ Smoke-tested แต่เวอร์ชันนี้ไม่?
ไฟล์ Python สามารถนำเข้าและเรียกใช้ได้โดยตรงในสภาพแวดล้อมนี้ — บรรทัดของ bars ที่สร้างขึ้นแบบ random ไม่กี่บรรทัดและการเรียกฟังก์ชัน 18 ครั้ง ทั้งหมดนี้มีค่าใช้จ่ายในการตั้งค่าเป็นศูนย์ Pine Script สามารถคอมไพล์ได้ภายใน TradingView Pine Editor เท่านั้น ซึ่งไม่มีให้เป็นเครื่องมือที่นี่ ตำแหน่งที่ซื่อสัตย์คือ: ตรวจสอบด้วยตนเองสำหรับไวยากรณ์ ไม่ใช่การตรวจสอบโดยคอมไพเลอร์ — วางลงในและตรวจสอบคอนโซลก่อนไว้วางใจ ตามที่การแจ้งเตือนอันตรายที่ด้านบนกล่าวไว้
ฉันสามารถ Backtest นี่ใน TradingView's Strategy Tester ได้จริงหรือ?
ได้ — นั่นคือจุดประสงค์ทั้งหมดของการใช้ strategy() แทน indicator() เลือกกลยุทธ์จากรายการแบบ dropdown, นำไปใช้กับ chart และแท็บ Strategy Tester จะแสดงการซื้อขายจริง, เส้นโค้งของ Equity และเมตริกประสิทธิภาพตามการเรียก strategy.entry() โปรดจำไว้ว่า: มันกำลังทดสอบตรรกะที่ไม่ได้รับการตรวจสอบแบบเดียวกับเวอร์ชัน Python ตอนนี้มีการแนบ Backtester ที่แท้จริง — ผลลัพธ์ Backtest นั้นดีเพียงเท่ากับตรรกะของกลยุทธ์ที่ป้อนเข้าไป
ทำไมจึงมีสคริปต์เดียวที่มีรายการแบบ dropdown แทนที่จะมีสคริปต์แยกกัน 18 สคริปต์?
ผู้เรียนที่เปรียบเทียบกลยุทธ์จะได้รับประโยชน์จาก chart ที่สอดคล้องกันชุดเดียว, ชุดอินพุตที่สอดคล้องกันชุดเดียว และการสลับทันที — การเผยแพร้สคริปต์แยกกัน 18 สคริปต์ (พร้อมกับแรงเสียดทานในการค้นหา, เพิ่ม และกำหนดค่าแต่ละรายการใน TradingView) จะขัดกับจุดประสงค์ทั้งหมดของ compendium: การดูว่ากลยุทธ์เหล่านี้มีความสัมพันธ์กันอย่างไร ไม่ใช่แค่การรันหนึ่งรายการแบบโดดเดี่ยว
ดาวน์โหลดไฟล์ฉบับสมบูรณ์: smc_ict_strategies.pine