// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ashwagamma
// enhancements
// 1. check if it's an original zone, original zones are ones where looking left there's no consolidation period that ate up the demand there
// 2. check how many indecision candles were in the zone before the trend move began. lots of indecision candles could mean that pending orders have been fulfilled already and this downgrades the zone
// 3.
//@version=5
indicator("HAZZONES", overlay = true, max_boxes_count = 500)
tf = input.timeframe("", "Timeframe")
maxNumOfDemandBoxes = input.int(defval=15, title='Maximum # of Demand Zones', minval=1, maxval=500, tooltip='Minimum = 1, Maximum = 500')
maxNumOfSupplyBoxes = input.int(defval=15, title='Maximum # of Supply Zones', minval=1, maxval=500, tooltip='Minimum = 1, Maximum = 500')
numBullHA = input.int(defval = 5, title='Min bull HA candles', minval=1, maxval=10)
numBearHA = input.int(defval = 5, title='Min bear HA candles', minval=1, maxval=10)
precision = input.float(defval = 0.25, title='Precision', minval=0.01, maxval=10, step=0.01)
srflipBoxWidth = input.float(defval = 1, title='Width of D/S flip zone', minval=0.01, maxval=6, step=0.01)
enableD2SFlip = input.bool(defval=true, title='Allow Demand->Supply Flip?', group="Options")
enableS2DFlip = input.bool(defval=true, title='Allow Supply->Demand Flip?', group="Options")
showMitigatedZones = input.bool(defval=true, title='Show mitigated zones?', group="Options")
enAlertNewDemand = input.bool(defval=true, title='New Demand Zone', group="Alerts")
enAlertNewSupply = input.bool(defval=true, title='New Supply Zone', group="Alerts")
enAlertDemand = input.bool(defval=true, title='Demand Zone Test', group="Alerts")
enAlertSupply = input.bool(defval=true, title='Supply Zone Test', group="Alerts")
enAlertS2D = input.bool(defval=true, title='S2D Flip Zone Test', group="Alerts")
enAlertD2S = input.bool(defval=true, title='D2S Flip Zone Test', group="Alerts")
demandBoxColor = input.color(defval=color.new(color.green, 80), title='Demand Zone Color', group="Zone Style")
demandBoxBorderColor = input.color(defval=color.new(color.green, 70), title='Demand Zone Border Color', group="Zone Style")
supplyBoxColor = input.color(defval=color.new(color.red, 80), title='Supply Zone Color', group="Zone Style")
supplyBoxBorderColor = input.color(defval=color.new(color.red, 70), title='Supply Zone Border Color', group="Zone Style")
d2sflipBoxColor = input.color(defval=color.new(color.fuchsia, 95), title='Demand->Supply Flip Zone Color', group="Zone Style")
d2sflipBoxBorderColor = input.color(defval=color.new(color.red, 70), title='Demand->Supply Flip Zone Border Color', group="Zone Style")
s2dflipBoxColor = input.color(defval=color.new(color.yellow, 95), title='Supply->Demand Flip Zone Color', group="Zone Style")
s2dflipBoxBorderColor = input.color(defval=color.new(color.green, 70), title='Supply->Demand Flip Zone Border Color', group="Zone Style")
mitDemandplyBoxColor = input.color(defval=color.new(color.gray, 85), title='Mitigated Supply Zone Color', group="Zone Style", tooltip='Set Transparency to 0 to make mitigated Boxes disappear')
mitDemandplyBoxBorderColor = input.color(defval=color.new(color.red, 70), title='Mitigated Supply Zone Border Color', group="Zone Style", tooltip='Set Transparency to 0 to make mitigated Boxes disappear')
mitDemandBoxColor = input.color(defval=color.new(color.gray, 85), title='Mitigated Demand Zone Color', group="Zone Style", tooltip='Set Transparency to 0 to make mitigated Boxes disappear')
mitDemandBoxBorderColor = input.color(defval=color.new(color.green, 70), title='Mitigated Demand Zone Border Color', group="Zone Style", tooltip='Set Transparency to 0 to make mitigated Boxes disappear')
isStrongBullishTrend(o, h, l, c) =>
ta.barssince(c<=o)-1>=numBullHA and numBullHA <= ta.barssince(not((c>o and h>c and l-o>-1*(h-l)/10) or ((c[1]>o[1] and h[1]>c[1] and l[1]-o[1]>-1*(h[1]-l[1])/10) and c>=o) or ((c[2]>o[2] and h[2]>c[2] and l[2]-o[2]>-1*(h[2]-l[2])/5) and c>=o and c[1]>=o[1])))
isStrongBearishTrend(o, h, l, c) =>
ta.barssince(c>=o)-1>=numBearHA and numBearHA <= ta.barssince(not((c<o and l<c and o-h>-1*(h-l)/10) or ((c[1]<o[1] and l[1]<c[1] and o[1]-h[1]>-1*(h[1]-l[1])/10) and c<=o) or ((c[2]<o[2] and l[2]<c[2] and o[2]-h[2]>-1*(h[2]-l[2])/5) and c<=o and c[1]<=o[1])))
roundToPrecision(val, precision) =>
float(int(val/precision+0.5)*precision)
getLowestAroundLastRedCandle(o,h,l,c) =>
var num_candles = 0
num_candles := nz(ta.barssince(c<=o),1)+2
if num_candles==0
num_candles:=1
ta.lowest(l, num_candles)
getHighestAroundLastGreenCandle(o,h,l,c) =>
var num_candles = 0
num_candles := nz(ta.barssince(c>=o),1)+2
if num_candles==0
num_candles:=1
ta.highest(h, num_candles)
getTFString(val) =>
switch val
"1" => "1m"
"5" => "5m"
"15" => "15m"
"60" => "1h"
"240" => "4h"
"480" => "8h"
"D" => "1D"
=>
val
var box[] _demandBoxes = array.new_box()
var box[] _s2dflip1 = array.new_box()
var box[] _supplyBoxes = array.new_box()
var box[] _d2sflip1 = array.new_box()
[o, h, l, c] = request.security(ticker.heikinashi(syminfo.tickerid), tf, [open, high, low, close])
[sz_open, sz_high, sz_low, sz_close] = request.security(ticker.heikinashi(syminfo.tickerid), tf, [open[ta.barssince(close<=open)], high[ta.barssince(close<=open)], getLowestAroundLastRedCandle(open,high,low,close), close[ta.barssince(close<=open)]])
[rz_open, rz_high, rz_low, rz_close] = request.security(ticker.heikinashi(syminfo.tickerid), tf, [open[ta.barssince(close>=open)], getHighestAroundLastGreenCandle(open,high,low,close), low[ta.barssince(close>=open)], close[ta.barssince(close>=open)]])
[isStrongBullishTrend, isStrongBearishTrend] = request.security(ticker.heikinashi(syminfo.tickerid), tf, [isStrongBullishTrend(open, high, low, close), isStrongBearishTrend(open, high, low, close)])
[demandBoxBarDelta, supplyBoxBarDelta] = request.security(ticker.heikinashi(syminfo.tickerid), tf, [(ta.barssince(close<=open)), (ta.barssince(close>=open))])
analysisTF = request.security(syminfo.tickerid, tf, getTFString(timeframe.period))
if isStrongBullishTrend and not(isStrongBullishTrend[1]) and barstate.isconfirmed
box demandBox = na
_boxLow = roundToPrecision(sz_low, precision)
_boxHigh = roundToPrecision(sz_high, precision)
demandBox := box.new(bar_index-demandBoxBarDelta, _boxHigh, bar_index+1, _boxLow, xloc = xloc.bar_index, border_color = demandBoxBorderColor, bgcolor=demandBoxColor)
if enAlertNewDemand
alert(analysisTF+" New Demand Zone ["+str.tostring(_boxHigh)+","+str.tostring(_boxLow)+"]", alert.freq_once_per_bar)
if array.size(_demandBoxes) > maxNumOfDemandBoxes
box.delete(array.shift(_demandBoxes))
array.push(_demandBoxes, demandBox)
if isStrongBearishTrend and not(isStrongBearishTrend[1]) and barstate.isconfirmed
box supplyBox = na
_boxLow = roundToPrecision(rz_low, precision)
_boxHigh = roundToPrecision(rz_high, precision)
supplyBox := box.new(bar_index-supplyBoxBarDelta, _boxHigh, bar_index+1, _boxLow, xloc = xloc.bar_index, border_color = supplyBoxBorderColor, bgcolor=supplyBoxColor)
if enAlertNewSupply
alert(analysisTF+" New Supply Zone ["+str.tostring(_boxLow)+","+str.tostring(_boxHigh)+"]", alert.freq_once_per_bar)
if array.size(_supplyBoxes) > maxNumOfSupplyBoxes
box.delete(array.shift(_supplyBoxes))
array.push(_supplyBoxes, supplyBox)
if array.size(_demandBoxes) > 0 and barstate.isconfirmed
for i = array.size(_demandBoxes) - 1 to 0 by 1
_box = array.get(_demandBoxes, i)
_boxLow = box.get_bottom(_box)
_boxHigh = box.get_top(_box)
_boxRight = box.get_right(_box)
_boxLeft = box.get_left(_box)
if bar_index == _boxRight
needMitigate = c < _boxLow and h < _boxLow
for j = 1 to 4 by 1
needMitigate := needMitigate and c[j]<_boxLow
for j = 1 to 2 by 1
needMitigate := needMitigate and h[j]<_boxLow
if needMitigate
if showMitigatedZones
box.set_bgcolor(_box, mitDemandBoxColor)
box.set_border_color(_box, mitDemandBoxBorderColor)
//array.set(_isSupMitigated, i, true)
else
array.remove(_demandBoxes, i)
box.delete(_box)
if enableD2SFlip
box supplyBox = na
if showMitigatedZones
supplyBox := box.new(bar_index, _boxHigh, bar_index+1, _boxLow, xloc = xloc.bar_index, border_color = d2sflipBoxBorderColor, bgcolor=d2sflipBoxColor)
else
supplyBox := box.new(_boxLeft, _boxHigh, bar_index+1, _boxLow, xloc = xloc.bar_index, border_color = d2sflipBoxBorderColor, bgcolor=d2sflipBoxColor)
if array.size(_d2sflip1) > maxNumOfSupplyBoxes
box.delete(array.shift(_d2sflip1))
array.push(_d2sflip1, supplyBox)
else
box.set_right(_box, bar_index + 1)
if array.size(_supplyBoxes) > 0 and barstate.isconfirmed
for i = array.size(_supplyBoxes) - 1 to 0 by 1
_box = array.get(_supplyBoxes, i)
_boxLow = box.get_bottom(_box)
_boxHigh = box.get_top(_box)
_boxRight = box.get_right(_box)
_boxLeft = box.get_left(_box)
if bar_index == _boxRight
needMitigate = c > _boxHigh and l > _boxHigh
for j = 1 to 4 by 1
needMitigate := needMitigate and c[j] > _boxHigh
for j = 1 to 2 by 1
needMitigate := needMitigate and l[j] > _boxHigh
if needMitigate
if showMitigatedZones
box.set_bgcolor(_box, mitDemandplyBoxColor)
box.set_border_color(_box, mitDemandplyBoxBorderColor)
else
array.remove(_supplyBoxes, i)
box.delete(_box)
if enableS2DFlip
box demandBox = na
if showMitigatedZones
demandBox := box.new(bar_index, _boxHigh, bar_index+1, _boxLow, xloc = xloc.bar_index, border_color = s2dflipBoxBorderColor, bgcolor=s2dflipBoxColor)
else
demandBox := box.new(_boxLeft, _boxHigh, bar_index+1, _boxLow, xloc = xloc.bar_index, border_color = s2dflipBoxBorderColor, bgcolor=s2dflipBoxColor)
if array.size(_s2dflip1) > maxNumOfDemandBoxes
box.delete(array.shift(_s2dflip1))
array.push(_s2dflip1, demandBox)
else
box.set_right(_box, bar_index + 1)
if array.size(_s2dflip1) > 0 and barstate.isconfirmed
for i = array.size(_s2dflip1) - 1 to 0 by 1
_box = array.get(_s2dflip1, i)
_boxLow = box.get_bottom(_box)
_boxHigh = box.get_top(_box)
_boxRight = box.get_right(_box)
_boxLeft = box.get_left(_box)
if bar_index == _boxRight
needMitigate = c < _boxLow and h < _boxLow
for j = 1 to 4 by 1
needMitigate := needMitigate and c[j]<_boxLow
for j = 1 to 2 by 1
needMitigate := needMitigate and h[j]<_boxLow
if needMitigate
if showMitigatedZones
box.set_bgcolor(_box, mitDemandBoxColor)
box.set_border_color(_box, s2dflipBoxBorderColor)
//array.set(_isSupMitigated, i, true)
else
array.remove(_s2dflip1, i)
box.delete(_box)
else
box.set_right(_box, bar_index + 1)
if array.size(_d2sflip1) > 0 and barstate.isconfirmed
for i = array.size(_d2sflip1) - 1 to 0 by 1
_box = array.get(_d2sflip1, i)
_boxLow = box.get_bottom(_box)
_boxHigh = box.get_top(_box)
_boxRight = box.get_right(_box)
if bar_index == _boxRight
needMitigate = c > _boxHigh and l > _boxHigh
for j = 1 to 4 by 1
needMitigate := needMitigate and c[j] > _boxHigh
for j = 1 to 2 by 1
needMitigate := needMitigate and l[j] > _boxHigh
if needMitigate
if showMitigatedZones
box.set_bgcolor(_box, mitDemandplyBoxColor)
box.set_border_color(_box, d2sflipBoxBorderColor)
else
array.remove(_d2sflip1, i)
box.delete(_box)
else
box.set_right(_box, bar_index + 1)
// If any overlapping s2d flip zones, make them the same so they can be deleted
if array.size(_s2dflip1) > 2 and barstate.isconfirmed
for i = array.size(_s2dflip1) - 1 to 1 by 1
_box = array.get(_s2dflip1, i)
_boxRight = box.get_right(_box)
_boxLow = box.get_bottom(_box)
_boxHigh = box.get_top(_box)
_boxLeft = box.get_left(_box)
for j = i-1 to 0 by 1
_box2 = array.get(_s2dflip1, j)
_box2Right = box.get_right(_box2)
_box2Low = box.get_bottom(_box2)
_box2High = box.get_top(_box2)
_box2Left = box.get_left(_box2)
if not(_box2Right < _boxLeft) // _box2 should not be an already mitigated sr flip zone
if ((_box2Low < _boxHigh) and (_boxHigh < _box2High)) and ((_box2Low < _boxLow) and (_boxLow < _box2High)) or ((_boxLow < _box2High) and (_box2High < _boxHigh)) and ((_boxLow < _box2Low) and (_box2Low < _boxHigh))
// i.e. if one box is fully inside other box
box.set_top(_box2, math.max(_boxHigh, _box2High))
box.set_top(_box, math.max(_boxHigh, _box2High))
box.set_bottom(_box2, math.min(_boxLow, _box2Low))
box.set_bottom(_box, math.min(_boxLow, _box2Low))
box.set_left(_box2, math.min(_boxLeft, _box2Left))
box.set_left(_box, math.min(_boxLeft, _box2Left))
else if ((_box2Low < _boxHigh) and (_boxHigh < _box2High)) and _boxLow < _box2Low
// i.e. if box is partial overlapping with box2, and box is under box2
box.set_bottom(_box2, _boxHigh+1)
box.set_left(_box2, math.min(_boxLeft, _box2Left))
box.set_left(_box, math.min(_boxLeft, _box2Left))
else if ((_boxLow < _box2High) and (_box2High < _boxHigh)) and _box2Low < _boxLow
// i.e. if box2 is partial overlapping with box, and box2 is under box
box.set_bottom(_box, _box2High+1)
box.set_left(_box2, math.min(_boxLeft, _box2Left))
box.set_left(_box, math.min(_boxLeft, _box2Left))
// If any overlapping d2s flip zones, make them the same so they can be deleted
if array.size(_d2sflip1) > 2 and barstate.isconfirmed
for i = array.size(_d2sflip1) - 1 to 1 by 1
_box = array.get(_d2sflip1, i)
_boxRight = box.get_right(_box)
_boxLow = box.get_bottom(_box)
_boxHigh = box.get_top(_box)
_boxLeft = box.get_left(_box)
for j = i-1 to 0 by 1
_box2 = array.get(_d2sflip1, j)
_box2Right = box.get_right(_box2)
_box2Low = box.get_bottom(_box2)
_box2High = box.get_top(_box2)
_box2Left = box.get_left(_box2)
if not(_box2Right < _boxLeft) // _box2 should not be an already mitigated sr flip zone
if ((_box2Low < _boxHigh) and (_boxHigh < _box2High)) and ((_box2Low < _boxLow) and (_boxLow < _box2High)) or ((_boxLow < _box2High) and (_box2High < _boxHigh)) and ((_boxLow < _box2Low) and (_box2Low < _boxHigh))
//if ((_box2Low < _boxHigh) and (_boxHigh < _box2High)) or ((_box2Low < _boxLow) and (_boxLow < _box2High)) or ((_boxLow < _box2High) and (_box2High < _boxHigh)) or ((_boxLow < _box2Low) and (_box2Low < _boxHigh))
box.set_top(_box2, math.max(_boxHigh, _box2High))
box.set_top(_box, math.max(_boxHigh, _box2High))
box.set_bottom(_box2, math.min(_boxLow, _box2Low))
box.set_bottom(_box, math.min(_boxLow, _box2Low))
box.set_left(_box2, math.min(_boxLeft, _box2Left))
box.set_left(_box, math.min(_boxLeft, _box2Left))
else if ((_box2Low < _boxHigh) and (_boxHigh < _box2High)) and _boxLow < _box2Low
// i.e. if box is partial overlapping with box2, and box is under box2
box.set_bottom(_box2, _boxHigh+1)
box.set_left(_box2, math.min(_boxLeft, _box2Left))
box.set_left(_box, math.min(_boxLeft, _box2Left))
else if ((_boxLow < _box2High) and (_box2High < _boxHigh)) and _box2Low < _boxLow
// i.e. if box2 is partial overlapping with box, and box2 is under box
box.set_bottom(_box, _box2High+1)
box.set_left(_box2, math.min(_boxLeft, _box2Left))
box.set_left(_box, math.min(_boxLeft, _box2Left))
// delete duplicate _d2sflip1 boxes
if array.size(_d2sflip1) > 2 and barstate.isconfirmed
for i = array.size(_d2sflip1) - 2 to 1 by 1
_box1 = array.get(_d2sflip1, i+1)
_box2 = array.get(_d2sflip1, i)
if box.get_right(_box1)==box.get_right(_box2) and box.get_top(_box1)==box.get_top(_box2) and box.get_bottom(_box1)==box.get_bottom(_box2)
box.delete(_box1)
array.remove(_d2sflip1, i+1)
// delete duplicate _s2dflip1 boxes
if array.size(_s2dflip1) > 2 and barstate.isconfirmed
for i = array.size(_s2dflip1) - 2 to 1 by 1
_box1 = array.get(_s2dflip1, i+1)
_box2 = array.get(_s2dflip1, i)
if box.get_right(_box1)==box.get_right(_box2) and box.get_top(_box1)==box.get_top(_box2) and box.get_bottom(_box1)==box.get_bottom(_box2)
box.delete(_box1)
array.remove(_s2dflip1, i+1)
// set the width of d2s zones per srflipBoxWidth
// If any overlapping s2d flip zones, make them the same so they can be deleted
if array.size(_d2sflip1) > 2 //and bar_index==last_bar_index
for i = array.size(_d2sflip1) - 1 to 1 by 1
_box = array.get(_d2sflip1, i)
_boxHigh = box.get_top(_box)
_boxLow = box.get_bottom(_box)
if math.abs(_boxHigh-_boxLow) > srflipBoxWidth
box.set_top(_box, _boxLow+srflipBoxWidth/2)
box.set_bottom(_box, _boxLow-srflipBoxWidth/2)
// set the width of s2d zones per srflipBoxWidth
// If any overlapping s2d flip zones, make them the same so they can be deleted
if array.size(_s2dflip1) > 2 //and bar_index==last_bar_index
for i = array.size(_s2dflip1) - 1 to 1 by 1
_box = array.get(_s2dflip1, i)
_boxHigh = box.get_top(_box)
_boxLow = box.get_bottom(_box)
//if bar_index == box.get_left(_box)+1 // the first time we've merged the box we need to set its width
if math.abs(_boxHigh-_boxLow) > srflipBoxWidth
box.set_top(_box, _boxHigh+srflipBoxWidth/2)
box.set_bottom(_box, _boxHigh-srflipBoxWidth/2)
// Alerts
if array.size(_demandBoxes) > 0 and barstate.isrealtime and enAlertDemand
for i = array.size(_demandBoxes) - 1 to 0 by 1
_box = array.get(_demandBoxes, i)
_boxHigh = box.get_top(_box)
_boxLow = box.get_bottom(_box)
if l[1]>_boxHigh and l<=_boxHigh
alert(analysisTF+" Demand ["+str.tostring(_boxHigh)+","+str.tostring(_boxLow)+"]", alert.freq_once_per_bar)
// Alerts
if array.size(_supplyBoxes) > 0 and barstate.isrealtime and enAlertSupply
for i = array.size(_supplyBoxes) - 1 to 0 by 1
_box = array.get(_supplyBoxes, i)
_boxHigh = box.get_top(_box)
_boxLow = box.get_bottom(_box)
if h[1]<_boxLow and h>=_boxLow
alert(analysisTF+" Supply ["+str.tostring(_boxLow)+","+str.tostring(_boxHigh)+"]", alert.freq_once_per_bar)
// Alerts
if array.size(_s2dflip1) > 0 and barstate.isrealtime and enAlertS2D
for i = array.size(_s2dflip1) - 1 to 0 by 1
_box = array.get(_s2dflip1, i)
_boxHigh = box.get_top(_box)
_boxLow = box.get_bottom(_box)
if l[1]>_boxHigh and l<=_boxHigh
alert(analysisTF+" S2D ["+str.tostring(_boxHigh)+","+str.tostring(_boxLow)+"]", alert.freq_once_per_bar)
// Alerts
if array.size(_d2sflip1) > 0 and barstate.isrealtime and enAlertD2S
for i = array.size(_d2sflip1) - 1 to 0 by 1
_box = array.get(_d2sflip1, i)
_boxHigh = box.get_top(_box)
_boxLow = box.get_bottom(_box)
if h[1]<_boxLow and h>=_boxLow
alert(analysisTF+" D2S ["+str.tostring(_boxLow)+","+str.tostring(_boxHigh)+"]", alert.freq_once_per_bar)
Show all 382 lines