Momentum-Confluence-Suite-v2.pine
Copy
//@version=6
// Momentum Confluence Suite [MCS] v2
// Price chart: EMAs, Supertrend, ATR stop, BUY/SELL labels and ATR targets.
// Lower pane: relative-volume columns, 1.5x threshold and signal diamonds.
// Signals are confirmed at bar close and request.security uses lookahead_off.
indicator("Momentum Confluence Suite [MCS] v2", shorttitle="MCS v2", overlay=false, max_labels_count=500)
// ── Input groups ────────────────────────────────────────────────
GRP_T = "Trend (EMA)"
GRP_ST = "Supertrend"
GRP_RG = "Regime Filter"
GRP_WT = "WaveTrend"
GRP_SR = "StochRSI"
GRP_DV = "Divergence"
GRP_ATR = "ATR Stop & Targets"
GRP_VL = "Relative Volume"
GRP_MTF = "Multi-Timeframe Filter"
GRP_BT = "Beta"
GRP_SG = "Signals & Display"
emaFastLen = input.int(21, "EMA Fast", minval=2, group=GRP_T)
emaSlowLen = input.int(50, "EMA Slow", minval=3, group=GRP_T)
emaLongLen = input.int(200, "EMA Long / Regime", minval=20, group=GRP_T)
stLen = input.int(10, "ATR Length", minval=1, group=GRP_ST)
stMult = input.float(3.0, "Multiplier", minval=0.1, step=0.1, group=GRP_ST)
adxLen = input.int(14, "ADX Length", minval=2, group=GRP_RG)
adxThresh = input.float(20.0, "ADX Threshold", minval=5, step=0.5, group=GRP_RG)
wtN1 = input.int(10, "Channel Length", minval=1, group=GRP_WT)
wtN2 = input.int(21, "Average Length", minval=1, group=GRP_WT)
rsiLen = input.int(14, "RSI Length", minval=2, group=GRP_SR)
stochLen = input.int(14, "Stochastic Length", minval=2, group=GRP_SR)
smoothK = input.int(3, "Smooth K", minval=1, group=GRP_SR)
smoothD = input.int(3, "Smooth D", minval=1, group=GRP_SR)
lbL = input.int(5, "Pivot Lookback Left", minval=1, group=GRP_DV)
lbR = input.int(5, "Pivot Lookback Right", minval=1, group=GRP_DV)
divMinBars = input.int(5, "Minimum Bars Between Pivots", minval=1, group=GRP_DV)
divMaxBars = input.int(60, "Maximum Bars Between Pivots", minval=5, group=GRP_DV)
showDivs = input.bool(true, "Show Divergence Markers", group=GRP_DV)
atrLen = input.int(14, "ATR Length", minval=1, group=GRP_ATR)
atrStopMult = input.float(2.0, "Stop Multiplier", minval=0.5, step=0.1, group=GRP_ATR)
showTargets = input.bool(true, "Show 1.5x / 2x / 3x ATR Targets", group=GRP_ATR)
volLen = input.int(20, "Normal Volume Lookback", minval=2, group=GRP_VL)
volThreshold = input.float(1.5, "High-Volume Threshold", minval=0.1, step=0.1, group=GRP_VL)
useVolFilter = input.bool(true, "Require High Volume For Signals", group=GRP_VL)
use5m = input.bool(true, "Use 5-minute Filter", group=GRP_MTF)
use15m = input.bool(true, "Use 15-minute Filter", group=GRP_MTF)
use1h = input.bool(true, "Use 1-hour Filter", group=GRP_MTF)
use4h = input.bool(true, "Use 4-hour Filter", group=GRP_MTF)
use24h = input.bool(true, "Use 24-hour Filter", group=GRP_MTF)
mtfMode = input.string("Majority", "Alignment Rule", options=["Majority", "All enabled"], group=GRP_MTF)
benchSym = input.symbol("AMEX:SPY", "Benchmark Symbol", group=GRP_BT)
betaLen = input.int(60, "Beta Lookback", minval=10, group=GRP_BT)
betaMin = input.float(0.7, "Minimum Beta", minval=0.0, step=0.1, group=GRP_BT)
useBetaFilter = input.bool(false, "Require Minimum Beta", group=GRP_BT)
longThresh = input.int(4, "Long Score >=", minval=1, maxval=7, group=GRP_SG)
shortThresh = input.int(4, "Short |Score| >=", minval=1, maxval=7, group=GRP_SG)
longOnly = input.bool(false, "Long Signals Only", group=GRP_SG)
showPriceLabels = input.bool(true, "Show BUY/SELL On Price Chart", group=GRP_SG)
showPaneSignals = input.bool(true, "Show Blue/Purple Diamonds In Lower Pane", group=GRP_SG)
showDash = input.bool(true, "Show Dashboard", group=GRP_SG)
showBg = input.bool(true, "Tint Lower Pane By Regime", group=GRP_SG)
// ── Core calculations ───────────────────────────────────────────
emaFast = ta.ema(close, emaFastLen)
emaSlow = ta.ema(close, emaSlowLen)
emaLong = ta.ema(close, emaLongLen)
[stLine, stDir] = ta.supertrend(stMult, stLen)
[dmp, dmn, adxV] = ta.dmi(adxLen, adxLen)
// WaveTrend oscillator.
ap = hlc3
esa = ta.ema(ap, wtN1)
dev = ta.ema(math.abs(ap - esa), wtN1)
ci = dev == 0.0 ? 0.0 : (ap - esa) / (0.015 * dev)
wt1 = ta.ema(ci, wtN2)
wt2 = ta.sma(wt1, 4)
// StochRSI.
rsiV = ta.rsi(close, rsiLen)
k = ta.sma(ta.stoch(rsiV, rsiV, rsiV, stochLen), smoothK)
dSm = ta.sma(k, smoothD)
// ── Regular + hidden WaveTrend divergence ───────────────────────
plFound = not na(ta.pivotlow(wt1, lbL, lbR))
phFound = not na(ta.pivothigh(wt1, lbL, lbR))
currPlOsc = wt1[lbR]
currPlPx = low[lbR]
prevPlOsc = ta.valuewhen(plFound, wt1[lbR], 1)
prevPlPx = ta.valuewhen(plFound, low[lbR], 1)
prevPlBar = ta.valuewhen(plFound, bar_index[lbR], 1)
plSpacing = bar_index[lbR] - prevPlBar
plInRange = not na(prevPlBar) and plSpacing >= divMinBars and plSpacing <= divMaxBars
currPhOsc = wt1[lbR]
currPhPx = high[lbR]
prevPhOsc = ta.valuewhen(phFound, wt1[lbR], 1)
prevPhPx = ta.valuewhen(phFound, high[lbR], 1)
prevPhBar = ta.valuewhen(phFound, bar_index[lbR], 1)
phSpacing = bar_index[lbR] - prevPhBar
phInRange = not na(prevPhBar) and phSpacing >= divMinBars and phSpacing <= divMaxBars
// Regular divergence: reversal. Hidden divergence: continuation.
bullDiv = plFound and plInRange and currPlPx < prevPlPx and currPlOsc > prevPlOsc
bearDiv = phFound and phInRange and currPhPx > prevPhPx and currPhOsc < prevPhOsc
hiddenBullDiv = plFound and plInRange and currPlPx > prevPlPx and currPlOsc < prevPlOsc
hiddenBearDiv = phFound and phInRange and currPhPx < prevPhPx and currPhOsc > prevPhOsc
// ── ATR trailing stop ───────────────────────────────────────────
atrV = ta.atr(atrLen)
stBullF = stDir < 0
stBearF = stDir > 0
newL = close - atrV * atrStopMult
newS = close + atrV * atrStopMult
var float lStop = na
var float sStop = na
if stBullF
lStop := not stBullF[1] ? newL : math.max(newL, nz(lStop[1], newL))
else
lStop := na
if stBearF
sStop := not stBearF[1] ? newS : math.min(newS, nz(sStop[1], newS))
else
sStop := na
stopLine = stBullF ? lStop : sStop
// ── Relative-volume lower-pane metric ───────────────────────────
volSMA = ta.sma(volume, volLen)
rvol = not na(volSMA) and volSMA > 0 ? volume / volSMA : na
volOk = not useVolFilter or (not na(rvol) and rvol >= volThreshold)
// ── Return-based beta vs benchmark ──────────────────────────────
bench = request.security(benchSym, timeframe.period, close, barmerge.gaps_off, barmerge.lookahead_off)
assetRet = close[1] > 0 ? math.log(close / close[1]) : na
benchRet = bench[1] > 0 ? math.log(bench / bench[1]) : na
meanA = ta.sma(assetRet, betaLen)
meanB = ta.sma(benchRet, betaLen)
betaCov = ta.sma((assetRet - meanA) * (benchRet - meanB), betaLen)
betaVar = ta.sma(math.pow(benchRet - meanB, 2), betaLen)
beta = not na(betaVar) and betaVar != 0 ? betaCov / betaVar : na
betaOk = not useBetaFilter or na(beta) or beta >= betaMin
// ── Multi-timeframe trend/regime states ─────────────────────────
// Returns +1 bull, -1 bear, 0 neutral for each requested timeframe.
f_tfState() =>
tfFast = ta.ema(close, emaFastLen)
tfSlow = ta.ema(close, emaSlowLen)
tfLong = ta.ema(close, emaLongLen)
[tfDmp, tfDmn, tfAdx] = ta.dmi(adxLen, adxLen)
tfBull = close > tfLong and tfFast > tfSlow and tfAdx > adxThresh and tfDmp > tfDmn
tfBear = close < tfLong and tfFast < tfSlow and tfAdx > adxThresh and tfDmn > tfDmp
tfBull ? 1 : tfBear ? -1 : 0
state5m = request.security(syminfo.tickerid, "5", f_tfState(), barmerge.gaps_off, barmerge.lookahead_off)
state15m = request.security(syminfo.tickerid, "15", f_tfState(), barmerge.gaps_off, barmerge.lookahead_off)
state1h = request.security(syminfo.tickerid, "60", f_tfState(), barmerge.gaps_off, barmerge.lookahead_off)
state4h = request.security(syminfo.tickerid, "240", f_tfState(), barmerge.gaps_off, barmerge.lookahead_off)
state24h = request.security(syminfo.tickerid, "D", f_tfState(), barmerge.gaps_off, barmerge.lookahead_off)
mtfEnabled = (use5m ? 1 : 0) + (use15m ? 1 : 0) + (use1h ? 1 : 0) + (use4h ? 1 : 0) + (use24h ? 1 : 0)
mtfBullCount = (use5m and state5m > 0 ? 1 : 0) + (use15m and state15m > 0 ? 1 : 0) + (use1h and state1h > 0 ? 1 : 0) + (use4h and state4h > 0 ? 1 : 0) + (use24h and state24h > 0 ? 1 : 0)
mtfBearCount = (use5m and state5m < 0 ? 1 : 0) + (use15m and state15m < 0 ? 1 : 0) + (use1h and state1h < 0 ? 1 : 0) + (use4h and state4h < 0 ? 1 : 0) + (use24h and state24h < 0 ? 1 : 0)
neededAlign = mtfMode == "All enabled" ? mtfEnabled : int(math.ceil(mtfEnabled * 0.60))
mtfLongOk = mtfEnabled == 0 or mtfBullCount >= neededAlign
mtfShortOk = mtfEnabled == 0 or mtfBearCount >= neededAlign
// ── Seven-vote confluence score ─────────────────────────────────
emaBullF = emaFast > emaSlow
emaBearF = emaFast < emaSlow
bullRegF = close > emaLong and adxV > adxThresh and dmp > dmn
bearRegF = close < emaLong and adxV > adxThresh and dmn > dmp
rangeRegF = not bullRegF and not bearRegF
bullishDiv = bullDiv or hiddenBullDiv
bearishDiv = bearDiv or hiddenBearDiv
voteEma = emaBullF ? 1 : emaBearF ? -1 : 0
voteSt = stBullF ? 1 : stBearF ? -1 : 0
voteReg = bullRegF ? 1 : bearRegF ? -1 : 0
voteWt = wt1 > wt2 ? 1 : wt1 < wt2 ? -1 : 0
voteSr = k > dSm ? 1 : k < dSm ? -1 : 0
voteDiv = bullishDiv ? 1 : bearishDiv ? -1 : 0
voteAtr = close > stopLine ? 1 : close < stopLine ? -1 : 0
score = voteEma + voteSt + voteReg + voteWt + voteSr + voteDiv + voteAtr
longSignal = score >= longThresh and bullRegF and volOk and betaOk and mtfLongOk
shortSignal = score <= -shortThresh and bearRegF and volOk and betaOk and mtfShortOk and not longOnly
newLong = barstate.isconfirmed and longSignal and not longSignal[1]
newShort = barstate.isconfirmed and shortSignal and not shortSignal[1]
// ── Fixed ATR targets from the latest confirmed signal ──────────
var int activeDir = 0
var float entryPx = na
var float entryAtr = na
if newLong
activeDir := 1
entryPx := close
entryAtr := atrV
else if newShort
activeDir := -1
entryPx := close
entryAtr := atrV
else if activeDir == 1 and (low <= entryPx - entryAtr * atrStopMult or high >= entryPx + entryAtr * 3.0)
activeDir := 0
else if activeDir == -1 and (high >= entryPx + entryAtr * atrStopMult or low <= entryPx - entryAtr * 3.0)
activeDir := 0
target1 = activeDir == 1 ? entryPx + entryAtr * 1.5 : activeDir == -1 ? entryPx - entryAtr * 1.5 : na
target2 = activeDir == 1 ? entryPx + entryAtr * 2.0 : activeDir == -1 ? entryPx - entryAtr * 2.0 : na
target3 = activeDir == 1 ? entryPx + entryAtr * 3.0 : activeDir == -1 ? entryPx - entryAtr * 3.0 : na
// ── Price-chart plots (forced out of the lower pane) ────────────
plot(emaFast, "EMA Fast", color=color.new(#00B8D9, 0), linewidth=2, force_overlay=true)
plot(emaSlow, "EMA Slow", color=color.new(#FF9F1C, 0), linewidth=2, force_overlay=true)
plot(emaLong, "EMA Long", color=color.new(#8A8F98, 45), linewidth=1, force_overlay=true)
plot(stLine, "Supertrend", color=stBullF ? color.new(#00C853, 0) : color.new(#FF1744, 0), linewidth=2, force_overlay=true)
plot(stopLine, "ATR Trailing Stop", color=color.new(#FFD600, 0), linewidth=2, style=plot.style_linebr, force_overlay=true)
plot(showTargets ? target1 : na, "Target 1 - 1.5 ATR", color=color.new(#42A5F5, 0), linewidth=2, style=plot.style_linebr, force_overlay=true)
plot(showTargets ? target2 : na, "Target 2 - 2 ATR", color=color.new(#7E57C2, 0), linewidth=2, style=plot.style_linebr, force_overlay=true)
plot(showTargets ? target3 : na, "Target 3 - 3 ATR", color=color.new(#EC407A, 0), linewidth=2, style=plot.style_linebr, force_overlay=true)
plotshape(showPriceLabels and newLong, title="BUY On Price Chart", style=shape.labelup, location=location.belowbar, color=color.new(#1565C0, 0), text="BUY", textcolor=color.white, size=size.tiny, force_overlay=true)
plotshape(showPriceLabels and newShort, title="SELL On Price Chart", style=shape.labeldown, location=location.abovebar, color=color.new(#7B1FA2, 0), text="SELL", textcolor=color.white, size=size.tiny, force_overlay=true)
// Divergence markers appear on the pivot bar; hidden = circle, regular = triangle.
plotshape(showDivs and bullDiv, title="Regular Bullish Divergence", style=shape.triangleup, location=location.belowbar, color=color.new(#00C853, 0), size=size.tiny, offset=-lbR, force_overlay=true)
plotshape(showDivs and bearDiv, title="Regular Bearish Divergence", style=shape.triangledown, location=location.abovebar, color=color.new(#FF1744, 0), size=size.tiny, offset=-lbR, force_overlay=true)
plotshape(showDivs and hiddenBullDiv, title="Hidden Bullish Divergence", style=shape.circle, location=location.belowbar, color=color.new(#00BFA5, 0), size=size.tiny, offset=-lbR, force_overlay=true)
plotshape(showDivs and hiddenBearDiv, title="Hidden Bearish Divergence", style=shape.circle, location=location.abovebar, color=color.new(#FF6D00, 0), size=size.tiny, offset=-lbR, force_overlay=true)
// ── Lower pane: relative volume + clean signal diamonds ─────────
rvColor = rvol >= volThreshold ? color.new(#00E5FF, 5) : color.new(#78909C, 55)
plot(rvol, "Relative Volume", style=plot.style_columns, color=rvColor, linewidth=2)
hline(1.0, "Normal Volume (1.0x)", color=color.new(#90A4AE, 45), linestyle=hline.style_dotted)
hline(volThreshold, "High Volume Threshold", color=color.new(#00E5FF, 0), linestyle=hline.style_dashed, linewidth=2)
plotshape(showPaneSignals and newLong, title="Buy Diamond", style=shape.diamond, location=location.bottom, color=color.new(#1565C0, 0), size=size.small)
plotshape(showPaneSignals and newShort, title="Sell Diamond", style=shape.diamond, location=location.bottom, color=color.new(#8E24AA, 0), size=size.small)
bgcolor(showBg ? bullRegF ? color.new(#00C853, 94) : bearRegF ? color.new(#FF1744, 94) : na : na)
// ── Alerts: transitions only, so a sustained state does not refire ──
alertcondition(newLong, title="MCS Long", message="MCS LONG on {{ticker}} {{interval}} at {{close}}")
alertcondition(newShort, title="MCS Short", message="MCS SHORT on {{ticker}} {{interval}} at {{close}}")
alertcondition(hiddenBullDiv and barstate.isconfirmed, title="MCS Hidden Bullish Divergence", message="Hidden bullish divergence on {{ticker}} {{interval}}")
alertcondition(hiddenBearDiv and barstate.isconfirmed, title="MCS Hidden Bearish Divergence", message="Hidden bearish divergence on {{ticker}} {{interval}}")
// ── Dashboard ───────────────────────────────────────────────────
bullC = color.new(#00C853, 22)
bearC = color.new(#FF1744, 22)
flatC = color.new(#607D8B, 28)
blueC = color.new(#1565C0, 18)
purpC = color.new(#8E24AA, 18)
txtC = color.new(#ECEFF1, 0)
var table dash = table.new(position.top_right, 2, 17, border_width=1, border_color=color.new(color.white, 85))
f_stateText(int state) =>
state > 0 ? "BULL" : state < 0 ? "BEAR" : "NEUTRAL"
f_stateColor(int state) =>
state > 0 ? bullC : state < 0 ? bearC : flatC
f_row(int row, string labelText, string valueText, color valueBg) =>
table.cell(dash, 0, row, labelText, text_color=txtC, text_size=size.small)
table.cell(dash, 1, row, valueText, bgcolor=valueBg, text_color=txtC, text_size=size.small)
f_tfRow(int row, string labelText, bool enabled, int state) =>
rowText = enabled ? f_stateText(state) : "OFF"
rowBg = enabled ? f_stateColor(state) : flatC
f_row(row, labelText, rowText, rowBg)
if barstate.islast and showDash
scoreText = (score > 0 ? "+" : "") + str.tostring(score)
scoreBg = score >= longThresh ? bullC : score <= -shortThresh ? bearC : flatC
regimeText = bullRegF ? "BULL" : bearRegF ? "BEAR" : "RANGE"
regimeBg = bullRegF ? bullC : bearRegF ? bearC : flatC
divText = hiddenBullDiv ? "HIDDEN BULL" : hiddenBearDiv ? "HIDDEN BEAR" : bullDiv ? "REG BULL" : bearDiv ? "REG BEAR" : "NONE"
divBg = bullishDiv ? bullC : bearishDiv ? bearC : flatC
volText = na(rvol) ? "n/a" : str.tostring(rvol, "#.##") + "x"
volBg = not na(rvol) and rvol >= volThreshold ? blueC : flatC
betaText = na(beta) ? "n/a" : str.tostring(beta, "#.##")
mtfText = str.tostring(mtfBullCount) + "B / " + str.tostring(mtfBearCount) + "S"
mtfBg = mtfLongOk ? bullC : mtfShortOk ? bearC : flatC
signalText = newLong ? "BUY" : newShort ? "SELL" : longSignal ? "LONG READY" : shortSignal ? "SHORT READY" : "WAIT"
signalBg = newLong or longSignal ? blueC : newShort or shortSignal ? purpC : flatC
targetText = activeDir == 1 ? "LONG ACTIVE" : activeDir == -1 ? "SHORT ACTIVE" : "NONE"
targetBg = activeDir == 1 ? bullC : activeDir == -1 ? bearC : flatC
f_row(0, "MCS v2 · " + syminfo.ticker, "SCORE " + scoreText, scoreBg)
f_row(1, "Regime", regimeText, regimeBg)
f_row(2, "WaveTrend", f_stateText(voteWt), f_stateColor(voteWt))
f_row(3, "StochRSI", f_stateText(voteSr), f_stateColor(voteSr))
f_row(4, "Divergence", divText, divBg)
f_row(5, "Relative Volume", volText, volBg)
f_row(6, "Beta vs " + benchSym, betaText, flatC)
f_row(7, "MTF FILTERS", mtfMode, flatC)
f_tfRow(8, "5 minute", use5m, state5m)
f_tfRow(9, "15 minute", use15m, state15m)
f_tfRow(10, "1 hour", use1h, state1h)
f_tfRow(11, "4 hour", use4h, state4h)
f_tfRow(12, "24 hour", use24h, state24h)
f_row(13, "MTF Alignment", mtfText, mtfBg)
f_row(14, "Signal", signalText, signalBg)
f_row(15, "ATR Targets", targetText, targetBg)
f_row(16, "RVOL Trigger", str.tostring(volThreshold, "#.##") + "x", blueC)
Show all 318 lines