本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
//@version=6
// ══════════════════════════════════════════════════════════
// Smart Money Engine [WillyAlgoTrader]
// ══════════════════════════════════════════════════════════
// Author: Willy | WillyAlgoTrader
// Version: 1.3.0
indicator(
title = "Smart Money Engine [WillyAlgoTrader]",
shorttitle = "Smart-ME",
overlay = true,
max_lines_count = 500,
max_labels_count = 500,
max_boxes_count = 500,
max_bars_back = 5000)
// ══════════════════════════════════════
// CONSTANTS
// ══════════════════════════════════════
GRP_MAIN = "⚙️ Main Settings"
GRP_STRUCT = "📐 Structure"
GRP_OB = "🧱 Order Blocks"
GRP_FVG = "📊 Fair Value Gaps"
GRP_IFVG = "🔄 Inverse FVG"
GRP_FIB = "📐 Fibonacci"
GRP_VISUAL = "🎨 Visual"
GRP_DASH = "📊 Dashboard"
GRP_ALERT = "🔔 Alerts"
GRP_COLORS = "🎨 Colors"
GRP_ADV = "🔧 Advanced"
INDICATOR_VERSION = "v1.3.0"
BULL = 1
BEAR = -1
NONE = 0
// ══════════════════════════════════════
// INPUTS
// ══════════════════════════════════════
// ── Main ────────────────────────────
swingLenInput = input.int(10, "Swing Detection Length", minval = 3, maxval = 50,
group = GRP_MAIN,
tooltip = "Lookback bars for swing pivot detection.\n" +
"• Lower (3-7): more pivots, faster reaction, more noise\n" +
"• Higher (10-20): fewer pivots, cleaner structure\n" +
"• Recommended: 8-15 for most timeframes")
internalLenInput = input.int(5, "Internal Structure Length", minval = 2, maxval = 20,
group = GRP_MAIN,
tooltip = "Lookback for internal (minor) structure pivots.\n" +
"• Should be smaller than Swing Length\n" +
"• Recommended: 3-7")
// ── Structure ───────────────────────
showSwingStructInput = input.bool(true, "Show Swing Structure", group = GRP_STRUCT,
tooltip = "Display major BOS/CHoCH swing structure lines")
showInternalStructInput = input.bool(true, "Show Internal Structure", group = GRP_STRUCT,
tooltip = "Display minor internal BOS/CHoCH structure")
showSwingLabelsInput = input.bool(true, "Show HH/HL/LH/LL Labels", group = GRP_STRUCT,
tooltip = "Display swing point classification labels")
showStrongWeakInput = input.bool(true, "Show Strong/Weak Levels", group = GRP_STRUCT,
tooltip = "Display trailing strong/weak high and low levels")
// ── Order Blocks ────────────────────
showObInput = input.bool(true, "Show Order Blocks", group = GRP_OB,
tooltip = "Display detected order blocks on chart")
obMaxInput = input.int(5, "Max Visible OBs", minval = 1, maxval = 15, group = GRP_OB,
tooltip = "Maximum number of active order blocks to display.\n• More = cluttered chart\n• Recommended: 3-7")
obMitigationInput = input.string("Wick", "OB Mitigation",
options = ["Close", "Wick"],
group = GRP_OB,
tooltip = "How to determine OB mitigation:\n" +
"• Close: price must close through the OB\n" +
"• Wick: any wick touch mitigates (stricter)")
showObGradeInput = input.bool(true, "Show OB Grade (A/B/C)", group = GRP_OB,
tooltip = "Grade order blocks by strength:\n" +
"• A: OB + FVG overlap + volume spike\n" +
"• B: OB + one confluence factor\n" +
"• C: basic OB without confluence")
showObMidlineInput = input.bool(true, "Show OB Midline (CE)", group = GRP_OB,
tooltip = "Display consequent encroachment (midline) of order blocks")
// ── Fair Value Gaps ─────────────────
showFvgInput = input.bool(true, "Show Fair Value Gaps", group = GRP_FVG,
tooltip = "Display fair value gaps on chart with fill tracking")
fvgFilterInput = input.bool(true, "Auto-Filter Small FVGs", group = GRP_FVG,
tooltip = "Filter out FVGs smaller than 0.5x ATR — removes noise")
fvgMaxInput = input.int(5, "Max Visible FVGs", minval = 1, maxval = 15, group = GRP_FVG,
tooltip = "Maximum active FVGs to display.\n• Recommended: 3-7")
showFvgMidlineInput = input.bool(true, "Show FVG Midline (CE)", group = GRP_FVG,
tooltip = "Display consequent encroachment (midline) of fair value gaps")
// ── Inverse FVG ─────────────────────
showIfvgInput = input.bool(false, "Show Inverse FVGs", group = GRP_IFVG,
tooltip = "When an FVG is fully filled, it flips into an IFVG.\n" +
"Bullish FVG → Bearish IFVG (resistance)\n" +
"Bearish FVG → Bullish IFVG (support)")
ifvgMaxInput = input.int(5, "Max Visible IFVGs", minval = 1, maxval = 15, group = GRP_IFVG,
tooltip = "Maximum active IFVGs to display")
ifvgBullColorInput = input.color(color.new(#2962FF, 70), "Bull IFVG Border", inline = "ifvgcol", group = GRP_IFVG)
ifvgBearColorInput = input.color(color.new(#FF6D00, 70), "Bear IFVG Border", inline = "ifvgcol", group = GRP_IFVG)
// ── Fibonacci Retracement ────────────
showFibInput = input.bool(true, "Show Fibonacci Retracement", group = GRP_FIB,
tooltip = "Display auto Fibonacci grid between last HH and LL.\nShifts when new HH or LL is detected.")
showFibOTEInput = input.bool(true, "Highlight OTE Zone (0.5–0.618)", group = GRP_FIB,
tooltip = "Highlight the Optimal Trade Entry zone between 0.5 and 0.618 levels")
fibColorInput = input.color(color.new(#FFD600, 95), "OTE Zone Color", group = GRP_FIB,
tooltip = "Color for the 0.5–0.618 highlighted zone")
// ── Visual ──────────────────────────
themeInput = input.string("Auto", "Theme",
options = ["Auto", "Dark", "Light"],
group = GRP_VISUAL,
tooltip = "Chart color theme.\n" +
"• Auto: detects from chart background\n" +
"• Dark: optimized for dark backgrounds\n" +
"• Light: optimized for light/white backgrounds")
showWatermarkInput = input.bool(true, "Show Watermark", group = GRP_VISUAL,
tooltip = "Display 'WillyAlgoTrader - by Willy' watermark on chart")
showBgInput = input.bool(false, "Show Trend Background", group = GRP_VISUAL,
tooltip = "Tint chart background based on swing trend direction")
// ── Dashboard ───────────────────────
showDashInput = input.bool(true, "Show Dashboard", group = GRP_DASH,
tooltip = "Display real-time status dashboard")
dashPosStr = input.string("Top Right", "Dashboard Position",
options = ["Top Left", "Top Right", "Bottom Left", "Bottom Right"],
group = GRP_DASH,
tooltip = "Position of the dashboard table on chart")
// ── Colors ──────────────────────────
bullColorInput = input.color(#1B5E20, "Bull", inline = "col1", group = GRP_COLORS)
bearColorInput = input.color(#FF5252, "Bear", inline = "col1", group = GRP_COLORS)
obBullColorInput = input.color(color.new(#1B5E20, 95), "Bull OB", inline = "col2", group = GRP_COLORS)
obBearColorInput = input.color(color.new(#FF5252, 95), "Bear OB", inline = "col2", group = GRP_COLORS)
fvgBullColorInput = input.color(color.new(#2962FF, 95), "Bull FVG", inline = "col3", group = GRP_COLORS)
fvgBearColorInput = input.color(color.new(#FF6D00, 95), "Bear FVG", inline = "col3", group = GRP_COLORS)
// ── Alerts ──────────────────────────
webhookInput = input.bool(false, "Webhook JSON Format", group = GRP_ALERT,
tooltip = "Send alerts in JSON format for webhook integrations\n" +
"e.g. 3Commas, Autoview, custom bots")
// ── Advanced ────────────────────────
htfInput = input.timeframe("", "HTF Trend Filter", group = GRP_ADV,
tooltip = "Higher timeframe for trend bias.\n" +
"Leave empty to disable.\n" +
"Adds +1 confluence when aligned")
volConfirmInput = input.bool(true, "Volume Confirmation", group = GRP_ADV,
tooltip = "Add +1 confluence score when volume exceeds 1.5x average.\n" +
"Auto-disabled on instruments without volume data")
// ══════════════════════════════════════
// THEME DETECTION & COLORS
// ══════════════════════════════════════
isDark = switch themeInput
"Dark" => true
"Light" => false
=> color.r(chart.bg_color) < 128
TEXT_COLOR = isDark ? #E0E0E0 : #1A1A1A
TEXT_MUTED = isDark ? color.new(#9E9E9E, 0) : color.new(#757575, 0)
TEXT_BRIGHT = isDark ? #FFFFFF : #000000
TABLE_BG = isDark ? color.new(#131722, 5) : color.new(#FFFFFF, 5)
TABLE_BORDER = isDark ? color.new(#2A2E39, 0) : color.new(#D0D0D0, 0)
TABLE_ROW_ALT = isDark ? color.new(#1C2030, 0) : color.new(#F0F4F8, 0)
HEADER_BG = color.new(#2962FF, 0)
HEADER_TEXT = #FFFFFF
LABEL_TEXT = #FFFFFF
LINE_SUBTLE = isDark ? color.new(#555555, 0) : color.new(#AAAAAA, 0)
WM_COLOR = isDark ? color.new(#FFFFFF, 80) : color.new(#000000, 80)
BULL_BG = color.new(bullColorInput, 92)
BEAR_BG = color.new(bearColorInput, 92)
// ══════════════════════════════════════
// FUNCTIONS
// ══════════════════════════════════════
safeDiv(float num, float den, float fallback = 0.0) =>
den != 0 and not na(num) and not na(den) ? num / den : fallback
pivotHi(int leftBars, int rightBars) =>
ta.pivothigh(high, leftBars, rightBars)
pivotLo(int leftBars, int rightBars) =>
ta.pivotlow(low, leftBars, rightBars)
getDashPos(string pos) =>
switch pos
"Top Left" => position.top_left
"Top Right" => position.top_right
"Bottom Left" => position.bottom_left
"Bottom Right" => position.bottom_right
=> position.top_right
// ══════════════════════════════════════
// WARMUP & BASE CALCULATIONS
// ══════════════════════════════════════
int WARMUP_BARS = math.max(swingLenInput * 3, 50)
bool isWarmedUp = bar_index >= WARMUP_BARS
float atrVal = nz(ta.atr(14), high - low)
float volSma = ta.sma(volume, 20)
bool hasVolume = nz(volume, 0) > 0
bool volSpike = hasVolume ? volume > nz(volSma, volume) * 1.5 : false
// HTF trend (non-repainting)
float htfMa = htfInput != "" ? request.security(syminfo.tickerid, htfInput,
ta.ema(close, 50)[1], barmerge.gaps_off, barmerge.lookahead_on) : na
bool htfBull = not na(htfMa) ? close > htfMa : true
bool htfBear = not na(htfMa) ? close < htfMa : true
// ══════════════════════════════════════
// SWING STRUCTURE DETECTION
// ══════════════════════════════════════
float swPivotHigh = pivotHi(swingLenInput, swingLenInput)
float swPivotLow = pivotLo(swingLenInput, swingLenInput)
var float swHighLevel = na
var float swLowLevel = na
var float swPrevHigh = na
var float swPrevLow = na
var int swHighBar = na
var int swLowBar = na
var int swTrend = NONE
var bool swHighBroken = false
var bool swLowBroken = false
// Trailing strong/weak
var float trailHigh = na
var float trailLow = na
var int trailHighBar = na
var int trailLowBar = na
// Label offset in price (so labels don't sit on candles)
float labelOffset = atrVal * 0.3
if not na(swPivotHigh)
swPrevHigh := swHighLevel
swHighLevel := swPivotHigh
swHighBar := bar_index - swingLenInput
swHighBroken := false
if showSwingLabelsInput and isWarmedUp
string lbl = swHighLevel > nz(swPrevHigh, swHighLevel) ? "HH" : "LH"
label.new(bar_index - swingLenInput, swHighLevel + labelOffset, lbl,
color = color(na),
textcolor = bearColorInput,
style = label.style_label_down,
size = size.small)
if not na(swPivotLow)
swPrevLow := swLowLevel
swLowLevel := swPivotLow
swLowBar := bar_index - swingLenInput
swLowBroken := false
if showSwingLabelsInput and isWarmedUp
string lbl = swLowLevel < nz(swPrevLow, swLowLevel) ? "LL" : "HL"
label.new(bar_index - swingLenInput, swLowLevel - labelOffset, lbl,
color = color(na),
textcolor = bullColorInput,
style = label.style_label_up,
size = size.small)
// Structure break detection — CONFIRMED only
bool swBullBreak = not na(swHighLevel) and not swHighBroken and
close > swHighLevel and barstate.isconfirmed and isWarmedUp
bool swBearBreak = not na(swLowLevel) and not swLowBroken and
close < swLowLevel and barstate.isconfirmed and isWarmedUp
var string swBullType = ""
var string swBearType = ""
if swBullBreak
swHighBroken := true
swBullType := swTrend == BEAR ? "CHoCH" : "BOS"
swTrend := BULL
trailLow := swLowLevel
trailLowBar := swLowBar
if swBearBreak
swLowBroken := true
swBearType := swTrend == BULL ? "CHoCH" : "BOS"
swTrend := BEAR
trailHigh := swHighLevel
trailHighBar := swHighBar
// Update trailing extremes
if swTrend == BULL
if not na(trailHigh)
if high > trailHigh
trailHigh := high
trailHighBar := bar_index
else
trailHigh := high
trailHighBar := bar_index
if swTrend == BEAR
if not na(trailLow)
if low < trailLow
trailLow := low
trailLowBar := bar_index
else
trailLow := low
trailLowBar := bar_index
// Draw swing structure lines
if swBullBreak and showSwingStructInput
line.new(nz(swHighBar, bar_index), swHighLevel, bar_index, swHighLevel,
color = bullColorInput, width = 2, style = line.style_solid)
label.new(math.round((nz(swHighBar, bar_index) + bar_index) / 2), swHighLevel, swBullType,
color = color(na), textcolor = bullColorInput,
style = label.style_label_down, size = size.small)
if swBearBreak and showSwingStructInput
line.new(nz(swLowBar, bar_index), swLowLevel, bar_index, swLowLevel,
color = bearColorInput, width = 2, style = line.style_solid)
label.new(math.round((nz(swLowBar, bar_index) + bar_index) / 2), swLowLevel, swBearType,
color = color(na), textcolor = bearColorInput,
style = label.style_label_up, size = size.small)
// --- Internal pivots (minor) ---
float intPivotHigh = pivotHi(internalLenInput, internalLenInput)
float intPivotLow = pivotLo(internalLenInput, internalLenInput)
var float intHighLevel = na
var float intLowLevel = na
var int intHighBar = na
var int intLowBar = na
var int intTrend = NONE
var bool intHighBroken = false
var bool intLowBroken = false
if not na(intPivotHigh)
intHighLevel := intPivotHigh
intHighBar := bar_index - internalLenInput
intHighBroken := false
if not na(intPivotLow)
intLowLevel := intPivotLow
intLowBar := bar_index - internalLenInput
intLowBroken := false
bool intBullBreak = not na(intHighLevel) and not intHighBroken and close > intHighLevel and barstate.isconfirmed and isWarmedUp
bool intBearBreak = not na(intLowLevel) and not intLowBroken and close < intLowLevel and barstate.isconfirmed and isWarmedUp
var string intBullType = ""
var string intBearType = ""
if intBullBreak
intHighBroken := true
intBullType := intTrend == BEAR ? "CHoCH" : "BOS"
intTrend := BULL
if intBearBreak
intLowBroken := true
intBearType := intTrend == BULL ? "CHoCH" : "BOS"
intTrend := BEAR
// Draw internal structure lines
if intBullBreak and showInternalStructInput
if intHighLevel != swHighLevel
line.new(nz(intHighBar, bar_index), intHighLevel, bar_index, intHighLevel,
color = bullColorInput, width = 1, style = line.style_dashed)
label.new(math.round((nz(intHighBar, bar_index) + bar_index) / 2), intHighLevel, intBullType,
color = color(na), textcolor = color.new(bullColorInput, 30),
style = label.style_label_down, size = size.small)
if intBearBreak and showInternalStructInput
if intLowLevel != swLowLevel
line.new(nz(intLowBar, bar_index), intLowLevel, bar_index, intLowLevel,
color = bearColorInput, width = 1, style = line.style_dashed)
label.new(math.round((nz(intLowBar, bar_index) + bar_index) / 2), intLowLevel, intBearType,
color = color(na), textcolor = color.new(bearColorInput, 30),
style = label.style_label_up, size = size.small)
// ══════════════════════════════════════
// ORDER BLOCKS
// ══════════════════════════════════════
var array<float> obTop = array.new<float>()
var array<float> obBottom = array.new<float>()
var array<int> obBarIdx = array.new<int>()
var array<int> obBias = array.new<int>()
var array<string> obGrade = array.new<string>()
var array<box> obBoxes = array.new<box>()
var array<line> obMidlines = array.new<line>()
var array<label> obLabels = array.new<label>()
var array<label> obSideLabels = array.new<label>()
// Find last bearish candle before bullish break
findBullishOB(int fromBar, int toBar) =>
float obHi = na
float obLo = na
int obBr = na
if fromBar > 0 and toBar > 0 and fromBar <= toBar
int lookback = math.min(toBar - fromBar, 30)
for i = 1 to lookback
if bar_index - i >= 0
if close[i] < open[i]
obHi := high[i]
obLo := low[i]
obBr := bar_index - i
break
[obHi, obLo, obBr]
// Find last bullish candle before bearish break
findBearishOB(int fromBar, int toBar) =>
float obHi = na
float obLo = na
int obBr = na
if fromBar > 0 and toBar > 0 and fromBar <= toBar
int lookback = math.min(toBar - fromBar, 30)
for i = 1 to lookback
if bar_index - i >= 0
if close[i] > open[i]
obHi := high[i]
obLo := low[i]
obBr := bar_index - i
break
[obHi, obLo, obBr]
// Create OB on swing structure breaks
if swBullBreak and showObInput
[oH, oL, oB] = findBullishOB(nz(swLowBar, bar_index - 10), bar_index)
if not na(oH)
string grade = "C"
if volSpike
grade := "B"
obTop.unshift(oH)
obBottom.unshift(oL)
obBarIdx.unshift(oB)
obBias.unshift(BULL)
obGrade.unshift(grade)
// Box: right edge = current bar (will be updated every bar)
box newBox = box.new(oB, oH, bar_index, oL,
bgcolor = obBullColorInput,
border_color = color.new(bullColorInput, 60),
border_width = 1,
border_style = line.style_dotted)
obBoxes.unshift(newBox)
// Midline (CE)
float mid = math.avg(oH, oL)
line newMid = line.new(oB, mid, bar_index, mid,
color = color.new(bullColorInput, 50),
style = line.style_dotted,
width = 1)
obMidlines.unshift(newMid)
label gradeLbl = na
if showObGradeInput
gradeLbl := label.new(oB, oH, grade,
color = color(na),
textcolor = bullColorInput,
style = label.style_label_down,
size = size.small,
tooltip = "Bullish OB | Grade: " + grade)
obLabels.unshift(gradeLbl)
// Right-side label for OB (2 bars outside box)
label sideLbl = label.new(bar_index + 2, mid, "OB",
color = color(na),
textcolor = color.new(bullColorInput, 30),
style = label.style_label_left,
size = size.small,
tooltip = "Bullish OB | Grade: " + grade)
obSideLabels.unshift(sideLbl)
if swBearBreak and showObInput
[oH, oL, oB] = findBearishOB(nz(swHighBar, bar_index - 10), bar_index)
if not na(oH)
string grade = "C"
if volSpike
grade := "B"
obTop.unshift(oH)
obBottom.unshift(oL)
obBarIdx.unshift(oB)
obBias.unshift(BEAR)
obGrade.unshift(grade)
box newBox = box.new(oB, oH, bar_index, oL,
bgcolor = obBearColorInput,
border_color = color.new(bearColorInput, 60),
border_width = 1,
border_style = line.style_dotted)
obBoxes.unshift(newBox)
float mid = math.avg(oH, oL)
line newMid = line.new(oB, mid, bar_index, mid,
color = color.new(bearColorInput, 50),
style = line.style_dotted,
width = 1)
obMidlines.unshift(newMid)
label gradeLbl = na
if showObGradeInput
gradeLbl := label.new(oB, oH, grade,
color = color(na),
textcolor = bearColorInput,
style = label.style_label_down,
size = size.small,
tooltip = "Bearish OB | Grade: " + grade)
obLabels.unshift(gradeLbl)
// Right-side label for OB (2 bars outside box)
label sideLbl = label.new(bar_index + 2, mid, "OB",
color = color(na),
textcolor = color.new(bearColorInput, 30),
style = label.style_label_left,
size = size.small,
tooltip = "Bearish OB | Grade: " + grade)
obSideLabels.unshift(sideLbl)
// Update OB right edges to current bar + mitigate
if obTop.size() > 0
for i = obTop.size() - 1 to 0
bool mitigated = false
int bias = obBias.get(i)
if bias == BULL
float checkPrice = obMitigationInput == "Close" ? close : low
if checkPrice < obBottom.get(i)
mitigated := true
else
float checkPrice = obMitigationInput == "Close" ? close : high
if checkPrice > obTop.get(i)
mitigated := true
if mitigated
box.delete(obBoxes.get(i))
line.delete(obMidlines.get(i))
label lbl = obLabels.get(i)
if not na(lbl)
label.delete(lbl)
label sideLbl = obSideLabels.get(i)
if not na(sideLbl)
label.delete(sideLbl)
obTop.remove(i)
obBottom.remove(i)
obBarIdx.remove(i)
obBias.remove(i)
obGrade.remove(i)
obBoxes.remove(i)
obMidlines.remove(i)
obLabels.remove(i)
obSideLabels.remove(i)
else
// Extend right edge to current bar
box.set_right(obBoxes.get(i), bar_index)
line.set_x2(obMidlines.get(i), bar_index)
// Update right-side label position (2 bars outside)
label sideLbl = obSideLabels.get(i)
if not na(sideLbl)
label.set_x(sideLbl, bar_index + 2)
label.set_y(sideLbl, math.avg(obTop.get(i), obBottom.get(i)))
// Show/hide midline based on toggle
line.set_color(obMidlines.get(i), showObMidlineInput ?
color.new(bias == BULL ? bullColorInput : bearColorInput, 50) :
color.new(color.black, 100))
// Cap OB count
while obTop.size() > obMaxInput
int last = obTop.size() - 1
box.delete(obBoxes.get(last))
line.delete(obMidlines.get(last))
label lbl = obLabels.get(last)
if not na(lbl)
label.delete(lbl)
label sideLbl = obSideLabels.get(last)
if not na(sideLbl)
label.delete(sideLbl)
obTop.remove(last)
obBottom.remove(last)
obBarIdx.remove(last)
obBias.remove(last)
obGrade.remove(last)
obBoxes.remove(last)
obMidlines.remove(last)
obLabels.remove(last)
obSideLabels.remove(last)
// ══════════════════════════════════════
// FAIR VALUE GAPS
// ══════════════════════════════════════
var array<float> fvgTop = array.new<float>()
var array<float> fvgBottom = array.new<float>()
var array<int> fvgBias = array.new<int>()
var array<box> fvgBoxes = array.new<box>()
var array<line> fvgMidlines = array.new<line>()
var array<label> fvgSideLabels = array.new<label>()
// Bullish FVG: current low > 2 bars ago high
bool rawBullFvg = low > high[2] and barstate.isconfirmed and isWarmedUp
// Bearish FVG: current high < 2 bars ago low
bool rawBearFvg = high < low[2] and barstate.isconfirmed and isWarmedUp
float fvgMinSize = fvgFilterInput ? atrVal * 0.5 : 0.0
bool bullFvg = rawBullFvg and (low - high[2]) > fvgMinSize and showFvgInput
bool bearFvg = rawBearFvg and (low[2] - high) > fvgMinSize and showFvgInput
if bullFvg
float top = low
float bot = high[2]
fvgTop.unshift(top)
fvgBottom.unshift(bot)
fvgBias.unshift(BULL)
box newBox = box.new(bar_index - 1, top, bar_index, bot,
bgcolor = fvgBullColorInput,
border_color = color.new(fvgBullColorInput, 50),
border_width = 1,
border_style = line.style_dotted)
fvgBoxes.unshift(newBox)
float mid = math.avg(top, bot)
line newMid = line.new(bar_index - 1, mid, bar_index, mid,
color = color.new(#2962FF, 50),
style = line.style_dotted,
width = 1)
fvgMidlines.unshift(newMid)
// Right-side label for FVG (2 bars outside box)
label sideLbl = label.new(bar_index + 2, mid, "FVG",
color = color(na),
textcolor = color.new(#2962FF, 30),
style = label.style_label_left,
size = size.small,
tooltip = "Bullish FVG")
fvgSideLabels.unshift(sideLbl)
if bearFvg
float top = low[2]
float bot = high
fvgTop.unshift(top)
fvgBottom.unshift(bot)
fvgBias.unshift(BEAR)
box newBox = box.new(bar_index - 1, top, bar_index, bot,
bgcolor = fvgBearColorInput,
border_color = color.new(fvgBearColorInput, 50),
border_width = 1,
border_style = line.style_dotted)
fvgBoxes.unshift(newBox)
float mid = math.avg(top, bot)
line newMid = line.new(bar_index - 1, mid, bar_index, mid,
color = color.new(#FF6D00, 50),
style = line.style_dotted,
width = 1)
fvgMidlines.unshift(newMid)
// Right-side label for FVG (2 bars outside box)
label sideLbl2 = label.new(bar_index + 2, mid, "FVG",
color = color(na),
textcolor = color.new(#FF6D00, 30),
style = label.style_label_left,
size = size.small,
tooltip = "Bearish FVG")
fvgSideLabels.unshift(sideLbl2)
// IFVG queue — filled FVGs get queued here with inverted bias
var array<float> ifvgQueueTop = array.new<float>()
var array<float> ifvgQueueBot = array.new<float>()
var array<int> ifvgQueueBias = array.new<int>()
// Update FVG: right edge, remove if filled
if fvgTop.size() > 0
for i = fvgTop.size() - 1 to 0
float top = fvgTop.get(i)
float bot = fvgBottom.get(i)
int bias = fvgBias.get(i)
bool filled = false
if bias == BULL
filled := low <= bot
else
filled := high >= top
if filled
// Queue IFVG with inverted bias (bull FVG → bear IFVG, bear FVG → bull IFVG)
if showIfvgInput
ifvgQueueTop.push(top)
ifvgQueueBot.push(bot)
ifvgQueueBias.push(bias == BULL ? BEAR : BULL)
box.delete(fvgBoxes.get(i))
line.delete(fvgMidlines.get(i))
label sideLbl = fvgSideLabels.get(i)
if not na(sideLbl)
label.delete(sideLbl)
fvgTop.remove(i)
fvgBottom.remove(i)
fvgBias.remove(i)
fvgBoxes.remove(i)
fvgMidlines.remove(i)
fvgSideLabels.remove(i)
else
// Extend right edge to current bar
box.set_right(fvgBoxes.get(i), bar_index)
line.set_x2(fvgMidlines.get(i), bar_index)
// Update right-side label position (2 bars outside)
label sideLbl = fvgSideLabels.get(i)
if not na(sideLbl)
label.set_x(sideLbl, bar_index + 2)
label.set_y(sideLbl, math.avg(top, bot))
// Show/hide midline
line.set_color(fvgMidlines.get(i), showFvgMidlineInput ?
color.new(bias == BULL ? #2962FF : #FF6D00, 50) :
color.new(color.black, 100))
// Cap FVG count
while fvgTop.size() > fvgMaxInput
int last = fvgTop.size() - 1
box.delete(fvgBoxes.get(last))
line.delete(fvgMidlines.get(last))
label sideLbl = fvgSideLabels.get(last)
if not na(sideLbl)
label.delete(sideLbl)
fvgTop.remove(last)
fvgBottom.remove(last)
fvgBias.remove(last)
fvgBoxes.remove(last)
fvgMidlines.remove(last)
fvgSideLabels.remove(last)
// ══════════════════════════════════════
// INVERSE FAIR VALUE GAPS (IFVG)
// ══════════════════════════════════════
var array<float> ifvgTop = array.new<float>()
var array<float> ifvgBottom = array.new<float>()
var array<int> ifvgBias = array.new<int>()
var array<int> ifvgBarIdx = array.new<int>()
var array<box> ifvgBoxes = array.new<box>()
var array<label> ifvgSideLabels = array.new<label>()
// IFVG creation happens inside FVG fill detection (see above).
// Queued IFVGs are processed here after the FVG loop.
// Process queued IFVGs
if ifvgQueueTop.size() > 0 and showIfvgInput
for q = 0 to ifvgQueueTop.size() - 1
float iTop = ifvgQueueTop.get(q)
float iBot = ifvgQueueBot.get(q)
int iBias = ifvgQueueBias.get(q)
color borderClr = iBias == BULL ? ifvgBullColorInput : ifvgBearColorInput
ifvgTop.unshift(iTop)
ifvgBottom.unshift(iBot)
ifvgBias.unshift(iBias)
ifvgBarIdx.unshift(bar_index)
box newBox = box.new(bar_index, iTop, bar_index, iBot,
bgcolor = color.new(color.black, 100),
border_color = borderClr,
border_width = 1,
border_style = line.style_dashed)
ifvgBoxes.unshift(newBox)
float iMid = math.avg(iTop, iBot)
label sideLbl = label.new(bar_index + 2, iMid, "IFVG",
color = color(na),
textcolor = color.new(borderClr, 10),
style = label.style_label_left,
size = size.small,
tooltip = (iBias == BULL ? "Bullish" : "Bearish") + " IFVG")
ifvgSideLabels.unshift(sideLbl)
ifvgQueueTop.clear()
ifvgQueueBot.clear()
ifvgQueueBias.clear()
// Update IFVG: extend right edge, mitigate on close
if ifvgTop.size() > 0
for i = ifvgTop.size() - 1 to 0
float iTop = ifvgTop.get(i)
float iBot = ifvgBottom.get(i)
int iBias = ifvgBias.get(i)
bool mitigated = false
// IFVG mitigated by close through the zone
if iBias == BULL
// Bullish IFVG (support) — mitigated when close below bottom
if close < iBot
mitigated := true
else
// Bearish IFVG (resistance) — mitigated when close above top
if close > iTop
mitigated := true
if mitigated
box.delete(ifvgBoxes.get(i))
label sideLbl = ifvgSideLabels.get(i)
if not na(sideLbl)
label.delete(sideLbl)
ifvgTop.remove(i)
ifvgBottom.remove(i)
ifvgBias.remove(i)
ifvgBarIdx.remove(i)
ifvgBoxes.remove(i)
ifvgSideLabels.remove(i)
else
// Extend right edge
box.set_right(ifvgBoxes.get(i), bar_index)
// Update side label position
label sideLbl = ifvgSideLabels.get(i)
if not na(sideLbl)
label.set_x(sideLbl, bar_index + 2)
label.set_y(sideLbl, math.avg(iTop, iBot))
// Cap IFVG count
while ifvgTop.size() > ifvgMaxInput
int last = ifvgTop.size() - 1
box.delete(ifvgBoxes.get(last))
label sideLbl = ifvgSideLabels.get(last)
if not na(sideLbl)
label.delete(sideLbl)
ifvgTop.remove(last)
ifvgBottom.remove(last)
ifvgBias.remove(last)
ifvgBarIdx.remove(last)
ifvgBoxes.remove(last)
ifvgSideLabels.remove(last)
// ══════════════════════════════════════
// FIBONACCI RETRACEMENT (Strong High ↔ Strong Low)
// ══════════════════════════════════════
// Fib drawing objects (persistent, redrawn each bar on last)
var line fibLine0 = na
var line fibLine236 = na
var line fibLine382 = na
var line fibLine5 = na
var line fibLine618 = na
var line fibLine786 = na
var line fibLine1 = na
var label fibLbl0 = na
var label fibLbl236 = na
var label fibLbl382 = na
var label fibLbl5 = na
var label fibLbl618 = na
var label fibLbl786 = na
var label fibLbl1 = na
var box fibOTEBox = na
color FIB_COLOR = isDark ? color.new(#9E9E9E, 50) : color.new(#757575, 50)
color FIB_TEXT = isDark ? color.new(#BDBDBD, 30) : color.new(#616161, 30)
if showFibInput and barstate.islast and not na(trailHigh) and not na(trailLow) and trailHigh != trailLow
// Delete previous drawings
line.delete(fibLine0)
line.delete(fibLine236)
line.delete(fibLine382)
line.delete(fibLine5)
line.delete(fibLine618)
line.delete(fibLine786)
line.delete(fibLine1)
label.delete(fibLbl0)
label.delete(fibLbl236)
label.delete(fibLbl382)
label.delete(fibLbl5)
label.delete(fibLbl618)
label.delete(fibLbl786)
label.delete(fibLbl1)
box.delete(fibOTEBox)
float fibRange = trailHigh - trailLow
float fib0 = trailHigh // 0
float fib236 = trailHigh - fibRange * 0.236
float fib382 = trailHigh - fibRange * 0.382
float fib5 = trailHigh - fibRange * 0.5
float fib618 = trailHigh - fibRange * 0.618
float fib786 = trailHigh - fibRange * 0.786
float fib1 = trailLow // 1
int leftBar = math.min(nz(trailHighBar, bar_index - 50), nz(trailLowBar, bar_index - 50))
int rightBar = bar_index + 10
// Draw fib lines
fibLine0 := line.new(leftBar, fib0, rightBar, fib0, color = FIB_COLOR, style = line.style_dotted, width = 1)
fibLine236 := line.new(leftBar, fib236, rightBar, fib236, color = FIB_COLOR, style = line.style_dotted, width = 1)
fibLine382 := line.new(leftBar, fib382, rightBar, fib382, color = FIB_COLOR, style = line.style_dotted, width = 1)
fibLine5 := line.new(leftBar, fib5, rightBar, fib5, color = FIB_COLOR, style = line.style_dotted, width = 1)
fibLine618 := line.new(leftBar, fib618, rightBar, fib618, color = FIB_COLOR, style = line.style_dotted, width = 1)
fibLine786 := line.new(leftBar, fib786, rightBar, fib786, color = FIB_COLOR, style = line.style_dotted, width = 1)
fibLine1 := line.new(leftBar, fib1, rightBar, fib1, color = FIB_COLOR, style = line.style_dotted, width = 1)
// Labels on right side
fibLbl0 := label.new(rightBar, fib0, "0", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
fibLbl236 := label.new(rightBar, fib236, "0.236", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
fibLbl382 := label.new(rightBar, fib382, "0.382", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
fibLbl5 := label.new(rightBar, fib5, "0.5", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
fibLbl618 := label.new(rightBar, fib618, "0.618", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
fibLbl786 := label.new(rightBar, fib786, "0.786", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
fibLbl1 := label.new(rightBar, fib1, "1", color = color(na), textcolor = FIB_TEXT, style = label.style_label_left, size = size.tiny)
// OTE zone highlight (0.5 – 0.618)
if showFibOTEInput
fibOTEBox := box.new(leftBar, fib5, rightBar, fib618,
bgcolor = fibColorInput,
border_color = color.new(#FFD600, 80),
border_width = 0)
// ══════════════════════════════════════
// PLOTS & BACKGROUND
// ══════════════════════════════════════
bgcolor(showBgInput and swTrend == BULL ? BULL_BG :
showBgInput and swTrend == BEAR ? BEAR_BG : na,
title = "Trend Background")
// ══════════════════════════════════════
// STRONG / WEAK HIGH-LOW LINES
// ══════════════════════════════════════
var line strongHighLine = na
var line strongLowLine = na
var label strongHighLbl = na
var label strongLowLbl = na
if showStrongWeakInput and barstate.islast and not na(trailHigh) and not na(trailLow)
int rightBar = bar_index + 20
line.delete(strongHighLine)
line.delete(strongLowLine)
label.delete(strongHighLbl)
label.delete(strongLowLbl)
strongHighLine := line.new(nz(trailHighBar, bar_index), trailHigh, rightBar, trailHigh,
color = bearColorInput, style = line.style_dashed, width = 1)
strongLowLine := line.new(nz(trailLowBar, bar_index), trailLow, rightBar, trailLow,
color = bullColorInput, style = line.style_dashed, width = 1)
string hiLabel = swTrend == BEAR ? "Strong High" : "Weak High"
string loLabel = swTrend == BULL ? "Strong Low" : "Weak Low"
strongHighLbl := label.new(rightBar, trailHigh, hiLabel,
color = color(na), textcolor = bearColorInput,
style = label.style_label_left, size = size.small)
strongLowLbl := label.new(rightBar, trailLow, loLabel,
color = color(na), textcolor = bullColorInput,
style = label.style_label_left, size = size.small)
// ══════════════════════════════════════
// DASHBOARD
// ══════════════════════════════════════
if showDashInput and barstate.islast
var dashTable = table.new(getDashPos(dashPosStr), 2, 10, TABLE_BG, TABLE_BORDER, 1, TABLE_BORDER, 1)
// Row 0: Header
table.cell(dashTable, 0, 0, "WillyAlgoTrader SME", text_color = HEADER_TEXT,
bgcolor = HEADER_BG, text_size = size.small, text_halign = text.align_center)
table.merge_cells(dashTable, 0, 0, 1, 0)
// Row 1: Swing Trend
string trendStr = swTrend == BULL ? "Bullish" : swTrend == BEAR ? "Bearish" : "—"
color trendClr = swTrend == BULL ? bullColorInput : swTrend == BEAR ? bearColorInput : TEXT_MUTED
table.cell(dashTable, 0, 1, "Swing Trend", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG)
table.cell(dashTable, 1, 1, trendStr, text_color = trendClr, text_size = size.small, bgcolor = TABLE_BG)
// Row 2: Internal Trend
string intStr = intTrend == BULL ? "Bullish" : intTrend == BEAR ? "Bearish" : "—"
color intClr = intTrend == BULL ? bullColorInput : intTrend == BEAR ? bearColorInput : TEXT_MUTED
table.cell(dashTable, 0, 2, "Internal", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT)
table.cell(dashTable, 1, 2, intStr, text_color = intClr, text_size = size.small, bgcolor = TABLE_ROW_ALT)
// Row 3: Fib Range
string fibStr = not na(trailHigh) and not na(trailLow) ? str.tostring(trailHigh, format.mintick) + " — " + str.tostring(trailLow, format.mintick) : "—"
table.cell(dashTable, 0, 3, "Fib Range", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG)
table.cell(dashTable, 1, 3, fibStr, text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_BG)
// Row 4: Active OBs
table.cell(dashTable, 0, 4, "Active OBs", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT)
table.cell(dashTable, 1, 4, str.tostring(obTop.size()), text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_ROW_ALT)
// Row 5: Active FVGs
table.cell(dashTable, 0, 5, "Active FVGs", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG)
table.cell(dashTable, 1, 5, str.tostring(fvgTop.size()), text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_BG)
// Row 6: Active IFVGs
table.cell(dashTable, 0, 6, "Active IFVGs", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT)
table.cell(dashTable, 1, 6, str.tostring(ifvgTop.size()), text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_ROW_ALT)
// Row 7: Timeframe
table.cell(dashTable, 0, 7, "Timeframe", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG)
table.cell(dashTable, 1, 7, timeframe.period, text_color = TEXT_COLOR, text_size = size.small, bgcolor = TABLE_BG)
// Row 8: HTF Bias
string htfStr = htfInput == "" ? "Off" : htfBull ? "Bullish" : "Bearish"
color htfClr = htfInput == "" ? TEXT_MUTED : htfBull ? bullColorInput : bearColorInput
table.cell(dashTable, 0, 8, "HTF Bias", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_ROW_ALT)
table.cell(dashTable, 1, 8, htfStr, text_color = htfClr, text_size = size.small, bgcolor = TABLE_ROW_ALT)
// Row 9: Version
table.cell(dashTable, 0, 9, "Version", text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG)
table.cell(dashTable, 1, 9, INDICATOR_VERSION, text_color = TEXT_MUTED, text_size = size.small, bgcolor = TABLE_BG)
// ══════════════════════════════════════
// WATERMARK — WillyAlgoTrader
// ══════════════════════════════════════
if barstate.islast and showWatermarkInput
var wmTable = table.new(
position = position.bottom_center,
columns = 1,
rows = 1,
bgcolor = color.new(color.black, 100),
border_color = color.new(color.black, 100),
border_width = 0,
frame_color = color.new(color.black, 100),
frame_width = 0)
table.cell(wmTable, 0, 0, "WillyAlgoTrader",
text_color = WM_COLOR,
text_size = size.normal,
text_halign = text.align_center,
bgcolor = color.new(color.black, 100))
// ══════════════════════════════════════
// ALERTS
// ══════════════════════════════════════
string bosMsg = webhookInput ? '{"event":"bos","ticker":"' + syminfo.tickerid + '","direction":"' + (swBullBreak ? "bull" : "bear") + '","price":' + str.tostring(close) + '}' : (swBullBreak ? "🟢" : "🔴") + " BOS | " + syminfo.tickerid + " | " + str.tostring(close, format.mintick)
string chochMsg = webhookInput ? '{"event":"choch","ticker":"' + syminfo.tickerid + '","direction":"' + (swBullBreak ? "bull" : "bear") + '","price":' + str.tostring(close) + '}' : (swBullBreak ? "🟢" : "🔴") + " CHoCH | " + syminfo.tickerid + " | " + str.tostring(close, format.mintick)
if swBullBreak and swBullType == "BOS"
alert(bosMsg, alert.freq_once_per_bar_close)
if swBearBreak and swBearType == "BOS"
alert(bosMsg, alert.freq_once_per_bar_close)
if swBullBreak and swBullType == "CHoCH"
alert(chochMsg, alert.freq_once_per_bar_close)
if swBearBreak and swBearType == "CHoCH"
alert(chochMsg, alert.freq_once_per_bar_close)
// Clean up excess visual objects
if array.size(label.all) > 450
label.delete(array.shift(label.all))
if array.size(line.all) > 450
line.delete(array.shift(line.all))