//@version=6
// ============================================================================
// Vein: Spread Context [WavesUnchained]
// Version: 0.1.4
// Author: WavesUnchained
// Build: 2026-10-12 00:00:00
// ============================================================================
// Optional commodity futures calendar spread context module.
// Auto-maps chart symbols to their front/next month futures spread.
// Provides bias, trend, momentum, divergence as context modifier.
//
// NOT a signal generator — only a context filter for other Vein modules.
//
// Supported: NatGas, WTI, Brent, Gold, Silver, Copper + manual override
// ============================================================================
indicator("Vein: Spread Context [WavesUnchained]", "Vein Spread", overlay=true, max_labels_count=10)
// ============================================================================
// INPUTS
// ============================================================================
var gSpread = "Spread Settings"
bool useSpread = input.bool(true, "Enable Spread Context", group=gSpread)
string spreadMode = input.string("Auto", "Symbol Mapping", options=["Auto", "Manual"], group=gSpread, tooltip="Auto: detect commodity from chart symbol.\nManual: specify front/next symbols yourself.")
string manualFront = input.symbol("NYMEX:NG1!", "Manual Front Month", group=gSpread)
string manualNext = input.symbol("NYMEX:NG2!", "Manual Next Month", group=gSpread)
string spreadCalc = input.string("Diff", "Spread Calculation", options=["Diff", "Ratio"], group=gSpread, tooltip="Diff: Front - Next (contango/backwardation).\nRatio: Front / Next.")
string spreadWeight = input.string("Normal", "Spread Weight", options=["Low", "Normal", "High"], group=gSpread, tooltip="How much the spread modifier influences other modules.")
var gTrend = "Spread Analysis"
int spreadEmaFast = input.int(5, "Spread EMA Fast", minval=2, group=gTrend)
int spreadEmaSlow = input.int(20, "Spread EMA Slow", minval=5, group=gTrend)
int divLookback = input.int(10, "Divergence Lookback", minval=3, group=gTrend)
var gDisplay = "Display"
bool showTable = input.bool(true, "Show Spread Table", group=gDisplay)
string displayMode = input.string("Light", "Display Mode", options=["Light", "Debug"], group=gDisplay)
// ============================================================================
// SYMBOL MAPPING
// ============================================================================
// Detect current symbol
string currentSym = syminfo.tickerid
string currentRoot = syminfo.root
// Map to front/next futures. The futures root is compared exactly — a substring
// match on two letters mapped unrelated roots (e.g. "PLTR" → Platinum, "HOOD" → Heating Oil).
// Returns [frontSymbol, nextSymbol, commodityName, isSupported]
f_mapSymbol() =>
string front = ""
string next = ""
string name = ""
bool found = false
// ── NatGas ──
if str.contains(currentSym, "NATURALGAS") or str.contains(currentSym, "NATGAS") or currentRoot == "NG" or str.contains(currentSym, "NG1!") or str.contains(currentSym, "NG2!")
front := "NYMEX:NG1!"
next := "NYMEX:NG2!"
name := "NatGas"
found := true
// ── WTI Crude ──
else if str.contains(currentSym, "OIL_CRUDE") or str.contains(currentSym, "USOIL") or str.contains(currentSym, "WTI") or currentRoot == "CL" or str.contains(currentSym, "CL1!")
front := "NYMEX:CL1!"
next := "NYMEX:CL2!"
name := "WTI"
found := true
// ── Brent ──
else if str.contains(currentSym, "OIL_BRENT") or str.contains(currentSym, "UKOIL") or str.contains(currentSym, "BRENT") or currentRoot == "BZ" or str.contains(currentSym, "BZ1!")
front := "NYMEX:BZ1!"
next := "NYMEX:BZ2!"
name := "Brent"
found := true
// ── Gold ──
else if str.contains(currentSym, "GOLD") or str.contains(currentSym, "XAUUSD") or currentRoot == "GC" or str.contains(currentSym, "GC1!")
front := "COMEX:GC1!"
next := "COMEX:GC2!"
name := "Gold"
found := true
// ── Silver ──
else if str.contains(currentSym, "SILVER") or str.contains(currentSym, "XAGUSD") or currentRoot == "SI" or str.contains(currentSym, "SI1!")
front := "COMEX:SI1!"
next := "COMEX:SI2!"
name := "Silver"
found := true
// ── Copper ──
else if str.contains(currentSym, "COPPER") or currentRoot == "HG" or str.contains(currentSym, "HG1!")
front := "COMEX:HG1!"
next := "COMEX:HG2!"
name := "Copper"
found := true
// ── Heating Oil ──
else if currentRoot == "HO" or str.contains(currentSym, "HO1!")
front := "NYMEX:HO1!"
next := "NYMEX:HO2!"
name := "Heating Oil"
found := true
// ── RBOB Gasoline ──
else if currentRoot == "RB" or str.contains(currentSym, "RB1!")
front := "NYMEX:RB1!"
next := "NYMEX:RB2!"
name := "RBOB"
found := true
// ── Corn ──
else if str.contains(currentSym, "CORN") or currentRoot == "ZC" or str.contains(currentSym, "ZC1!")
front := "CBOT:ZC1!"
next := "CBOT:ZC2!"
name := "Corn"
found := true
// ── Wheat ──
else if str.contains(currentSym, "WHEAT") or currentRoot == "ZW" or str.contains(currentSym, "ZW1!")
front := "CBOT:ZW1!"
next := "CBOT:ZW2!"
name := "Wheat"
found := true
// ── Soybeans ──
else if str.contains(currentSym, "SOYBEAN") or currentRoot == "ZS" or str.contains(currentSym, "ZS1!")
front := "CBOT:ZS1!"
next := "CBOT:ZS2!"
name := "Soybeans"
found := true
// ── Coffee ──
else if str.contains(currentSym, "COFFEE") or currentRoot == "KC" or str.contains(currentSym, "KC1!")
front := "ICEUS:KC1!"
next := "ICEUS:KC2!"
name := "Coffee"
found := true
// ── Sugar ──
else if str.contains(currentSym, "SUGAR") or currentRoot == "SB" or str.contains(currentSym, "SB1!")
front := "ICEUS:SB1!"
next := "ICEUS:SB2!"
name := "Sugar"
found := true
// ── Cotton ──
else if str.contains(currentSym, "COTTON") or currentRoot == "CT" or str.contains(currentSym, "CT1!")
front := "ICEUS:CT1!"
next := "ICEUS:CT2!"
name := "Cotton"
found := true
// ── Cocoa ──
else if str.contains(currentSym, "COCOA") or currentRoot == "CC" or str.contains(currentSym, "CC1!")
front := "ICEUS:CC1!"
next := "ICEUS:CC2!"
name := "Cocoa"
found := true
// ── Platinum ──
else if str.contains(currentSym, "PLATINUM") or currentRoot == "PL" or str.contains(currentSym, "PL1!")
front := "NYMEX:PL1!"
next := "NYMEX:PL2!"
name := "Platinum"
found := true
// ── Palladium ──
else if str.contains(currentSym, "PALLADIUM") or currentRoot == "PA" or str.contains(currentSym, "PA1!")
front := "NYMEX:PA1!"
next := "NYMEX:PA2!"
name := "Palladium"
found := true
[front, next, name, found]
// Resolve mapping
[autoFront, autoNext, commodityName, isAutoMapped] = f_mapSymbol()
bool isManual = spreadMode == "Manual"
string frontSymbol = isManual ? manualFront : autoFront
string nextSymbol = isManual ? manualNext : autoNext
string commodity = isManual ? "Manual" : commodityName
bool isSupported = isManual or isAutoMapped
// ============================================================================
// DATA FETCH
// ============================================================================
float frontClose = isSupported and useSpread ? request.security(frontSymbol, timeframe.period, close, barmerge.gaps_off, barmerge.lookahead_off) : na
float nextClose = isSupported and useSpread ? request.security(nextSymbol, timeframe.period, close, barmerge.gaps_off, barmerge.lookahead_off) : na
bool hasData = not na(frontClose) and not na(nextClose)
// ============================================================================
// SPREAD CALCULATION
// ============================================================================
float spreadValue = na
if hasData
if spreadCalc == "Diff"
spreadValue := frontClose - nextClose
else
spreadValue := nextClose != 0 ? frontClose / nextClose : na
// ============================================================================
// SPREAD ANALYSIS
// ============================================================================
float spreadEmaF = ta.ema(spreadValue, spreadEmaFast)
float spreadEmaS = ta.ema(spreadValue, spreadEmaSlow)
float spreadSlope = not na(spreadEmaF) ? spreadEmaF - nz(spreadEmaF[1]) : 0.0
float spreadROC = not na(spreadValue) and nz(spreadValue[divLookback]) != 0 ? (spreadValue - spreadValue[divLookback]) / math.abs(spreadValue[divLookback]) * 100 : 0.0
// ── Spread Trend ──
string spreadTrend = "flat"
if not na(spreadEmaF) and not na(spreadEmaS)
if spreadEmaF > spreadEmaS and spreadSlope > 0
spreadTrend := "rising"
else if spreadEmaF < spreadEmaS and spreadSlope < 0
spreadTrend := "falling"
// ── Spread Momentum ──
string spreadMomentum = "steady"
float absROC = math.abs(spreadROC)
if absROC > 10
spreadMomentum := spreadROC > 0 ? "accelerating_up" : "accelerating_down"
else if absROC > 3
spreadMomentum := spreadROC > 0 ? "rising" : "falling"
// ── Contango / Backwardation ──
string termStructure = "unknown"
if hasData and spreadCalc == "Diff"
if spreadValue > 0
termStructure := "backwardation"
else if spreadValue < 0
termStructure := "contango"
else
termStructure := "flat"
else if hasData and spreadCalc == "Ratio"
if spreadValue > 1.001
termStructure := "backwardation"
else if spreadValue < 0.999
termStructure := "contango"
else
termStructure := "flat"
// ── Price-Spread Divergence ──
float priceChange = close - close[divLookback]
float spreadChange = not na(spreadValue) ? spreadValue - nz(spreadValue[divLookback]) : 0.0
string spreadDivergence = "none"
if hasData
if priceChange < 0 and spreadChange > 0
spreadDivergence := "bullish"
else if priceChange > 0 and spreadChange < 0
spreadDivergence := "bearish"
// ── Spread Bias ──
string spreadBias = "neutral"
if hasData
int bullPoints = 0
int bearPoints = 0
// Term structure
if termStructure == "backwardation"
bullPoints += 1
else if termStructure == "contango"
bearPoints += 1
// Trend
if spreadTrend == "rising"
bullPoints += 1
else if spreadTrend == "falling"
bearPoints += 1
// Divergence
if spreadDivergence == "bullish"
bullPoints += 1
else if spreadDivergence == "bearish"
bearPoints += 1
if bullPoints >= 2
spreadBias := "bullish"
else if bearPoints >= 2
spreadBias := "bearish"
// ── Spread Strength ──
string spreadStrength = "weak"
if hasData
int factors = 0
if termStructure == "backwardation" or termStructure == "contango"
factors += 1
if spreadTrend != "flat"
factors += 1
if spreadDivergence != "none"
factors += 1
if str.contains(spreadMomentum, "accelerating")
factors += 1
spreadStrength := factors >= 3 ? "strong" : factors >= 2 ? "moderate" : "weak"
// ── Spread Modifier ──
float weightMult = spreadWeight == "Low" ? 0.5 : spreadWeight == "High" ? 1.5 : 1.0
float spreadModifier = 0.0
if hasData and useSpread
if spreadBias == "bullish"
spreadModifier := (spreadStrength == "strong" ? 1.5 : spreadStrength == "moderate" ? 1.0 : 0.5) * weightMult
else if spreadBias == "bearish"
spreadModifier := -(spreadStrength == "strong" ? 1.5 : spreadStrength == "moderate" ? 1.0 : 0.5) * weightMult
// ============================================================================
// VISUALIZATION — Status Table
// ============================================================================
bool isDebug = displayMode == "Debug"
var table spreadTbl = na
if showTable and barstate.islast
int rows = isDebug ? 7 : 4
if na(spreadTbl)
spreadTbl := table.new(position.bottom_left, 2, rows, bgcolor=color.new(color.white, 100), border_color=color.new(color.gray, 80), border_width=0, frame_color=color.new(color.gray, 80), frame_width=0)
// Colors
color biasCol = spreadBias == "bullish" ? #00e676 : spreadBias == "bearish" ? #ff1744 : color.new(chart.fg_color, 40)
color divCol = spreadDivergence == "bullish" ? #00e676 : spreadDivergence == "bearish" ? #ff1744 : color.new(chart.fg_color, 60)
color trendCol = spreadTrend == "rising" ? #00e676 : spreadTrend == "falling" ? #ff1744 : color.new(chart.fg_color, 40)
// Row 0: Header
string headerText = isSupported ? ("Vein Spread: " + commodity) : "Vein Spread: unsupported"
color headerCol = isSupported and hasData ? chart.fg_color : color.new(chart.fg_color, 60)
table.cell(spreadTbl, 0, 0, headerText, text_color=headerCol, text_size=size.normal, tooltip="Spread Context Module\n────────────────\nCommodity: " + commodity + "\nFront: " + frontSymbol + "\nNext: " + nextSymbol + "\nMapping: " + (isManual ? "Manual" : "Auto") + "\nData: " + (hasData ? "OK" : "missing") + "\n\nProvides futures term structure context.\nNot a signal — a modifier for other Vein modules.")
string biasText = not isSupported ? "—" : not hasData ? "no data" : spreadBias
table.cell(spreadTbl, 1, 0, str.upper(biasText), text_color=biasCol, text_size=size.normal, text_halign=text.align_right, tooltip="Spread Bias\n────────────────\nCombines: term structure + trend + divergence\n\nBullish: ≥2 bull factors (backwardation, rising, bull divergence)\nBearish: ≥2 bear factors (contango, falling, bear divergence)\nNeutral: mixed or insufficient")
if isSupported and hasData
// Row 1: Term structure + Trend
color termCol = termStructure == "backwardation" ? #00e676 : termStructure == "contango" ? #ff1744 : color.new(chart.fg_color, 40)
table.cell(spreadTbl, 0, 1, termStructure, text_color=termCol, text_size=size.normal, tooltip="Term Structure\n────────────────\nBackwardation: front > next → demand exceeds supply → bullish\nContango: front < next → supply exceeds demand → bearish\nFlat: near-equal")
table.cell(spreadTbl, 1, 1, spreadTrend + " " + (spreadModifier > 0 ? "+" : "") + str.tostring(spreadModifier, "#.1"), text_color=trendCol, text_size=size.normal, text_halign=text.align_right, tooltip="Spread Trend + Modifier\n────────────────\nTrend: " + spreadTrend + "\nMomentum: " + spreadMomentum + "\nModifier: " + str.tostring(spreadModifier, "#.1") + "\n\nModifier is applied to other Vein modules:\n+1.5 = strong bullish context\n-1.5 = strong bearish context\n0 = neutral")
// Row 2: Divergence + Strength
table.cell(spreadTbl, 0, 2, spreadDivergence != "none" ? "DIV " + spreadDivergence : "no divergence", text_color=divCol, text_size=size.normal, tooltip="Price-Spread Divergence\n────────────────\nCompares price direction vs spread direction over " + str.tostring(divLookback) + " bars.\n\nBullish: price falls but spread rises → hidden demand\nBearish: price rises but spread falls → hidden weakness\nNone: aligned")
table.cell(spreadTbl, 1, 2, spreadStrength, text_color=biasCol, text_size=size.normal, text_halign=text.align_right, tooltip="Signal Strength\n────────────────\nStrong: 3-4 aligned factors\nModerate: 2 factors\nWeak: 0-1 factors")
// Row 3: Spread value
string spreadStr = spreadCalc == "Diff" ? str.tostring(spreadValue, format.mintick) : str.tostring(spreadValue, "#.4")
table.cell(spreadTbl, 0, 3, "Spread: " + spreadStr, text_color=color.new(chart.fg_color, 30), text_size=size.normal, tooltip="Raw Spread Value\n────────────────\nFront (" + frontSymbol + "): " + str.tostring(frontClose, format.mintick) + "\nNext (" + nextSymbol + "): " + str.tostring(nextClose, format.mintick) + "\nSpread: " + spreadStr + "\nMode: " + spreadCalc)
table.cell(spreadTbl, 1, 3, "", text_size=size.normal)
// Debug rows
if isDebug
table.cell(spreadTbl, 0, 4, "EMA " + str.tostring(spreadEmaF, format.mintick) + " / " + str.tostring(spreadEmaS, format.mintick), text_color=color.new(chart.fg_color, 50), text_size=size.normal)
table.cell(spreadTbl, 1, 4, "Slope " + str.tostring(spreadSlope, format.mintick), text_color=color.new(chart.fg_color, 50), text_size=size.normal, text_halign=text.align_right)
table.cell(spreadTbl, 0, 5, "ROC " + str.tostring(spreadROC, "#.1") + "%", text_color=color.new(chart.fg_color, 50), text_size=size.normal)
table.cell(spreadTbl, 1, 5, "Mom: " + spreadMomentum, text_color=color.new(chart.fg_color, 50), text_size=size.normal, text_halign=text.align_right)
table.cell(spreadTbl, 0, 6, "Weight: " + spreadWeight, text_color=color.new(chart.fg_color, 50), text_size=size.normal)
table.cell(spreadTbl, 1, 6, currentSym, text_color=color.new(chart.fg_color, 50), text_size=size.normal, text_halign=text.align_right)
else
// No data — show status
table.cell(spreadTbl, 0, 1, not isSupported ? "Symbol not mapped" : "Waiting for data", text_color=color.new(chart.fg_color, 50), text_size=size.normal)
table.cell(spreadTbl, 1, 1, "", text_size=size.normal)
table.cell(spreadTbl, 0, 2, not isSupported ? "Use Manual mode or switch to supported symbol" : "", text_color=color.new(chart.fg_color, 60), text_size=size.normal)
table.cell(spreadTbl, 1, 2, "", text_size=size.normal)
table.cell(spreadTbl, 0, 3, "", text_size=size.normal)
table.cell(spreadTbl, 1, 3, "", text_size=size.normal)
// ============================================================================
// DATA WINDOW
// ============================================================================
plot(spreadValue, "Spread Value [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(spreadEmaF, "Spread EMA Fast [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(spreadEmaS, "Spread EMA Slow [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(spreadModifier, "Spread Modifier [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(spreadDivergence == "bullish" ? 1 : spreadDivergence == "bearish" ? -1 : 0, "Spread Div [dw]", display=display.data_window, color=color.new(color.gray, 100))
// ============================================================================
// ALERTS
// ============================================================================
bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed
alertcondition(alertOk and (spreadBias == "bullish" and spreadBias[1] != "bullish"), "Spread Turns Bullish", "VEIN-SPC · SPREAD BULLISH · {{ticker}} {{interval}}")
alertcondition(alertOk and (spreadBias == "bearish" and spreadBias[1] != "bearish"), "Spread Turns Bearish", "VEIN-SPC · SPREAD BEARISH · {{ticker}} {{interval}}")
alertcondition(alertOk and (spreadDivergence == "bullish" and spreadDivergence[1] != "bullish"), "Spread Bull Divergence", "VEIN-SPC · SPREAD BULL DIVERGENCE · {{ticker}} {{interval}}")
alertcondition(alertOk and (spreadDivergence == "bearish" and spreadDivergence[1] != "bearish"), "Spread Bear Divergence", "VEIN-SPC · SPREAD BEAR DIVERGENCE · {{ticker}} {{interval}}")
Overview
Vein: Spread Context [WavesUnchained] maps the chart symbol to front and next month contracts automatically, or uses manual symbols, then calculates the spread as a difference or ratio. Fast and slow EMAs, a weight setting, and a lookback describe trend, momentum, and divergence as modifiers for other Vein modules. It is not a signal generator and has no entry or exit rules. A table and light or debug display modes show the context.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Enable Spread Context
bool
true
useSpread
Symbol Mapping
string
Auto
spreadMode
Manual Front Month
symbol
NYMEX:NG1!
manualFront
Manual Next Month
symbol
NYMEX:NG2!
manualNext
Spread Calculation
string
Diff
spreadCalc
Spread Weight
string
Normal
spreadWeight
Spread EMA Fast
int
5
spreadEmaFast
Spread EMA Slow
int
20
spreadEmaSlow
Divergence Lookback
int
10
divLookback
Show Spread Table
bool
true
showTable
Display Mode
string
Light
displayMode
Alerts only on bar close (confirmed)
bool
true
alertsOnBarClose
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 indicator screens multiple symbols for regular RSI divergence and extreme RSI readings. It compares pivot lows and highs in price with a 14-period RSI, then alerts when price makes a lower low against a higher RSI low, or a higher high against a lower RSI
TradersPost Trading Checklist is an overlay indicator that evaluates a suite of popular technical conditions for long and short trade setups. It checks session-anchored VWAP, fast and slow EMA alignments and crosses, 200 SMA, RSI overbought or oversold conditi
This indicator analyzes historical candle direction and price range over a configurable lookback window to gauge market trend. It categorizes trend direction using candle count, price range sum, or both. Signals are filtered using a Simple Moving Average to en
Pocket Scout - SPY/QQQ is a mobile-optimized intraday bias indicator designed for major US equity ETFs and indices. It overlays a 9/21/50 EMA ribbon, session VWAP, prior day high/low/close levels, and intraday high/low lines directly on the main price chart. T
This indicator implements a full Chanlun pipeline on the price chart. It merges contained bars, marks top and bottom fractals, and connects alternating fractals into strokes. Overlapping strokes form pivot boxes, and MACD area ratios flag trend or consolidatio
The script is a Pine Script v5 indicator that emulates strategy backtesting directly on the price chart. It evaluates modular entry strategies, trade directions, entry stop-loss types such as ATR multiples or fixed percentages, and a wide variety of trade filt