Indicator · Pine Script v6 · On the chart

Futures Context Module [WavesUnchained]

by casoon

This daily-timeframe indicator provides futures market context by evaluating open interest and term structure without altering chart price scales.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code225
Settings7inputs
LicenceMIT
Last updateyesterday27 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

225 lines of Pine Script v6 from indicators/composite/futures_context_module/futures_context_module.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

futures_context_module.pine
//@version=6
// ============================================================================
// Futures Context Module [WavesUnchained]
// Version: 1.0.2
// Author: WavesUnchained
// Build: 2026-10-12 00:00:00
// ============================================================================
// Daily-only, non-price futures context module: Open Interest, ΔOI read
// against the front contract's own price change, and front/next term
// structure (Contango/Backwardation, curve trend) for an explicitly
// configured futures pair. Pure context display — it never compares the
// chart symbol's price against the futures price. The front/next spread is
// two legs of the same futures feed and is therefore internally consistent.
//
// Features:
//   ✓ Explicit Front/Next futures symbol inputs — no auto-mapping guesswork
//   ✓ Daily-only gate — visible notice on intraday charts, no interpretation
//   ✓ Open Interest + ΔOI read against the front contract's own price change
//   ✓ OI × Price quadrant classification (new longs/shorts, covering/liquidation)
//   ✓ Front/Next term structure — Contango/Backwardation/Flat + curve trend
//   ✓ Real-volume validity check on the front contract (delivers volume, not "tick")
//   ✓ Visible "not configured" / "no data feed" states — never a silent blank
//   ✓ Light-theme dashboard table, repo-standard style
//
// Data Contract:
//   Price:     NO
//   Volume:    OPTIONAL   real trade volume of the front contract only — degrades to neutral
//   OI:        OPTIONAL   na when no OI feed exists for the configured symbol — shown, not hidden
//   Reference: NYMEX:NG1! / NYMEX:NG2! (Daily)
//   Verdict:   Reference-required
// ============================================================================

indicator("Futures Context Module [WavesUnchained]", "Futures Context", overlay=true)

// ============================================================================
// INPUTS
// ============================================================================

var gRef = "Futures Reference"
string frontSymbolInput = input.symbol("NYMEX:NG1!", "Front-Month Futures Symbol", group=gRef, tooltip="Explicit futures contract — no auto-mapping from the chart symbol. Default matches the repo reference instrument (NatGas).")
string nextSymbolInput  = input.symbol("NYMEX:NG2!", "Next-Month Futures Symbol", group=gRef, tooltip="Second leg for the term-structure spread. Must be the next contract month of the same futures curve as the Front symbol above.")

var gOi = "Open Interest"
int oiLookback = input.int(1, "ΔOI / Price-Change Lookback (bars)", minval=1, maxval=20, group=gOi, tooltip="Number of Daily bars back for the ΔOI and front-contract price-change comparison.")

var gTerm = "Term Structure"
int termEmaFast = input.int(5, "Curve EMA Fast", minval=2, group=gTerm)
int termEmaSlow = input.int(20, "Curve EMA Slow", minval=5, group=gTerm)

var gDisplay = "Display"
bool   showTable = input.bool(true, "Show Context Table", group=gDisplay)
string tablePos  = input.string("bottom_left", "Table Position", options=["top_left", "top_center", "top_right", "middle_left", "middle_center", "middle_right", "bottom_left", "bottom_center", "bottom_right"], group=gDisplay)

// ============================================================================
// GATES — Daily-only, explicit configuration
// ============================================================================

bool isDailyPlus  = timeframe.isdaily or timeframe.isweekly or timeframe.ismonthly
bool isConfigured = frontSymbolInput != "" and nextSymbolInput != "" and frontSymbolInput != nextSymbolInput

// ============================================================================
// DATA FETCH — request.security() always unconditional, top-level (§4.3 DATA_VALIDITY.md)
// ============================================================================

[frontClose, frontVolume] = request.security(frontSymbolInput, timeframe.period, [close, volume], lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)
string frontVolType       = request.security(frontSymbolInput, timeframe.period, syminfo.volumetype, lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)
float  nextClose          = request.security(nextSymbolInput, timeframe.period, close, lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)
float  frontOI            = request.security(frontSymbolInput + "_OI", timeframe.period, close, lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)

// ============================================================================
// DATA VALIDITY
// ============================================================================

bool hasFrontData  = not na(frontClose)
bool hasNextData   = not na(nextClose)
bool hasSpreadData = hasFrontData and hasNextData
bool hasOiData     = not na(frontOI)
// "n/a" heißt laut Referenz "kein Volumen ODER Art nicht angegeben" — CME/EUREX-
// Kontrakte melden das trotz echter Kontrakte. Nur "tick" schließt aus; sonst
// zählt, ob der Kontrakt überhaupt Volumen liefert.
bool hasRealVolume = ta.cum(nz(frontVolume)) > 0 and frontVolType != "tick"
bool volumeDataOk  = hasRealVolume and not na(frontVolume)

bool noDataFeed = isConfigured and isDailyPlus and barstate.islast and not hasSpreadData

// ============================================================================
// TERM STRUCTURE — Front vs. Next, two legs of the same feed
// ============================================================================

float  spread        = hasSpreadData ? frontClose - nextClose : na
float  spreadEmaFast  = ta.ema(spread, termEmaFast)
float  spreadEmaSlow  = ta.ema(spread, termEmaSlow)
float  spreadSlope    = not na(spreadEmaFast) ? spreadEmaFast - nz(spreadEmaFast[1]) : na

string termStructure = "n/a"
string curveTrend    = "n/a"
if isDailyPlus and hasSpreadData
    termStructure := spread > 0 ? "Backwardation" : spread < 0 ? "Contango" : "Flat"
    if not na(spreadEmaFast) and not na(spreadEmaSlow) and not na(spreadSlope)
        if spreadEmaFast > spreadEmaSlow and spreadSlope > 0
            curveTrend := "rising"
        else if spreadEmaFast < spreadEmaSlow and spreadSlope < 0
            curveTrend := "falling"
        else
            curveTrend := "flat"

// ============================================================================
// OPEN INTEREST — ΔOI vs. the front contract's own price change (single feed)
// ============================================================================

// A missing prior value stays na (no nz() to 0) — otherwise the change would be
// measured against zero and still feed the OI x Price classification.
float frontPriceChange = hasFrontData ? frontClose - frontClose[oiLookback] : na
float oiChange          = hasOiData ? frontOI - frontOI[oiLookback] : na
float oiChangePct       = not na(oiChange) and frontOI[oiLookback] != 0 ? oiChange / frontOI[oiLookback] * 100 : na

string oiPriceState = "n/a"
if isDailyPlus and hasOiData and hasFrontData and not na(oiChange) and not na(frontPriceChange)
    if oiChange > 0 and frontPriceChange > 0
        oiPriceState := "OI rising + Price rising -> new longs"
    else if oiChange > 0 and frontPriceChange < 0
        oiPriceState := "OI rising + Price falling -> new shorts"
    else if oiChange < 0 and frontPriceChange > 0
        oiPriceState := "OI falling + Price rising -> short covering"
    else if oiChange < 0 and frontPriceChange < 0
        oiPriceState := "OI falling + Price falling -> long liquidation"
    else
        oiPriceState := "OI flat"

// ============================================================================
// VOLUME — real-volume validity of the front contract only
// ============================================================================

float  frontVolAvg  = ta.sma(frontVolume, 20)
string volumeState  = "n/a"
if isDailyPlus
    if not volumeDataOk
        volumeState := frontVolType == "tick" ? "not real (tick)" : "no volume"
    else if not na(frontVolAvg)
        volumeState := frontVolume > frontVolAvg ? "above avg" : "below avg"

// ============================================================================
// VISUALIZATION — Dashboard Table (light theme, repo-standard style)
// ============================================================================

color tc = color.new(color.gray, 20)
color hd = color.new(color.gray, 90)
color warnCol = #ff6f00

var table t = na
if showTable and barstate.islast
    if na(t)
        t := table.new(tablePos, 2, 10, bgcolor=color.new(color.white, 5), border_color=color.new(color.gray, 60), border_width=1, frame_color=color.new(color.gray, 40), frame_width=1)

    // Row 0 — Header + module status
    string statusText = not isDailyPlus ? "DAILY ONLY" : not isConfigured ? "NOT CONFIGURED" : noDataFeed ? "NO DATA FEED" : "OK"
    table.cell(t, 0, 0, "Futures Context", text_color=tc, bgcolor=hd, text_size=size.small)
    table.cell(t, 1, 0, statusText, text_color=tc, bgcolor=hd, text_size=size.small, text_halign=text.align_right)

    // Row 1 — configured symbols
    table.cell(t, 0, 1, "Front / Next", text_color=tc, text_size=size.small)
    table.cell(t, 1, 1, frontSymbolInput + " / " + nextSymbolInput, text_color=tc, text_size=size.small, text_halign=text.align_right)

    if not isDailyPlus
        table.cell(t, 0, 2, "Status", text_color=color.white, bgcolor=warnCol, text_size=size.small)
        table.cell(t, 1, 2, "not valid on this timeframe — switch to Daily or higher", text_color=color.white, bgcolor=warnCol, text_size=size.small, text_halign=text.align_right)
        for r = 3 to 9
            table.cell(t, 0, r, "", text_size=size.small)
            table.cell(t, 1, r, "", text_size=size.small)
    else if not isConfigured
        table.cell(t, 0, 2, "Status", text_color=color.white, bgcolor=warnCol, text_size=size.small)
        table.cell(t, 1, 2, "Front/Next symbol not configured", text_color=color.white, bgcolor=warnCol, text_size=size.small, text_halign=text.align_right)
        for r = 3 to 9
            table.cell(t, 0, r, "", text_size=size.small)
            table.cell(t, 1, r, "", text_size=size.small)
    else if noDataFeed
        table.cell(t, 0, 2, "Status", text_color=color.white, bgcolor=warnCol, text_size=size.small)
        table.cell(t, 1, 2, "no data feed for " + frontSymbolInput + " / " + nextSymbolInput, text_color=color.white, bgcolor=warnCol, text_size=size.small, text_halign=text.align_right)
        for r = 3 to 9
            table.cell(t, 0, r, "", text_size=size.small)
            table.cell(t, 1, r, "", text_size=size.small)
    else
        // Row 2 — separator
        table.cell(t, 0, 2, "", bgcolor=color.new(color.gray, 80), text_size=size.small)
        table.cell(t, 1, 2, "", bgcolor=color.new(color.gray, 80), text_size=size.small)

        // Row 3 — Open Interest
        string oiText = hasOiData ? str.tostring(frontOI, "#") : "no OI feed for " + frontSymbolInput
        table.cell(t, 0, 3, "Open Interest", text_color=tc, text_size=size.small)
        table.cell(t, 1, 3, oiText, text_color=hasOiData ? tc : warnCol, text_size=size.small, text_halign=text.align_right)

        // Row 4 — ΔOI
        string deltaOiText = hasOiData and not na(oiChangePct) ? str.tostring(oiChange, "#") + "  (" + str.tostring(oiChangePct, "#.#") + "%)" : "n/a"
        table.cell(t, 0, 4, "ΔOI (" + str.tostring(oiLookback) + "d)", text_color=tc, text_size=size.small)
        table.cell(t, 1, 4, deltaOiText, text_color=tc, text_size=size.small, text_halign=text.align_right)

        // Row 5 — OI x Price
        table.cell(t, 0, 5, "OI x Price", text_color=tc, text_size=size.small)
        table.cell(t, 1, 5, oiPriceState, text_color=tc, text_size=size.small, text_halign=text.align_right)

        // Row 6 — separator
        table.cell(t, 0, 6, "", bgcolor=color.new(color.gray, 80), text_size=size.small)
        table.cell(t, 1, 6, "", bgcolor=color.new(color.gray, 80), text_size=size.small)

        // Row 7 — Term Structure
        string spreadText = not na(spread) ? termStructure + "  (" + str.tostring(spread, format.mintick) + ")" : "n/a"
        table.cell(t, 0, 7, "Term Structure", text_color=tc, text_size=size.small)
        table.cell(t, 1, 7, spreadText, text_color=tc, text_size=size.small, text_halign=text.align_right)

        // Row 8 — Curve Trend
        table.cell(t, 0, 8, "Curve Trend", text_color=tc, text_size=size.small)
        table.cell(t, 1, 8, curveTrend, text_color=tc, text_size=size.small, text_halign=text.align_right)

        // Row 9 — Volume (front contract only)
        table.cell(t, 0, 9, "Volume (Front)", text_color=tc, text_size=size.small)
        table.cell(t, 1, 9, volumeState, text_color=volumeDataOk ? tc : warnCol, text_size=size.small, text_halign=text.align_right)

// ============================================================================
// DATA WINDOW
// ============================================================================

plot(frontOI, "Open Interest (Front) [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(oiChange, "ΔOI [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(spread, "Front-Next Spread [dw]", display=display.data_window, color=color.new(color.gray, 100))
plot(frontVolume, "Volume (Front) [dw]", display=display.data_window, color=color.new(color.gray, 100))

Overview

Futures Context Module [WavesUnchained] requests front-month and next-month futures contracts to measure spread relationships like contango or backwardation and curve momentum via fast and slow EMAs. Additionally, it analyzes open interest changes against front-contract price movements to classify market activity into long buildup, short buildup, long liquidation, or short covering. Results are displayed in an on-chart table, requiring daily or higher chart resolutions for accurate evaluation.

Details

Published by

casoon on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Volume, Screeners & dashboards

Markets

Oil & commodities

Timeframes

H4 and above

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

pine-scriptpinescripttechnical-analysistrading-indicatorstrading-strategiestradingview

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Front-Month Futures SymbolsymbolNYMEX:NG1!frontSymbolInput
Next-Month Futures SymbolsymbolNYMEX:NG2!nextSymbolInput
ΔOI / Price-Change Lookback (bars)int1oiLookback
Curve EMA Fastint5termEmaFast
Curve EMA Slowint20termEmaSlow
Show Context TablebooltrueshowTable
Table Positionstringbottom_lefttablePos
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.

Similar Pine Scripts

This overlay indicator screens a pasted list of up to forty symbols and draws a table on the price chart. It classifies bullish or bearish setups from the percent change between the current candle and the candle 250 bars back, then shows RSI with a simple movi

Indicator v5 Oscillators & momentumVolume
Updated 1 year ago · MPL-2.0 GitHub repository of Momentum Setup - Ankush Bajaj Momentum Investing Setup

Daily Volume

by benso87
24 stars

Daily Volume is an overlay indicator that displays current daily volume statistics inside an on-chart table in the top-right corner. It fetches higher-timeframe daily data to report real-time current daily volume, yesterday's total volume, and a simple moving

Indicator v5 VolumeScreeners & dashboards
Updated 4 years ago · MPL-2.0 GitHub repository of Daily Volume

Equity Intraday Analyst

by chris-c-thomas
14 stars

Equity Intraday Analyst is a comprehensive intraday overlay indicator for US equities and ETFs. It evaluates multi-EMA ribbons, session-anchored VWAP bands, OBV trends, momentum oscillators, TTM Squeeze, and optional market breadth indicators like NYSE TICK an

Indicator v6 TrendVolume
Updated 6 months ago · MIT GitHub repository of Equity Intraday Analyst

Nexural QWAP

by JasonTeixeira
9 stars

Nexural QWAP calculates a volume-weighted average price combined with a cumulative volume delta order flow engine directly on the main price chart. It analyzes market structure by detecting volume absorption, liquidity sweeps, price-CVD divergence, stacked imb

Indicator v6 SMC / ICT / price actionVolume
Updated 12 days ago · Custom licence GitHub repository of Nexural QWAP
9 stars

Peak Trading Activity Graphs displays historical market activity across cyclical timeframes directly on the main chart using an on-screen dashboard table. It evaluates volume or volatility across selected periods, including hours of the day, days of the week,

Indicator v6 VolumeVolatility & bands
Updated 4 months ago · MIT GitHub repository of Peak Trading Activity Graphs [LuxAlgo]

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers