This strategy calculates regular and hidden divergences across selectable momentum oscillators, including RSI, CCI, and MFI, and plots volatility-based divergence zones.
//@version=6
// ============================================================================
// Oscillator Divergence Zones [WavesUnchained] — Strategy (standalone)
// Version: 1.2.4
// Author: WavesUnchained
// Build: 2026-10-12 00:00:00
// ============================================================================
// Detects regular and hidden divergences for RSI, CCI, or MFI.
//
// Features:
// ✓ Oscillator selector: RSI (default), CCI, MFI
// ✓ Regular divergence: price/oscillator structural opposition at pivots
// ✓ Hidden divergence: trend-continuation counterpart (toggle separately)
// ✓ Three-mode level filter: Off / Fixed OB-OS / Dynamic Zones (percentile-based)
// ✓ StdDev filter: optional statistical overextension gate for regular divergences
// ✓ ATR-based zone width: band height locked to creation-time volatility
// ✓ Configurable break trigger: full-candle (default) or close-based
// ✓ Zone retest counter: touch count displayed at zone right edge
// ✓ Dual-pane visualization: oscillator pane markers and chart overlay
// ✓ Alert conditions for all four divergence types
//
// ============================================================================
strategy("Oscillator Divergence Zones [WavesUnchained] — Strategy", overlay=false,
format=format.price,
precision=2,
max_lines_count=500,
max_labels_count=500,
max_boxes_count=500,
default_qty_type=strategy.percent_of_equity, default_qty_value=10,
commission_type=strategy.commission.percent, commission_value=0.02,
slippage=1, max_bars_back=500)
// Synthetische Bar-Preise (Heikin Ashi, Renko …): Orders füllen zu Marktpreisen,
// der Backtest gilt dort nicht. Kurzer Hinweis unten rechts statt auf der Kerze.
var table chartTypeWarn = table.new(position.bottom_right, 1, 1)
if barstate.islast and not chart.is_standard
table.cell(chartTypeWarn, 0, 0, "ODZ Strategy · backtest invalid on this chart type",
bgcolor = color.new(color.red, 10), text_color = color.white, text_size = size.small)
// ─── Inputs ──────────────────────────────────────────────────────────────────
g_osc = "Oscillator"
oscType = input.string("RSI", "Type", options=["RSI", "CCI", "MFI"], group=g_osc)
oscSrc = input.source(close, "Source", group=g_osc)
oscLen = input.int(14, "Length", minval=2, group=g_osc)
autoLevels = input.bool(true, "Auto OB/OS per Oscillator", group=g_osc,
tooltip="Automatically applies standard OB/OS levels per oscillator: RSI 70/30 · CCI 100/−100 · MFI 80/20. Disable to set custom values below.")
obLevel = input.int(70, "Overbought", minval=-500, maxval=500, group=g_osc,
tooltip="Used only when Auto OB/OS is disabled. RSI: 70 · CCI: 100 · MFI: 80")
osLevel = input.int(30, "Oversold", minval=-500, maxval=500, group=g_osc,
tooltip="Used only when Auto OB/OS is disabled. RSI: 30 · CCI: −100 · MFI: 20")
g_div = "Divergence"
pivLeft = input.int(7, "Pivot Left Bars", minval=1, group=g_div)
pivRight = input.int(7, "Pivot Right Bars", minval=1, group=g_div)
minRange = input.int(10, "Min Bars Between Pivots", minval=1, group=g_div)
maxRange = input.int(100, "Max Bars Between Pivots", minval=5, group=g_div)
showHidden = input.bool(false, "Show Hidden Divergence", group=g_div)
filterMode = input.string("Fixed", "Level Filter", options=["Off", "Fixed", "Dynamic Zones"], group=g_div,
tooltip="Off: no level filter. Fixed: suppress signals outside a fixed % of the OB–OS range. Dynamic Zones: adaptive percentile-based thresholds computed from recent oscillator history.")
bullFilterPct = input.int(75, "Max for Bull (%)", minval=0, maxval=100, group=g_div,
tooltip="Fixed mode only. Upper limit for bullish signals as % of the OB–OS range. 75% ≈ RSI 60 · CCI 50 · MFI 65")
bearFilterPct = input.int(25, "Min for Bear (%)", minval=0, maxval=100, group=g_div,
tooltip="Fixed mode only. Lower limit for bearish signals as % of the OB–OS range. 25% ≈ RSI 40 · CCI −50 · MFI 35")
dynSampleLen = input.int(100, "DZ Sample Length", minval=10, group=g_div,
tooltip="Dynamic Zones mode only. Number of bars used to compute the percentile distribution of the oscillator.")
dynPcntile = input.float(90.0, "DZ Threshold (%)", minval=50.0, maxval=99.0, step=1.0, group=g_div,
tooltip="Dynamic Zones mode only. Symmetric threshold: bull filter uses the (100−X)th percentile as upper limit; bear filter uses the Xth percentile as lower limit. E.g. 90 → only fire when oscillator is in the bottom/top 10% of its recent history.")
g_std = "StdDev Filter"
useStdFilter = input.bool(false, "Statistical Overextension Filter", group=g_std,
tooltip="Only fire regular divergences when the oscillator is outside its rolling mean ± N×StdDev band at the pivot. Reduces false signals in flat/choppy markets. Does not apply to hidden divergences.")
stdPeriod = input.int(100, "Lookback Period", minval=10, group=g_std)
stdMult = input.float(1.5, "StdDev Multiplier", minval=0.5, step=0.1, group=g_std)
g_zone = "Zones"
showZones = input.bool(true, "Show Zones on Chart", group=g_zone)
maxZones = input.int(10, "Max Active Zones", minval=1, maxval=50, group=g_zone)
zoneExtend = input.int(15, "Zone Lookahead (bars)", minval=1, group=g_zone)
atrLen = input.int(14, "ATR Length (zone width)", minval=1, group=g_zone)
zoneFactor = input.float(0.25, "Zone Width (ATR×)", minval=0.05, maxval=3.0, step=0.05, group=g_zone)
breakOnClose = input.bool(false, "Break on Close", group=g_zone)
showTouches = input.bool(true, "Show Retest Count", group=g_zone)
g_col = "Colors"
bullCol = input.color(#26a69a, "Bullish", group=g_col)
bearCol = input.color(#ef5350, "Bearish", group=g_col)
g_debug = "Debug"
showPivots = input.bool(false, "Show All Pivots", group=g_debug,
tooltip="Marks every detected price pivot as a small circle — helps diagnose why a divergence was not triggered. Filled = passed all filters. Semi-transparent = pivot detected but no divergence signal.")
// ─── Types ───────────────────────────────────────────────────────────────────
type Zone
line edge
box area
int touches
label info
float halfW
bool bull
// ─── State ───────────────────────────────────────────────────────────────────
var array<Zone> bullZones = array.new<Zone>()
var array<Zone> bearZones = array.new<Zone>()
var array<Zone> hidBullZones = array.new<Zone>()
var array<Zone> hidBearZones = array.new<Zone>()
var int prevBullBar = na
var float prevBullOsc = na
var int prevBearBar = na
var float prevBearOsc = na
var int prevHidBullBar = na
var float prevHidBullOsc = na
var int prevHidBearBar = na
var float prevHidBearOsc = na
// ─── Oscillator ──────────────────────────────────────────────────────────────
atr = ta.atr(atrLen)
rsiSeries = ta.rsi(oscSrc, oscLen)
cciSeries = ta.cci(oscSrc, oscLen)
mfiSeries = ta.mfi(oscSrc, oscLen)
oscVal = oscType == "CCI" ? cciSeries :
oscType == "MFI" ? mfiSeries : rsiSeries
// CCI is centered on 0; all others on 50
midLevel = oscType == "CCI" ? 0 : 50
oscAtPiv = oscVal[pivRight]
effectiveOB = autoLevels ? (oscType == "CCI" ? 100 : oscType == "MFI" ? 80 : 70) : obLevel
effectiveOS = autoLevels ? (oscType == "CCI" ? -100 : oscType == "MFI" ? 20 : 30) : osLevel
// ─── Pivots on price ─────────────────────────────────────────────────────────
foundPL = not na(ta.pivotlow(low, pivLeft, pivRight))
foundPH = not na(ta.pivothigh(high, pivLeft, pivRight))
pivBarIdx = bar_index - pivRight
bullFilterLvl = effectiveOS + bullFilterPct / 100.0 * (effectiveOB - effectiveOS)
bearFilterLvl = effectiveOS + bearFilterPct / 100.0 * (effectiveOB - effectiveOS)
dZoneAbove = ta.percentile_nearest_rank(oscVal, dynSampleLen, dynPcntile)
dZoneBelow = ta.percentile_nearest_rank(oscVal, dynSampleLen, 100.0 - dynPcntile)
bullOk = switch filterMode
"Fixed" => oscAtPiv < bullFilterLvl
"Dynamic Zones" => oscAtPiv < dZoneBelow[pivRight]
=> true
bearOk = switch filterMode
"Fixed" => oscAtPiv > bearFilterLvl
"Dynamic Zones" => oscAtPiv > dZoneAbove[pivRight]
=> true
// StdDev overextension filter (regular divergences only)
_oscMean = ta.sma(oscVal, stdPeriod)
_oscStd = ta.stdev(oscVal, stdPeriod)
_oscUpper = _oscMean + stdMult * _oscStd
_oscLower = _oscMean - stdMult * _oscStd
stdBullOk = not useStdFilter or oscAtPiv < _oscLower[pivRight]
stdBearOk = not useStdFilter or oscAtPiv > _oscUpper[pivRight]
// Check divergence against one of the last 3 preceding pivots so that
// intermediate pivots do not prevent detection of the structurally relevant pair.
// ta.valuewhen must run on every bar: inside the short-circuited `and`/`or` chains below
// it would only see the bars where it happened to be evaluated.
plP1 = ta.valuewhen(foundPL, low[pivRight], 1)
plO1 = ta.valuewhen(foundPL, oscAtPiv, 1)
plB1 = ta.valuewhen(foundPL, pivBarIdx, 1)
plP2 = ta.valuewhen(foundPL, low[pivRight], 2)
plO2 = ta.valuewhen(foundPL, oscAtPiv, 2)
plB2 = ta.valuewhen(foundPL, pivBarIdx, 2)
plP3 = ta.valuewhen(foundPL, low[pivRight], 3)
plO3 = ta.valuewhen(foundPL, oscAtPiv, 3)
plB3 = ta.valuewhen(foundPL, pivBarIdx, 3)
phP1 = ta.valuewhen(foundPH, high[pivRight], 1)
phO1 = ta.valuewhen(foundPH, oscAtPiv, 1)
phB1 = ta.valuewhen(foundPH, pivBarIdx, 1)
phP2 = ta.valuewhen(foundPH, high[pivRight], 2)
phO2 = ta.valuewhen(foundPH, oscAtPiv, 2)
phB2 = ta.valuewhen(foundPH, pivBarIdx, 2)
phP3 = ta.valuewhen(foundPH, high[pivRight], 3)
phO3 = ta.valuewhen(foundPH, oscAtPiv, 3)
phB3 = ta.valuewhen(foundPH, pivBarIdx, 3)
_distOk(prevB) =>
dist = pivBarIdx - prevB
not na(prevB) and dist >= minRange and dist <= maxRange
_bullDivWith(prevP, prevO, prevB) => _distOk(prevB) and low[pivRight] < prevP and oscAtPiv > prevO
_bearDivWith(prevP, prevO, prevB) => _distOk(prevB) and high[pivRight] > prevP and oscAtPiv < prevO
_bullHidWith(prevP, prevO, prevB) => _distOk(prevB) and low[pivRight] > prevP and oscAtPiv < prevO
_bearHidWith(prevP, prevO, prevB) => _distOk(prevB) and high[pivRight] < prevP and oscAtPiv > prevO
// Regular: price/oscillator oppose → reversal signal
bullDiv = foundPL and bullOk and stdBullOk and (_bullDivWith(plP1, plO1, plB1) or _bullDivWith(plP2, plO2, plB2) or _bullDivWith(plP3, plO3, plB3))
bearDiv = foundPH and bearOk and stdBearOk and (_bearDivWith(phP1, phO1, phB1) or _bearDivWith(phP2, phO2, phB2) or _bearDivWith(phP3, phO3, phB3))
// Hidden: price/oscillator diverge in trend direction → continuation signal
// Always calculated so strategy/alerts can use them; display gated by showHidden
bullHidDiv = foundPL and bullOk and (_bullHidWith(plP1, plO1, plB1) or _bullHidWith(plP2, plO2, plB2) or _bullHidWith(plP3, plO3, plB3))
bearHidDiv = foundPH and bearOk and (_bearHidWith(phP1, phO1, phB1) or _bearHidWith(phP2, phO2, phB2) or _bearHidWith(phP3, phO3, phB3))
// ─── Zone functions ───────────────────────────────────────────────────────────
_addZone(arr, col, priceLevel, isHidden, isBull) =>
halfW = atr * zoneFactor / 2
x1 = bar_index - pivRight
x2 = bar_index + zoneExtend
// Label sits outside the zone box — below for bull, above for bear
labelY = isBull ? priceLevel - halfW : priceLevel + halfW
edgeLine = line.new(x1, priceLevel, x2, priceLevel,
color=col, width=1,
style=isHidden ? line.style_dashed : line.style_solid,
force_overlay=true)
areaBox = box.new(x1, priceLevel + halfW, x2, priceLevel - halfW,
border_color=color.new(col, 60),
bgcolor=isHidden ? color.new(col, 100) : color.new(col, 90),
force_overlay=true)
infoLbl = label.new(x2, labelY, "",
xloc=xloc.bar_index, style=label.style_label_left,
color=color.new(col, 20), textcolor=color.white, size=size.small,
tooltip="Retest count: how many times price re-entered this zone without breaking it.",
force_overlay=true)
arr.push(Zone.new(edgeLine, areaBox, 0, infoLbl, halfW, isBull))
while arr.size() > maxZones
old = arr.shift()
old.edge.delete()
old.area.delete()
old.info.delete()
_updateZones(arr, isBull) =>
if arr.size() > 0
toRemove = array.new<int>()
for i = 0 to arr.size() - 1
z = arr.get(i)
lvl = z.edge.get_y1()
broken = breakOnClose ? (isBull ? close < lvl : close > lvl) : (isBull ? high <= lvl : low >= lvl)
touched = not broken and (isBull ? low <= lvl + z.halfW : high >= lvl - z.halfW)
if broken
z.edge.set_x2(bar_index)
z.edge.set_style(line.style_dotted)
z.area.set_right(bar_index)
z.area.set_bgcolor(color.new(color.gray, 94))
z.area.set_border_color(color.new(color.gray, 88))
z.info.delete()
toRemove.unshift(i)
else
z.edge.set_x2(bar_index + zoneExtend)
z.area.set_right(bar_index + zoneExtend)
if touched
z.touches += 1
if showTouches and z.touches > 0
z.info.set_x(bar_index + zoneExtend)
z.info.set_text("×" + str.tostring(z.touches))
if toRemove.size() > 0
for j = 0 to toRemove.size() - 1
arr.remove(toRemove.get(j))
// ─── Execute ─────────────────────────────────────────────────────────────────
_updateZones(bullZones, true)
_updateZones(bearZones, false)
_updateZones(hidBullZones, true)
_updateZones(hidBearZones, false)
if showZones
if bullDiv
_addZone(bullZones, bullCol, low[pivRight], false, true)
if bearDiv
_addZone(bearZones, bearCol, high[pivRight], false, false)
if showHidden and bullHidDiv
_addZone(hidBullZones, bullCol, low[pivRight], true, true)
if showHidden and bearHidDiv
_addZone(hidBearZones, bearCol, high[pivRight], true, false)
// Regular connector lines: dotted, full opacity
if bullDiv
currBar = bar_index - pivRight
if not na(prevBullBar)
line.new(prevBullBar, prevBullOsc, currBar, oscAtPiv,
color=color.new(bullCol, 20), style=line.style_dotted, width=2)
prevBullBar := currBar
prevBullOsc := oscAtPiv
if bearDiv
currBar = bar_index - pivRight
if not na(prevBearBar)
line.new(prevBearBar, prevBearOsc, currBar, oscAtPiv,
color=color.new(bearCol, 20), style=line.style_dotted, width=2)
prevBearBar := currBar
prevBearOsc := oscAtPiv
// Hidden connector lines: dashed, reduced opacity
if showHidden and bullHidDiv
currBar = bar_index - pivRight
if not na(prevHidBullBar)
line.new(prevHidBullBar, prevHidBullOsc, currBar, oscAtPiv,
color=color.new(bullCol, 55), style=line.style_dashed, width=1)
prevHidBullBar := currBar
prevHidBullOsc := oscAtPiv
if showHidden and bearHidDiv
currBar = bar_index - pivRight
if not na(prevHidBearBar)
line.new(prevHidBearBar, prevHidBearOsc, currBar, oscAtPiv,
color=color.new(bearCol, 55), style=line.style_dashed, width=1)
prevHidBearBar := currBar
prevHidBearOsc := oscAtPiv
// ─── Oscillator pane plots ───────────────────────────────────────────────────
noneCol = color.new(color.white, 100)
p_ob = plot(effectiveOB, "OB", color=color.new(bearCol, 50))
p_os = plot(effectiveOS, "OS", color=color.new(bullCol, 50))
p_mid = plot(midLevel, "Mid", color=color.new(color.gray, 70))
plot(filterMode == "Dynamic Zones" ? dZoneAbove : na, "DZ Upper",
color=color.new(bearCol, 20), linewidth=1, style=plot.style_circles)
plot(filterMode == "Dynamic Zones" ? dZoneBelow : na, "DZ Lower",
color=color.new(bullCol, 20), linewidth=1, style=plot.style_circles)
p_osc = plot(oscVal, "Oscillator",
color=color.from_gradient(oscVal, effectiveOS, effectiveOB, bullCol, bearCol),
linewidth=2)
fill(p_osc, p_mid, effectiveOB, effectiveOS, color.new(bearCol, 85), na)
fill(p_osc, p_mid, effectiveOB, effectiveOS, na, color.new(bullCol, 85))
plotshape(bullDiv ? oscAtPiv : na, offset=-pivRight,
title="Bull (pane)", style=shape.labelup, location=location.absolute,
color=bullCol, textcolor=color.white, text="▲", size=size.small)
plotshape(bearDiv ? oscAtPiv : na, offset=-pivRight,
title="Bear (pane)", style=shape.labeldown, location=location.absolute,
color=bearCol, textcolor=color.white, text="▼", size=size.small)
_hidDist = (effectiveOB - effectiveOS) * 0.08
plotshape(showHidden and bullHidDiv ? oscAtPiv - _hidDist : na, offset=-pivRight,
title="Bull hidden (pane)", style=shape.triangleup, location=location.absolute,
color=color.new(bullCol, 40), size=size.small)
plotshape(showHidden and bearHidDiv ? oscAtPiv + _hidDist : na, offset=-pivRight,
title="Bear hidden (pane)", style=shape.triangledown, location=location.absolute,
color=color.new(bearCol, 40), size=size.small)
// ─── Chart overlay shapes ────────────────────────────────────────────────────
plotshape(bullDiv ? low[pivRight] : na, offset=-pivRight,
title="Bull reversal (chart)", style=shape.labelup, location=location.absolute,
color=noneCol, textcolor=bullCol, text="▲\nReversal", force_overlay=true)
plotshape(bearDiv ? high[pivRight] : na, offset=-pivRight,
title="Bear reversal (chart)", style=shape.labeldown, location=location.absolute,
color=noneCol, textcolor=bearCol, text="Reversal\n▼", force_overlay=true)
plotshape(showHidden and bullHidDiv ? low[pivRight] : na, offset=-pivRight,
title="Bull continuation (chart)", style=shape.labelup, location=location.absolute,
color=noneCol, textcolor=color.new(bullCol, 35), text="▲\nCont.", force_overlay=true)
plotshape(showHidden and bearHidDiv ? high[pivRight] : na, offset=-pivRight,
title="Bear continuation (chart)", style=shape.labeldown, location=location.absolute,
color=noneCol, textcolor=color.new(bearCol, 35), text="Cont.\n▼", force_overlay=true)
// ─── Debug: all detected pivots ──────────────────────────────────────────────
// Semi-transparent circles on every pivot — bull pivots teal, bear pivots red.
// A circle without a divergence label means a filter blocked the signal.
plotshape(showPivots and foundPL ? oscAtPiv : na, offset=-pivRight,
title="Debug: Pivot Low (pane)", style=shape.circle, location=location.absolute,
color=color.new(bullCol, 60), size=size.tiny)
plotshape(showPivots and foundPH ? oscAtPiv : na, offset=-pivRight,
title="Debug: Pivot High (pane)", style=shape.circle, location=location.absolute,
color=color.new(bearCol, 60), size=size.tiny)
plotshape(showPivots and foundPL ? low[pivRight] : na, offset=-pivRight,
title="Debug: Pivot Low (chart)", style=shape.circle, location=location.absolute,
color=color.new(bullCol, 60), size=size.tiny, force_overlay=true)
plotshape(showPivots and foundPH ? high[pivRight] : na, offset=-pivRight,
title="Debug: Pivot High (chart)", style=shape.circle, location=location.absolute,
color=color.new(bearCol, 60), size=size.tiny, force_overlay=true)
// ─── Alerts ──────────────────────────────────────────────────────────────────
// alertcondition: named alerts, require one setup per condition in the Alert dialog
// alert(): all signals through a single "any alert() call" setup on the indicator
_ticker = syminfo.tickerid + " (" + timeframe.period + ")"
if bullDiv
alert("Bullish Divergence — " + _ticker + " | " + oscType, alert.freq_once_per_bar)
if bearDiv
alert("Bearish Divergence — " + _ticker + " | " + oscType, alert.freq_once_per_bar)
if bullHidDiv
alert("Bullish Hidden Divergence — " + _ticker + " | " + oscType, alert.freq_once_per_bar)
if bearHidDiv
alert("Bearish Hidden Divergence — " + _ticker + " | " + oscType, alert.freq_once_per_bar)
// ─── Strategy inputs ─────────────────────────────────────────────
g_strat = "Strategy"
tradeDirInput = input.string("Both", "Trade Direction",
options=["Both", "Long Only", "Short Only"], group=g_strat,
tooltip="Filter entries by direction. Useful for asymmetric instruments.")
confirmClose = input.bool(true, "Entries on Confirmed Bar Only", group=g_strat,
tooltip="Only trigger entries on closed bars. Prevents repainting in live trading.")
cooldownBars = input.int(0, "Cooldown Bars After Exit", minval=0, group=g_strat,
tooltip="Block new entries for N bars after a position closes. Set 0 to disable.")
enableBE = input.bool(false, "Break-Even Stop", group=g_strat,
tooltip="Move SL to entry price once the trade is N×ATR in profit.")
beATRInput = input.float(1.0, "Break-Even Trigger (ATR×)", minval=0.1, step=0.1, group=g_strat,
tooltip="ATR distance from entry that triggers the break-even move.")
g_filters = "Filters"
useSession = input.bool(false, "Session Filter", group=g_filters)
sessionInput = input.session("0000-2345", "Session", group=g_filters)
useDateRange = input.bool(false, "Date Range", group=g_filters)
dateFrom = input.time(timestamp("01 Jan 2020 00:00 +0000"), "From", group=g_filters)
dateTo = input.time(timestamp("31 Dec 2030 23:59 +0000"), "To", group=g_filters)
useMaxDD = input.bool(false, "Max Drawdown", group=g_filters)
maxDD = input.float(20.0, "Max Drawdown (%)", minval=1, step=1, group=g_filters)
useMaxIntraDD = input.bool(false, "Max Intraday Loss", group=g_filters)
maxIntraDD = input.float(5.0, "Max Intraday Loss (%)", minval=1, step=1, group=g_filters)
useLossStreak = input.bool(false, "Losing Streak Limit", group=g_filters)
maxLossStreak = input.int(5, "Max Consecutive Losses", minval=1, group=g_filters)
strategy.risk.max_drawdown(useMaxDD ? maxDD : 100, strategy.percent_of_equity)
strategy.risk.max_intraday_loss(useMaxIntraDD ? maxIntraDD : 100, strategy.percent_of_equity)
var int _genLossStreak = 0
if strategy.losstrades > strategy.losstrades[1]
_genLossStreak := _genLossStreak + 1
if strategy.wintrades > strategy.wintrades[1]
_genLossStreak := 0
_genFilterOk = (not useSession or not na(time(timeframe.period, sessionInput))) and
(not useDateRange or (time >= dateFrom and time <= dateTo)) and
(not useLossStreak or _genLossStreak < maxLossStreak)
var int _lastExitBar = na
_justClosed = strategy.closedtrades > 0 and strategy.closedtrades.exit_bar_index(strategy.closedtrades - 1) == bar_index
if _justClosed
_lastExitBar := bar_index
_inCooldown = cooldownBars > 0 and not na(_lastExitBar) and (bar_index - _lastExitBar) < cooldownBars
_canEntry = not _inCooldown and (not confirmClose or barstate.isconfirmed) and _genFilterOk
slBufInput = input.float(0.5, "SL ATR Buffer (×)", minval=0.1, step=0.1, group=g_strat,
tooltip="ATR distance beyond the divergence pivot for the stop loss.")
tpRRInput = input.float(2.0, "TP R:R Ratio", minval=0.5, step=0.5, group=g_strat,
tooltip="Take profit at this R:R multiple of the SL distance from entry.")
// ─── Strategy execution ──────────────────────────────────────────
_canLong = tradeDirInput != "Short Only"
_canShort = tradeDirInput != "Long Only"
var float _sl = na
var float _tp = na
// Computed every bar — inside the entry `if` it would only average signal bars
_atr = ta.atr(14)
if (bullDiv) and _canLong and _canEntry
_sl := low[pivRight] - _atr * slBufInput
_slDist = math.max(close - _sl, syminfo.mintick)
_tp := close + _slDist * tpRRInput
strategy.entry("Long", strategy.long)
if (bearHidDiv) and _canShort and _canEntry
_sl := high[pivRight] + _atr * slBufInput
_slDist = math.max(_sl - close, syminfo.mintick)
_tp := close - _slDist * tpRRInput
strategy.entry("Short", strategy.short)
// Break-even integrated into the same exit (single stop level — no exit races)
_beAtr = _atr * beATRInput
_beLong = enableBE and strategy.position_size > 0 and close >= strategy.position_avg_price + _beAtr
_beShort = enableBE and strategy.position_size < 0 and close <= strategy.position_avg_price - _beAtr
_finalLongStop = _beLong ? math.max(_sl, strategy.position_avg_price) : _sl
_finalShortStop = _beShort ? math.min(_sl, strategy.position_avg_price) : _sl
if strategy.position_size > 0
strategy.exit("Long Exit", "Long", stop=_finalLongStop, limit=_tp)
if strategy.position_size < 0
strategy.exit("Short Exit", "Short", stop=_finalShortStop, limit=_tp)
Overview
Oscillator Divergence Zones [WavesUnchained] — Strategy displays oscillator dynamics in a separate pane while projecting support and resistance zones onto the main chart. Trades trigger on confirmed oscillator divergences filtered by overbought, oversold, or percentile-based dynamic levels, using a default position size of ten percent of equity. Key user settings configure oscillator parameters, pivot detection lengths, statistical deviation filters, and ATR-derived zone widths. Because pivot detection requires right-side confirmation bars, entry signals lag market turning points.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Type
string
RSI
oscType
Source
source
close
oscSrc
Length
int
14
oscLen
Auto OB/OS per Oscillator
bool
true
autoLevels
Overbought
int
70
obLevel
Oversold
int
30
osLevel
Pivot Left Bars
int
7
pivLeft
Pivot Right Bars
int
7
pivRight
Min Bars Between Pivots
int
10
minRange
Max Bars Between Pivots
int
100
maxRange
Show Hidden Divergence
bool
false
showHidden
Level Filter
string
Fixed
filterMode
Show all 40 settings
Setting
Type
Default
Variable
Max for Bull (%)
int
75
bullFilterPct
Min for Bear (%)
int
25
bearFilterPct
DZ Sample Length
int
100
dynSampleLen
DZ Threshold (%)
float
90.0
dynPcntile
Statistical Overextension Filter
bool
false
useStdFilter
Lookback Period
int
100
stdPeriod
StdDev Multiplier
float
1.5
stdMult
Show Zones on Chart
bool
true
showZones
Max Active Zones
int
10
maxZones
Zone Lookahead (bars)
int
15
zoneExtend
ATR Length (zone width)
int
14
atrLen
Zone Width (ATR×)
float
0.25
zoneFactor
Break on Close
bool
false
breakOnClose
Show Retest Count
bool
true
showTouches
Bullish
color
#26a69a
bullCol
Bearish
color
#ef5350
bearCol
Show All Pivots
bool
false
showPivots
Trade Direction
string
Both
tradeDirInput
Entries on Confirmed Bar Only
bool
true
confirmClose
Cooldown Bars After Exit
int
0
cooldownBars
Break-Even Stop
bool
false
enableBE
Break-Even Trigger (ATR×)
float
1.0
beATRInput
Session Filter
bool
false
useSession
Session
session
0000-2345
sessionInput
Date Range
bool
false
useDateRange
From
time
timestamp("01 Jan 2020 00:00 +0000")
dateFrom
To
time
timestamp("31 Dec 2030 23:59 +0000")
dateTo
Max Drawdown
bool
false
useMaxDD
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
MACD + Divergences [Alorse] is a momentum strategy that calculates the MACD oscillator and identifies regular and hidden divergences using pivot high and low points. It filters trade signals with dual Exponential Moving Averages (EMA 150 and 600) to ensure tra
This Pine Script strategy uses the Commodity Channel Index (CCI) with a 20-period SMA to generate long-only signals. It plots the CCI line and dashed bands at 150 and -150 on the chart, filling the area between bands. Entries occur when CCI crosses above the l
The Stochastic Divergence Strategy calculates the Stochastic oscillator (%K and %D) to identify regular bullish and bearish divergence patterns against price pivots. It plots the oscillator and divergence lines in a separate indicator pane. Entries trigger on
This strategy implements Bill Williams' Awesome Oscillator (AO) calculated as the difference between two simple moving averages of the median price. It generates long and short entries using three classic signal triggers: zero-line crossovers, twin peaks diver
This strategy plots the previous daily high and low on the price chart and calculates the Andean oscillator bull and bear components with an exponential moving average signal. It looks for a bull or bear cross of that signal, then confirms the setup against re
This strategy trades 1-minute futures contracts on Binance. It plots pivot high and low resistance and support levels on the price chart, along with an EMA and a custom Cto indicator. Long and short entries trigger on EMA and Cto crossovers combined with price