1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
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.
Multi-timeframe liquidity sweep and breaker block detection
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Alerts?
bool
true
enableAlerts
Potential Breaker?
bool
true
alertPotential
Activation?
bool
true
alertActivation
Target Reached?
bool
true
alertTarget
Invalidation Hit?
bool
true
alertInvalidation
History
int
1
historyCount
Bias
string
Neutral
biasSelect
Unicorn Mode?
bool
true
unicornMode
Use Swing as Invalidation?
bool
false
useSwingInvalidation
Discard Invalidated from History?
bool
false
discardInvalidated
Enable FVG ATR Filter
bool
false
enableFvgAtrFilter
FVG ATR Multiplier
float
0.5
fvgAtrMult
Show all 40 settings
Setting
Type
Default
Variable
Mode
string
Automatic
htfMode
Source A
bool
true
useA
tfA
timeframe
15
tfA
typeA
string
OHLC + Swing
typeA
Source B
bool
true
useB
tfB
timeframe
60
tfB
typeB
string
OHLC + Swing
typeB
Source C
bool
true
useC
tfC
timeframe
240
tfC
typeC
string
OHLC + Swing
typeC
Source D
bool
true
useD
tfD
timeframe
1440
tfD
typeD
string
OHLC + Swing
typeD
Show R:R Target?
bool
true
showRRTarget
R:R
float
2.0
rrRatio
Show Additional Targets?
bool
false
showAddTargets
Additional Targets
string
1, 3, 4, 5
addTargetsStr
Labels Size
string
Small
labelSizeStr
Label Text Color
color
color.black
labelTextColor
Bull Breaker
color
color.new(#089981, 85)
bullColorActive
bullColorBorder
color
color.new(#089981, 100)
bullColorBorder
bullColorInactive
color
color.new(#787b86, 85)
bullColorInactive
Bull FVG
color
color.new(#089981, 70)
bullFvgColor
Bear Breaker
color
color.new(#f23645, 85)
bearColorActive
bearColorBorder
color
color.new(#f23645, 100)
bearColorBorder
bearColorInactive
color
color.new(#787b86, 85)
bearColorInactive
Bear FVG
color
color.new(#f23645, 70)
bearFvgColor
Sweep Line
color
color.gray
sweepLineColor
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.
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
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
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
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
Indicatorv6SMC / ICT / price actionSessions & time
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
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