// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © claygrace74
//@version=6
indicator("SMA Spread OSC", shorttitle="SMA Spread", overlay=false, max_labels_count=500)
// ==================================================================================
// Settings
// ==================================================================================
string GRP1 = "Fast SMA"
int i_fastLen = input.int(20, "Length", minval=1, maxval=500, group=GRP1)
color i_colUp1 = input.color(color.rgb(0, 230, 118), "Bull Color", group=GRP1)
color i_colDn1 = input.color(color.rgb(255, 82, 82), "Bear Color", group=GRP1)
string GRP2 = "Slow SMA"
int i_slowLen = input.int(200, "Length", minval=1, maxval=1000, group=GRP2)
color i_colUp2 = input.color(color.rgb(100, 181, 246), "Bull Color", group=GRP2)
color i_colDn2 = input.color(color.rgb(255, 167, 38), "Bear Color", group=GRP2)
string GRP3 = "Smoothing"
int i_smooth = input.int(1, "Smooth Spread (SMA)", minval=1, maxval=100, group=GRP3, tooltip="1 = no smoothing")
string GRP4 = "Display"
bool i_showHist = input.bool(true, "Show Histogram", group=GRP4)
bool i_showFills = input.bool(true, "Show Fills", group=GRP4)
bool i_showBands = input.bool(true, "Show Avg Distance Bands", group=GRP4)
bool i_showDash = input.bool(true, "Show Dashboard", group=GRP4)
bool i_showZero = input.bool(true, "Show Zero-Line Crosses", group=GRP4)
bool i_showDiv = input.bool(false, "Show Divergence Signals", group=GRP4)
bool i_showAccel = input.bool(true, "Show Acceleration Coloring", group=GRP4)
bool i_showExt = input.bool(true, "Show Extension Warnings", group=GRP4)
int i_divLB = input.int(5, "Divergence Lookback", minval=3, maxval=20, group=GRP4)
// ==================================================================================
// Core Calculation
// ==================================================================================
float fastSMA = ta.sma(close, i_fastLen)
float slowSMA = ta.sma(close, i_slowLen)
// Spread as percentage distance: (fast - slow) / slow * 100
float rawSpread = not na(slowSMA) and slowSMA > 0 ? ((fastSMA - slowSMA) / slowSMA) * 100.0 : 0.0
float spread = i_smooth > 1 ? ta.sma(rawSpread, i_smooth) : rawSpread
// ==================================================================================
// Average Distance Statistics
// ==================================================================================
float avgDistAbove = na
float avgDistBelow = na
float midLine = na
float pctAbove = na
if not na(spread) and i_slowLen > 0
float sumAbove = 0.0
int countAbove = 0
float sumBelow = 0.0
int countBelow = 0
for i = 0 to i_slowLen - 1
float s = nz(spread[i])
if s > 0
sumAbove += s
countAbove += 1
else if s < 0
sumBelow += math.abs(s)
countBelow += 1
avgDistAbove := countAbove > 0 ? sumAbove / countAbove : 0.0
avgDistBelow := countBelow > 0 ? sumBelow / countBelow : 0.0
pctAbove := (countAbove / i_slowLen) * 100.0
midLine := (avgDistAbove - avgDistBelow) / 2.0
// ==================================================================================
// Momentum, Acceleration & Signals
// ==================================================================================
bool rising = not na(spread) and spread > nz(spread[1])
bool isAbove = spread > nz(midLine)
// Acceleration
float accel = ta.change(spread)
bool accelUp = accel > 0
// Cross signals
bool crossAboveMid = ta.crossover(spread, midLine)
bool crossBelowMid = ta.crossunder(spread, midLine)
bool zeroCrossUp = ta.crossover(spread, 0)
bool zeroCrossDn = ta.crossunder(spread, 0)
// Extension detection — spread beyond avg distance bands
bool extendedBull = not na(avgDistAbove) and spread > avgDistAbove
bool extendedBear = not na(avgDistBelow) and spread < -avgDistBelow
// Divergence detection — price vs spread
bool priceHigher = high > ta.highest(high, i_divLB)[1]
bool spreadNotHigh = spread < ta.highest(spread, i_divLB)[1]
bool bearDiv = priceHigher and spreadNotHigh and spread > 0
bool priceLower = low < ta.lowest(low, i_divLB)[1]
bool spreadNotLow = spread > ta.lowest(spread, i_divLB)[1]
bool bullDiv = priceLower and spreadNotLow and spread < 0
// ==================================================================================
// Colors
// ==================================================================================
// 4-color histogram momentum
bool growBull = spread >= nz(midLine) and accelUp
bool shrinkBull = spread >= nz(midLine) and not accelUp
bool growBear = spread < nz(midLine) and not accelUp
bool shrinkBear = spread < nz(midLine) and accelUp
color histCol = growBull ? i_colUp1 : shrinkBull ? color.new(i_colUp1, 50) : growBear ? i_colDn1 : color.new(i_colDn1, 50)
// Acceleration-aware line color
color cLine = i_showAccel ? (spread >= nz(midLine) ? (accelUp ? i_colUp1 : color.new(i_colUp1, 50)) : (accelUp ? color.new(i_colDn1, 50) : i_colDn1)) : (rising ? i_colUp1 : i_colDn1)
color cSpread = spread >= nz(midLine) ? i_colUp1 : i_colDn1
// ==================================================================================
// Plots
// ==================================================================================
// Overlay price bands (projected avg distance on chart)
priceAbove = avgDistAbove > 0 ? slowSMA * (1 + avgDistAbove / 100) : na
priceBelow = avgDistBelow > 0 ? slowSMA * (1 - avgDistBelow / 100) : na
plot(priceAbove, force_overlay=true, color=color.new(color.red, 30), offset=i_fastLen, title="Avg Dist Above (Overlay)")
plot(priceBelow, force_overlay=true, color=color.new(color.green, 30), offset=i_fastLen, title="Avg Dist Below (Overlay)")
// Midline
pMid = plot(midLine, "Mid Line", color=color.rgb(180, 180, 200, 40), linewidth=1, style=plot.style_line)
// Avg distance bands
plot(i_showBands ? avgDistAbove : na, "Avg Above Band", color=color.new(i_colUp1, 65), linewidth=1, style=plot.style_stepline)
plot(i_showBands ? -avgDistBelow : na, "Avg Below Band", color=color.new(i_colDn1, 65), linewidth=1, style=plot.style_stepline)
// 4-color histogram
plot(i_showHist ? spread : na, "Histogram", style=plot.style_histogram, color=color.new(histCol, 40), linewidth=2)
// Spread line
pSpread = plot(spread, "Spread", color=cLine, linewidth=2)
// Zero reference
hline(0, "Zero", color.new(color.gray, 75), hline.style_dotted)
// Fills
fill(pSpread, pMid, color=i_showFills ? color.new(cSpread, 85) : na, title="Spread/Mid Fill")
// Cross markers on midline
plotshape(crossAboveMid ? spread : na, "Cross Above Mid", shape.triangleup, location.absolute, i_colUp1, size=size.small)
plotshape(crossBelowMid ? spread : na, "Cross Below Mid", shape.triangledown, location.absolute, i_colDn1, size=size.small)
// Zero-line crosses
plotshape(i_showZero and zeroCrossUp, "Zero Up", shape.circle, location.absolute, color.new(i_colUp1, 20), size=size.tiny)
plotshape(i_showZero and zeroCrossDn, "Zero Down", shape.circle, location.absolute, color.new(i_colDn1, 20), size=size.tiny)
// Extension warnings
plotshape(i_showExt and extendedBull and not extendedBull[1], "Extended Bull", shape.diamond, location.absolute, color.new(color.yellow, 0), size=size.tiny)
plotshape(i_showExt and extendedBear and not extendedBear[1], "Extended Bear", shape.diamond, location.absolute, color.new(color.yellow, 0), size=size.tiny)
bgcolor(i_showExt and (extendedBull or extendedBear) ? color.new(color.yellow, 93) : na, title="Extension BG")
// Divergence
bgcolor(i_showDiv and bearDiv ? color.new(color.orange, 88) : na, title="Bearish Divergence")
bgcolor(i_showDiv and bullDiv ? color.new(color.teal, 88) : na, title="Bullish Divergence")
// ==================================================================================
// Dashboard
// ==================================================================================
if barstate.islast and i_showDash
var table dash = table.new(position.top_right, 3, 13, bgcolor=color.rgb(20, 20, 30, 10), border_color=color.rgb(60, 60, 80), border_width=1, frame_color=color.rgb(60, 60, 80), frame_width=1)
color headerClr = color.rgb(200, 200, 220)
color labelClr = color.rgb(160, 160, 180)
// Header
table.cell(dash, 0, 0, "SMA Spread", text_color=headerClr, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 1, 0, "Fast", text_color=i_colUp1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 0, "Slow", text_color=i_colUp2, text_size=size.small, text_halign=text.align_center)
// SMA Length
table.cell(dash, 0, 1, "SMA Len", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 1, str.tostring(i_fastLen), text_color=headerClr, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 1, str.tostring(i_slowLen), text_color=headerClr, text_size=size.small, text_halign=text.align_center)
// SMA Value
table.cell(dash, 0, 2, "SMA Value", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 2, str.tostring(fastSMA, "#.##"), text_color=headerClr, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 2, str.tostring(slowSMA, "#.##"), text_color=headerClr, text_size=size.small, text_halign=text.align_center)
// Price vs SMAs
table.cell(dash, 0, 3, "Price vs SMA", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 3, close > fastSMA ? "Above" : "Below", text_color=close > fastSMA ? i_colUp1 : i_colDn1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 3, close > slowSMA ? "Above" : "Below", text_color=close > slowSMA ? i_colUp2 : i_colDn2, text_size=size.small, text_halign=text.align_center)
// Spread
table.cell(dash, 0, 4, "Spread", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 4, str.tostring(spread, "#.####") + " %", text_color=cLine, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 4, "", text_size=size.small)
// Momentum
table.cell(dash, 0, 5, "Momentum", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 5, rising ? "Rising" : "Falling", text_color=rising ? i_colUp1 : i_colDn1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 5, "", text_size=size.small)
// Acceleration
table.cell(dash, 0, 6, "Accel", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 6, accelUp ? "Accelerating" : "Decelerating", text_color=accelUp ? i_colUp1 : i_colDn1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 6, "", text_size=size.small)
// Extension status
string extTxt = extendedBull ? "EXTENDED BULL" : extendedBear ? "EXTENDED BEAR" : "Normal"
color extClr = extendedBull ? color.yellow : extendedBear ? color.yellow : color.gray
table.cell(dash, 0, 7, "Extension", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 7, extTxt, text_color=extClr, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 7, "", text_size=size.small)
// Bias
string biasText = spread > avgDistAbove ? "Extended Bull" : spread > midLine and spread > 0 ? "Bullish" : spread > midLine and spread <= 0 ? "Weak Bull" : spread > -avgDistBelow ? "Bearish" : "Extended Bear"
color biasColor = spread > nz(midLine) ? i_colUp1 : i_colDn1
table.cell(dash, 0, 8, "Bias", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 8, biasText, text_color=biasColor, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 8, "", text_size=size.small)
// --- Spread Stats ---
table.cell(dash, 0, 9, "Spread Stats", text_color=headerClr, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 1, 9, "(" + str.tostring(i_slowLen) + " bars)", text_color=color.rgb(120, 120, 140), text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 9, "", text_size=size.small)
table.cell(dash, 0, 10, "Avg Above", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 10, str.tostring(avgDistAbove, "#.####") + " %", text_color=i_colUp1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 10, spread > 0 ? str.tostring(spread, "#.####") + " % now" : "—", text_color=spread > 0 ? i_colUp1 : color.rgb(120, 120, 140), text_size=size.small, text_halign=text.align_center)
table.cell(dash, 0, 11, "Avg Below", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 11, str.tostring(avgDistBelow, "#.####") + " %", text_color=i_colDn1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 11, spread <= 0 ? str.tostring(math.abs(spread), "#.####") + " % now" : "—", text_color=spread <= 0 ? i_colDn1 : color.rgb(120, 120, 140), text_size=size.small, text_halign=text.align_center)
table.cell(dash, 0, 12, "% Above", text_color=labelClr, text_size=size.small, text_halign=text.align_left)
table.cell(dash, 1, 12, str.tostring(pctAbove, "#.#") + " %", text_color=pctAbove >= 50 ? i_colUp1 : i_colDn1, text_size=size.small, text_halign=text.align_center)
table.cell(dash, 2, 12, str.tostring(100.0 - pctAbove, "#.#") + " % below", text_color=pctAbove < 50 ? i_colUp1 : i_colDn1, text_size=size.small, text_halign=text.align_center)
// ==================================================================================
// Alert Conditions
// ==================================================================================
alertcondition(crossAboveMid, "Spread Crossed Above Mid", "SMA spread crossed above midline — bullish bias")
alertcondition(crossBelowMid, "Spread Crossed Below Mid", "SMA spread crossed below midline — bearish bias")
alertcondition(zeroCrossUp, "Spread Above Zero", "SMA spread crossed above zero — fast SMA above slow")
alertcondition(zeroCrossDn, "Spread Below Zero", "SMA spread crossed below zero — fast SMA below slow")
alertcondition(extendedBull and not extendedBull[1], "Extended Bull", "Spread exceeded avg bullish distance — potential mean reversion")
alertcondition(extendedBear and not extendedBear[1], "Extended Bear", "Spread exceeded avg bearish distance — potential mean reversion")
alertcondition(bullDiv, "Bullish Divergence", "Price making new low but spread is not — potential reversal")
alertcondition(bearDiv, "Bearish Divergence", "Price making new high but spread is not — potential reversal")
alertcondition(growBull, "Bull Accelerating", "Spread above mid and accelerating")
Show all 248 lines