// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/premium-discount-zones/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Builds a dealing range from the latest confirmed swing high and swing low and splits it
// into premium (upper half), equilibrium (50% band) and discount (lower half). The range
// expands immediately if price trades beyond either end.
indicator("FXP Premium Discount Zones", shorttitle = "FXP P/D Zones", overlay = true, max_boxes_count = 10, max_lines_count = 10, max_labels_count = 10)

// ── Inputs ─────────────────────────────────────────────────────────────
grpRange   = "Dealing range"
swingLen   = input.int(10, "Swing length (bars each side)", minval = 2, maxval = 100, group = grpRange)
eqBandPct  = input.float(5.0, "Equilibrium band (% of range, each side)", minval = 0.0, maxval = 25.0, step = 0.5, group = grpRange)

grpDisp    = "Display"
extendBars = input.int(20, "Extend zones right (bars)", minval = 0, maxval = 200, group = grpDisp)
showHist   = input.bool(true, "Plot equilibrium history", group = grpDisp)
showPrices = input.bool(true, "Show prices in labels", group = grpDisp)
colPrem    = input.color(#ef5350, "Premium", group = grpDisp)
colEq      = input.color(color.gray, "Equilibrium", group = grpDisp)
colDisc    = input.color(#26a69a, "Discount", group = grpDisp)
fillTransp = input.int(88, "Zone transparency", minval = 0, maxval = 100, group = grpDisp)

// ── Range ──────────────────────────────────────────────────────────────
ph = ta.pivothigh(high, swingLen, swingLen)
pl = ta.pivotlow(low, swingLen, swingLen)

var float rTop    = na
var int   rTopBar = na
var float rBot    = na
var int   rBotBar = na

if not na(ph)
    rTop    := ph
    rTopBar := bar_index - swingLen
if not na(pl)
    rBot    := pl
    rBotBar := bar_index - swingLen
if not na(rTop) and high > rTop
    rTop    := high
    rTopBar := bar_index
if not na(rBot) and low < rBot
    rBot    := low
    rBotBar := bar_index

float rng    = rTop - rBot
bool  valid  = not na(rTop) and not na(rBot) and rng > 0
float eq     = (rTop + rBot) / 2
float half   = rng * eqBandPct / 100
float eqTop  = eq + half
float eqBot  = eq - half

// 1 = premium, -1 = discount, 0 = equilibrium band or no range
int zone = not valid ? 0 : close > eqTop ? 1 : close < eqBot ? -1 : 0

// ── Drawing (last bar only) ────────────────────────────────────────────
var box   premBox = na
var box   discBox = na
var box   eqBox   = na
var line  eqLine  = na

fmt(float v) =>
    showPrices ? "  " + str.tostring(v, format.mintick) : ""

if barstate.islast and valid
    int left  = math.min(rTopBar, rBotBar)
    int right = bar_index + extendBars
    string premTxt = "Premium" + fmt(rTop)
    string discTxt = "Discount" + fmt(rBot)
    string eqTxt   = "Equilibrium" + fmt(eq)
    if na(premBox)
        premBox := box.new(left, rTop, right, eqTop, border_color = color.new(colPrem, 60), bgcolor = color.new(colPrem, fillTransp), text = premTxt, text_color = colPrem, text_size = size.small, text_halign = text.align_right, text_valign = text.align_top)
        discBox := box.new(left, eqBot, right, rBot, border_color = color.new(colDisc, 60), bgcolor = color.new(colDisc, fillTransp), text = discTxt, text_color = colDisc, text_size = size.small, text_halign = text.align_right, text_valign = text.align_bottom)
        eqBox   := box.new(left, eqTop, right, eqBot, border_color = color.new(colEq, 100), bgcolor = color.new(colEq, math.min(fillTransp + 4, 100)), text = eqTxt, text_color = colEq, text_size = size.small, text_halign = text.align_right, text_valign = text.align_center)
        eqLine  := line.new(left, eq, right, eq, color = colEq, style = line.style_dashed, width = 1)
    else
        premBox.set_lefttop(left, rTop)
        premBox.set_rightbottom(right, eqTop)
        premBox.set_text(premTxt)
        discBox.set_lefttop(left, eqBot)
        discBox.set_rightbottom(right, rBot)
        discBox.set_text(discTxt)
        eqBox.set_lefttop(left, eqTop)
        eqBox.set_rightbottom(right, eqBot)
        eqBox.set_text(eqTxt)
        eqLine.set_xy1(left, eq)
        eqLine.set_xy2(right, eq)

plot(showHist and valid ? eq : na, "Equilibrium", color = color.new(colEq, 40), style = plot.style_linebr)

// ── Alerts ─────────────────────────────────────────────────────────────
bool enterPrem = barstate.isconfirmed and zone == 1 and zone[1] != 1
bool enterDisc = barstate.isconfirmed and zone == -1 and zone[1] != -1
bool crossEq   = barstate.isconfirmed and valid and ((close > eq and close[1] <= eq[1]) or (close < eq and close[1] >= eq[1]))

alertcondition(enterPrem, "Price entered premium", "{{ticker}} {{interval}}: price closed in the premium zone at {{close}}")
alertcondition(enterDisc, "Price entered discount", "{{ticker}} {{interval}}: price closed in the discount zone at {{close}}")
alertcondition(crossEq, "Price crossed equilibrium", "{{ticker}} {{interval}}: price crossed the dealing-range equilibrium at {{close}}")
