Smart Money Engine [WillyAlgoTrader]

WillyAlgoTrader · study · 1046 行 · 点赞 2,784 · TradingView 原页

本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。

Pine Script

//@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))

← 返回列表