OPEN-SOURCE SCRIPT
Market Structure- Zig Zag, BoS and Supply/Demand Zones LIMITLESS

// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at mozilla.org/MPL/2.0/
// © The_Forex_Steward
//version=6
indicator("Market Structure- Zig Zag, BoS and Supply/Demand Zones", overlay=true)
// === User Inputs ===
htf = input.timeframe("", title="Timeframe")
internalShiftMode = input.string("Engulfment", title="Calculate Zig-Zag By", options=["Engulfment", "Market Shift (Engulfment)"])
x = input.int(1, minval=1, maxval= 3, title="# of Candles for Zones (1-3)")
showBearishOrderBlocks = input.bool(true, title="Show Supply Zones")
showBullishOrderBlocks = input.bool(true, title="Show Demand Zones")
orderBlockDuration = input.int(10, title="Zone Duration (bars)")
deleteMitigatedBoxes = input.bool(false, title="Delete Mitigated Zones")
deleteBrokenBoxes= input.bool(true, title = "Delete Broken Zones")
dimMitigatedBoxes = input.bool(true, title="Dim Mitigated Zones")
bearishBlockColor = input.color(color.rgb(255, 82, 82, 50), title="Supply Zone Fill")
bullishBlockColor = input.color(color.rgb(76, 175, 79, 50), title="Demand Zone Fill")
lighterBullishColor = color.new(bullishBlockColor, 85) // More transparent
lighterBearishColor = color.new(bearishBlockColor, 85)
zigzagLineColor = input.color(color.black, title="ZigZag Line Color")
zigzagLineWidth = input.int(2, title="Width of Lines", minval=1, maxval=10)
zigzagLineStyle = input.string("Solid", title="ZigZag Line Style", options=["Solid", "Dotted", "Dashed"])
internalShiftColor = color.new(zigzagLineColor, 75) // More transparent
bosBullishLineColor = input.color(color.green, title="Bullish BOS Line Color")
bosBearishLineColor = input.color(color.red, title="Bearish BOS Line Color")
bosLineStyle = input.string("Dotted", title="BOS Line Style", options=["Solid", "Dotted", "Dashed"])
bosLineStyleConst = bosLineStyle == "Solid" ? line.style_solid : bosLineStyle == "Dotted" ? line.style_dotted : line.style_dashed
alertMode = input.string("MTF", title= "Enable/Disable for Any Alert() Function Call↓ Alert Status→", options= ["LTF", "MTF", "HTF"])
alertSupplyandDemand = input.bool(true, title= "Supply & Demand Zones")
alertHighsandLows = input.bool(true, title= "Swing Highs & Lows")
alertBoS = input.bool(true, title= "BoS")
alertMS = input.bool(true, title= "Market Shifts")
hhBackgroundColor = color.rgb(76, 175, 79, 100)
hhTextColor = color.green
lhBackgroundColor = color.rgb(0, 137, 123, 100)
lhTextColor = color.red
llBackgroundColor = color.rgb(255, 82, 82, 100)
llTextColor = color.red
hlBackgroundColor = color.rgb(255, 153, 0, 100)
hlTextColor = color.green
HtfOpen = request.security(syminfo.tickerid, htf, open)
HtfHigh = request.security(syminfo.tickerid, htf, high)
HtfLow = request.security(syminfo.tickerid, htf, low)
HtfClose = request.security(syminfo.tickerid, htf, close)
prevHtfHigh = request.security(syminfo.tickerid, htf, high[1])
prevHtfLow = request.security(syminfo.tickerid, htf, low[1])
isHTFBarClose = ta.change(HtfClose) != 0
// Track the bar_index of the current bar when HTF closes
var int HtfBarIndex = na
if isHTFBarClose
HtfBarIndex := bar_index
// === Initialization ===
var int lastSignal = 0 // 0 = none, 1 = bull, -1 = bear
var float runningLowestHigh = na
var float runningHighestLow = na
// Track engulfed ranges
var float engulfedHigh = na
var float engulfedLow = na
// === Step 1: Detect "starter" engulfing ===
starterBull = HtfClose[1] < HtfOpen[1] and HtfClose > HtfOpen and HtfClose > HtfHigh[1]
starterBear = HtfClose[1] > HtfOpen[1] and HtfClose < HtfOpen and HtfClose < HtfLow[1]
if lastSignal == 0
if starterBull
lastSignal := 1
runningHighestLow := HtfLow
engulfedHigh := HtfHigh[1]
engulfedLow := HtfLow[1]
else if starterBear
lastSignal := -1
runningLowestHigh := HtfHigh
engulfedHigh := HtfHigh[1]
engulfedLow := HtfLow[1]
// === Step 2: Update running references ===
if lastSignal == -1 // last was bearish → waiting for bullish
runningLowestHigh := na(runningLowestHigh) ? HtfHigh : math.min(runningLowestHigh, HtfHigh)
else if lastSignal == 1 // last was bullish → waiting for bearish
runningHighestLow := na(runningHighestLow) ? HtfLow : math.max(runningHighestLow, HtfLow)
// === Step 3: Check for new engulfment ===
newBull = lastSignal == -1 and not na(runningLowestHigh) and HtfClose > runningLowestHigh
newBear = lastSignal == 1 and not na(runningHighestLow) and HtfClose < runningHighestLow
var int lastBullIndex = na
var int lastBearIndex = na
if newBull
lastBullIndex := HtfBarIndex
// store engulfed candle values (the one we just broke over)
engulfedHigh := runningLowestHigh
engulfedLow := HtfLow[1] // or HtfLow depending on how you define "engulfed"
if newBear
lastBearIndex := HtfBarIndex
engulfedLow := runningHighestLow
engulfedHigh := HtfHigh[1]
// === Step 4: Confirm and flip state ===
if newBull
lastSignal := 1
runningLowestHigh := na
runningHighestLow := HtfLow
else if newBear
lastSignal := -1
runningHighestLow := na
runningLowestHigh := HtfHigh
// === Track Boxes ===
var box[] bullishBoxes = array.new<box>()
var box[] bearishBoxes = array.new<box>()
// === Mitigation Flags ===
var bool bullishMitigated = false
var bool bearishMitigated = false
var bool bullishBreak = false
var bool bearishBreak = false
// === Delete invalidated boxes ===
if deleteBrokenBoxes
if array.size(bullishBoxes) > 0
for i = array.size(bullishBoxes) - 1 to 0
boxItem = array.get(bullishBoxes, i)
if HtfClose < box.get_bottom(boxItem)
box.delete(boxItem)
array.remove(bullishBoxes, i)
if array.size(bearishBoxes) > 0
for i = array.size(bearishBoxes) - 1 to 0
boxItem = array.get(bearishBoxes, i)
if HtfClose > box.get_top(boxItem)
box.delete(boxItem)
array.remove(bearishBoxes, i)
// === Delete mitigated boxes (optional) ===
if deleteMitigatedBoxes
if array.size(bullishBoxes) > 0
for i = array.size(bullishBoxes) - 1 to 0
boxItem = array.get(bullishBoxes, i)
if HtfLow < box.get_top(boxItem)
bullishMitigated := true
box.delete(boxItem)
array.remove(bullishBoxes, i)
if array.size(bearishBoxes) > 0
for i = array.size(bearishBoxes) - 1 to 0
boxItem = array.get(bearishBoxes, i)
if HtfHigh > box.get_bottom(boxItem)
bearishMitigated := true
box.delete(boxItem)
array.remove(bearishBoxes, i)
if dimMitigatedBoxes
if array.size(bullishBoxes) > 0
for i = 0 to array.size(bullishBoxes) - 1
boxItem = array.get(bullishBoxes, i)
if HtfLow < box.get_top(boxItem)
bullishMitigated := true
box.set_bgcolor(boxItem, lighterBullishColor)
box.set_border_color(boxItem, lighterBullishColor)
if array.size(bearishBoxes) > 0
for i = 0 to array.size(bearishBoxes) - 1
boxItem = array.get(bearishBoxes, i)
if HtfHigh > box.get_bottom(boxItem)
bearishMitigated := true
box.set_bgcolor(boxItem, lighterBearishColor)
box.set_border_color(boxItem, lighterBearishColor)
// Peramters for boxes
zoneHigh = ta.highest(HtfHigh[1], x)
zoneLow = ta.lowest(HtfLow[1], x)
// Create new order blocks with adjusted alignment
if showBullishOrderBlocks and newBull
bullishBox = box.new(left= HtfBarIndex[x], right=HtfBarIndex[x] + orderBlockDuration, top=zoneHigh, bottom=zoneLow, border_color=bullishBlockColor, bgcolor=bullishBlockColor)
array.push(bullishBoxes, bullishBox)
if showBearishOrderBlocks and newBear
bearishBox = box.new(left= HtfBarIndex[x], right=HtfBarIndex[x] + orderBlockDuration, top=zoneHigh, bottom=zoneLow, border_color=bearishBlockColor, bgcolor=bearishBlockColor)
array.push(bearishBoxes, bearishBox)
// === Internal Structure Logic ===
var int bullishCount = 0
var int bearishCount = 0
var float lowestBullishPrice = na
var float highestBearishPrice = na
var float firstBullishOpen = na
var float firstBearishOpen = na
var int lastInternalShift = 0
var float lastBullishInternalShiftPrice = na
var float lastBearishInternalShiftPrice = na
var float currentSwingHigh = na
var int currentSwingHighIndex = na
var float currentSwingLow = na
var int currentSwingLowIndex = na
var float prevSwingHigh = na
var float prevSwingLow = na
var bool isHH = false
var bool isHL = false
var bool isLL = false
var bool isLH = false
var bool isLiquiditySweep = false
var float lastOpposingLow = na // For HH
var float lastOpposingHigh = na // For LL
var bool internalShiftBullish = false
var bool internalShiftBearish = false
if ((internalShiftMode == "Engulfment") or (internalShiftMode == "Market Shift (Engulfment)"))
internalShiftBullish := newBull
internalShiftBearish := newBear
allowInternalShiftBearish = internalShiftBearish and lastInternalShift != -1
allowInternalShiftBullish = internalShiftBullish and lastInternalShift != 1
var bool plotBearishInternalShift = false
var bool plotBullishInternalShift = false
// === Determine Internal Shift Based on User Input ===
plotBearishInternalShift := false
plotBullishInternalShift := false
if allowInternalShiftBearish
plotBearishInternalShift := true
lastInternalShift := -1
if allowInternalShiftBullish
plotBullishInternalShift := true
lastInternalShift := 1
// === Plot internal shift markers ==
plotshape(plotBullishInternalShift, title="Bullish Internal Shift", location=location.belowbar, color=internalShiftColor, style=shape.triangleup, size=size.tiny)
plotshape(plotBearishInternalShift, title="Bearish Internal Shift", location=location.abovebar, color=internalShiftColor, style=shape.triangledown, size=size.tiny)
// === Highest High Between Alternate Bearish Break and Last Bullish Break (Safe) ===
var float localHigh = na
var int localHighIndex = na
maxHistory = 10000
if plotBearishInternalShift and ((internalShiftMode == "Engulfment") or (internalShiftMode == "Market Shift (Engulfment)"))
float highestHigh = na
int highestIndex = na
int startIndex = math.max(lastBullIndex, bar_index - maxHistory)
int endIndex = HtfBarIndex
for i = startIndex to endIndex
int lookback = bar_index - i // Convert i to relative offset for series access
if lookback >= 0 and lookback < maxHistory and not na(HtfHigh[lookback])
if na(highestHigh) or HtfHigh[lookback] > highestHigh
highestHigh := HtfHigh[lookback]
highestIndex := i
localHigh := highestHigh
localHighIndex := highestIndex
// === Lowest Low Between Alternate Bullish Break and Last Bearish Break (Safe) ===
var float localLow = na
var int localLowIndex = na
if plotBullishInternalShift and ((internalShiftMode == "Engulfment") or (internalShiftMode == "Market Shift (Engulfment)"))
float lowestLow = na
int lowestIndex = na
int startIndex = math.max(lastBearIndex, HtfBarIndex - maxHistory)
int endIndex = bar_index
for i = startIndex to endIndex
int lookback = bar_index - i // Convert i to relative offset
if lookback >= 0 and lookback < maxHistory and not na(HtfLow[lookback])
if na(lowestLow) or HtfLow[lookback] < lowestLow
lowestLow := HtfLow[lookback]
lowestIndex := i
localLow := lowestLow
localLowIndex := lowestIndex
// === Track Last Non-Alternating Break of Structure (BoS) ===
var int lastBullishBoSBarNA = na
var int lastBearishBoSBarNA = na
var float lastBullishBoSPriceNA = na
var float lastBearishBoSPriceNA = na
var bool bullishBOSOccurred = false
var bool bearishBOSOccurred = false
var int lastLowIndex = na
var int lastHighIndex = na
var float lastSwingHigh = na
var float lastSwingLow = na
// Reset flags
var bool canBreakBullish = true
var bool canBreakBearish = true
// BoS Conditions (non-alternating)
bullishBoS = canBreakBullish and HtfOpen[1] < localHigh and HtfClose > localHigh
bearishBoS = canBreakBearish and HtfOpen[1] > localLow and HtfClose < localLow
if bullishBoS and internalShiftMode == "Engulfment"
lastBullishBoSBarNA := bar_index
lastBullishBoSPriceNA := HtfClose
canBreakBullish := false // prevent further BoS on same localHigh
bullishBOSOccurred := true
line.new(x1=localHighIndex, y1=localHigh, x2=bar_index, y2=localHigh, color=bosBullishLineColor, width=zigzagLineWidth, style=bosLineStyleConst)
lastSwingHigh := na
if bearishBoS and internalShiftMode == "Engulfment"
lastBearishBoSBarNA := bar_index
lastBearishBoSPriceNA := HtfClose
canBreakBearish := false // prevent further BoS on same localLow
bearishBOSOccurred := true
line.new(x1=localLowIndex, y1=localLow, x2=bar_index, y2=localLow, color=bosBearishLineColor, width=zigzagLineWidth, style=bosLineStyleConst)
lastSwingLow := na
// Reset logic — allow new break only if local high/low changes
if ta.change(localHigh) != 0
canBreakBullish := true
if ta.change(localLow) != 0
canBreakBearish := true
// === Track Last MS Event ===
var int lastBullishBoSBar = na
var int lastBearishBoSBar = na
var float lastBullishBoSPrice = na
var float lastBearishBoSPrice = na
var bool SwingHighBOSOccurred = false
var bool SwingLowBOSOccurred = false
var int lastSwingLowIndex = na
var int lastSwingHighIndex = na
var float lastSSwingHigh = na
var float lastSSwingLow = na
// Track last BoS type: 1 = bullish, -1 = bearish, 0 = none yet
var int lastBoSType = 0
// === Track Last MS Type ===
var int lastMSType = na // 1 = bullish, -1 = bearish
// === MS Detection Logic ===
rawBullishMS = HtfClose > localHigh
rawBearishMS = HtfClose < localLow
// === Enforce Alternation ===
canBullishMS = na(lastMSType) or lastMSType == -1
canBearishMS = na(lastMSType) or lastMSType == 1
bullishMS = rawBullishMS and canBullishMS
bearishMS = rawBearishMS and canBearishMS
plotshape(bullishMS, title="Bullish Market Shift", location=location.belowbar, color=zigzagLineColor, style=shape.triangleup, size=size.tiny)
plotshape(bearishMS, title="Bearish Market Shift", location=location.abovebar, color=zigzagLineColor, style=shape.triangledown, size=size.tiny)
// === Update Last MS Type and BoS Bars ===
if bullishMS
lastMSType := 1
lastBullishBoSBar := bar_index
if bearishMS
lastMSType := -1
lastBearishBoSBar := bar_index
// === Lowest Low Between Last Bearish MS and This Bullish MS ===
var float msLocalLow = na
var int msLocalLowIndex = na
msMaxHistory = 5000
if bullishMS
float msLowestLow = na
int msLowestIndex = na
int msStartIndex = na(lastBearishBoSBar) ? bar_index - msMaxHistory : lastBearishBoSBar
int msEndIndex = bar_index // safer than using HtfBarIndex unless defined
for i = msStartIndex to msEndIndex
int msLookback = bar_index - i
if msLookback >= 0 and msLookback < msMaxHistory and not na(HtfLow[msLookback])
if na(msLowestLow) or HtfLow[msLookback] < msLowestLow
msLowestLow := HtfLow[msLookback]
msLowestIndex := i
msLocalLow := msLowestLow
msLocalLowIndex := msLowestIndex
// === Highest High Between Last Bullish MS and This Bearish MS ===
var float msLocalHigh = na
var int msLocalHighIndex = na
if bearishMS
float msHighestHigh = na
int msHighestIndex = na
int msStartIndex = na(lastBullishBoSBar) ? bar_index - msMaxHistory : lastBullishBoSBar
int msEndIndex = bar_index
for i = msStartIndex to msEndIndex
int msLookback = bar_index - i
if msLookback >= 0 and msLookback < msMaxHistory and not na(HtfHigh[msLookback])
if na(msHighestHigh) or HtfHigh[msLookback] > msHighestHigh
msHighestHigh := HtfHigh[msLookback]
msHighestIndex := i
msLocalHigh := msHighestHigh
msLocalHighIndex := msHighestIndex
// === Persistent variables for multiple line handling ===
var line[] zigzagLines = array.new<line>()
var int lastBearishShiftBar = na
var int lastBullishShiftBar = na
var float lastZigzagPrice = na
var string lastSwingType = ""
// Save shift bar indices
if plotBearishInternalShift
lastBearishShiftBar := bar_index
if plotBullishInternalShift
lastBullishShiftBar := bar_index
// Bearish shift followed by Bullish shift → Track lowest low
if plotBullishInternalShift and internalShiftMode == "Engulfment"
// Plot zigzag line
// Plot zigzag line for LL and HL separately
if not na(prevSwingLow)
if localLow < prevSwingLow // LL
if zigzagLineStyle == "Solid"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing low and plot label (HL or LL)
if not na(prevSwingLow)
isLL := not na(prevSwingLow) and localLow < prevSwingLow
isHL := not na(prevSwingLow) and localLow > prevSwingLow
if isLL
if bearishBOSOccurred
label.new(localLowIndex, localLow, "LL", color=llBackgroundColor, style=label.style_label_up, textcolor=llTextColor, size=size.small)
isLiquiditySweep := false // Definitely not a sweep if BOS occurred
else
label.new(localLowIndex, localLow, "LS", color=color.rgb(155, 39, 176, 100), style=label.style_label_up, textcolor=color.orange, size=size.small)
isLiquiditySweep := true
else
isLiquiditySweep := false // Reset only if not LL
lastOpposingHigh := prevSwingHigh
bearishBOSOccurred := false
if isHL
label.new(localLowIndex, localLow, "HL", color=hlBackgroundColor, style=label.style_label_up, textcolor=hlTextColor, size=size.small)
lastOpposingHigh := prevSwingHigh
bearishBOSOccurred := false
prevSwingLow := localLow
lastZigzagPrice := localLow
lastSwingLow := localLow
lastLowIndex := localLowIndex
lastBearishShiftBar := bar_index
if bullishMS and internalShiftMode == "Market Shift (Engulfment)"
// Plot zigzag line
// Plot zigzag line for LL and HL separately
if not na(prevSwingLow)
if msLocalLow < prevSwingLow // LL
if zigzagLineStyle == "Solid"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing low and plot label (HL or LL)
if not na(prevSwingLow)
isLL := not na(prevSwingLow) and msLocalLow < prevSwingLow
isHL := not na(prevSwingLow) and msLocalLow > prevSwingLow
if isLL
label.new(msLocalLowIndex, msLocalLow, "LL", color=llBackgroundColor, style=label.style_label_up, textcolor=llTextColor, size=size.small)
if isHL
label.new(msLocalLowIndex, msLocalLow, "HL", color=hlBackgroundColor, style=label.style_label_up, textcolor=hlTextColor, size=size.small)
lastOpposingHigh := prevSwingHigh
SwingLowBOSOccurred := false
prevSwingLow := msLocalLow
lastZigzagPrice := msLocalLow
lastSwingLow := msLocalLow
lastLowIndex := msLocalLowIndex
lastBearishShiftBar := bar_index
//========================================================================================
if plotBearishInternalShift and internalShiftMode == "Engulfment"
// Plot zigzag line
if not na(prevSwingHigh)
if localHigh > prevSwingHigh // HH
if zigzagLineStyle == "Solid"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing high and plot label (HH or LH)
if not na(prevSwingHigh)
isHH := not na(prevSwingHigh) and localHigh > prevSwingHigh
isLH := not na(prevSwingHigh) and localHigh < prevSwingHigh
if isHH
if bullishBOSOccurred
label.new(localHighIndex, localHigh, "HH", color=hhBackgroundColor, style=label.style_label_down, textcolor=hhTextColor, size=size.small)
isLiquiditySweep := false
else
label.new(localHighIndex, localHigh, "LS", color=color.rgb(155, 39, 176, 100), style=label.style_label_down, textcolor=color.orange, size=size.small)
isLiquiditySweep := true
else
isLiquiditySweep := false
bullishBOSOccurred := false
if isLH
label.new(localHighIndex, localHigh, "LH", color=lhBackgroundColor, style=label.style_label_down, textcolor=lhTextColor, size=size.small)
lastOpposingLow := prevSwingLow
bullishBOSOccurred := false
prevSwingHigh := localHigh
lastZigzagPrice := localHigh
lastSwingHigh := localHigh
lastHighIndex := localHighIndex
lastBullishShiftBar := bar_index
if bearishMS and internalShiftMode == "Market Shift (Engulfment)"
// Plot zigzag line
if not na(prevSwingHigh)
if msLocalHigh > prevSwingHigh // HH
if zigzagLineStyle == "Solid"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing high and plot label (HH or LH)
if not na(prevSwingHigh)
isHH := not na(prevSwingHigh) and msLocalHigh > prevSwingHigh
isLH := not na(prevSwingHigh) and msLocalHigh < prevSwingHigh
if isHH
label.new(msLocalHighIndex, msLocalHigh, "HH", color=hhBackgroundColor, style=label.style_label_down, textcolor=hhTextColor, size=size.small)
SwingHighBOSOccurred := false
if isLH
label.new(msLocalHighIndex, msLocalHigh, "LH", color=lhBackgroundColor, style=label.style_label_down, textcolor=lhTextColor, size=size.small)
lastOpposingLow := prevSwingLow
SwingHighBOSOccurred := false
prevSwingHigh := msLocalHigh
lastZigzagPrice := msLocalHigh
lastSwingHigh := msLocalHigh
lastHighIndex := msLocalHighIndex
lastBullishShiftBar := bar_index
// === Alert Conditions ===
alertcondition(newBull, title="New Supply Zone", message="New supply zone available.")
alertcondition(newBear, title="New Demand Zone", message="New demand zone available.")
alertcondition(plotBullishInternalShift, title="Bullish Internal Shift (All Lows)", message="Bullish Internal Shift detected! Check Swing Low.")
alertcondition(plotBearishInternalShift, title="Bearish Internal Shift (All Highs)", message="Bearish Internal Shift detected! Check Swing High.")
alertcondition(bullishBOSOccurred, title="Bullish Break of Structure", message="Bullish BoS detected.")
alertcondition(bearishBOSOccurred, title="Bearish Break of Structure", message="Bearish BoS detected.")
alertcondition(bullishMS, title="Bullish Market Shift", message="Bullish market shift detected.")
alertcondition(bearishMS, title="Bearish Market Shift", message="Bearish market shift detected.")
alertcondition(isHH and plotBearishInternalShift and not isLiquiditySweep, title="Higher High (HH)", message="Higher High (HH) detected")
alertcondition(isHL and plotBullishInternalShift, title="Higher Low (HL)", message="Higher Low (HL) detected")
alertcondition(isLL and plotBullishInternalShift and not isLiquiditySweep, title="Lower Low (LL)", message="Lower Low (LL) detected")
alertcondition(isLH and plotBearishInternalShift, title="Lower High (LH)", message="Lower High (LH) detected")
alertcondition((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift), title="Liquidity Sweep (LS)", message="Liquidity Sweep (LS) detected")
// === Alerts ===
if alertMode == "LTF"
if isHH and plotBearishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Higher High (HH) detected (LTF)", alert.freq_once_per_bar_close)
if isHL and plotBullishInternalShift and (alertHighsandLows == true)
alert("Higher Low (HL) detected (LTF)", alert.freq_once_per_bar_close)
if isLL and plotBullishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Lower Low (LL) detected (LTF)" , alert.freq_once_per_bar_close)
if isLH and plotBearishInternalShift and (alertHighsandLows == true)
alert("Lower High (LH) detected (LTF)", alert.freq_once_per_bar_close)
if ((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift)) and (alertHighsandLows == true)
alert("Liquidity Sweep (LS) detected (LTF)", alert.freq_once_per_bar_close)
if newBear and (alertSupplyandDemand == true)
alert("New supply zone available. (LTF)", alert.freq_once_per_bar_close)
if newBull and (alertSupplyandDemand == true)
alert("New demand zone available. (LTF)", alert.freq_once_per_bar_close)
if bullishBOSOccurred and (alertBoS == true)
alert("Bullish BoS detected. (LTF)", alert.freq_once_per_bar_close)
if bearishBOSOccurred and (alertBoS == true)
alert("Bearish BoS detected. (LTF)", alert.freq_once_per_bar_close)
if bullishMS and (alertMS == true)
alert("Bullish market shift detected (LTF).", alert.freq_once_per_bar_close)
if bearishMS and (alertMS == true)
alert("Bearish market shift detected (LTF).", alert.freq_once_per_bar_close)
if alertMode == "MTF"
if isHH and plotBearishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Higher High (HH) detected (MTF)", alert.freq_once_per_bar_close)
if isHL and plotBullishInternalShift and (alertHighsandLows == true)
alert("Higher Low (HL) detected (MTF)", alert.freq_once_per_bar_close)
if isLL and plotBullishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Lower Low (LL) detected (MTF)" , alert.freq_once_per_bar_close)
if isLH and plotBearishInternalShift and (alertHighsandLows == true)
alert("Lower High (LH) detected (MTF)", alert.freq_once_per_bar_close)
if ((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift)) and (alertHighsandLows == true)
alert("Liquidity Sweep (LS) detected (MTF)", alert.freq_once_per_bar_close)
if newBear and (alertSupplyandDemand == true)
alert("New supply zone available. (MTF)", alert.freq_once_per_bar_close)
if newBull and (alertSupplyandDemand == true)
alert("New demand zone available. (MTF)", alert.freq_once_per_bar_close)
if bullishBOSOccurred and (alertBoS == true)
alert("Bullish BoS detected. (MTF)", alert.freq_once_per_bar_close)
if bearishBOSOccurred and (alertBoS == true)
alert("Bearish BoS detected. (MTF)", alert.freq_once_per_bar_close)
if bullishMS and (alertMS == true)
alert("Bullish market shift detected (MTF).", alert.freq_once_per_bar_close)
if bearishMS and (alertMS == true)
alert("Bearish market shift detected (MTF).", alert.freq_once_per_bar_close)
if alertMode == "HTF"
if isHH and plotBearishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Higher High (HH) detected (HTF)", alert.freq_once_per_bar_close)
if isHL and plotBullishInternalShift and (alertHighsandLows == true)
alert("Higher Low (HL) detected (HTF)", alert.freq_once_per_bar_close)
if isLL and plotBullishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Lower Low (LL) detected (HTF)" , alert.freq_once_per_bar_close)
if isLH and plotBearishInternalShift and (alertHighsandLows == true)
alert("Lower High (LH) detected (HTF)", alert.freq_once_per_bar_close)
if ((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift)) and (alertHighsandLows == true)
alert("Liquidity Sweep (LS) detected (HTF)", alert.freq_once_per_bar_close)
if newBear and (alertSupplyandDemand == true)
alert("New supply zone available. (HTF)", alert.freq_once_per_bar_close)
if newBull and (alertSupplyandDemand == true)
alert("New demand zone available. (HTF)", alert.freq_once_per_bar_close)
if bullishBOSOccurred and (alertBoS == true)
alert("Bullish BoS detected. (HTF)", alert.freq_once_per_bar_close)
if bearishBOSOccurred and (alertBoS == true)
alert("Bearish BoS detected. (HTF)", alert.freq_once_per_bar_close)
if bullishMS and (alertMS == true)
alert("Bullish market shift detected (HTF).", alert.freq_once_per_bar_close)
if bearishMS and (alertMS == true)
alert("Bearish market shift detected (HTF).", alert.freq_once_per_bar_close)
// © The_Forex_Steward
//version=6
indicator("Market Structure- Zig Zag, BoS and Supply/Demand Zones", overlay=true)
// === User Inputs ===
htf = input.timeframe("", title="Timeframe")
internalShiftMode = input.string("Engulfment", title="Calculate Zig-Zag By", options=["Engulfment", "Market Shift (Engulfment)"])
x = input.int(1, minval=1, maxval= 3, title="# of Candles for Zones (1-3)")
showBearishOrderBlocks = input.bool(true, title="Show Supply Zones")
showBullishOrderBlocks = input.bool(true, title="Show Demand Zones")
orderBlockDuration = input.int(10, title="Zone Duration (bars)")
deleteMitigatedBoxes = input.bool(false, title="Delete Mitigated Zones")
deleteBrokenBoxes= input.bool(true, title = "Delete Broken Zones")
dimMitigatedBoxes = input.bool(true, title="Dim Mitigated Zones")
bearishBlockColor = input.color(color.rgb(255, 82, 82, 50), title="Supply Zone Fill")
bullishBlockColor = input.color(color.rgb(76, 175, 79, 50), title="Demand Zone Fill")
lighterBullishColor = color.new(bullishBlockColor, 85) // More transparent
lighterBearishColor = color.new(bearishBlockColor, 85)
zigzagLineColor = input.color(color.black, title="ZigZag Line Color")
zigzagLineWidth = input.int(2, title="Width of Lines", minval=1, maxval=10)
zigzagLineStyle = input.string("Solid", title="ZigZag Line Style", options=["Solid", "Dotted", "Dashed"])
internalShiftColor = color.new(zigzagLineColor, 75) // More transparent
bosBullishLineColor = input.color(color.green, title="Bullish BOS Line Color")
bosBearishLineColor = input.color(color.red, title="Bearish BOS Line Color")
bosLineStyle = input.string("Dotted", title="BOS Line Style", options=["Solid", "Dotted", "Dashed"])
bosLineStyleConst = bosLineStyle == "Solid" ? line.style_solid : bosLineStyle == "Dotted" ? line.style_dotted : line.style_dashed
alertMode = input.string("MTF", title= "Enable/Disable for Any Alert() Function Call↓ Alert Status→", options= ["LTF", "MTF", "HTF"])
alertSupplyandDemand = input.bool(true, title= "Supply & Demand Zones")
alertHighsandLows = input.bool(true, title= "Swing Highs & Lows")
alertBoS = input.bool(true, title= "BoS")
alertMS = input.bool(true, title= "Market Shifts")
hhBackgroundColor = color.rgb(76, 175, 79, 100)
hhTextColor = color.green
lhBackgroundColor = color.rgb(0, 137, 123, 100)
lhTextColor = color.red
llBackgroundColor = color.rgb(255, 82, 82, 100)
llTextColor = color.red
hlBackgroundColor = color.rgb(255, 153, 0, 100)
hlTextColor = color.green
HtfOpen = request.security(syminfo.tickerid, htf, open)
HtfHigh = request.security(syminfo.tickerid, htf, high)
HtfLow = request.security(syminfo.tickerid, htf, low)
HtfClose = request.security(syminfo.tickerid, htf, close)
prevHtfHigh = request.security(syminfo.tickerid, htf, high[1])
prevHtfLow = request.security(syminfo.tickerid, htf, low[1])
isHTFBarClose = ta.change(HtfClose) != 0
// Track the bar_index of the current bar when HTF closes
var int HtfBarIndex = na
if isHTFBarClose
HtfBarIndex := bar_index
// === Initialization ===
var int lastSignal = 0 // 0 = none, 1 = bull, -1 = bear
var float runningLowestHigh = na
var float runningHighestLow = na
// Track engulfed ranges
var float engulfedHigh = na
var float engulfedLow = na
// === Step 1: Detect "starter" engulfing ===
starterBull = HtfClose[1] < HtfOpen[1] and HtfClose > HtfOpen and HtfClose > HtfHigh[1]
starterBear = HtfClose[1] > HtfOpen[1] and HtfClose < HtfOpen and HtfClose < HtfLow[1]
if lastSignal == 0
if starterBull
lastSignal := 1
runningHighestLow := HtfLow
engulfedHigh := HtfHigh[1]
engulfedLow := HtfLow[1]
else if starterBear
lastSignal := -1
runningLowestHigh := HtfHigh
engulfedHigh := HtfHigh[1]
engulfedLow := HtfLow[1]
// === Step 2: Update running references ===
if lastSignal == -1 // last was bearish → waiting for bullish
runningLowestHigh := na(runningLowestHigh) ? HtfHigh : math.min(runningLowestHigh, HtfHigh)
else if lastSignal == 1 // last was bullish → waiting for bearish
runningHighestLow := na(runningHighestLow) ? HtfLow : math.max(runningHighestLow, HtfLow)
// === Step 3: Check for new engulfment ===
newBull = lastSignal == -1 and not na(runningLowestHigh) and HtfClose > runningLowestHigh
newBear = lastSignal == 1 and not na(runningHighestLow) and HtfClose < runningHighestLow
var int lastBullIndex = na
var int lastBearIndex = na
if newBull
lastBullIndex := HtfBarIndex
// store engulfed candle values (the one we just broke over)
engulfedHigh := runningLowestHigh
engulfedLow := HtfLow[1] // or HtfLow depending on how you define "engulfed"
if newBear
lastBearIndex := HtfBarIndex
engulfedLow := runningHighestLow
engulfedHigh := HtfHigh[1]
// === Step 4: Confirm and flip state ===
if newBull
lastSignal := 1
runningLowestHigh := na
runningHighestLow := HtfLow
else if newBear
lastSignal := -1
runningHighestLow := na
runningLowestHigh := HtfHigh
// === Track Boxes ===
var box[] bullishBoxes = array.new<box>()
var box[] bearishBoxes = array.new<box>()
// === Mitigation Flags ===
var bool bullishMitigated = false
var bool bearishMitigated = false
var bool bullishBreak = false
var bool bearishBreak = false
// === Delete invalidated boxes ===
if deleteBrokenBoxes
if array.size(bullishBoxes) > 0
for i = array.size(bullishBoxes) - 1 to 0
boxItem = array.get(bullishBoxes, i)
if HtfClose < box.get_bottom(boxItem)
box.delete(boxItem)
array.remove(bullishBoxes, i)
if array.size(bearishBoxes) > 0
for i = array.size(bearishBoxes) - 1 to 0
boxItem = array.get(bearishBoxes, i)
if HtfClose > box.get_top(boxItem)
box.delete(boxItem)
array.remove(bearishBoxes, i)
// === Delete mitigated boxes (optional) ===
if deleteMitigatedBoxes
if array.size(bullishBoxes) > 0
for i = array.size(bullishBoxes) - 1 to 0
boxItem = array.get(bullishBoxes, i)
if HtfLow < box.get_top(boxItem)
bullishMitigated := true
box.delete(boxItem)
array.remove(bullishBoxes, i)
if array.size(bearishBoxes) > 0
for i = array.size(bearishBoxes) - 1 to 0
boxItem = array.get(bearishBoxes, i)
if HtfHigh > box.get_bottom(boxItem)
bearishMitigated := true
box.delete(boxItem)
array.remove(bearishBoxes, i)
if dimMitigatedBoxes
if array.size(bullishBoxes) > 0
for i = 0 to array.size(bullishBoxes) - 1
boxItem = array.get(bullishBoxes, i)
if HtfLow < box.get_top(boxItem)
bullishMitigated := true
box.set_bgcolor(boxItem, lighterBullishColor)
box.set_border_color(boxItem, lighterBullishColor)
if array.size(bearishBoxes) > 0
for i = 0 to array.size(bearishBoxes) - 1
boxItem = array.get(bearishBoxes, i)
if HtfHigh > box.get_bottom(boxItem)
bearishMitigated := true
box.set_bgcolor(boxItem, lighterBearishColor)
box.set_border_color(boxItem, lighterBearishColor)
// Peramters for boxes
zoneHigh = ta.highest(HtfHigh[1], x)
zoneLow = ta.lowest(HtfLow[1], x)
// Create new order blocks with adjusted alignment
if showBullishOrderBlocks and newBull
bullishBox = box.new(left= HtfBarIndex[x], right=HtfBarIndex[x] + orderBlockDuration, top=zoneHigh, bottom=zoneLow, border_color=bullishBlockColor, bgcolor=bullishBlockColor)
array.push(bullishBoxes, bullishBox)
if showBearishOrderBlocks and newBear
bearishBox = box.new(left= HtfBarIndex[x], right=HtfBarIndex[x] + orderBlockDuration, top=zoneHigh, bottom=zoneLow, border_color=bearishBlockColor, bgcolor=bearishBlockColor)
array.push(bearishBoxes, bearishBox)
// === Internal Structure Logic ===
var int bullishCount = 0
var int bearishCount = 0
var float lowestBullishPrice = na
var float highestBearishPrice = na
var float firstBullishOpen = na
var float firstBearishOpen = na
var int lastInternalShift = 0
var float lastBullishInternalShiftPrice = na
var float lastBearishInternalShiftPrice = na
var float currentSwingHigh = na
var int currentSwingHighIndex = na
var float currentSwingLow = na
var int currentSwingLowIndex = na
var float prevSwingHigh = na
var float prevSwingLow = na
var bool isHH = false
var bool isHL = false
var bool isLL = false
var bool isLH = false
var bool isLiquiditySweep = false
var float lastOpposingLow = na // For HH
var float lastOpposingHigh = na // For LL
var bool internalShiftBullish = false
var bool internalShiftBearish = false
if ((internalShiftMode == "Engulfment") or (internalShiftMode == "Market Shift (Engulfment)"))
internalShiftBullish := newBull
internalShiftBearish := newBear
allowInternalShiftBearish = internalShiftBearish and lastInternalShift != -1
allowInternalShiftBullish = internalShiftBullish and lastInternalShift != 1
var bool plotBearishInternalShift = false
var bool plotBullishInternalShift = false
// === Determine Internal Shift Based on User Input ===
plotBearishInternalShift := false
plotBullishInternalShift := false
if allowInternalShiftBearish
plotBearishInternalShift := true
lastInternalShift := -1
if allowInternalShiftBullish
plotBullishInternalShift := true
lastInternalShift := 1
// === Plot internal shift markers ==
plotshape(plotBullishInternalShift, title="Bullish Internal Shift", location=location.belowbar, color=internalShiftColor, style=shape.triangleup, size=size.tiny)
plotshape(plotBearishInternalShift, title="Bearish Internal Shift", location=location.abovebar, color=internalShiftColor, style=shape.triangledown, size=size.tiny)
// === Highest High Between Alternate Bearish Break and Last Bullish Break (Safe) ===
var float localHigh = na
var int localHighIndex = na
maxHistory = 10000
if plotBearishInternalShift and ((internalShiftMode == "Engulfment") or (internalShiftMode == "Market Shift (Engulfment)"))
float highestHigh = na
int highestIndex = na
int startIndex = math.max(lastBullIndex, bar_index - maxHistory)
int endIndex = HtfBarIndex
for i = startIndex to endIndex
int lookback = bar_index - i // Convert i to relative offset for series access
if lookback >= 0 and lookback < maxHistory and not na(HtfHigh[lookback])
if na(highestHigh) or HtfHigh[lookback] > highestHigh
highestHigh := HtfHigh[lookback]
highestIndex := i
localHigh := highestHigh
localHighIndex := highestIndex
// === Lowest Low Between Alternate Bullish Break and Last Bearish Break (Safe) ===
var float localLow = na
var int localLowIndex = na
if plotBullishInternalShift and ((internalShiftMode == "Engulfment") or (internalShiftMode == "Market Shift (Engulfment)"))
float lowestLow = na
int lowestIndex = na
int startIndex = math.max(lastBearIndex, HtfBarIndex - maxHistory)
int endIndex = bar_index
for i = startIndex to endIndex
int lookback = bar_index - i // Convert i to relative offset
if lookback >= 0 and lookback < maxHistory and not na(HtfLow[lookback])
if na(lowestLow) or HtfLow[lookback] < lowestLow
lowestLow := HtfLow[lookback]
lowestIndex := i
localLow := lowestLow
localLowIndex := lowestIndex
// === Track Last Non-Alternating Break of Structure (BoS) ===
var int lastBullishBoSBarNA = na
var int lastBearishBoSBarNA = na
var float lastBullishBoSPriceNA = na
var float lastBearishBoSPriceNA = na
var bool bullishBOSOccurred = false
var bool bearishBOSOccurred = false
var int lastLowIndex = na
var int lastHighIndex = na
var float lastSwingHigh = na
var float lastSwingLow = na
// Reset flags
var bool canBreakBullish = true
var bool canBreakBearish = true
// BoS Conditions (non-alternating)
bullishBoS = canBreakBullish and HtfOpen[1] < localHigh and HtfClose > localHigh
bearishBoS = canBreakBearish and HtfOpen[1] > localLow and HtfClose < localLow
if bullishBoS and internalShiftMode == "Engulfment"
lastBullishBoSBarNA := bar_index
lastBullishBoSPriceNA := HtfClose
canBreakBullish := false // prevent further BoS on same localHigh
bullishBOSOccurred := true
line.new(x1=localHighIndex, y1=localHigh, x2=bar_index, y2=localHigh, color=bosBullishLineColor, width=zigzagLineWidth, style=bosLineStyleConst)
lastSwingHigh := na
if bearishBoS and internalShiftMode == "Engulfment"
lastBearishBoSBarNA := bar_index
lastBearishBoSPriceNA := HtfClose
canBreakBearish := false // prevent further BoS on same localLow
bearishBOSOccurred := true
line.new(x1=localLowIndex, y1=localLow, x2=bar_index, y2=localLow, color=bosBearishLineColor, width=zigzagLineWidth, style=bosLineStyleConst)
lastSwingLow := na
// Reset logic — allow new break only if local high/low changes
if ta.change(localHigh) != 0
canBreakBullish := true
if ta.change(localLow) != 0
canBreakBearish := true
// === Track Last MS Event ===
var int lastBullishBoSBar = na
var int lastBearishBoSBar = na
var float lastBullishBoSPrice = na
var float lastBearishBoSPrice = na
var bool SwingHighBOSOccurred = false
var bool SwingLowBOSOccurred = false
var int lastSwingLowIndex = na
var int lastSwingHighIndex = na
var float lastSSwingHigh = na
var float lastSSwingLow = na
// Track last BoS type: 1 = bullish, -1 = bearish, 0 = none yet
var int lastBoSType = 0
// === Track Last MS Type ===
var int lastMSType = na // 1 = bullish, -1 = bearish
// === MS Detection Logic ===
rawBullishMS = HtfClose > localHigh
rawBearishMS = HtfClose < localLow
// === Enforce Alternation ===
canBullishMS = na(lastMSType) or lastMSType == -1
canBearishMS = na(lastMSType) or lastMSType == 1
bullishMS = rawBullishMS and canBullishMS
bearishMS = rawBearishMS and canBearishMS
plotshape(bullishMS, title="Bullish Market Shift", location=location.belowbar, color=zigzagLineColor, style=shape.triangleup, size=size.tiny)
plotshape(bearishMS, title="Bearish Market Shift", location=location.abovebar, color=zigzagLineColor, style=shape.triangledown, size=size.tiny)
// === Update Last MS Type and BoS Bars ===
if bullishMS
lastMSType := 1
lastBullishBoSBar := bar_index
if bearishMS
lastMSType := -1
lastBearishBoSBar := bar_index
// === Lowest Low Between Last Bearish MS and This Bullish MS ===
var float msLocalLow = na
var int msLocalLowIndex = na
msMaxHistory = 5000
if bullishMS
float msLowestLow = na
int msLowestIndex = na
int msStartIndex = na(lastBearishBoSBar) ? bar_index - msMaxHistory : lastBearishBoSBar
int msEndIndex = bar_index // safer than using HtfBarIndex unless defined
for i = msStartIndex to msEndIndex
int msLookback = bar_index - i
if msLookback >= 0 and msLookback < msMaxHistory and not na(HtfLow[msLookback])
if na(msLowestLow) or HtfLow[msLookback] < msLowestLow
msLowestLow := HtfLow[msLookback]
msLowestIndex := i
msLocalLow := msLowestLow
msLocalLowIndex := msLowestIndex
// === Highest High Between Last Bullish MS and This Bearish MS ===
var float msLocalHigh = na
var int msLocalHighIndex = na
if bearishMS
float msHighestHigh = na
int msHighestIndex = na
int msStartIndex = na(lastBullishBoSBar) ? bar_index - msMaxHistory : lastBullishBoSBar
int msEndIndex = bar_index
for i = msStartIndex to msEndIndex
int msLookback = bar_index - i
if msLookback >= 0 and msLookback < msMaxHistory and not na(HtfHigh[msLookback])
if na(msHighestHigh) or HtfHigh[msLookback] > msHighestHigh
msHighestHigh := HtfHigh[msLookback]
msHighestIndex := i
msLocalHigh := msHighestHigh
msLocalHighIndex := msHighestIndex
// === Persistent variables for multiple line handling ===
var line[] zigzagLines = array.new<line>()
var int lastBearishShiftBar = na
var int lastBullishShiftBar = na
var float lastZigzagPrice = na
var string lastSwingType = ""
// Save shift bar indices
if plotBearishInternalShift
lastBearishShiftBar := bar_index
if plotBullishInternalShift
lastBullishShiftBar := bar_index
// Bearish shift followed by Bullish shift → Track lowest low
if plotBullishInternalShift and internalShiftMode == "Engulfment"
// Plot zigzag line
// Plot zigzag line for LL and HL separately
if not na(prevSwingLow)
if localLow < prevSwingLow // LL
if zigzagLineStyle == "Solid"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localHighIndex, y1=localHigh, x2=localLowIndex, y2=localLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing low and plot label (HL or LL)
if not na(prevSwingLow)
isLL := not na(prevSwingLow) and localLow < prevSwingLow
isHL := not na(prevSwingLow) and localLow > prevSwingLow
if isLL
if bearishBOSOccurred
label.new(localLowIndex, localLow, "LL", color=llBackgroundColor, style=label.style_label_up, textcolor=llTextColor, size=size.small)
isLiquiditySweep := false // Definitely not a sweep if BOS occurred
else
label.new(localLowIndex, localLow, "LS", color=color.rgb(155, 39, 176, 100), style=label.style_label_up, textcolor=color.orange, size=size.small)
isLiquiditySweep := true
else
isLiquiditySweep := false // Reset only if not LL
lastOpposingHigh := prevSwingHigh
bearishBOSOccurred := false
if isHL
label.new(localLowIndex, localLow, "HL", color=hlBackgroundColor, style=label.style_label_up, textcolor=hlTextColor, size=size.small)
lastOpposingHigh := prevSwingHigh
bearishBOSOccurred := false
prevSwingLow := localLow
lastZigzagPrice := localLow
lastSwingLow := localLow
lastLowIndex := localLowIndex
lastBearishShiftBar := bar_index
if bullishMS and internalShiftMode == "Market Shift (Engulfment)"
// Plot zigzag line
// Plot zigzag line for LL and HL separately
if not na(prevSwingLow)
if msLocalLow < prevSwingLow // LL
if zigzagLineStyle == "Solid"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalHighIndex, y1=msLocalHigh, x2=msLocalLowIndex, y2=msLocalLow, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing low and plot label (HL or LL)
if not na(prevSwingLow)
isLL := not na(prevSwingLow) and msLocalLow < prevSwingLow
isHL := not na(prevSwingLow) and msLocalLow > prevSwingLow
if isLL
label.new(msLocalLowIndex, msLocalLow, "LL", color=llBackgroundColor, style=label.style_label_up, textcolor=llTextColor, size=size.small)
if isHL
label.new(msLocalLowIndex, msLocalLow, "HL", color=hlBackgroundColor, style=label.style_label_up, textcolor=hlTextColor, size=size.small)
lastOpposingHigh := prevSwingHigh
SwingLowBOSOccurred := false
prevSwingLow := msLocalLow
lastZigzagPrice := msLocalLow
lastSwingLow := msLocalLow
lastLowIndex := msLocalLowIndex
lastBearishShiftBar := bar_index
//========================================================================================
if plotBearishInternalShift and internalShiftMode == "Engulfment"
// Plot zigzag line
if not na(prevSwingHigh)
if localHigh > prevSwingHigh // HH
if zigzagLineStyle == "Solid"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=localLowIndex, y1=localLow, x2=localHighIndex, y2=localHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing high and plot label (HH or LH)
if not na(prevSwingHigh)
isHH := not na(prevSwingHigh) and localHigh > prevSwingHigh
isLH := not na(prevSwingHigh) and localHigh < prevSwingHigh
if isHH
if bullishBOSOccurred
label.new(localHighIndex, localHigh, "HH", color=hhBackgroundColor, style=label.style_label_down, textcolor=hhTextColor, size=size.small)
isLiquiditySweep := false
else
label.new(localHighIndex, localHigh, "LS", color=color.rgb(155, 39, 176, 100), style=label.style_label_down, textcolor=color.orange, size=size.small)
isLiquiditySweep := true
else
isLiquiditySweep := false
bullishBOSOccurred := false
if isLH
label.new(localHighIndex, localHigh, "LH", color=lhBackgroundColor, style=label.style_label_down, textcolor=lhTextColor, size=size.small)
lastOpposingLow := prevSwingLow
bullishBOSOccurred := false
prevSwingHigh := localHigh
lastZigzagPrice := localHigh
lastSwingHigh := localHigh
lastHighIndex := localHighIndex
lastBullishShiftBar := bar_index
if bearishMS and internalShiftMode == "Market Shift (Engulfment)"
// Plot zigzag line
if not na(prevSwingHigh)
if msLocalHigh > prevSwingHigh // HH
if zigzagLineStyle == "Solid"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
else // LH
if zigzagLineStyle == "Solid"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_solid)
else if zigzagLineStyle == "Dotted"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dotted)
else if zigzagLineStyle == "Dashed"
line.new(x1=msLocalLowIndex, y1=msLocalLow, x2=msLocalHighIndex, y2=msLocalHigh, color=zigzagLineColor, width=zigzagLineWidth, style=line.style_dashed)
// Update swing high and plot label (HH or LH)
if not na(prevSwingHigh)
isHH := not na(prevSwingHigh) and msLocalHigh > prevSwingHigh
isLH := not na(prevSwingHigh) and msLocalHigh < prevSwingHigh
if isHH
label.new(msLocalHighIndex, msLocalHigh, "HH", color=hhBackgroundColor, style=label.style_label_down, textcolor=hhTextColor, size=size.small)
SwingHighBOSOccurred := false
if isLH
label.new(msLocalHighIndex, msLocalHigh, "LH", color=lhBackgroundColor, style=label.style_label_down, textcolor=lhTextColor, size=size.small)
lastOpposingLow := prevSwingLow
SwingHighBOSOccurred := false
prevSwingHigh := msLocalHigh
lastZigzagPrice := msLocalHigh
lastSwingHigh := msLocalHigh
lastHighIndex := msLocalHighIndex
lastBullishShiftBar := bar_index
// === Alert Conditions ===
alertcondition(newBull, title="New Supply Zone", message="New supply zone available.")
alertcondition(newBear, title="New Demand Zone", message="New demand zone available.")
alertcondition(plotBullishInternalShift, title="Bullish Internal Shift (All Lows)", message="Bullish Internal Shift detected! Check Swing Low.")
alertcondition(plotBearishInternalShift, title="Bearish Internal Shift (All Highs)", message="Bearish Internal Shift detected! Check Swing High.")
alertcondition(bullishBOSOccurred, title="Bullish Break of Structure", message="Bullish BoS detected.")
alertcondition(bearishBOSOccurred, title="Bearish Break of Structure", message="Bearish BoS detected.")
alertcondition(bullishMS, title="Bullish Market Shift", message="Bullish market shift detected.")
alertcondition(bearishMS, title="Bearish Market Shift", message="Bearish market shift detected.")
alertcondition(isHH and plotBearishInternalShift and not isLiquiditySweep, title="Higher High (HH)", message="Higher High (HH) detected")
alertcondition(isHL and plotBullishInternalShift, title="Higher Low (HL)", message="Higher Low (HL) detected")
alertcondition(isLL and plotBullishInternalShift and not isLiquiditySweep, title="Lower Low (LL)", message="Lower Low (LL) detected")
alertcondition(isLH and plotBearishInternalShift, title="Lower High (LH)", message="Lower High (LH) detected")
alertcondition((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift), title="Liquidity Sweep (LS)", message="Liquidity Sweep (LS) detected")
// === Alerts ===
if alertMode == "LTF"
if isHH and plotBearishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Higher High (HH) detected (LTF)", alert.freq_once_per_bar_close)
if isHL and plotBullishInternalShift and (alertHighsandLows == true)
alert("Higher Low (HL) detected (LTF)", alert.freq_once_per_bar_close)
if isLL and plotBullishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Lower Low (LL) detected (LTF)" , alert.freq_once_per_bar_close)
if isLH and plotBearishInternalShift and (alertHighsandLows == true)
alert("Lower High (LH) detected (LTF)", alert.freq_once_per_bar_close)
if ((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift)) and (alertHighsandLows == true)
alert("Liquidity Sweep (LS) detected (LTF)", alert.freq_once_per_bar_close)
if newBear and (alertSupplyandDemand == true)
alert("New supply zone available. (LTF)", alert.freq_once_per_bar_close)
if newBull and (alertSupplyandDemand == true)
alert("New demand zone available. (LTF)", alert.freq_once_per_bar_close)
if bullishBOSOccurred and (alertBoS == true)
alert("Bullish BoS detected. (LTF)", alert.freq_once_per_bar_close)
if bearishBOSOccurred and (alertBoS == true)
alert("Bearish BoS detected. (LTF)", alert.freq_once_per_bar_close)
if bullishMS and (alertMS == true)
alert("Bullish market shift detected (LTF).", alert.freq_once_per_bar_close)
if bearishMS and (alertMS == true)
alert("Bearish market shift detected (LTF).", alert.freq_once_per_bar_close)
if alertMode == "MTF"
if isHH and plotBearishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Higher High (HH) detected (MTF)", alert.freq_once_per_bar_close)
if isHL and plotBullishInternalShift and (alertHighsandLows == true)
alert("Higher Low (HL) detected (MTF)", alert.freq_once_per_bar_close)
if isLL and plotBullishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Lower Low (LL) detected (MTF)" , alert.freq_once_per_bar_close)
if isLH and plotBearishInternalShift and (alertHighsandLows == true)
alert("Lower High (LH) detected (MTF)", alert.freq_once_per_bar_close)
if ((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift)) and (alertHighsandLows == true)
alert("Liquidity Sweep (LS) detected (MTF)", alert.freq_once_per_bar_close)
if newBear and (alertSupplyandDemand == true)
alert("New supply zone available. (MTF)", alert.freq_once_per_bar_close)
if newBull and (alertSupplyandDemand == true)
alert("New demand zone available. (MTF)", alert.freq_once_per_bar_close)
if bullishBOSOccurred and (alertBoS == true)
alert("Bullish BoS detected. (MTF)", alert.freq_once_per_bar_close)
if bearishBOSOccurred and (alertBoS == true)
alert("Bearish BoS detected. (MTF)", alert.freq_once_per_bar_close)
if bullishMS and (alertMS == true)
alert("Bullish market shift detected (MTF).", alert.freq_once_per_bar_close)
if bearishMS and (alertMS == true)
alert("Bearish market shift detected (MTF).", alert.freq_once_per_bar_close)
if alertMode == "HTF"
if isHH and plotBearishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Higher High (HH) detected (HTF)", alert.freq_once_per_bar_close)
if isHL and plotBullishInternalShift and (alertHighsandLows == true)
alert("Higher Low (HL) detected (HTF)", alert.freq_once_per_bar_close)
if isLL and plotBullishInternalShift and not isLiquiditySweep and (alertHighsandLows == true)
alert("Lower Low (LL) detected (HTF)" , alert.freq_once_per_bar_close)
if isLH and plotBearishInternalShift and (alertHighsandLows == true)
alert("Lower High (LH) detected (HTF)", alert.freq_once_per_bar_close)
if ((isLiquiditySweep and isLL and plotBullishInternalShift) or (isLiquiditySweep and isHH and plotBearishInternalShift)) and (alertHighsandLows == true)
alert("Liquidity Sweep (LS) detected (HTF)", alert.freq_once_per_bar_close)
if newBear and (alertSupplyandDemand == true)
alert("New supply zone available. (HTF)", alert.freq_once_per_bar_close)
if newBull and (alertSupplyandDemand == true)
alert("New demand zone available. (HTF)", alert.freq_once_per_bar_close)
if bullishBOSOccurred and (alertBoS == true)
alert("Bullish BoS detected. (HTF)", alert.freq_once_per_bar_close)
if bearishBOSOccurred and (alertBoS == true)
alert("Bearish BoS detected. (HTF)", alert.freq_once_per_bar_close)
if bullishMS and (alertMS == true)
alert("Bullish market shift detected (HTF).", alert.freq_once_per_bar_close)
if bearishMS and (alertMS == true)
alert("Bearish market shift detected (HTF).", alert.freq_once_per_bar_close)
Script open-source
Nello spirito di TradingView, l'autore di questo script lo ha reso open source, in modo che i trader possano esaminarne e verificarne la funzionalità. Complimenti all'autore! Sebbene sia possibile utilizzarlo gratuitamente, ricordiamo che la ripubblicazione del codice è soggetta al nostro Regolamento.
Declinazione di responsabilità
Le informazioni e le pubblicazioni non sono intese come, e non costituiscono, consulenza o raccomandazioni finanziarie, di investimento, di trading o di altro tipo fornite o approvate da TradingView. Per ulteriori informazioni, consultare i Termini di utilizzo.
Script open-source
Nello spirito di TradingView, l'autore di questo script lo ha reso open source, in modo che i trader possano esaminarne e verificarne la funzionalità. Complimenti all'autore! Sebbene sia possibile utilizzarlo gratuitamente, ricordiamo che la ripubblicazione del codice è soggetta al nostro Regolamento.
Declinazione di responsabilità
Le informazioni e le pubblicazioni non sono intese come, e non costituiscono, consulenza o raccomandazioni finanziarie, di investimento, di trading o di altro tipo fornite o approvate da TradingView. Per ulteriori informazioni, consultare i Termini di utilizzo.