//@version=6
// ============================================================================
// Chandelier Flip Radar [WavesUnchained] — Strategy (standalone)
// Version: 1.6.3
// Author: WavesUnchained
// Build: 2026-10-12 00:00:00
// ============================================================================
// ATR-based trailing stop with a directional, multi-level trend state. Trend
// weakening becomes visible before the actual direction change through
// progressive bar coloring and configurable warning/danger zones.
//
// Features:
// ✓ Ratchet trailing stop — holds until an accepted flip, never loosened by a body-rejected break
// ✓ Close-based or wick-based extremum mode
// ✓ Body filter gates actual dir change — weak flips are ignored, not hidden
// ✓ Directional state: strong / soft / danger per side — direction kept even in the danger zone
// ✓ Distance measured to the real flip trigger (longStopPrev / shortStopPrev)
// ✓ Inactive stop visible in gray (see where the flip trigger sits)
// ✓ Three flip qualities: confirmed flip / weak flip (body too weak) / trap (wick crossed, close held)
// ✓ Four adaptive modes: Off / Simple (vol-regime) / K-means (best Chandelier factor) / Conviction (trend-aware)
// ✓ K-means cluster selector: Best / Average / Worst cluster as multiplier source
// ✓ Performance AMA: adaptive smoothed stop line weighted by cluster performance index
// ✓ Optional MTF confluence: HTF Chandelier direction, pullback zones, diamond confluence flips
// ✓ Alert conditions and alert() calls for flips and traps
//
// ============================================================================
strategy("Chandelier Flip Radar [WavesUnchained] — Strategy", overlay=true,
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, "CFR Strategy · backtest invalid on this chart type",
bgcolor = color.new(color.red, 10), text_color = color.white, text_size = size.small)
// ─── Inputs ──────────────────────────────────────────────────────────────────
g_calc = "Calculation"
atrLen = input.int(30, "ATR Period", minval=1, group=g_calc)
atrMult = input.float(4.5, "ATR Multiplier", minval=0.1, step=0.1, group=g_calc)
useClose = input.bool(true, "Use Close for Extremums", group=g_calc,
tooltip="On: highest/lowest close over the ATR period. Off: highest high / lowest low (wick-based).")
adaptMode = input.string("Off", "Adaptive Multiplier",
options=["Off", "Simple", "K-means", "Conviction"], group=g_calc,
tooltip="Off: fixed multiplier. Simple: volatility-regime scaler — widens the stop in high volatility, tightens in low (NOT trend strength). K-means: adaptive K-means selection of the best-performing Chandelier factor. Conviction: trend-aware — tightens in strong trends, widens in chop.")
aiRange = input.float(0.5, "K-means Factor Range (±)", minval=0.5, step=0.5, group=g_calc,
tooltip="Search range around the ATR Multiplier. E.g. ±1.5 with multiplier 3.5 searches factors 2.0–5.0.", active=adaptMode == "K-means")
aiStep = input.float(0.5, "K-means Factor Step", minval=0.1, step=0.1, group=g_calc,
tooltip="Step between factor candidates. Smaller = finer resolution, more CPU.", active=adaptMode == "K-means")
perfAlpha = input.float(10, "K-means Performance Memory", minval=2, step=1, group=g_calc,
tooltip="EMA smoothing for each factor's performance tracking. Higher = slower adaptation.", active=adaptMode == "K-means")
aiCluster = input.string("Best", "K-means Cluster", options=["Best", "Average", "Worst"], group=g_calc,
tooltip="Which performance cluster to use as the multiplier source. Best = highest recent performance. Average = middle cluster. Worst = lowest.", active=adaptMode == "K-means")
convAdapt = input.float(0.7, "Conviction Adaptivity", minval=0.0, maxval=2.0, step=0.1, group=g_calc, active=adaptMode == "Conviction",
tooltip="Conviction mode only. How strongly trend conviction modulates the multiplier. 0 = fixed at ATR Multiplier. Higher = stronger tightening in trends / widening in chop.")
convFast = input.int(5, "Conviction Fast EMA", minval=2, group=g_calc, active=adaptMode == "Conviction")
convSlow = input.int(50, "Conviction Slow EMA", minval=5, group=g_calc, active=adaptMode == "Conviction")
convNormCap = input.float(2.0, "Conviction Trend-Force Cap", minval=0.5, step=0.1, group=g_calc, active=adaptMode == "Conviction",
tooltip="Trend force (|fastEMA − slowEMA| / ATR) that maps to full conviction.")
convChopCut = input.float(0.5, "Conviction Chop Cutoff", minval=0.05, step=0.05, group=g_calc, active=adaptMode == "Conviction",
tooltip="Trend force below this counts as chop and halves conviction (widening the stop).")
bodyFilter = input.float(0.80, "Min Body for Flip (ATR×)", minval=0.0, step=0.05, group=g_calc,
tooltip="Direction only changes when the flipping candle has a minimum body relative to ATR. Set 0 to disable. Prevents doji and inside-bar flips from changing the trend state.")
g_state = "State Thresholds"
dangerDist = input.float(0.35, "Danger Zone (ATR×)", minval=0.05, step=0.05, group=g_state,
tooltip="Distance to the flip trigger below which bars turn orange. Measured in ATR multiples.")
warnDist = input.float(0.75, "Warning Zone (ATR×)", minval=0.10, step=0.05, group=g_state,
tooltip="Distance to the flip trigger below which bars turn yellow. Must be greater than Danger Zone.")
g_vis = "Visuals"
showInactive = input.bool(true, "Show Inactive Stop", group=g_vis,
tooltip="Shows the opposing stop as a gray line — lets you see where the flip trigger is.")
showLabels = input.bool(true, "Show Flip Labels", group=g_vis)
showTraps = input.bool(true, "Show Traps", group=g_vis,
tooltip="Marks bars where a wick crossed the flip trigger but the close held — often a stop hunt or liquidity grab.")
showWeakFlips = input.bool(true, "Show Weak Flips", group=g_vis,
tooltip="Marks bars where the close broke the flip trigger but the body was too weak to confirm a flip (body filter rejected it). Distinct from a trap, where only the wick crossed and the close held.")
showAMA = input.bool(true, "Show Performance AMA", group=g_vis,
tooltip="Adaptive Moving Average of the active stop, weighted by cluster performance index. Only visible in K-means mode.")
showAIFactor = input.bool(true, "Show K-means Factor Label", group=g_vis,
tooltip="Displays the current K-means selected multiplier as a label on the last bar. Only visible in K-means mode.")
colorBars = input.bool(true, "Color Bars by State", group=g_vis)
highlightState = input.bool(true, "Highlight Fill", group=g_vis)
g_col = "Colors"
bullCol = input.color(#26a69a, "Bullish", group=g_col)
bearCol = input.color(#ef5350, "Bearish", group=g_col)
warnCol = input.color(#ffd600, "Warning", group=g_col)
dangerCol = input.color(#ff6f00, "Danger", group=g_col)
g_mtf = "MTF Confluence"
showMtf = input.bool(false, "Enable MTF Confluence", group=g_mtf,
tooltip="Higher-timeframe Chandelier direction as a structural filter: pullback-zone background, diamond confluence markers on flips aligned with the HTF trend, and an HTF stop step-line. Off by default — the base signal path is unchanged.")
htfTF = input.timeframe("240", "Higher Timeframe", group=g_mtf,
tooltip="Timeframe of the HTF reference stop. Same ATR Period, Multiplier and extremum mode as the chart, with a fixed multiplier (no adaptive mode) — a stable structural anchor. Should be higher than the chart TF.")
g_alert = "Alerts"
awaitConfirm = input.bool(true, "Await Bar Confirmation", group=g_alert,
tooltip="When enabled, alerts only fire on confirmed (closed) bars.")
// ─── HTF reference Chandelier stop — fixed multiplier, structural anchor ───────
f_htfChandelier(simple int aLen, simple float mult, simple bool useCl) =>
var float _ls = na
var float _ss = na
var int _d = 1
_atr = ta.atr(aLen)
_lsR = (useCl ? ta.highest(close, aLen) : ta.highest(high, aLen)) - mult * _atr
_ssR = (useCl ? ta.lowest(close, aLen) : ta.lowest(low, aLen)) + mult * _atr
_lsp = nz(_ls[1], _lsR)
_ssp = nz(_ss[1], _ssR)
_ls := close[1] > _lsp ? math.max(_lsR, _lsp) : _lsR
_ss := close[1] < _ssp ? math.min(_ssR, _ssp) : _ssR
_d := close > _ssp ? 1 : close < _lsp ? -1 : _d
_line = _d == 1 ? _ls : _ss
[_line, _d]
// ─── Calculation ─────────────────────────────────────────────────────────────
atrRaw = ta.atr(atrLen)
// Chandelier extremum bands — computed every bar; reused by the stops and the
// K-means performance instances (so the factor is optimized on the real model).
chHi = useClose ? ta.highest(close, atrLen) : ta.highest(high, atrLen)
chLo = useClose ? ta.lowest(close, atrLen) : ta.lowest(low, atrLen)
// ── Simple adaptive: scale with volatility regime vs. 100-bar average ─────────
atrAvg = nz(ta.sma(atrRaw, 100), atrRaw)
volRatio = atrRaw / atrAvg
// ── Conviction adaptive: trend-aware band — tighten in strong trends, widen in chop ──
// Unlike "Simple" (volatility only), this reads trend strength: ATR percentile
// rank + EMA-separation trend force, with a chop penalty. High conviction → the
// multiplier drops below base (tighter stop); low conviction → above base (wider).
convFastEma = ta.ema(close, convFast)
convSlowEma = ta.ema(close, convSlow)
convTfRaw = atrRaw > 0 ? math.abs(convFastEma - convSlowEma) / atrRaw : 0.0
convTfNorm = math.min(convTfRaw / convNormCap, 1.0)
convChop = convTfRaw < convChopCut
convAtrPct = ta.percentrank(atrRaw, 100)
convAtrPart = convTfRaw > convChopCut ? math.min(convAtrPct / 100.0, 0.85) : 0.50
convBase = (convAtrPart + convTfNorm) / 2.0
conviction = convChop ? convBase * 0.50 : convBase
convRawMult = atrMult * (1.0 + convAdapt * (0.5 - conviction))
convMult = math.max(atrMult * 0.60, math.min(atrMult * 1.80, convRawMult))
// ── K-means: run multiple Chandelier instances, cluster by performance ─────────
type _STPerf
float upper
float lower
float output
float perf = 0.0
float factor = 0.0
int trend = 1
var array<_STPerf> _stInst = array.new<_STPerf>()
var array<float> _stFactors = array.new<float>()
if barstate.isfirst
_f = atrMult - aiRange
while _f <= atrMult + aiRange + aiStep * 0.01
_stFactors.push(_f)
_stInst.push(_STPerf.new(factor=_f))
_f += aiStep
var float aiMult = atrMult
var float aiPerfIdx = 0.0
var float aiAMA = na
if adaptMode == "K-means" and _stInst.size() > 0
// Update each instance — a real Chandelier stop per factor (HH/LL ± k·ATR),
// so the clustered performance reflects the model actually traded, not a proxy.
for _inst in _stInst
_up = chLo + atrRaw * _inst.factor
_dn = chHi - atrRaw * _inst.factor
_inst.upper := na(_inst.upper) ? _up : (close[1] < _inst.upper ? math.min(_up, _inst.upper) : _up)
_inst.lower := na(_inst.lower) ? _dn : (close[1] > _inst.lower ? math.max(_dn, _inst.lower) : _dn)
_inst.trend := close > _inst.upper ? 1 : close < _inst.lower ? 0 : _inst.trend
_diff = nz(math.sign(close[1] - nz(_inst.output, hl2)))
_inst.perf += 2 / (perfAlpha + 1) * (nz(close - close[1]) * _diff - _inst.perf)
_inst.output := _inst.trend == 1 ? _inst.lower : _inst.upper
// Build perf + factor arrays for clustering
_pd = array.new<float>()
_fd = array.new<float>()
for _inst in _stInst
_pd.push(_inst.perf)
_fd.push(_inst.factor)
// K-means: 3 clusters, quartile-initialized centroids
_c = array.from(
_pd.percentile_linear_interpolation(25),
_pd.percentile_linear_interpolation(50),
_pd.percentile_linear_interpolation(75))
_cf0 = array.new<float>()
_cf1 = array.new<float>()
_cf2 = array.new<float>()
_cp0 = array.new<float>()
_cp1 = array.new<float>()
_cp2 = array.new<float>()
for _ = 0 to 49
_cf0.clear()
_cf1.clear()
_cf2.clear()
_cp0.clear()
_cp1.clear()
_cp2.clear()
for i = 0 to _pd.size() - 1
_d0 = math.abs(_pd.get(i) - _c.get(0))
_d1 = math.abs(_pd.get(i) - _c.get(1))
_d2 = math.abs(_pd.get(i) - _c.get(2))
_idx = _d0 <= _d1 and _d0 <= _d2 ? 0 : _d1 <= _d2 ? 1 : 2
switch _idx
0 =>
_cf0.push(_fd.get(i))
_cp0.push(_pd.get(i))
1 =>
_cf1.push(_fd.get(i))
_cp1.push(_pd.get(i))
2 =>
_cf2.push(_fd.get(i))
_cp2.push(_pd.get(i))
_n0 = _cp0.size() > 0 ? _cp0.avg() : _c.get(0)
_n1 = _cp1.size() > 0 ? _cp1.avg() : _c.get(1)
_n2 = _cp2.size() > 0 ? _cp2.avg() : _c.get(2)
if _n0 == _c.get(0) and _n1 == _c.get(1) and _n2 == _c.get(2)
break
_c.set(0, _n0)
_c.set(1, _n1)
_c.set(2, _n2)
// Identify cluster ordering by centroid value
_c0v = _c.get(0)
_c1v = _c.get(1)
_c2v = _c.get(2)
_bestIdx = _c0v >= _c1v and _c0v >= _c2v ? 0 : _c1v >= _c2v ? 1 : 2
_worstIdx = _c0v <= _c1v and _c0v <= _c2v ? 0 : _c1v <= _c2v ? 1 : 2
_avgIdx = math.max(0, math.min(2, 3 - _bestIdx - _worstIdx))
_tIdx = aiCluster == "Best" ? _bestIdx : aiCluster == "Worst" ? _worstIdx : _avgIdx
_tFactors = _tIdx == 0 ? _cf0 : _tIdx == 1 ? _cf1 : _cf2
_tPerfs = _tIdx == 0 ? _cp0 : _tIdx == 1 ? _cp1 : _cp2
aiMult := _tFactors.size() > 0 ? nz(_tFactors.avg(), atrMult) : atrMult
// Normalize cluster perf for AMA smoothing factor
_den = math.max(nz(ta.ema(math.abs(close - close[1]), int(perfAlpha)), 1e-10), 1e-10)
aiPerfIdx := _tPerfs.size() > 0 ? math.min(math.max(_tPerfs.avg(), 0.0) / _den, 1.0) : 0.0
dynMult = switch adaptMode
"Simple" => volRatio > 1.3 ? atrMult * 1.2 : volRatio < 0.8 ? atrMult * 0.85 : atrMult
"K-means" => aiMult
"Conviction" => convMult
=> atrMult
atr = dynMult * atrRaw
// Direction state — declared before the stops so the ratchet can read the prior
// accepted direction (the stop must not loosen until a flip is actually accepted).
var int dir = 1
// Trailing stops with ratchet mechanism. The ratchet holds while the accepted
// direction persists and only re-seeds when dir flips — so a weak (body-rejected)
// close past the trigger can never loosen the still-active stop.
longStopRaw = chHi - atr
shortStopRaw = chLo + atr
longStop = longStopRaw
longStopPrev = nz(longStop[1], longStopRaw)
longStop := dir[1] == 1 ? math.max(longStopRaw, longStopPrev) : longStopRaw
shortStop = shortStopRaw
shortStopPrev = nz(shortStop[1], shortStopRaw)
shortStop := dir[1] == -1 ? math.min(shortStopRaw, shortStopPrev) : shortStopRaw
// Body filter gates the actual dir change — weak flips don't alter state
bodyOk = bodyFilter <= 0.0 or math.abs(close - open) >= atrRaw * bodyFilter
flipLong = close > shortStopPrev and bodyOk
flipShort = close < longStopPrev and bodyOk
dir := flipLong ? 1 : flipShort ? -1 : dir
buySignal = dir == 1 and dir[1] == -1
sellSignal = dir == -1 and dir[1] == 1
// Weak flip: close broke the trigger but the body was too weak to confirm (body
// filter rejected) — direction holds. Distinct from a trap (wick crossed, close held).
weakLong = dir == -1 and close > shortStopPrev and not bodyOk
weakShort = dir == 1 and close < longStopPrev and not bodyOk
// MTF confluence: HTF reference direction filters/grades flips (no effect when off)
[htfLine, htfDir] = request.security(syminfo.tickerid, htfTF,
f_htfChandelier(atrLen, atrMult, useClose), lookahead=barmerge.lookahead_off)
pullbackBull = showMtf and htfDir == 1 and dir == -1
pullbackBear = showMtf and htfDir == -1 and dir == 1
confLong = buySignal and htfDir == 1
confShort = sellSignal and htfDir == -1
plainBuy = buySignal and not (showMtf and confLong)
plainSell = sellSignal and not (showMtf and confShort)
// Distance to the real flip trigger in ATR units
// Long mode: flips when close < longStopPrev → measure to longStopPrev
// Short mode: flips when close > shortStopPrev → measure to shortStopPrev
triggerStop = dir == 1 ? longStopPrev : shortStopPrev
distAtr = math.abs(close - triggerStop) / atrRaw
// Directional state levels — danger keeps its sign so the trend direction is never lost:
// +3 strong long · +2 soft long · +1 danger long · -1 danger short · -2 soft short · -3 strong short
// Warning/danger are also a bar-color layer (yellow/orange) on top, as imminent-flip flags.
warnLevel = math.max(warnDist, dangerDist + 0.05)
danger = distAtr < dangerDist
warn = not danger and distAtr < warnLevel
ema5 = ta.ema(close, 5)
pullbackLong = dir == 1 and close < ema5
pullbackShort = dir == -1 and close > ema5
int state = 0
if dir == 1
state := danger ? 1 : (warn or pullbackLong) ? 2 : 3
else
state := danger ? -1 : (warn or pullbackShort) ? -2 : -3
// Trap markers: wick crossed the flip trigger intrabar but close held
bullTrap = dir == -1 and high > shortStopPrev and close <= shortStopPrev
bearTrap = dir == 1 and low < longStopPrev and close >= longStopPrev
// Performance AMA: stop smoothed by cluster perf index (K-means mode only)
_activeStop = dir == 1 ? longStop : shortStop
if na(aiAMA)
aiAMA := _activeStop
else if adaptMode == "K-means"
aiAMA += aiPerfIdx * (_activeStop - aiAMA)
// ─── Colors ──────────────────────────────────────────────────────────────────
// The stop line/fill stay directional (green/red) even in the danger zone — full
// at strong (±3), faded at soft/danger — so direction is never lost.
color dirCol = color.new(bearCol, 45)
if dir == 1
dirCol := math.abs(state) >= 3 ? bullCol : color.new(bullCol, 45)
else
dirCol := math.abs(state) >= 3 ? bearCol : color.new(bearCol, 45)
// Bars layer the imminent-flip warning on top: yellow (warning) → orange (danger).
barCol = danger ? dangerCol : warn ? warnCol : dirCol
// ─── Plots ───────────────────────────────────────────────────────────────────
inactiveCol = color.new(color.gray, 55)
// HTF reference stop: step line, drawn first so it sits behind the active stop
htfUpLine = showMtf and htfDir == 1 ? htfLine : na
htfDnLine = showMtf and htfDir == -1 ? htfLine : na
plot(htfUpLine, "HTF Stop Up", color=color.new(bullCol, 60), linewidth=1, style=plot.style_stepline)
plot(htfDnLine, "HTF Stop Down", color=color.new(bearCol, 60), linewidth=1, style=plot.style_stepline)
longStopPlot = plot(dir == 1 ? longStop : na, "Long Stop",
color=dirCol, linewidth=2, style=plot.style_linebr)
shortStopPlot = plot(dir == -1 ? shortStop : na, "Short Stop",
color=dirCol, linewidth=2, style=plot.style_linebr)
plot(showInactive and dir == -1 ? longStop : na, "Long Stop (inactive)",
color=inactiveCol, linewidth=1, style=plot.style_linebr)
plot(showInactive and dir == 1 ? shortStop : na, "Short Stop (inactive)",
color=inactiveCol, linewidth=1, style=plot.style_linebr)
plot(showAMA and adaptMode == "K-means" ? aiAMA : na, "Performance AMA",
color=ta.cross(close, aiAMA) ? na : close > aiAMA ? color.new(bullCol, 45) : color.new(bearCol, 45),
linewidth=1, style=plot.style_linebr)
midPricePlot = plot(ohlc4, display=display.none, editable=false)
fill(midPricePlot, longStopPlot, title="Long Fill",
color=highlightState and dir == 1 ? color.new(dirCol, 88) : na)
fill(midPricePlot, shortStopPlot, title="Short Fill",
color=highlightState and dir == -1 ? color.new(dirCol, 88) : na)
plotshape(plainBuy ? longStop : na, "Buy Dot",
style=shape.circle, location=location.absolute, size=size.tiny, color=bullCol)
plotshape(plainSell ? shortStop : na, "Sell Dot",
style=shape.circle, location=location.absolute, size=size.tiny, color=bearCol)
plotshape(showLabels and plainBuy ? longStop : na, "Buy Label",
style=shape.labelup, location=location.absolute, size=size.tiny,
color=bullCol, textcolor=color.white, text="Buy")
plotshape(showLabels and plainSell ? shortStop : na, "Sell Label",
style=shape.labeldown, location=location.absolute, size=size.tiny,
color=bearCol, textcolor=color.white, text="Sell")
// HTF confluence flips: diamond replaces the plain dot when the flip aligns with the HTF trend
plotshape(showMtf and confLong ? longStop : na, "HTF Confluence Buy",
style=shape.diamond, location=location.absolute, size=size.tiny, color=bullCol)
plotshape(showMtf and confShort ? shortStop : na, "HTF Confluence Sell",
style=shape.diamond, location=location.absolute, size=size.tiny, color=bearCol)
// Bull trap: buyers caught at the top — mark at the high, pointing down
// Bear trap: sellers caught at the bottom — mark at the low, pointing up
plotshape(showTraps and bullTrap ? high : na, "Bull Trap",
style=shape.triangledown, location=location.absolute,
color=color.new(bearCol, 30), size=size.tiny)
plotshape(showTraps and bearTrap ? low : na, "Bear Trap",
style=shape.triangleup, location=location.absolute,
color=color.new(bullCol, 30), size=size.tiny)
// Weak flips: close broke the trigger but the body was too weak to confirm
plotshape(showWeakFlips and weakLong ? shortStopPrev : na, "Weak Long Flip",
style=shape.cross, location=location.absolute, color=color.new(bullCol, 35), size=size.tiny)
plotshape(showWeakFlips and weakShort ? longStopPrev : na, "Weak Short Flip",
style=shape.cross, location=location.absolute, color=color.new(bearCol, 35), size=size.tiny)
barcolor(colorBars ? barCol : na)
// MTF pullback zone: LTF trend opposes HTF trend — subtle tint in the HTF direction
color mtfZone = na
if pullbackBull
mtfZone := color.new(bullCol, 90)
else if pullbackBear
mtfZone := color.new(bearCol, 90)
bgcolor(mtfZone, title="MTF Pullback Zone")
// K-means factor label: shows selected multiplier on the last bar
var label _aiFactorLbl = na
if showAIFactor and adaptMode == "K-means" and barstate.islast
label.delete(_aiFactorLbl)
_aiFactorLbl := label.new(bar_index, _activeStop,
"×" + str.tostring(aiMult, "#.##"),
style = dir == 1 ? label.style_label_upper_left : label.style_label_lower_left,
color = color.new(color.gray, 75),
textcolor = color.new(color.gray, 10),
size = size.small)
// ─── Alerts ──────────────────────────────────────────────────────────────────
await = awaitConfirm ? barstate.isconfirmed : true
_ticker = syminfo.tickerid + " (" + timeframe.period + ")"
if buySignal and await
alert("Chandelier Flip Radar Buy — " + _ticker, alert.freq_once_per_bar_close)
if sellSignal and await
alert("Chandelier Flip Radar Sell — " + _ticker, alert.freq_once_per_bar_close)
if bullTrap and await
alert("Chandelier Flip Radar Bull Trap — " + _ticker, alert.freq_once_per_bar_close)
if bearTrap and await
alert("Chandelier Flip Radar Bear Trap — " + _ticker, alert.freq_once_per_bar_close)
// ─── 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
g_exits = "Strategy — Advanced Exits"
enableVolExit = input.bool(true, "Volatility Exit (ATR spike)", group=g_exits,
tooltip="Close the position when current ATR jumps above its baseline by the configured ratio — captures climax / exhaustion bars where the rest of the move is unlikely to continue.")
volExitRatio = input.float(1.6, " Volatility Spike Ratio", minval=1.2, maxval=5.0, step=0.1, group=g_exits,
tooltip="Exit when ta.atr(atrLen) >= ratio × ema(ta.atr(atrLen), 50). Default 1.6 = current ATR is 60% above its 50-bar average.")
// ─── Strategy execution ──────────────────────────────────────────
_canLong = tradeDirInput != "Short Only"
_canShort = tradeDirInput != "Long Only"
if (buySignal) and _canLong and _canEntry
strategy.entry("Long", strategy.long)
if (sellSignal) and _canShort and _canEntry
strategy.entry("Short", strategy.short)
// Volatility exit — close on ATR spike (climax / exhaustion bars)
// Baseline computed every bar — inside the position block it would only average in-position bars
_atrCur = ta.atr(atrLen)
_atrBase = ta.ema(_atrCur, 50)
if enableVolExit and strategy.position_size != 0
_spike = not na(_atrBase) and _atrCur >= _atrBase * volExitRatio
if _spike and strategy.position_size > 0
strategy.close("Long", comment="Vol Exit (ATR×" + str.tostring(volExitRatio, '#.#') + ")")
if _spike and strategy.position_size < 0
strategy.close("Short", comment="Vol Exit (ATR×" + str.tostring(volExitRatio, '#.#') + ")")
// Break-even integrated into the same exit (single stop level — no exit races)
_beAtr = ta.atr(14) * 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(longStop, strategy.position_avg_price) : longStop
_finalShortStop = _beShort ? math.min(shortStop, strategy.position_avg_price) : shortStop
if strategy.position_size > 0
strategy.exit("Long Exit", "Long", stop=_finalLongStop)
if strategy.position_size < 0
strategy.exit("Short Exit", "Short", stop=_finalShortStop)
Overview
Chandelier Flip Radar [WavesUnchained] marks confirmed flips, weak body-rejected flips, and wick traps, and colors bars as price nears warning or danger zones around the flip trigger. Adaptive modes can keep a fixed multiplier, scale it by volatility, select a K-means factor, or adjust it with a conviction filter. The collection notes a trailing stop, confirmed-bar gate, cooldown, and optional break-even exit. Entries follow accepted direction flips; position size defaults to a percent of equity.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
ATR Period
int
30
atrLen
ATR Multiplier
float
4.5
atrMult
Use Close for Extremums
bool
true
useClose
Adaptive Multiplier
string
Off
adaptMode
K-means Factor Range (±)
float
0.5
aiRange
K-means Factor Step
float
0.5
aiStep
K-means Performance Memory
float
10
perfAlpha
K-means Cluster
string
Best
aiCluster
Conviction Adaptivity
float
0.7
convAdapt
Conviction Fast EMA
int
5
convFast
Conviction Slow EMA
int
50
convSlow
Conviction Trend-Force Cap
float
2.0
convNormCap
Show all 40 settings
Setting
Type
Default
Variable
Conviction Chop Cutoff
float
0.5
convChopCut
Min Body for Flip (ATR×)
float
0.80
bodyFilter
Danger Zone (ATR×)
float
0.35
dangerDist
Warning Zone (ATR×)
float
0.75
warnDist
Show Inactive Stop
bool
true
showInactive
Show Flip Labels
bool
true
showLabels
Show Traps
bool
true
showTraps
Show Weak Flips
bool
true
showWeakFlips
Show Performance AMA
bool
true
showAMA
Show K-means Factor Label
bool
true
showAIFactor
Color Bars by State
bool
true
colorBars
Highlight Fill
bool
true
highlightState
Bullish
color
#26a69a
bullCol
Bearish
color
#ef5350
bearCol
Warning
color
#ffd600
warnCol
Danger
color
#ff6f00
dangerCol
Enable MTF Confluence
bool
false
showMtf
Higher Timeframe
timeframe
240
htfTF
Await Bar Confirmation
bool
true
awaitConfirm
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
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.
This strategy implements a single-layer perceptron machine learning model built from scratch to generate trading signals directly on the price chart. It calculates predictions based on sample size, threshold settings, and optional volatility or volume filters.
This overlay strategy combines a Supertrend with a k-nearest-neighbors filter. It smooths price and the Supertrend with weighted moving averages, then uses neighbor count and lookback length to form an AI trend signal. Traders can allow long, short, or both di
JAT+BSM Pro v13 Extended is a multi-timeframe trading strategy overlay that combines kinematic regression models, Bayesian inference, and consolidation zone analysis. It processes multi-layer volatility bands, volume profile buckets, and pattern structure targ
TN Sovereign Lorentzian v6 is a quantitative strategy that classifies price action regimes using multi-dimensional indicators and causal volume auditing. It generates entry signals based on RSI center-line crossovers, 10-period momentum, and price alignment re
This strategy calculates Bollinger Bands and Aroon oscillator values to identify mean-reversion opportunities. It enters a long position using full equity when the closing price crosses below the lower Bollinger Band while Aroon Up remains above a user-specifi
Donald - AI Futures 5M 15M Timeframe is an overlay trading strategy designed for intraday futures trading. It utilizes rational quadratic kernel regression to construct dynamic momentum zones and smoothed price boundaries on the main price chart. The strategy