Indicator · Pine Script v6 · On the chart

Unicorn Model

by fxraptor-alpha

The Unicorn Model indicator identifies liquidity sweeps across multiple higher timeframes and highlights breaker blocks on the price chart.

View on GitHub

GitHub stars137on the repository
Pine versionv6current
Lines of code1,383
Settings40inputs
LicenceCustom licencefile: MPL-2.0
Last update3 weeks ago6 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,383 lines of Pine Script v6 from unicorn-model/unicorn-model.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

unicorn-model.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Copyright (c) 2026 fxraptor-alpha. Attribution notices are governed by the license in this repository.
//@version=6
indicator("Unicorn Model", shorttitle="UM", overlay=true, max_boxes_count=500, max_lines_count=500, max_labels_count=500)

// ========================================
// USER DEFINED TYPES
// ========================================
type HTFLevel
    string tfLabel      // e.g. "4H", "1H"
    string levelType    // "Swing" or "OHLC"
    bool isHigh         // true = High / false = Low
    float price
    int startBar
    bool isSwept
    int sweepBar
    string labelText

type Setup
    string dir                // "BULL" or "BEAR"
    int sweepIndex            // Bar index of the sweep extreme
    float sweepExtreme        // Highest high or lowest low of the sweep leg
    float sweptLevel          // The HTF liquidity level that was swept
    int sweptLevelStartBar   // Bar index when the swept level was formed
    int breakerStartIdx       // Start bar index of the breaker candles series
    int breakerEndIdx         // End bar index of the breaker candles series
    float breakerTop          // Max high of the breaker candles series
    float breakerBottom       // Min low of the breaker candles series
    bool isConfirmed          // Confirmed yet?
    int confIndex             // Bar index of confirmation
    float entryPrice          // Price at confirmation (close)
    float invalidationPrice   // Invalidation level
    bool isInvalidated        // Has it been invalidated?
    bool isTargetHit          // Has target been hit?
    line levelLine            // Dotted level line drawn when setup confirmed
    label levelLabel          // Text label at origin of level line
    box breakerBox            // Box drawing for breaker
    box fvgBox                // Box drawing for FVG
    line[] targetLines        // Array of lines for target projections
    label[] targetLabels      // Array of labels for targets
    label setupLabel          // Monospaced text label (+Unicorn / -Breaker)

// ========================================
// GLOBAL STATE ARRAYS (Defined before functions)
// ========================================
var HTFLevel[] activeHTFLevels = array.new<HTFLevel>()
var Setup[] activeSetups = array.new<Setup>()

// ========================================
// GLOBAL USER FUNCTIONS (Defined next)
// ========================================

// Convert TF to label dynamically using seconds
f_getTFLabel(string tf) =>
    seconds = timeframe.in_seconds(tf)
    res = ""
    if seconds < 60
        res := str.tostring(seconds) + "S"
    else if (seconds / 60) < 60
        res := str.tostring(math.round(seconds / 60)) + "m"
    else if (seconds / 3600) < 24
        res := str.tostring(math.round(seconds / 3600)) + "H"
    else if (seconds / 86400) < 7
        res := str.tostring(math.round(seconds / 86400)) + "D"
    else if (seconds / 86400) < 30
        res := str.tostring(math.round(seconds / 604800)) + "W"
    else
        res := str.tostring(math.round(seconds / 2592000)) + "M"
    res

// Resolve standard TradingView timeframe strings corresponding to hierarchy indexes
f_getHTFByVal(idx) =>
    switch idx
        0 => "5"
        1 => "15"
        2 => "30"
        3 => "60"
        4 => "150"
        5 => "240"
        6 => "1D"
        7 => "720"
        8 => "1D"
        9 => "2D"
        10 => "W"
        11 => "4D"
        12 => "W"
        13 => "15D"
        14 => "45D"
        15 => "15W"
        16 => "15M"
        => "60"

// Automatic HTF selection mapping (Mirroring Chronology Hierarchy)
getAutoHTFs() =>
    int tf = timeframe.in_seconds()
    int k = 0
    if tf <= 15           // 15S and below → 5m
        k := 0
    else if tf <= 60      // 1m and below → 15m
        k := 1
    else if tf <= 180     // 3m and below → 30m
        k := 2
    else if tf <= 300     // 5m and below → 1H
        k := 3
    else if tf <= 600     // 10m and below → 2.5H (150m)
        k := 4
    else if tf <= 900     // 15m and below → 4H
        k := 5
    else if tf <= 1800    // 30m and below → 1D
        k := 6
    else if tf <= 2700    // 45m and below → 12H (720m)
        k := 7
    else if tf <= 3600    // 1H and below → 1D
        k := 8
    else if tf <= 7200    // 2H and below → 2D
        k := 9
    else if tf <= 14400   // 4H and below → 1W
        k := 10
    else if tf <= 21600   // 6H and below → 4D
        k := 11
    else if tf <= 43200   // 12H and below → 1W
        k := 12
    else if tf <= 86400   // 1D and below → 15D
        k := 13
    else if tf <= 259200  // 3D and below → 45D
        k := 14
    else if tf <= 604800  // 1W and below → 15W
        k := 15
    else if tf <= 2592000 // 1M and below → 15M
        k := 16
    else                  // 3M+ → 45M
        k := 17

    string resolvedA = k - 1 >= 0 ? f_getHTFByVal(k - 1) : ""
    string resolvedB = f_getHTFByVal(k)
    string resolvedC = f_getHTFByVal(k + 1)
    string resolvedD = f_getHTFByVal(k + 2)
    [resolvedA, resolvedB, resolvedC, resolvedD]

// Parse comma separated values to array of floats
f_parseTargets(string s) =>
    float[] tgts = array.new_float()
    string cur = ""
    for i = 0 to str.length(s) - 1
        char = str.substring(s, i, i + 1)
        if char == ","
            val = str.tonumber(cur)
            if not na(val)
                array.push(tgts, val)
            cur := ""
        else
            if char != " "
                cur := cur + char
    if str.length(cur) > 0
        val = str.tonumber(cur)
        if not na(val)
            array.push(tgts, val)
    tgts

// Convert text size parameter to Pine size constant
f_getLabelSize(string sz) =>
    switch sz
        "Tiny"   => size.tiny
        "Small"  => size.small
        "Normal" => size.normal
        "Large"  => size.large
        => size.small

// Convert text style parameter to Pine style constant
f_getLineStyle(string st) =>
    switch st
        "Solid"  => line.style_solid
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        => line.style_dotted

// Convert table text size parameter to Pine size constant
f_getTableTextSize(string sz) =>
    switch sz
        "tiny"   => size.tiny
        "small"  => size.small
        "normal" => size.normal
        "large"  => size.large
        "huge"   => size.huge
        => size.tiny

// Convert table position string to position constant
f_getTablePosition(string pos) =>
    switch pos
        "Top Left"      => position.top_left
        "Top Center"    => position.top_center
        "Top Right"     => position.top_right
        "Middle Left"   => position.middle_left
        "Middle Center" => position.middle_center
        "Middle Right"  => position.middle_right
        "Bottom Left"   => position.bottom_left
        "Bottom Center" => position.bottom_center
        "Bottom Right"  => position.bottom_right
        => position.top_right

// Swing points definition helper
f_getHTFData() =>
    hPrev = high[1]
    lPrev = low[1]
    isSwingHigh = high > high[1] and close < close[1]
    isSwingLow = low < low[1] and close > close[1]
    latestSH = ta.valuewhen(isSwingHigh, high, 0)
    latestSL = ta.valuewhen(isSwingLow, low, 0)
    [hPrev, lPrev, latestSH, latestSL]

// Find opposing candle runs
f_findDownCandlesSeries(sweepOffset) =>
    int startOffset = na
    int endOffset = na
    for i = sweepOffset to sweepOffset + 40
        if i >= bar_index
            break
        if close[i] < open[i]
            int tempEnd = i
            for j = i + 1 to i + 20
                if j >= bar_index
                    break
                if close[j] < open[j]
                    tempEnd := j
                else
                    break
            runLength = tempEnd - i + 1
            if runLength >= 2
                startOffset := i
                endOffset := tempEnd
                break
    [startOffset, endOffset]

f_findUpCandlesSeries(sweepOffset) =>
    int startOffset = na
    int endOffset = na
    for i = sweepOffset to sweepOffset + 40
        if i >= bar_index
            break
        if close[i] > open[i]
            int tempEnd = i
            for j = i + 1 to i + 20
                if j >= bar_index
                    break
                if close[j] > open[j]
                    tempEnd := j
                else
                    break
            runLength = tempEnd - i + 1
            if runLength >= 2
                startOffset := i
                endOffset := tempEnd
                break
    [startOffset, endOffset]

f_getSeriesRange(startOffset, endOffset) =>
    float maxH = na
    float minL = na
    for i = startOffset to endOffset
        maxH := na(maxH) ? high[i] : math.max(maxH, high[i])
        minL := na(minL) ? low[i] : math.min(minL, low[i])
    [maxH, minL]

// FVG Overlap Finders
f_findBullishFVG(breakerT, breakerB, startIdx, enableFvgAtrFilter, fvgAtrMult) =>
    float fvgT = na
    float fvgB = na
    int fvgIdx = na
    int fvgRightIdx = na
    int limit = math.min(bar_index - startIdx, 100)
    if limit >= 0
        for i = 0 to limit
            if low[i] > high[i+2]
                fTop = low[i]
                fBtm = high[i+2]
                atrVal = ta.atr(14)[i]
                if enableFvgAtrFilter and (fTop - fBtm) < (atrVal * fvgAtrMult)
                    continue
                bool overlaps = math.max(fBtm, breakerB) < math.min(fTop, breakerT)
                if overlaps
                    bool unmitigated = true
                    for j = 0 to i
                        if low[j] < fBtm
                            unmitigated := false
                            break
                    if unmitigated
                        fvgT := fTop
                        fvgB := fBtm
                        fvgIdx := bar_index - i - 1
                        fvgRightIdx := bar_index - i  // right boundary: the newer candle
                        break
    [fvgT, fvgB, fvgIdx, fvgRightIdx]

f_findBearishFVG(breakerT, breakerB, startIdx, enableFvgAtrFilter, fvgAtrMult) =>
    float fvgT = na
    float fvgB = na
    int fvgIdx = na
    int fvgRightIdx = na
    int limit = math.min(bar_index - startIdx, 100)
    if limit >= 0
        for i = 0 to limit
            if high[i] < low[i+2]
                fTop = low[i+2]
                fBtm = high[i]
                atrVal = ta.atr(14)[i]
                if enableFvgAtrFilter and (fTop - fBtm) < (atrVal * fvgAtrMult)
                    continue
                bool overlaps = math.max(fBtm, breakerB) < math.min(fTop, breakerT)
                if overlaps
                    bool unmitigated = true
                    for j = 0 to i
                        if high[j] > fTop
                            unmitigated := false
                            break
                    if unmitigated
                        fvgT := fTop
                        fvgB := fBtm
                        fvgIdx := bar_index - i - 1
                        fvgRightIdx := bar_index - i  // right boundary: the newer candle
                        break
    [fvgT, fvgB, fvgIdx, fvgRightIdx]

// Size filter
f_checkSizeFilter(breakerTop, breakerBottom, sizeFilterMode, minBreakerSize, maxBreakerSize, breakerSizeUnit) =>
    bool pass = true
    if sizeFilterMode == "Filtered"
        float bSize = breakerTop - breakerBottom
        float checkSize = bSize
        if breakerSizeUnit == "Ticks"
            checkSize := bSize / syminfo.mintick
        else if breakerSizeUnit == "ATR"
            checkSize := bSize / ta.atr(14)
        if checkSize < minBreakerSize or checkSize > maxBreakerSize
            pass := false
    pass

// Find exact extreme index where price was hit (looks back up to 1000 bars)
f_findExtremeBar(float priceVal, bool isHigh) =>
    int extBar = na
    if not na(priceVal)
        for i = 0 to 1000
            if i >= bar_index
                break
            float checkVal = isHigh ? high[i] : low[i]
            if math.abs(checkVal - priceVal) < (syminfo.mintick * 0.1)
                extBar := bar_index - i
                break
    extBar

// Register active levels in memory (no visual rendering here)
f_registerLevel(string tfLabel, string lvlType, bool isHigh, float priceVal) =>
    if not na(priceVal)
        int extBar = f_findExtremeBar(priceVal, isHigh)
        int startB = na(extBar) ? bar_index : extBar

        // Check if the level has already been breached in the past (between startB + 1 and bar_index - 1, capped at 999 bars max)
        int startSearch = math.max(startB + 1, bar_index - 999)
        bool alreadyBreached = false
        if not na(extBar) and startSearch <= bar_index - 1
            for i = startSearch to bar_index - 1
                float checkHigh = high[bar_index - i]
                float checkLow = low[bar_index - i]
                if isHigh and checkHigh >= priceVal
                    alreadyBreached := true
                    break
                else if not isHigh and checkLow <= priceVal
                    alreadyBreached := true
                    break

        if not alreadyBreached
            bool exists = false
            if array.size(activeHTFLevels) > 0
                for i = 0 to array.size(activeHTFLevels) - 1
                    lvl = array.get(activeHTFLevels, i)
                    if not lvl.isSwept and lvl.isHigh == isHigh and math.abs(lvl.price - priceVal) < (syminfo.mintick * 0.1)
                        string searchStr = tfLabel + " " + lvlType
                        if not str.contains(lvl.labelText, searchStr)
                            lvl.labelText := lvl.labelText + ", " + searchStr
                        exists := true
                        break

            if not exists
                string labelTxt = tfLabel + " " + lvlType
                HTFLevel newLvl = HTFLevel.new(
                     tfLabel = tfLabel,
                     levelType = lvlType,
                     isHigh = isHigh,
                     price = priceVal,
                     startBar = startB,
                     isSwept = false,
                     sweepBar = na,
                     labelText = labelTxt
                     )
                array.push(activeHTFLevels, newLvl)

// Position Sizing Calculations
getContractSize() =>
    ticker = syminfo.ticker
    if str.contains(ticker, "ES") or str.contains(ticker, "MES")
        50.0
    else if str.contains(ticker, "NQ") or str.contains(ticker, "MNQ")
        20.0
    else if str.contains(ticker, "YM") or str.contains(ticker, "MYM")
        5.0
    else if str.contains(ticker, "RTY") or str.contains(ticker, "M2K")
        50.0
    else if str.contains(ticker, "CL") or str.contains(ticker, "MCL")
        1000.0
    else if str.contains(ticker, "GC") or str.contains(ticker, "MGC")
        100.0
    else if str.contains(ticker, "SI") or str.contains(ticker, "SIL")
        5000.0
    else if str.contains(ticker, "BTC")
        1.0
    else if str.contains(ticker, "ETH")
        1.0
    else
        1.0

calculatePositionSize(float entry, float stop, float riskUsd) =>
    riskPerUnit = math.abs(entry - stop)
    positionSize = riskPerUnit != 0 ? riskUsd / riskPerUnit : 0

    detectedMarket = syminfo.type
    if detectedMarket == "forex"
        ticker = syminfo.ticker
        needsConversion = not (str.contains(ticker, "USD") and str.endswith(ticker, "USD")) or str.contains(ticker, "6")
        if needsConversion
            positionSize := (positionSize * entry) / 100000.0
        else
            positionSize := positionSize / 100000.0
    else if detectedMarket == "futures" or detectedMarket == "future" or detectedMarket == "cfd" or detectedMarket == "index"
        contractSize = getContractSize()
        positionSize := positionSize / contractSize

    positionSize

formatPositionSize(float size) =>
    detectedMarket = syminfo.type
    sizeText = ""
    if detectedMarket == "forex"
        sizeText := str.tostring(size, '#.##') + ' lots'
    else if detectedMarket == "futures" or detectedMarket == "future" or detectedMarket == "cfd" or detectedMarket == "index"
        sizeText := str.tostring(size, '#.##') + ' ct'
    else if detectedMarket == "crypto" or detectedMarket == "stock"
        sizeText := str.tostring(size, '#.####') + ' units'
    else
        sizeText := str.tostring(size, '#.####')
    sizeText

// ========================================
// INPUTS - Group 1: Alerts
// ========================================
string GRP_ALERTS = "Alerts"
enableAlerts = input.bool(true, "Alerts?", tooltip="Globally toggle the alert system on or off.", group=GRP_ALERTS)
alertPotential = input.bool(true, "Potential Breaker?", tooltip="Trigger an alert on the current bar when a potential breaker is forming (once per bar before bar close).", group=GRP_ALERTS)
alertActivation = input.bool(true, "Activation?", tooltip="Trigger an alert when a breaker setup is confirmed on bar close.", group=GRP_ALERTS)
alertTarget = input.bool(true, "Target Reached?", tooltip="Trigger an alert when price touches one of the projected target levels.", group=GRP_ALERTS)
alertInvalidation = input.bool(true, "Invalidation Hit?", tooltip="Trigger an alert when price wicks past the invalidation level and kills the setup.", group=GRP_ALERTS)

// ========================================
// INPUTS - Group 2: General Settings
// ========================================
string GRP_GEN = "General Settings"
historyCount = input.int(1, "History", minval=1, maxval=100, tooltip="The number of setups to retain on the chart. 1 shows only the latest; higher values display older setups as well.", group=GRP_GEN)
biasSelect = input.string("Neutral", "Bias", options=["Neutral", "Bullish", "Bearish"], tooltip="Filter setups directionally: Neutral (show both), Bullish (show only longs), Bearish (show only shorts).", group=GRP_GEN)
unicornMode = input.bool(true, "Unicorn Mode?", tooltip="When enabled, filters out breaker blocks that do not overlap with a Fair Value Gap (FVG) at the time of confirmation.", group=GRP_GEN)
useSwingInvalidation = input.bool(false, "Use Swing as Invalidation?", tooltip="When enabled, invalidation occurs at the extreme high/low of the sweep leg. When disabled, invalidation occurs at the opposite extreme of the breaker block.", group=GRP_GEN)
discardInvalidated = input.bool(false, "Discard Invalidated from History?", tooltip="When enabled, completely deletes invalidated setups from the chart. When disabled, retains them but changes their color to grey.", group=GRP_GEN)
enableFvgAtrFilter = input.bool(false, "Enable FVG ATR Filter", tooltip="If enabled, FVGs must be larger than a multiplier of ATR to be considered valid.", group=GRP_GEN)
fvgAtrMult = input.float(0.5, "FVG ATR Multiplier", minval=0.0, step=0.1, tooltip="ATR multiplier for minimum FVG height.", group=GRP_GEN, active=enableFvgAtrFilter)

// ========================================
// INPUTS - Group 3: Liquidity Sources
// ========================================
string GRP_LIQ = "Liquidity Sources"
htfMode = input.string("Automatic", "Mode", options=["Automatic", "Manual"], tooltip="Automatic maps timeframes based on chart resolution. Manual uses user-defined timeframes.", group=GRP_LIQ)

// Source A
useA = input.bool(true, "Source A", inline="src_a", tooltip="Enable or disable Source A.", group=GRP_LIQ)
tfA = input.timeframe("15", "", inline="src_a", tooltip="Timeframe for Source A (ignored if Mode is Automatic).", group=GRP_LIQ, active=useA and htfMode == "Manual")
typeA = input.string("OHLC + Swing", "", options=["OHLC", "Swing", "OHLC + Swing"], inline="src_a", tooltip="Liquidity level type for Source A.", group=GRP_LIQ, active=useA)

// Source B
useB = input.bool(true, "Source B", inline="src_b", tooltip="Enable or disable Source B.", group=GRP_LIQ)
tfB = input.timeframe("60", "", inline="src_b", tooltip="Timeframe for Source B (ignored if Mode is Automatic).", group=GRP_LIQ, active=useB and htfMode == "Manual")
typeB = input.string("OHLC + Swing", "", options=["OHLC", "Swing", "OHLC + Swing"], inline="src_b", tooltip="Liquidity level type for Source B.", group=GRP_LIQ, active=useB)

// Source C
useC = input.bool(true, "Source C", inline="src_c", tooltip="Enable or disable Source C.", group=GRP_LIQ)
tfC = input.timeframe("240", "", inline="src_c", tooltip="Timeframe for Source C (ignored if Mode is Automatic).", group=GRP_LIQ, active=useC and htfMode == "Manual")
typeC = input.string("OHLC + Swing", "", options=["OHLC", "Swing", "OHLC + Swing"], inline="src_c", tooltip="Liquidity level type for Source C.", group=GRP_LIQ, active=useC)

// Source D
useD = input.bool(true, "Source D", inline="src_d", tooltip="Enable or disable Source D.", group=GRP_LIQ)
tfD = input.timeframe("1440", "", inline="src_d", tooltip="Timeframe for Source D (ignored if Mode is Automatic).", group=GRP_LIQ, active=useD and htfMode == "Manual")
typeD = input.string("OHLC + Swing", "", options=["OHLC", "Swing", "OHLC + Swing"], inline="src_d", tooltip="Liquidity level type for Source D.", group=GRP_LIQ, active=useD)

// ========================================
// INPUTS - Group 4: Target Projections
// ========================================
string GRP_TGT = "Target Projections"
showRRTarget = input.bool(true, "Show R:R Target?", tooltip="Toggle visibility of the primary risk-reward target.", group=GRP_TGT)
rrRatio = input.float(2.0, "R:R", minval=0.1, step=0.1, tooltip="Risk-to-reward ratio for the primary target.", group=GRP_TGT, active=showRRTarget)

showAddTargets = input.bool(false, "Show Additional Targets?", tooltip="Toggle visibility of additional targets.", group=GRP_TGT)
addTargetsStr = input.string("1, 3, 4, 5", "Additional Targets", tooltip="Comma-separated list of additional risk-reward multiples to plot.", group=GRP_TGT, active=showAddTargets)

// ========================================
// INPUTS - Group 5: Style Settings
// ========================================
string GRP_STYLE = "Style Settings"
labelSizeStr = input.string("Small", "Labels Size", options=["Tiny", "Small", "Normal", "Large"], tooltip="Font size of visual text markers on the chart.", group=GRP_STYLE)
labelTextColor = input.color(color.black, "Label Text Color", tooltip="Color of all chart text labels (level names, setup type, R:R targets).", group=GRP_STYLE)

// Bull Breaker Colors
bullColorActive = input.color(color.new(#089981, 85), "Bull Breaker", inline="bull_breaker_inline", tooltip="Fill color for active bullish breaker blocks (default opacity 15%).", group=GRP_STYLE)
bullColorBorder = input.color(color.new(#089981, 100), "", inline="bull_breaker_inline", tooltip="Border color for bullish breaker blocks (default opacity 0%).", group=GRP_STYLE)
bullColorInactive = input.color(color.new(#787b86, 85), "", inline="bull_breaker_inline", tooltip="Color of bullish breaker blocks after invalidation or target hit (default opacity 15%).", group=GRP_STYLE)

bullFvgColor = input.color(color.new(#089981, 70), "Bull FVG", tooltip="Highlight color for overlapping Fair Value Gaps inside bullish breakers (default opacity 30%).", group=GRP_STYLE)

// Bear Breaker Colors
bearColorActive = input.color(color.new(#f23645, 85), "Bear Breaker", inline="bear_breaker_inline", tooltip="Fill color for active bearish breaker blocks (default opacity 15%).", group=GRP_STYLE)
bearColorBorder = input.color(color.new(#f23645, 100), "", inline="bear_breaker_inline", tooltip="Border color for bearish breaker blocks (default opacity 0%).", group=GRP_STYLE)
bearColorInactive = input.color(color.new(#787b86, 85), "", inline="bear_breaker_inline", tooltip="Color of bearish breaker blocks after invalidation or target hit (default opacity 15%).", group=GRP_STYLE)

bearFvgColor = input.color(color.new(#f23645, 70), "Bear FVG", tooltip="Highlight color for overlapping Fair Value Gaps inside bearish breakers (default opacity 30%).", group=GRP_STYLE)

// Line Styles
sweepLineColor = input.color(color.gray, "Sweep Line", inline="sweep_line_inline", tooltip="Color of the horizontal line marking swept liquidity levels.", group=GRP_STYLE)
sweepLineStyle = input.string("Dotted", "", options=["Solid", "Dashed", "Dotted"], inline="sweep_line_inline", tooltip="Style of the sweep line.", group=GRP_STYLE)
sweepLineWidth = input.int(1, "", minval=1, maxval=4, inline="sweep_line_inline", tooltip="Width of the sweep line.", group=GRP_STYLE)

projLineColor = input.color(color.black, "Projection Line", inline="proj_line_inline", tooltip="Color of target projection lines.", group=GRP_STYLE)
projLineStyle = input.string("Dotted", "", options=["Solid", "Dashed", "Dotted"], inline="proj_line_inline", tooltip="Style of target projection lines.", group=GRP_STYLE)
projLineWidth = input.int(1, "", minval=1, maxval=4, inline="proj_line_inline", tooltip="Width of target projection lines.", group=GRP_STYLE)

// ========================================
// INPUTS - Group 6: Breaker Size Filter
// ========================================
string GRP_SIZE_FLT = "Breaker Size Filter"
sizeFilterMode = input.string("Show All", "Size Filter Mode", options=["Show All", "Filtered"], tooltip="Show All displays all breakers. Filtered applies the size limit checks below.", group=GRP_SIZE_FLT)
minBreakerSize = input.float(0.0, "Minimum Size", inline="size_inline", tooltip="Minimum allowed size for the breaker block.", group=GRP_SIZE_FLT)
maxBreakerSize = input.float(999999.0, "Maximum Size", inline="size_inline", tooltip="Maximum allowed size for the breaker block.", group=GRP_SIZE_FLT)
breakerSizeUnit = input.string("ATR", "Unit", options=["Points", "Ticks", "ATR"], inline="size_inline", tooltip="Measurement unit for size limits: price Points, Ticks, or ATR multiples.", group=GRP_SIZE_FLT)

// ========================================
// INPUTS - Group 7: Time Filter
// ========================================
string GRP_TIME = "Time Filter"
enableTimeFilter = input.bool(false, "Enable Time Filter?", tooltip="If enabled, setups will only be detected if they are confirmed during the designated sessions below.", group=GRP_TIME)
customTimezone = input.bool(false, "Custom Timezone?", inline="timezone_inline", tooltip="Check to override the chart timezone with a custom UTC offset.", group=GRP_TIME)
tzHourOffset = input.int(0, "Hours", inline="timezone_inline", tooltip="UTC hours offset for custom timezone.", group=GRP_TIME, active=customTimezone)
tzMinOffset = input.int(0, "Minutes", inline="timezone_inline", tooltip="UTC minutes offset for custom timezone.", group=GRP_TIME, active=customTimezone)

session1Enable = input.bool(true, "Session 1", inline="sess1_inline", tooltip="Enable trading window for Session 1.", group=GRP_TIME)
session1Time = input.session("0830-1200", "", inline="sess1_inline", tooltip="Session 1 time range.", group=GRP_TIME, active=session1Enable)

session2Enable = input.bool(false, "Session 2", inline="sess2_inline", tooltip="Enable trading window for Session 2.", group=GRP_TIME)
session2Time = input.session("1300-1600", "", inline="sess2_inline", tooltip="Session 2 time range.", group=GRP_TIME, active=session2Enable)

session3Enable = input.bool(false, "Session 3", inline="sess3_inline", tooltip="Enable trading window for Session 3.", group=GRP_TIME)
session3Time = input.session("1900-2200", "", inline="sess3_inline", tooltip="Session 3 time range.", group=GRP_TIME, active=session3Enable)

// ========================================
// INPUTS - Group 8: Position Sizing Settings
// ========================================
string GRP_SIZE_SESS = "Position Sizing"
showPositionSize = input.bool(false, "Show Position Size", group=GRP_SIZE_SESS)
accountBalance = input.float(10000.0, "Account Balance (USD)", minval=0.0, group=GRP_SIZE_SESS, active=showPositionSize)
riskType = input.string("Percentage", "Risk Type", options=["Percentage", "Fixed Amount"], group=GRP_SIZE_SESS, active=showPositionSize)
riskAmount = input.float(1.0, "Risk Amount (% or USD)", minval=0.0, group=GRP_SIZE_SESS, active=showPositionSize)

// ========================================
// INPUTS - Group 9: Dashboard Settings
// ========================================
string GRP_DASH = "Dashboard Settings"
showDashboard = input.bool(true, "Show Dashboard", tooltip="Display summary table on the chart.", group=GRP_DASH)
dashboardPosition = input.string("Top Right", "Table Position", options=["Top Left", "Top Center", "Top Right", "Middle Left", "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", "Bottom Right"], tooltip="Anchor position of the dashboard table.", group=GRP_DASH)
dashboardSize = input.string("tiny", "Table Text Size", options=["tiny", "small", "normal", "large", "huge"], tooltip="Font size inside the dashboard table.", group=GRP_DASH)
tableBgColor = input.color(color.new(color.white, 90), "Table Background", tooltip="Dashboard table cell background color.", group=GRP_DASH)
tableTextColor = input.color(color.black, "Table Text Color", tooltip="Dashboard table text color.", group=GRP_DASH)
tableHeaderBgColor = input.color(color.new(color.gray, 70), "Header Background", tooltip="Dashboard header cell background color.", group=GRP_DASH)
tableHeaderTextColor = input.color(color.black, "Header Text Color", tooltip="Dashboard header text color.", group=GRP_DASH)
tableBorderWidth = input.int(1, "Border Width", minval=0, maxval=3, tooltip="Dashboard border width.", group=GRP_DASH)
tableBorderColor = input.color(color.gray, "Border Color", tooltip="Dashboard border line color.", group=GRP_DASH)
tableFrameColor = input.color(color.black, "Table Frame Color", tooltip="Dashboard outer border line color.", group=GRP_DASH)
tableFrameWidth = input.int(1, "Table Frame Width", minval=0, maxval=5, tooltip="Dashboard outer border width.", group=GRP_DASH)

// ========================================
// MAX BARS BACK ALLOCATION
// ========================================
max_bars_back(high, 1000)
max_bars_back(low, 1000)
max_bars_back(close, 1000)
max_bars_back(open, 1000)

// ========================================
// SYSTEM VARIABLES & TIME RESOLUTION
// ========================================

// Pending Setup State Variables
var int pendingBullSweepIdx = na
var int pendingBullFirstSweepBar = na
var float pendingBullSweepExtreme = na
var float pendingBullSweptLevel = na
var int pendingBullSweptStartBar = na
var string pendingBullSweptLabelText = ""

var int pendingBearSweepIdx = na
var int pendingBearFirstSweepBar = na
var float pendingBearSweepExtreme = na
var float pendingBearSweptLevel = na
var int pendingBearSweptStartBar = na
var string pendingBearSweptLabelText = ""

// Time Filter Logic (Uses inputs, must be declared after them)
f_isInSession() =>
    bool inSess = false
    if not enableTimeFilter
        inSess := true
    else
        tz = customTimezone ? "GMT" + (tzHourOffset >= 0 ? "+" : "-") + str.tostring(math.abs(tzHourOffset), "00") + str.tostring(math.abs(tzMinOffset), "00") : syminfo.timezone
        s1 = session1Enable and not na(time(timeframe.period, session1Time, tz))
        s2 = session2Enable and not na(time(timeframe.period, session2Time, tz))
        s3 = session3Enable and not na(time(timeframe.period, session3Time, tz))
        inSess := s1 or s2 or s3
    inSess

// ========================================
// FETCHING DATA RESOLUTION
// ========================================
[autoTA, autoTB, autoTC, autoTD] = getAutoHTFs()

// Check if A is empty (e.g. on 15s chart resolution where k-1 < 0)
bool autoA_valid = autoTA != ""
string finalAutoTA = autoA_valid ? autoTA : timeframe.period

// Resolve final timeframes
htfA_resolved = htfMode == "Automatic" ? finalAutoTA : tfA
htfB_resolved = htfMode == "Automatic" ? autoTB : tfB
htfC_resolved = htfMode == "Automatic" ? autoTC : tfC
htfD_resolved = htfMode == "Automatic" ? autoTD : tfD

// Override the 'use' toggles in Automatic Mode if Source A is invalid
useA_resolved = useA and (htfMode == "Manual" or (htfMode == "Automatic" and autoA_valid))
useB_resolved = useB
useC_resolved = useC
useD_resolved = useD

// Request security for all sources
[prevH_A, prevL_A, sh_A, sl_A] = request.security(syminfo.tickerid, htfA_resolved, f_getHTFData(), barmerge.gaps_off, barmerge.lookahead_off)
[prevH_B, prevL_B, sh_B, sl_B] = request.security(syminfo.tickerid, htfB_resolved, f_getHTFData(), barmerge.gaps_off, barmerge.lookahead_off)
[prevH_C, prevL_C, sh_C, sl_C] = request.security(syminfo.tickerid, htfC_resolved, f_getHTFData(), barmerge.gaps_off, barmerge.lookahead_off)
[prevH_D, prevL_D, sh_D, sl_D] = request.security(syminfo.tickerid, htfD_resolved, f_getHTFData(), barmerge.gaps_off, barmerge.lookahead_off)

// Track formation bar index for each liquidity level
var float lastPrevH_A = na
var float lastSh_A = na
var float lastPrevL_A = na
var float lastSl_A = na

var float lastPrevH_B = na
var float lastSh_B = na
var float lastPrevL_B = na
var float lastSl_B = na

var float lastPrevH_C = na
var float lastSh_C = na
var float lastPrevL_C = na
var float lastSl_C = na

var float lastPrevH_D = na
var float lastSh_D = na
var float lastPrevL_D = na
var float lastSl_D = na

// Register newly formed levels on change
// Source A
if useA_resolved
    if (typeA == "OHLC" or typeA == "OHLC + Swing")
        if prevH_A != lastPrevH_A or (na(lastPrevH_A) and not na(prevH_A))
            lastPrevH_A := prevH_A
            f_registerLevel(f_getTFLabel(htfA_resolved), "OHLC", true, prevH_A)
        if prevL_A != lastPrevL_A or (na(lastPrevL_A) and not na(prevL_A))
            lastPrevL_A := prevL_A
            f_registerLevel(f_getTFLabel(htfA_resolved), "OHLC", false, prevL_A)
    if (typeA == "Swing" or typeA == "OHLC + Swing")
        if sh_A != lastSh_A or (na(lastSh_A) and not na(sh_A))
            lastSh_A := sh_A
            f_registerLevel(f_getTFLabel(htfA_resolved), "Swing", true, sh_A)
        if sl_A != lastSl_A or (na(lastSl_A) and not na(sl_A))
            lastSl_A := sl_A
            f_registerLevel(f_getTFLabel(htfA_resolved), "Swing", false, sl_A)

// Source B
if useB_resolved
    if (typeB == "OHLC" or typeB == "OHLC + Swing")
        if prevH_B != lastPrevH_B or (na(lastPrevH_B) and not na(prevH_B))
            lastPrevH_B := prevH_B
            f_registerLevel(f_getTFLabel(htfB_resolved), "OHLC", true, prevH_B)
        if prevL_B != lastPrevL_B or (na(lastPrevL_B) and not na(prevL_B))
            lastPrevL_B := prevL_B
            f_registerLevel(f_getTFLabel(htfB_resolved), "OHLC", false, prevL_B)
    if (typeB == "Swing" or typeB == "OHLC + Swing")
        if sh_B != lastSh_B or (na(lastSh_B) and not na(sh_B))
            lastSh_B := sh_B
            f_registerLevel(f_getTFLabel(htfB_resolved), "Swing", true, sh_B)
        if sl_B != lastSl_B or (na(lastSl_B) and not na(sl_B))
            lastSl_B := sl_B
            f_registerLevel(f_getTFLabel(htfB_resolved), "Swing", false, sl_B)

// Source C
if useC_resolved
    if (typeC == "OHLC" or typeC == "OHLC + Swing")
        if prevH_C != lastPrevH_C or (na(lastPrevH_C) and not na(prevH_C))
            lastPrevH_C := prevH_C
            f_registerLevel(f_getTFLabel(htfC_resolved), "OHLC", true, prevH_C)
        if prevL_C != lastPrevL_C or (na(lastPrevL_C) and not na(prevL_C))
            lastPrevL_C := prevL_C
            f_registerLevel(f_getTFLabel(htfC_resolved), "OHLC", false, prevL_C)
    if (typeC == "Swing" or typeC == "OHLC + Swing")
        if sh_C != lastSh_C or (na(lastSh_C) and not na(sh_C))
            lastSh_C := sh_C
            f_registerLevel(f_getTFLabel(htfC_resolved), "Swing", true, sh_C)
        if sl_C != lastSl_C or (na(lastSl_C) and not na(sl_C))
            lastSl_C := sl_C
            f_registerLevel(f_getTFLabel(htfC_resolved), "Swing", false, sl_C)

// Source D
if useD_resolved
    if (typeD == "OHLC" or typeD == "OHLC + Swing")
        if prevH_D != lastPrevH_D or (na(lastPrevH_D) and not na(prevH_D))
            lastPrevH_D := prevH_D
            f_registerLevel(f_getTFLabel(htfD_resolved), "OHLC", true, prevH_D)
        if prevL_D != lastPrevL_D or (na(lastPrevL_D) and not na(prevL_D))
            lastPrevL_D := prevL_D
            f_registerLevel(f_getTFLabel(htfD_resolved), "OHLC", false, prevL_D)
    if (typeD == "Swing" or typeD == "OHLC + Swing")
        if sh_D != lastSh_D or (na(lastSh_D) and not na(sh_D))
            lastSh_D := sh_D
            f_registerLevel(f_getTFLabel(htfD_resolved), "Swing", true, sh_D)
        if sl_D != lastSl_D or (na(lastSl_D) and not na(sl_D))
            lastSl_D := sl_D
            f_registerLevel(f_getTFLabel(htfD_resolved), "Swing", false, sl_D)

// ========================================
// MAIN EXECUTION STATE MACHINE (Level update & sweep check)
// ========================================
bool sweptBull = false
float sweptBullPrice = na
int sweptBullStartBar = na
string sweptBulllabelText = ""

bool sweptBear = false
float sweptBearPrice = na
int sweptBearStartBar = na
string sweptBearlabelText = ""

if array.size(activeHTFLevels) > 0
    for idx = array.size(activeHTFLevels) - 1 to 0
        lvl = array.get(activeHTFLevels, idx)

        bool swept = false
        if lvl.isHigh
            if high >= lvl.price
                swept := true
                sweptBull := true
                if na(sweptBullPrice) or lvl.price > sweptBullPrice
                    sweptBullPrice := lvl.price
                    sweptBullStartBar := lvl.startBar
                    sweptBulllabelText := lvl.labelText
                else if lvl.price == sweptBullPrice
                    if not str.contains(sweptBulllabelText, lvl.labelText)
                        sweptBulllabelText := sweptBulllabelText + ", " + lvl.labelText
        else
            if low <= lvl.price
                swept := true
                sweptBear := true
                if na(sweptBearPrice) or lvl.price < sweptBearPrice
                    sweptBearPrice := lvl.price
                    sweptBearStartBar := lvl.startBar
                    sweptBearlabelText := lvl.labelText
                else if lvl.price == sweptBearPrice
                    if not str.contains(sweptBearlabelText, lvl.labelText)
                        sweptBearlabelText := sweptBearlabelText + ", " + lvl.labelText

        if swept
            lvl.isSwept := true
            lvl.sweepBar := bar_index
            // Performance Optimization: Immediately remove swept level from execution array
            array.remove(activeHTFLevels, idx)

// Limit level memory history to prevent array overflow
while array.size(activeHTFLevels) > 100
    array.shift(activeHTFLevels)

// 2. Process & Track Sweeps
if sweptBear and (biasSelect == "Neutral" or biasSelect == "Bullish")
    pendingBullSweepIdx := bar_index
    pendingBullFirstSweepBar := bar_index
    pendingBullSweepExtreme := low
    pendingBullSweptLevel := sweptBearPrice
    pendingBullSweptStartBar := sweptBearStartBar
    pendingBullSweptLabelText := sweptBearlabelText
else if not na(pendingBullSweepIdx) and low < pendingBullSweepExtreme
    pendingBullSweepExtreme := low
    pendingBullSweepIdx := bar_index

if sweptBull and (biasSelect == "Neutral" or biasSelect == "Bearish")
    pendingBearSweepIdx := bar_index
    pendingBearFirstSweepBar := bar_index
    pendingBearSweepExtreme := high
    pendingBearSweptLevel := sweptBullPrice
    pendingBearSweptStartBar := sweptBullStartBar
    pendingBearSweptLabelText := sweptBulllabelText
else if not na(pendingBearSweepIdx) and high > pendingBearSweepExtreme
    pendingBearSweepExtreme := high
    pendingBearSweepIdx := bar_index

// 3. Track & Confirm Pending Setups

// Track Bullish setups
if not na(pendingBullSweepIdx)
    sweepOffset = bar_index - pendingBullSweepIdx
    [startOffset, endOffset] = f_findUpCandlesSeries(sweepOffset)

    if not na(startOffset)
        [breakerT, breakerB] = f_getSeriesRange(startOffset, endOffset)

        // Validation: Breaker bottom must not be below (more extreme than) the sweep low extreme
        if close > breakerT and breakerB >= pendingBullSweepExtreme and f_isInSession()
            if f_checkSizeFilter(breakerT, breakerB, sizeFilterMode, minBreakerSize, maxBreakerSize, breakerSizeUnit)
                breakerStartBar = bar_index - endOffset
                [fvgT, fvgB, fvgIdx, fvgRightIdx] = f_findBullishFVG(breakerT, breakerB, breakerStartBar, enableFvgAtrFilter, fvgAtrMult)
                hasFvg = not na(fvgIdx)

                if not unicornMode or (unicornMode and hasFvg)
                    float invPrice = useSwingInvalidation ? pendingBullSweepExtreme : breakerB
                    float riskRange = breakerT - invPrice

                    if riskRange > 0
                        Setup newBull = Setup.new(
                             dir = "BULL",
                             sweepIndex = pendingBullFirstSweepBar,
                             sweepExtreme = pendingBullSweepExtreme,
                             sweptLevel = pendingBullSweptLevel,
                             sweptLevelStartBar = pendingBullSweptStartBar,
                             breakerStartIdx = bar_index - startOffset,
                             breakerEndIdx = bar_index - endOffset,
                             breakerTop = breakerT,
                             breakerBottom = breakerB,
                             isConfirmed = true,
                             confIndex = bar_index,
                             entryPrice = breakerT,
                             invalidationPrice = invPrice,
                             isInvalidated = false,
                             isTargetHit = false,
                             levelLine = na,
                             levelLabel = na,
                             breakerBox = na,
                             fvgBox = na,
                             targetLines = array.new_line(),
                             targetLabels = array.new_label(),
                             setupLabel = na
                             )

                        // Setup-linked level line (starts at exact extreme index, ends at sweepIndex)
                        int lineStartB = math.max(newBull.sweptLevelStartBar, bar_index - 4500)
                        newBull.levelLine := line.new(
                             x1 = lineStartB,
                             y1 = newBull.sweptLevel,
                             x2 = newBull.sweepIndex,
                             y2 = newBull.sweptLevel,
                             color = sweepLineColor,
                             style = f_getLineStyle(sweepLineStyle),
                             width = sweepLineWidth
                             )

                        newBull.levelLabel := label.new(
                             x = lineStartB,
                             y = newBull.sweptLevel + (nz(ta.atr(14)) * 0.1),
                             text = pendingBullSweptLabelText,
                             color = color.new(na, na),
                             textcolor = labelTextColor,
                             style = label.style_label_left,
                             size = f_getLabelSize(labelSizeStr),
                             text_font_family = font.family_monospace
                             )

                        newBull.breakerBox := box.new(
                             left = bar_index - endOffset,
                             top = newBull.breakerTop,
                             right = bar_index,
                             bottom = newBull.breakerBottom,
                             border_color = bullColorBorder,
                             bgcolor = bullColorActive,
                             xloc = xloc.bar_index
                             )

                        if hasFvg
                            newBull.fvgBox := box.new(
                                 left = fvgIdx,
                                 top = fvgT,
                                 right = fvgRightIdx,
                                 bottom = fvgB,
                                 border_color = color.new(na, na),
                                 bgcolor = bullFvgColor,
                                 xloc = xloc.bar_index
                                 )

                        labelText = hasFvg ? "+Unicorn" : "+Breaker"
                        tooltipText = "Setup: " + (hasFvg ? "Bullish Unicorn" : "Bullish Breaker") + "\n" +
                                     "Swept Level: " + str.tostring(newBull.sweptLevel, format.mintick) + "\n" +
                                     "Entry: " + str.tostring(newBull.entryPrice, format.mintick) + "\n" +
                                     "Stop Loss: " + str.tostring(newBull.invalidationPrice, format.mintick)

                        newBull.setupLabel := label.new(
                             x = bar_index,
                             y = (newBull.breakerTop + newBull.breakerBottom) / 2.0,
                             text = labelText,
                             color = color.new(na, na),
                             textcolor = labelTextColor,
                             style = label.style_label_right,
                             size = f_getLabelSize(labelSizeStr),
                             tooltip = tooltipText,
                             text_font_family = font.family_monospace
                             )

                        primaryTargetPrice = newBull.entryPrice + rrRatio * riskRange
                        if showRRTarget
                            l = line.new(
                                 x1 = newBull.breakerEndIdx,
                                 y1 = primaryTargetPrice,
                                 x2 = newBull.confIndex,
                                 y2 = primaryTargetPrice,
                                 color = labelTextColor,
                                 style = f_getLineStyle(projLineStyle),
                                 width = projLineWidth
                                 )
                            lbl = label.new(
                                 x = newBull.confIndex,
                                 y = primaryTargetPrice,
                                 text = str.tostring(rrRatio, "#.##") + "R",
                                 color = color.new(na, na),
                                 textcolor = labelTextColor,
                                 style = label.style_label_left,
                                 size = f_getLabelSize(labelSizeStr),
                                 tooltip = "Target level: " + str.tostring(primaryTargetPrice, format.mintick),
                                 text_font_family = font.family_monospace
                                 )
                            array.push(newBull.targetLines, l)
                            array.push(newBull.targetLabels, lbl)

                        if showAddTargets
                            tgts = f_parseTargets(addTargetsStr)
                            if array.size(tgts) > 0
                                for i = 0 to array.size(tgts) - 1
                                    m = array.get(tgts, i)
                                    tgtVal = newBull.entryPrice + m * riskRange
                                    l = line.new(
                                         x1 = newBull.breakerEndIdx,
                                         y1 = tgtVal,
                                         x2 = newBull.confIndex,
                                         y2 = tgtVal,
                                         color = labelTextColor,
                                         style = f_getLineStyle(projLineStyle),
                                         width = projLineWidth
                                         )
                                    lbl = label.new(
                                         x = newBull.confIndex,
                                         y = tgtVal,
                                         text = str.tostring(m, "#.##") + "R",
                                         color = color.new(na, na),
                                         textcolor = labelTextColor,
                                         style = label.style_label_left,
                                         size = f_getLabelSize(labelSizeStr),
                                         tooltip = "Target level: " + str.tostring(tgtVal, format.mintick),
                                         text_font_family = font.family_monospace
                                         )
                                    array.push(newBull.targetLines, l)
                                    array.push(newBull.targetLabels, lbl)

                        array.push(activeSetups, newBull)

                        if enableAlerts and alertActivation
                            alert("Activation: Bullish Breaker activated on " + syminfo.ticker + " at " + str.tostring(newBull.entryPrice), alert.freq_once_per_bar_close)

                        pendingBullSweepIdx := na
                        pendingBullFirstSweepBar := na

// Track Bearish setups
if not na(pendingBearSweepIdx)
    sweepOffset = bar_index - pendingBearSweepIdx
    [startOffset, endOffset] = f_findDownCandlesSeries(sweepOffset)

    if not na(startOffset)
        [breakerT, breakerB] = f_getSeriesRange(startOffset, endOffset)

        // Validation: Breaker top must not be above (more extreme than) the sweep high extreme
        if close < breakerB and breakerT <= pendingBearSweepExtreme and f_isInSession()
            if f_checkSizeFilter(breakerT, breakerB, sizeFilterMode, minBreakerSize, maxBreakerSize, breakerSizeUnit)
                breakerStartBar = bar_index - endOffset
                [fvgT, fvgB, fvgIdx, fvgRightIdx] = f_findBearishFVG(breakerT, breakerB, breakerStartBar, enableFvgAtrFilter, fvgAtrMult)
                hasFvg = not na(fvgIdx)

                if not unicornMode or (unicornMode and hasFvg)
                    float invPrice = useSwingInvalidation ? pendingBearSweepExtreme : breakerT
                    float riskRange = invPrice - breakerB

                    if riskRange > 0
                        Setup newBear = Setup.new(
                             dir = "BEAR",
                             sweepIndex = pendingBearFirstSweepBar,
                             sweepExtreme = pendingBearSweepExtreme,
                             sweptLevel = pendingBearSweptLevel,
                             sweptLevelStartBar = pendingBearSweptStartBar,
                             breakerStartIdx = bar_index - startOffset,
                             breakerEndIdx = bar_index - endOffset,
                             breakerTop = breakerT,
                             breakerBottom = breakerB,
                             isConfirmed = true,
                             confIndex = bar_index,
                             entryPrice = breakerB,
                             invalidationPrice = invPrice,
                             isInvalidated = false,
                             isTargetHit = false,
                             levelLine = na,
                             levelLabel = na,
                             breakerBox = na,
                             fvgBox = na,
                             targetLines = array.new_line(),
                             targetLabels = array.new_label(),
                             setupLabel = na
                             )

                        // Setup-linked level line (starts at exact extreme index, ends at sweepIndex)
                        int lineStartB = math.max(newBear.sweptLevelStartBar, bar_index - 4500)
                        newBear.levelLine := line.new(
                             x1 = lineStartB,
                             y1 = newBear.sweptLevel,
                             x2 = newBear.sweepIndex,
                             y2 = newBear.sweptLevel,
                             color = sweepLineColor,
                             style = f_getLineStyle(sweepLineStyle),
                             width = sweepLineWidth
                             )

                        newBear.levelLabel := label.new(
                             x = lineStartB,
                             y = newBear.sweptLevel + (nz(ta.atr(14)) * 0.1),
                             text = pendingBearSweptLabelText,
                             color = color.new(na, na),
                             textcolor = labelTextColor,
                             style = label.style_label_left,
                             size = f_getLabelSize(labelSizeStr),
                             text_font_family = font.family_monospace
                             )

                        newBear.breakerBox := box.new(
                             left = bar_index - endOffset,
                             top = newBear.breakerTop,
                             right = bar_index,
                             bottom = newBear.breakerBottom,
                             border_color = bearColorBorder,
                             bgcolor = bearColorActive,
                             xloc = xloc.bar_index
                             )

                        if hasFvg
                            newBear.fvgBox := box.new(
                                 left = fvgIdx,
                                 top = fvgT,
                                 right = fvgRightIdx,
                                 bottom = fvgB,
                                 border_color = color.new(na, na),
                                 bgcolor = bearFvgColor,
                                 xloc = xloc.bar_index
                                 )

                        labelText = hasFvg ? "-Unicorn" : "-Breaker"
                        tooltipText = "Setup: " + (hasFvg ? "Bearish Unicorn" : "Bearish Breaker") + "\n" +
                                     "Swept Level: " + str.tostring(newBear.sweptLevel, format.mintick) + "\n" +
                                     "Entry: " + str.tostring(newBear.entryPrice, format.mintick) + "\n" +
                                     "Stop Loss: " + str.tostring(newBear.invalidationPrice, format.mintick)

                        newBear.setupLabel := label.new(
                             x = bar_index,
                             y = (newBear.breakerTop + newBear.breakerBottom) / 2.0,
                             text = labelText,
                             color = color.new(na, na),
                             textcolor = labelTextColor,
                             style = label.style_label_right,
                             size = f_getLabelSize(labelSizeStr),
                             tooltip = tooltipText,
                             text_font_family = font.family_monospace
                             )

                        primaryTargetPrice = newBear.entryPrice - rrRatio * riskRange
                        if showRRTarget
                            l = line.new(
                                 x1 = newBear.breakerEndIdx,
                                 y1 = primaryTargetPrice,
                                 x2 = newBear.confIndex,
                                 y2 = primaryTargetPrice,
                                 color = labelTextColor,
                                 style = f_getLineStyle(projLineStyle),
                                 width = projLineWidth
                                 )
                            lbl = label.new(
                                 x = newBear.confIndex,
                                 y = primaryTargetPrice,
                                 text = str.tostring(rrRatio, "#.##") + "R",
                                 color = color.new(na, na),
                                 textcolor = labelTextColor,
                                 style = label.style_label_left,
                                 size = f_getLabelSize(labelSizeStr),
                                 tooltip = "Target level: " + str.tostring(primaryTargetPrice, format.mintick),
                                 text_font_family = font.family_monospace
                                 )
                            array.push(newBear.targetLines, l)
                            array.push(newBear.targetLabels, lbl)

                        if showAddTargets
                            tgts = f_parseTargets(addTargetsStr)
                            if array.size(tgts) > 0
                                for i = 0 to array.size(tgts) - 1
                                    m = array.get(tgts, i)
                                    tgtVal = newBear.entryPrice - m * riskRange
                                    l = line.new(
                                         x1 = newBear.breakerEndIdx,
                                         y1 = tgtVal,
                                         x2 = newBear.confIndex,
                                         y2 = tgtVal,
                                         color = labelTextColor,
                                         style = f_getLineStyle(projLineStyle),
                                         width = projLineWidth
                                         )
                                    lbl = label.new(
                                         x = newBear.confIndex,
                                         y = tgtVal,
                                         text = str.tostring(m, "#.##") + "R",
                                         color = color.new(na, na),
                                         textcolor = labelTextColor,
                                         style = label.style_label_left,
                                         size = f_getLabelSize(labelSizeStr),
                                         tooltip = "Target level: " + str.tostring(tgtVal, format.mintick),
                                         text_font_family = font.family_monospace
                                         )
                                    array.push(newBear.targetLines, l)
                                    array.push(newBear.targetLabels, lbl)

                        array.push(activeSetups, newBear)

                        if enableAlerts and alertActivation
                            alert("Activation: Bearish Breaker activated on " + syminfo.ticker + " at " + str.tostring(newBear.entryPrice), alert.freq_once_per_bar_close)

                        pendingBearSweepIdx := na
                        pendingBearFirstSweepBar := na

// 4. Update Existing Active Setups
if array.size(activeSetups) > 0
    for idx = array.size(activeSetups) - 1 to 0
        setup = array.get(activeSetups, idx)

        if not setup.isInvalidated and not setup.isTargetHit
            // Invalidation checks
            bool invHit = false
            if setup.dir == "BULL"
                if low < setup.invalidationPrice
                    invHit := true
            else
                if high > setup.invalidationPrice
                    invHit := true

            if invHit
                setup.isInvalidated := true
                if enableAlerts and alertInvalidation
                    alert("Invalidation Hit: Setup " + setup.dir + " invalidated on " + syminfo.ticker, alert.freq_once_per_bar_close)

                if discardInvalidated
                    line.delete(setup.levelLine)
                    label.delete(setup.levelLabel)
                    box.delete(setup.breakerBox)
                    if not na(setup.fvgBox)
                        box.delete(setup.fvgBox)
                    label.delete(setup.setupLabel)
                    for l in setup.targetLines
                        line.delete(l)
                    for lb in setup.targetLabels
                        label.delete(lb)
                    array.remove(activeSetups, idx)
                    continue
                else
                    box.set_border_color(setup.breakerBox, setup.dir == "BULL" ? bullColorInactive : bearColorInactive)
                    box.set_bgcolor(setup.breakerBox, setup.dir == "BULL" ? bullColorInactive : bearColorInactive)
                    if not na(setup.fvgBox)
                        box.set_bgcolor(setup.fvgBox, setup.dir == "BULL" ? bullColorInactive : bearColorInactive)
                    label.set_textcolor(setup.setupLabel, setup.dir == "BULL" ? bullColorInactive : bearColorInactive)

                    for l in setup.targetLines
                        line.delete(l)
                    for lb in setup.targetLabels
                        label.delete(lb)
                    array.clear(setup.targetLines)
                    array.clear(setup.targetLabels)
            else
                // Extend breaker box only (FVG and targets are static)
                box.set_right(setup.breakerBox, bar_index)
                label.set_x(setup.setupLabel, bar_index)

                // Track targets hit (primary target)
                float targetPrice = setup.dir == "BULL" ?
                     (setup.entryPrice + rrRatio * (setup.entryPrice - setup.invalidationPrice)) :
                     (setup.entryPrice - rrRatio * (setup.invalidationPrice - setup.entryPrice))

                bool hit = false
                if setup.dir == "BULL"
                    if high >= targetPrice
                        hit := true
                else
                    if low <= targetPrice
                        hit := true

                if hit
                    setup.isTargetHit := true
                    if enableAlerts and alertTarget
                        alert("Target Reached: Setup " + setup.dir + " target reached at " + str.tostring(targetPrice), alert.freq_once_per_bar_close)

// 5. Handle Setup History Limit
while array.size(activeSetups) > historyCount
    oldSetup = array.shift(activeSetups)
    line.delete(oldSetup.levelLine)
    label.delete(oldSetup.levelLabel)
    box.delete(oldSetup.breakerBox)
    if not na(oldSetup.fvgBox)
        box.delete(oldSetup.fvgBox)
    label.delete(oldSetup.setupLabel)
    for l in oldSetup.targetLines
        line.delete(l)
    for lb in oldSetup.targetLabels
        label.delete(lb)

// ========================================
// POTENTIAL BREAKER ALERT (ON UNCONFIRMED BAR)
// ========================================
var int lastPotentialAlertBar = na
if alertPotential and barstate.isrealtime and not barstate.isconfirmed and bar_index != lastPotentialAlertBar
    if not na(pendingBullSweepIdx)
        sweepOffset = bar_index - pendingBullSweepIdx
        [startOff, endOff] = f_findUpCandlesSeries(sweepOffset)
        if not na(startOff)
            [bTop, bBtm] = f_getSeriesRange(startOff, endOff)
            if close > bTop and bBtm >= pendingBullSweepExtreme
                alert("Potential Breaker: Potential Bullish Breaker forming on " + syminfo.ticker, alert.freq_once_per_bar)
                lastPotentialAlertBar := bar_index

    if not na(pendingBearSweepIdx)
        sweepOffset = bar_index - pendingBearSweepIdx
        [startOff, endOff] = f_findDownCandlesSeries(sweepOffset)
        if not na(startOff)
            [bTop, bBtm] = f_getSeriesRange(startOff, endOff)
            if close < bBtm and bTop <= pendingBearSweepExtreme
                alert("Potential Breaker: Potential Bearish Breaker forming on " + syminfo.ticker, alert.freq_once_per_bar)
                lastPotentialAlertBar := bar_index

// ========================================
// DASHBOARD RENDERING & DYNAMIC INVAL SYNC
// ========================================

// Extract latest active (non-invalidated, non-target hit) setup details dynamically
string showSetupDir = "NONE"
float showEntryPrice = na
float showInvalidationPrice = na
float showTargetPrice = na
float showRiskUsd = na
float showPositionSizeVal = na

if array.size(activeSetups) > 0
    for i = array.size(activeSetups) - 1 to 0
        s = array.get(activeSetups, i)
        if not s.isInvalidated and not s.isTargetHit
            showSetupDir := s.dir == "BULL" ? "BULLISH" : "BEARISH"
            showEntryPrice := s.entryPrice
            showInvalidationPrice := s.invalidationPrice
            if array.size(s.targetLines) > 0
                showTargetPrice := line.get_y1(array.get(s.targetLines, 0))
            else
                showTargetPrice := s.entryPrice + (s.dir == "BULL" ? 1 : -1) * rrRatio * math.abs(s.entryPrice - s.invalidationPrice)

            showRiskUsd := riskType == "Percentage" ? (accountBalance * riskAmount / 100.0) : riskAmount
            showPositionSizeVal := calculatePositionSize(showEntryPrice, showInvalidationPrice, showRiskUsd)
            break

var table dashboardTable = table.new(
     position = f_getTablePosition(dashboardPosition),
     columns = 3,
     rows = 14,
     bgcolor = tableBgColor,
     border_width = tableBorderWidth,
     border_color = tableBorderColor,
     frame_color = tableFrameColor,
     frame_width = tableFrameWidth
     )

if barstate.islast and showDashboard
    tSize = f_getTableTextSize(dashboardSize)

    // Header
    table.cell(dashboardTable, 0, 0, "Unicorn Model", bgcolor=tableHeaderBgColor, text_color=tableHeaderTextColor, text_size=tSize, text_font_family=font.family_monospace)
    table.merge_cells(dashboardTable, 0, 0, 2, 0)

    // Stats Rows
    table.cell(dashboardTable, 0, 1, "Bias:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 1, 1, biasSelect, text_color=biasSelect == "Neutral" ? color.orange : (biasSelect == "Bullish" ? color.green : color.red), text_size=tSize, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 2, 1, "", text_size=tSize)

    table.cell(dashboardTable, 0, 2, "Unicorn Mode:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 1, 2, unicornMode ? "ENABLED" : "DISABLED", text_color=unicornMode ? color.green : color.gray, text_size=tSize, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 2, 2, "", text_size=tSize)

    table.cell(dashboardTable, 0, 3, "Liquidity Source:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
    activeSrcs = ""
    if useA_resolved
        activeSrcs := activeSrcs + "A "
    if useB_resolved
        activeSrcs := activeSrcs + "B "
    if useC_resolved
        activeSrcs := activeSrcs + "C "
    if useD_resolved
        activeSrcs := activeSrcs + "D "
    table.cell(dashboardTable, 1, 3, activeSrcs, text_color=color.orange, text_size=tSize, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 2, 3, "(" + htfMode + ")", text_color=color.gray, text_size=tSize, text_font_family=font.family_monospace)

    table.cell(dashboardTable, 0, 4, "Invalidation:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 1, 4, useSwingInvalidation ? "Swing Extreme" : "Breaker Extreme", text_color=color.orange, text_size=tSize, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 2, 4, "", text_size=tSize)

    table.cell(dashboardTable, 0, 5, "Size Filter:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
    filterDesc = sizeFilterMode == "Filtered" ? str.tostring(minBreakerSize) + "-" + str.tostring(maxBreakerSize) + " " + breakerSizeUnit : "DISABLED"
    table.cell(dashboardTable, 1, 5, filterDesc, text_color=sizeFilterMode == "Filtered" ? color.green : color.gray, text_size=tSize, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 2, 5, "", text_size=tSize)

    table.cell(dashboardTable, 0, 6, "Time Filter:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 1, 6, enableTimeFilter ? "ENABLED" : "DISABLED", text_color=enableTimeFilter ? color.green : color.gray, text_size=tSize, text_font_family=font.family_monospace)
    table.cell(dashboardTable, 2, 6, "", text_size=tSize)

    // Latest Setup Section
    table.cell(dashboardTable, 0, 7, "── LATEST SETUP ──", bgcolor=tableHeaderBgColor, text_color=tableHeaderTextColor, text_size=tSize, text_font_family=font.family_monospace)
    table.merge_cells(dashboardTable, 0, 7, 2, 7)

    if showSetupDir != "NONE"
        table.cell(dashboardTable, 0, 8, "Direction:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 1, 8, showSetupDir, text_color=showSetupDir == "BULLISH" ? color.green : color.red, text_size=tSize, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 2, 8, "", text_size=tSize)

        table.cell(dashboardTable, 0, 9, "Entry Price:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 1, 9, str.tostring(showEntryPrice, format.mintick), text_color=tableTextColor, text_size=tSize, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 2, 9, "", text_size=tSize)

        table.cell(dashboardTable, 0, 10, "Invalidation:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 1, 10, str.tostring(showInvalidationPrice, format.mintick), text_color=color.red, text_size=tSize, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 2, 10, "", text_size=tSize)

        table.cell(dashboardTable, 0, 11, "Target (" + str.tostring(rrRatio, "#.##") + "R):", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 1, 11, str.tostring(showTargetPrice, format.mintick), text_color=color.green, text_size=tSize, text_font_family=font.family_monospace)
        table.cell(dashboardTable, 2, 11, "", text_size=tSize)

        if showPositionSize and not na(showPositionSizeVal)
            table.cell(dashboardTable, 0, 12, "Size:", text_color=tableTextColor, text_size=tSize, text_halign=text.align_left, text_font_family=font.family_monospace)
            table.cell(dashboardTable, 1, 12, formatPositionSize(showPositionSizeVal), text_color=color.aqua, text_size=tSize, text_font_family=font.family_monospace)
            table.cell(dashboardTable, 2, 12, "", text_size=tSize)
        else
            table.cell(dashboardTable, 0, 12, "", text_size=tSize)
            table.cell(dashboardTable, 1, 12, "", text_size=tSize)
            table.cell(dashboardTable, 2, 12, "", text_size=tSize)
    else
        table.cell(dashboardTable, 0, 8, "No active setups detected.", text_color=color.gray, text_size=tSize, text_font_family=font.family_monospace)
        table.merge_cells(dashboardTable, 0, 8, 2, 8)
        for r = 9 to 12
            table.cell(dashboardTable, 0, r, "", text_size=tSize)
            table.merge_cells(dashboardTable, 0, r, 2, r)

Overview

When Unicorn Mode is enabled, it filters setups where breaker blocks overlap with Fair Value Gaps. It draws breaker boxes, FVG boxes, invalidation levels, and target lines based on customizable Risk-to-Reward ratios. Traders can adjust higher-timeframe source inputs, directional bias, and an optional ATR filter for FVGs. It plots visual elements directly on the price chart and supports alerts for setup activations, target completions, and invalidations.

Details

Published by

fxraptor-alpha on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Alerts?booltrueenableAlerts
Potential Breaker?booltruealertPotential
Activation?booltruealertActivation
Target Reached?booltruealertTarget
Invalidation Hit?booltruealertInvalidation
Historyint1historyCount
BiasstringNeutralbiasSelect
Unicorn Mode?booltrueunicornMode
Use Swing as Invalidation?boolfalseuseSwingInvalidation
Discard Invalidated from History?boolfalsediscardInvalidated
Enable FVG ATR FilterboolfalseenableFvgAtrFilter
FVG ATR Multiplierfloat0.5fvgAtrMult
Show all 40 settings
SettingTypeDefaultVariable
ModestringAutomatichtfMode
Source AbooltrueuseA
tfAtimeframe15tfA
typeAstringOHLC + SwingtypeA
Source BbooltrueuseB
tfBtimeframe60tfB
typeBstringOHLC + SwingtypeB
Source CbooltrueuseC
tfCtimeframe240tfC
typeCstringOHLC + SwingtypeC
Source DbooltrueuseD
tfDtimeframe1440tfD
typeDstringOHLC + SwingtypeD
Show R:R Target?booltrueshowRRTarget
R:Rfloat2.0rrRatio
Show Additional Targets?boolfalseshowAddTargets
Additional Targetsstring1, 3, 4, 5addTargetsStr
Labels SizestringSmalllabelSizeStr
Label Text Colorcolorcolor.blacklabelTextColor
Bull Breakercolorcolor.new(#089981, 85)bullColorActive
bullColorBordercolorcolor.new(#089981, 100)bullColorBorder
bullColorInactivecolorcolor.new(#787b86, 85)bullColorInactive
Bull FVGcolorcolor.new(#089981, 70)bullFvgColor
Bear Breakercolorcolor.new(#f23645, 85)bearColorActive
bearColorBordercolorcolor.new(#f23645, 100)bearColorBorder
bearColorInactivecolorcolor.new(#787b86, 85)bearColorInactive
Bear FVGcolorcolor.new(#f23645, 70)bearFvgColor
Sweep Linecolorcolor.graysweepLineColor
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

ICT Equal Highs and Lows

by ArunKBhaskar
575 stars

This overlay indicator finds pivot highs and lows on the price chart and compares the last two highs and the last two lows. When the percentage difference between a pair is below the Equal HL threshold, it treats them as equal levels and draws a box. Left and

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Equal Highs and Lows

DE-tracker

by Allen1212
303 stars

This indicator plots labels on the TradingView price chart to mark ARK Invest's buy and sell trades for the DE stock. Buy transactions appear in green with X1 or X2 multipliers showing the number of ARK ETFs involved on the same day; sells appear in red. The m

Indicator v4 Support & resistanceSMC / ICT / price action
Updated 2 years ago · None stated GitHub repository of DE-tracker

This Pine Script indicator plots a ZigZag on the price chart. It identifies reversal points when price moves more than a set percentage from the last pivot. The script calculates forecasts using zigzag data and labels detected geometric patterns including harm

Indicator v4 TrendSupport & resistance

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

Open Source Fractal

by cantolab
183 stars

This indicator implements the Fractal Model concept to plot market structure shifts, liquidity sweeps, and Change in State of Delivery (CISD) levels. It overlays custom higher-timeframe candle previews, open prices, and time markers directly on the price chart

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · MIT GitHub repository of Open Source Fractal

Swing Failure Signals

by Ganador1
154 stars

This indicator calculates pivot highs and pivot lows to detect liquidity sweeps and Swing Failure Patterns. It identifies when price breaks past recent key swing highs or lows within a configurable historical window, then looks for a quick reversal or change i

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · Apache-2.0 GitHub repository of Swing Failure Signals

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers