Delete Demand Zone that are violated now: //@version=5
indicator("Demand Zone Detector (RBR and DBR)", overlay=true)

// ‣ Input Parameters

// Average body period for calculating average body size
avgBodyPeriod = input.int(title="Average Body Period", defval=10, minval=1)

// Big candle factor
bigCandleFactor = input.float(title="Big Candle Factor (Multiplier of Avg Body Size)", defval=1.5, minval=0.1)

// Max number of base candles
maxBaseCandles = input.int(title="Maximum Base Candles", defval=6, minval=1)

// Max base candle body size as percentage of big candle's body
maxBaseBodyPercent = input.float(title="Max Base Candle Body Size (% of Big Candle)", defval=50.0, minval=0.1, maxval=100.0)

// Option for drawing historical zones
drawHistoricalZones = input.bool(title="Draw Historical Demand Zones", defval=true)

// New input parameter to limit the number of recent demand zones displayed
maxZonesToDisplay = input.int(title="Number of Recent Demand Zones to Display", defval=5, minval=1)

// ‣ Calculations

// Calculate average body size
avgBodySize = ta.sma(math.abs(close - open), avgBodyPeriod)

// Define thresholds
bigCandleBodyThreshold = bigCandleFactor * avgBodySize
maxBaseCandleBodySize = (maxBaseBodyPercent / 100.0) * bigCandleBodyThreshold

// Identify candle types
bodySize = math.abs(close - open)
isBigGreenCandle = (close > open) and (bodySize >= bigCandleBodyThreshold)
isBigRedCandle = (close < open) and (bodySize >= bigCandleBodyThreshold)
isSmallBodyCandle = bodySize <= maxBaseCandleBodySize
isGreenCandle = close > open

// ‣ Variables for Patterns

// RBR Pattern Variables
var int rbr_state = 0  // 0: idle, 1: collecting base
var int rbr_baseCount = 0
var float rbr_highestBody = na
var float rbr_lowestLow = na
var int rbr_startIndex = na

// DBR Pattern Variables
var int dbr_state = 0  // 0: idle, 1: collecting base
var int dbr_baseCount = 0
var float dbr_highestBody = na
var float dbr_lowestLow = na
var int dbr_startIndex = na

// Arrays to store active demand zones
var box[] demandZones = array.new<box>()
var float[] proximalLines = array.new<float>()
var float[] distalLines = array.new<float>()
var bool[] zoneIsRealtime = array.new<bool>()      // True if zone created during real-time data
var int[] zoneTypes = array.new<int>()             // 1 for RBR, 2 for DBR
var bool[] zoneStatus = array.new<bool>()          // True if active, False if violated

// ‣ RBR Pattern Detection

if rbr_state == 0
    if isBigGreenCandle
        // Leg-in detected
        rbr_state := 1
        rbr_baseCount := 0
        rbr_startIndex := bar_index
        rbr_highestBody := na
        rbr_lowestLow := na
else if rbr_state == 1
    if isSmallBodyCandle
        // Base candle detected
        rbr_baseCount += 1
        // Update highest body (proximal line)
        float candleHighBody = math.max(open, close)
        rbr_highestBody := na(rbr_highestBody) ? candleHighBody : math.max(rbr_highestBody, candleHighBody)
        // Update lowest low (distal line)
        rbr_lowestLow := na(rbr_lowestLow) ? low : math.min(rbr_lowestLow, low)
    else if isGreenCandle
        // Leg-out detected
        if rbr_baseCount >= 1
            // Include leg-out candle in distal line
            rbr_lowestLow := math.min(rbr_lowestLow, low)
            // Draw the demand zone
            box demandZone = box.new(left=rbr_startIndex, right=bar_index, top=rbr_highestBody, bottom=rbr_lowestLow,
                                 border_color=color.green, bgcolor=color.new(color.green, 85))
            box.set_extend(demandZone, extend.right)
            // Store the zone
            array.push(demandZones, demandZone)
            array.push(proximalLines, rbr_highestBody)
            array.push(distalLines, rbr_lowestLow)
            array.push(zoneIsRealtime, barstate.isrealtime)
            array.push(zoneTypes, 1)  // 1 for RBR
            array.push(zoneStatus, true)  // Zone is active when created

            // Limit the number of active zones displayed
            int activeZonesCount = 0
            for int idx = 0 to array.size(zoneStatus) - 1
                if array.get(zoneStatus, idx)
                    activeZonesCount += 1

            // If number of active zones exceeds the limit, remove the oldest active zone
            while activeZonesCount > maxZonesToDisplay
                // Find the oldest active zone
                int oldestActiveIndex = na
                for int idx = 0 to array.size(zoneStatus) - 1
                    if array.get(zoneStatus, idx)
                        oldestActiveIndex := idx
                        break
                if not na(oldestActiveIndex)
                    // Remove the oldest active zone
                    box dzBoxToRemove = array.get(demandZones, oldestActiveIndex)
                    box.delete(dzBoxToRemove)
                    // Remove from arrays
                    array.remove(demandZones, oldestActiveIndex)
                    array.remove(proximalLines, oldestActiveIndex)
                    array.remove(distalLines, oldestActiveIndex)
                    array.remove(zoneIsRealtime, oldestActiveIndex)
                    array.remove(zoneTypes, oldestActiveIndex)
                    array.remove(zoneStatus, oldestActiveIndex)
                    // Decrease activeZonesCount
                    activeZonesCount -= 1
                else
                    // No active zones found
                    break

        // Reset pattern
        rbr_state := 0
        rbr_baseCount := 0
    else
        // Pattern invalidated
        rbr_state := 0
        rbr_baseCount := 0

// ‣ DBR Pattern Detection

if dbr_state == 0
    if isBigRedCandle
        // Leg-in detected
        dbr_state := 1
        dbr_baseCount := 0
        dbr_startIndex := bar_index
        dbr_highestBody := na
        dbr_lowestLow := low  // Include leg-in candle's low
else if dbr_state == 1
    if isSmallBodyCandle
        // Base candle detected
        dbr_baseCount += 1
        // Update highest body (proximal line)
        float candleHighBody = math.max(open, close)
        dbr_highestBody := na(dbr_highestBody) ? candleHighBody : math.max(dbr_highestBody, candleHighBody)
        // Update lowest low (distal line)
        dbr_lowestLow := math.min(dbr_lowestLow, low)
    else if isGreenCandle
        // Leg-out detected
        if dbr_baseCount >= 1
            // Include leg-out candle's low
            dbr_lowestLow := math.min(dbr_lowestLow, low)
            // Draw the demand zone
            box demandZone = box.new(left=dbr_startIndex, right=bar_index, top=dbr_highestBody, bottom=dbr_lowestLow,
                                 border_color=color.new(color.blue, 0), bgcolor=color.new(color.blue, 85))
            box.set_extend(demandZone, extend.right)
            // Store the zone
            array.push(demandZones, demandZone)
            array.push(proximalLines, dbr_highestBody)
            array.push(distalLines, dbr_lowestLow)
            array.push(zoneIsRealtime, barstate.isrealtime)
            array.push(zoneTypes, 2)  // 2 for DBR
            array.push(zoneStatus, true)  // Zone is active when created

            // Limit the number of active zones displayed
            int activeZonesCount = 0
            for int idx = 0 to array.size(zoneStatus) - 1
                if array.get(zoneStatus, idx)
                    activeZonesCount += 1

            // If number of active zones exceeds the limit, remove the oldest active zone
            while activeZonesCount > maxZonesToDisplay
                // Find the oldest active zone
                int oldestActiveIndex = na
                for int idx = 0 to array.size(zoneStatus) - 1
                    if array.get(zoneStatus, idx)
                        oldestActiveIndex := idx
                        break
                if not na(oldestActiveIndex)
                    // Remove the oldest active zone
                    box dzBoxToRemove = array.get(demandZones, oldestActiveIndex)
                    box.delete(dzBoxToRemove)
                    // Remove from arrays
                    array.remove(demandZones, oldestActiveIndex)
                    array.remove(proximalLines, oldestActiveIndex)
                    array.remove(distalLines, oldestActiveIndex)
                    array.remove(zoneIsRealtime, oldestActiveIndex)
                    array.remove(zoneTypes, oldestActiveIndex)
                    array.remove(zoneStatus, oldestActiveIndex)
                    // Decrease activeZonesCount
                    activeZonesCount -= 1
                else
                    // No active zones found
                    break

        // Reset pattern
        dbr_state := 0
        dbr_baseCount := 0
    else
        // Pattern invalidated
        dbr_state := 0
        dbr_baseCount := 0

// ‣ Zone Violation Checking and Management

if array.size(demandZones) > 0
    var int i = array.size(demandZones) - 1
    while i >= 0
        box dzBox = array.get(demandZones, i)
        float proximalLine = array.get(proximalLines, i)
        float distalLine = array.get(distalLines, i)
        bool isRealtimeZone = array.get(zoneIsRealtime, i)
        int zoneType = array.get(zoneTypes, i)
        bool zoneIsActive = array.get(zoneStatus, i)
        bool isZoneActive = (drawHistoricalZones or isRealtimeZone)
        bool isViolated = close < distalLine  // Using closing price

        if isZoneActive
            if isViolated and zoneIsActive
                // Mark zone as violated
                array.set(zoneStatus, i, false)
                // Keep violated zone and color it gray
                box.set_border_color(dzBox, color.gray)
                box.set_bgcolor(dzBox, color.new(color.gray, 85))
            else if not isViolated and not zoneIsActive
                // Zone was previously violated but is no longer
                array.set(zoneStatus, i, true)
                // Restore original color based on zoneType
                if zoneType == 1  // RBR
                    box.set_border_color(dzBox, color.green)
                    box.set_bgcolor(dzBox, color.new(color.green, 85))
                else if zoneType == 2  // DBR
                    box.set_border_color(dzBox, color.new(color.blue, 0))
                    box.set_bgcolor(dzBox, color.new(color.blue, 85))
        else
            // Delete zone if it's historical and not set to be displayed
            box.delete(dzBox)
            array.remove(demandZones, i)
            array.remove(proximalLines, i)
            array.remove(distalLines, i)
            array.remove(zoneIsRealtime, i)
            array.remove(zoneTypes, i)
            array.remove(zoneStatus, i)
        i -= 1  // Decrement i

// ‣ Plot Markers (Optional)

// RBR Markers
plotshape(rbr_state == 1 and isBigGreenCandle and rbr_baseCount == 0, title="RBR Leg-in", style=shape.triangleup, location=location.belowbar, color=color.lime, size=size.small)
plotshape(rbr_state == 1 and isGreenCandle and rbr_baseCount >= 1 and not isBigGreenCandle, title="RBR Leg-out", style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small)

// DBR Markers
plotshape(dbr_state == 1 and isBigRedCandle and dbr_baseCount == 0, title="DBR Leg-in", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small)
plotshape(dbr_state == 1 and isGreenCandle and dbr_baseCount >= 1, title="DBR Leg-out", style=shape.triangleup, location=location.belowbar, color=color.blue, size=size.small)