Flag Detector
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © millerrh

//@version=5
indicator("Flag Detector", overlay=true)

// USER INPUTS
flagLookback = input.int(title="Top of Flag Pole Lookback", defval=15,
  tooltip = "Lookback period for detecting the top of the flag pole.")
poleLookback = input.int(title="Bottom of Flag Pole Lookback", defval=20,
  tooltip = "Lookback period for detecting the bottom of the flag pole from the top of the pole. These
  two points are used to determine the flag pole length.")
poleLengthInput = input.int(title="Min Flag Pole Length (%)", defval=15,
  tooltip = "Only look for consolildations that are preceded by a move of this magnatude.")
flagDepthInput = input.int(title="Max Flag Depth (%)", defval=25,
  tooltip = "Flag bottom must be within this distance from the top.")
flagRatioInput = input.int(title="Max Flag Retracement (%)", defval=50,
  tooltip = "Max percentage the flag can retrace the pole. Similar to Fibonacci Retracement levels.")
phBarInput = input.int(title="Min Flag Length (%)", defval=1,
  tooltip = "Minimum number of bars after the top of the flagpole necessary for consolidation to count.")


PoleHigh = ta.highest(high, flagLookback)
PHBar = 1
for i = 1 to flagLookback
    if high[i] >= PoleHigh
        PHBar := i

FlagLow = ta.lowest(low, PHBar)

PoleLow = ta.lowest(low, poleLookback + PHBar)

// Flag characteristics
PoleLength = (PoleHigh - PoleLow)/PoleLow  // Percentage move that forms the flag pole
FlagDepth = (PoleHigh - FlagLow)/PoleHigh  // Percentage retracement off the highs
FlagRatio = FlagDepth/PoleLength  // Flag-to-Pole ratio

// Flag Requirements
Flag = 
  PoleLength > poleLengthInput/100 and  // Minimum 15% initial move before consolication
  FlagDepth < flagDepthInput/100 and  // Bottom of consolidation less than 25% from the  top of the consolidtion
  FlagRatio < flagRatioInput/100 and  // Flag can't have retraced more than 50% the initial move
  PHBar > phBarInput // Minimum one bar after that local top acts as a pullback
  
// Highlight the flag bars
plotshape(Flag, title = 'Flag', style = shape.circle, location = location.belowbar, color = color.new(color.blue, 60))

// Plot channels/lines showing the current flag boundaries for troubleshooting
// plot(PoleHigh, color = color.new(color.white, 60), style = plot.style_line,  linewidth = 1)
// plot(FlagLow, color = color.new(color.red, 60), style = plot.style_line,  linewidth = 1)
// plot(PoleLow, color = color.new(color.purple, 60), style = plot.style_line,  linewidth = 1)