What it does
The indicator takes the latest swing high and swing low as a dealing range and splits it in two. The upper half is the premium zone, the lower half is the discount zone, and a thin band around the 50% level marks equilibrium. The idea is simple: within a range, buying is relatively cheap in the lower half and selling is relatively expensive in the upper half.
How it works
- Swing highs and lows are confirmed pivots, using the swing length on each side.
- The most recent swing high is the top of the range and the most recent swing low is the bottom. If price trades beyond either end before a new swing confirms, that end moves with price straight away.
- Equilibrium = (range top + range bottom) ÷ 2. The equilibrium band extends the set percentage of the range height above and below that level.
- Premium runs from the top of the band to the range top. Discount runs from the range bottom to the bottom of the band.
- The zones are drawn from the older of the two swings to a few bars past the last candle, with the prices printed in the labels. A plotted line records where equilibrium was on each past bar, so you can see how the range evolved.
For example, with an illustrative range from 1.0800 to 1.0900, equilibrium is 1.0850 and a 5% band covers 1.0845 to 1.0855.
How to use it
- In an uptrend, many traders prefer to look for long setups only when price pulls back into discount, and avoid buying in premium. In a downtrend the opposite applies.
- Pair the zones with a structure or trend tool so you know which side of the range matters. Zones alone do not tell you the trend.
- A larger swing length (15 to 30) gives a broader range suited to swing trading. A smaller one tracks short intraday ranges.
- Combine with Fibonacci retracement levels: the 50% level is the same in both tools.
Alerts: price closed in premium, price closed in discount, price crossed equilibrium.
Settings
| Input | Default | What it changes |
|---|---|---|
| Swing length | 10 | Which swings define the range |
| Equilibrium band | 5% | Width of the neutral band |
| Extend zones right | 20 bars | Projection past the last candle |
| Plot equilibrium history | On | Historical 50% line |
| Show prices in labels | On | Prices next to zone names |
| Colors and zone transparency | Red / gray / teal, 88 | Appearance only |
Limitations
The range depends on swings that confirm late, and it changes whenever a new swing forms, so the zones you see today will not stay fixed. The historical equilibrium line shows only what was known on each bar. In strong trends price can stay in premium (or discount) for a long time, so "expensive" does not mean "about to fall". Premium and discount describe location, not timing.
Pine Script source
In TradingView open Pine Editor, paste the code, click Save and then Add to chart.
FXP_PremiumDiscountZones.pine
98 lines
// © 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}}")

