Displacement detector
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © bkomoroczy

//@version=5
indicator(title = "Displacement detector", overlay = true)

////inputs
overlaySignals = input.bool(defval = true, title = "Overlay signals?", group = "display")
bullishBarOverlayColor = input.color(defval = color.rgb(152, 245, 245), title = "Bullish Overlay", group = "display")
bearishBarOverlayColor = input.color(defval = color.rgb(252, 202, 138), title = "Bearish Overlay", group = "display")
minDisplacementRatio = input.float(defval = 0.514, title = "Minimum displacement (%)", step = 0.01, group = "detection parameters") / 100
maxBarsInDisplacement = input.int(defval = 12, title = "Max bars in displacement", step = 1, minval = 0, maxval = 12, group = "detection parameters")
maxIrregularBars = input.int(defval = 2, title = "Max bars allowed to be irregular", step = 1, minval = 0, maxval = 12, group = "detection parameters")
maxCountermove = input.float(defval = 0.06, title = "Max countermove of irregular bars(%)", step = 0.01, group = "detection parameters") / 100
//maxNumberOfDisplacements = input.int(defval = 10, title = "Max number of displacements", step = 1, minval = 0, maxval = 100, group = "detection parameters")

////create variables
var displacementBoxes = array.new_box(0)
////follow displacement backward
//functions
displacement(myBarIndex) =>
    close[myBarIndex] - open[myBarIndex]
bullish(myBarIndex) =>
    displacement(myBarIndex) > 0.0
bearish(myBarIndex) =>
    displacement(myBarIndex) < 0.0


actualBullishness = bullish(0)
actualBearishness = bearish(0)
//barIndexToCompare = 1
//if actualBullishness
//    while bullish(barIndexToCompare) or (displacement(barIndexToCompare) >= -(close * maxCountermove))
//        barIndexToCompare := barIndexToCompare + 1
//if actualBearishness
//    while bearish(barIndexToCompare) or (displacement(barIndexToCompare) <= (close * maxCountermove))
//        barIndexToCompare := barIndexToCompare + 1
//minWickOfRange = ta.lowest(low, (barIndexToCompare - 1))
//lastBarOfDisplacement = barIndexToCompare - 1

// Try a new way to detect displacement: look for max and min for increasing ranges and detect inbetween which bars were the max and min bars.
barIndexToCompare = 0
maxBarIndex = 0
minBarIndex = 0
maxWickOfRange = high
minWickOfRange = low
maxBodyOfRange = (open > close) ? open : close
minBodyOfRange = (open < close) ? open : close
maxChanged = true
minChanged = true
maxStop = false
minStop = false

IrregularityCounter = maxIrregularBars
for i = 1 to maxBarsInDisplacement
    maxWickOfRange_i = high[i]
    maxBodyOfRange_i = (open[i] > close[i]) ? open[i] : close[i]
//    if ((maxWickOfRange_i > maxWickOfRange) or (maxBodyOfRange_i > maxBodyOfRange)) and (minChanged or maxChanged)
    if ((maxWickOfRange_i > maxWickOfRange)) and (IrregularityCounter > 0) and not maxStop
        maxBarIndex := i
        maxWickOfRange := maxWickOfRange_i 
        maxBodyOfRange := maxBodyOfRange_i
        maxChanged := true
    else
        maxChanged := false
        IrregularityCounter := IrregularityCounter - 1
        maxStop := (math.abs(displacement(0)) > (close * maxCountermove))

IrregularityCounter := maxIrregularBars
for i = 1 to maxBarsInDisplacement
    minWickOfRange_i = low[i]
    minBodyOfRange_i = (open[i] < close[i]) ? open[i] : close[i]
//    if ((minWickOfRange_i < minWickOfRange) or (minBodyOfRange_i < minBodyOfRange)) and (minChanged or maxChanged)
    if ((minWickOfRange_i < minWickOfRange)) and (IrregularityCounter > 0) and not minStop
        minBarIndex := i
        minWickOfRange := minWickOfRange_i
        minBodyOfRange := minBodyOfRange_i
        minChanged := true
    else
        minChanged := false
        IrregularityCounter := IrregularityCounter - 1
        minStop := (math.abs(displacement(0)) > (close * maxCountermove))

plot(maxBarIndex, title = "maxBarIndex", display = display.none)
plot(minBarIndex, title = "minBarIndex", display = display.none)

if minBarIndex != maxBarIndex
    actualBullishness := minBarIndex > maxBarIndex
    actualBearishness := maxBarIndex > minBarIndex
freshDisplacement = false
lastBarOfDisplacement = 0
if actualBullishness
    freshDisplacement := maxBarIndex == 0
    lastBarOfDisplacement := minBarIndex
if actualBearishness
    freshDisplacement := minBarIndex == 0
    lastBarOfDisplacement := maxBarIndex


//lastBarOfDisplacement := lastBarOfDisplacement > maxBarsInDisplacement-1 ? maxBarsInDisplacement-1 : lastBarOfDisplacement

rangeLength = lastBarOfDisplacement + 1
//minWickOfRange = -1.0
//maxWickOfRange = -1.0
//if rangeLength > 0
//    minWickOfRange := ta.lowest(low, rangeLength + 1)
//    maxWickOfRange := ta.highest(high, rangeLength + 1)
displacementLength = maxWickOfRange - minWickOfRange
//plot(lastBarOfDisplacement, display = display.none)
//plot(barIndexToCompare, display = display.none)
plot(displacementLength, title = "displacementLength", display = display.none)
plot(rangeLength, title = "rangeLength", display = display.none)
//Compare the max displacement to the limit And Alert in case requested
displacementThreshold = close * minDisplacementRatio
displacementFound = freshDisplacement and (math.abs(displacementLength) >= (displacementThreshold)) and maxBarsInDisplacement >= rangeLength
plot(displacementThreshold, title = "displacementThreshold", display = display.none)
//display

//plot bullish displacement top
plotshape(displacementFound and overlaySignals and actualBullishness, "bullish displacement top", shape.triangleup, location.abovebar, offset = 0, size = size.normal, color = bullishBarOverlayColor)
//plot bullish displacement bottom
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 0, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -0, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 1, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -1, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 2, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -2, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 3, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -3, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 4, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -4, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 5, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -5, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 6, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -6, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 7, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -7, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 8, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -8, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 9, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -9, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 10, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -10, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 11, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -11, size = size.small, color = bullishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBullishness and lastBarOfDisplacement == 12, "bullish displacement bottom",
  shape.circle, location.belowbar, offset = -12, size = size.small, color = bullishBarOverlayColor)


//plot bearish displacement bottom
plotshape(displacementFound and overlaySignals and actualBearishness, "bearish displacement bottom", shape.triangledown, location.belowbar, offset = 0, size = size.normal, color = bearishBarOverlayColor)

//plot bearish displacement top
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 0, "bearish displacement top",
  shape.circle, location.abovebar, offset = -0, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 1, "bearish displacement top",
  shape.circle, location.abovebar, offset = -1, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 2, "bearish displacement top",
  shape.circle, location.abovebar, offset = -2, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 3, "bearish displacement top",
  shape.circle, location.abovebar, offset = -3, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 4, "bearish displacement top",
  shape.circle, location.abovebar, offset = -4, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 5, "bearish displacement top",
  shape.circle, location.abovebar, offset = -5, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 6, "bearish displacement top",
  shape.circle, location.abovebar, offset = -6, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 7, "bearish displacement top",
  shape.circle, location.abovebar, offset = -7, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 8, "bearish displacement top",
  shape.circle, location.abovebar, offset = -8, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 9, "bearish displacement top",
  shape.circle, location.abovebar, offset = -9, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 10, "bearish displacement top",
  shape.circle, location.abovebar, offset = -10, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 11, "bearish displacement top",
  shape.circle, location.abovebar, offset = -11, size = size.small, color = bearishBarOverlayColor)
plotshape(displacementFound and overlaySignals and actualBearishness and lastBarOfDisplacement == 12, "bearish displacement top",
  shape.circle, location.abovebar, offset = -12, size = size.small, color = bearishBarOverlayColor)

//plotshape(displacementFound and overlaySignals and actualBearishness, "bearish displacement top", shape.circle, location.abovebar, offset = startOffset, size = size.small, color = bearishBarOverlayColor)

if displacementFound and actualBullishness
    alert("bullish displacement")
if displacementFound and actualBearishness
    alert("bearish displacement")

