Indicator · Pine Script v6 · Own pane

Meridian - Futures Context

by sukesan7

Meridian - Futures Context is an intraday analysis indicator designed for US index futures.

View on GitHub

GitHub stars4on the repository
Pine versionv6current
Lines of code651
Settings40inputs
LicenceMPL-2.0
Last update7 weeks ago8 Sep 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

651 lines of Pine Script v6 from src/futures/meridian-futures-context.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

meridian-futures-context.pine
//@version=6
// SPDX-License-Identifier: MPL-2.0
// Meridian — Futures Context · v0.2.0

indicator(
     title = "Meridian - Futures Context",
     shorttitle = "Meridian Futures Context",
     overlay = false,
     format = format.price,
     precision = 2,
     max_bars_back = 5000,
     explicit_plot_zorder = true
 )

const string G_GENERAL = "01 — General"
const string G_SESSION = "02 — Session + Baselines"
const string G_RVOL    = "03 — Participation / RVOL"
const string G_VOL     = "04 — Realized Volatility"
const string G_VWAP    = "05 — VWAP Context"
const string G_EMA     = "06 — EMA Structure"
const string G_TREND   = "07 — Trend Quality"
const string G_RS      = "08 — Intermarket"
const string G_REGIME  = "09 — Regime Engine"
const string G_PLOTS   = "10 — Supporting Plots"
const string G_THEME   = "11 — Meridian Theme"
const string G_DASH    = "12 — Dashboard + Alerts"

bool indicatorEnabled = input.bool(true, "Enable indicator", group = G_GENERAL)
string sessionTimezone = input.string("America/New_York", "Analysis time zone", group = G_GENERAL,
     tooltip = "Use an IANA time zone. America/New_York is recommended for CME index futures.")
bool classifyOutsideRTH = input.bool(false, "Classify outside RTH", group = G_GENERAL,
     tooltip = "When disabled, the dashboard reports Outside RTH while participation metrics pause. Technical metrics continue calculating.")

string rthSession = input.session("0930-1600:23456", "RTH session", group = G_SESSION)
int rthOpenHour = input.int(9, "RTH open hour", minval = 0, maxval = 23, inline = "rthopen", group = G_SESSION)
int rthOpenMinute = input.int(30, "Minute", minval = 0, maxval = 59, inline = "rthopen", group = G_SESSION)
int rthCloseHour = input.int(16, "RTH close hour", minval = 0, maxval = 23, inline = "rthclose", group = G_SESSION)
int rthCloseMinute = input.int(0, "Minute", minval = 0, maxval = 59, inline = "rthclose", group = G_SESSION)
bool excludeIncompleteSessions = input.bool(true, "Exclude incomplete RTH sessions from baselines", group = G_SESSION,
     tooltip = "Recommended. Sessions that end materially before the configured RTH close, such as many exchange holiday sessions or truncated data days, are not committed to same-time volume/range baselines.")
int baselineSessions = input.int(40, "Historical sessions", minval = 5, maxval = 60, group = G_SESSION,
     tooltip = "Number of prior completed sessions retained for each same-time chart slot.")
int minimumBaselineSamples = input.int(10, "Minimum samples before display", minval = 3, maxval = 40, group = G_SESSION)
int maximumSupportedMinutes = input.int(30, "Maximum supported chart timeframe", minval = 1, maxval = 60, group = G_SESSION,
     tooltip = "Same-time RVOL is intended for minute-based intraday charts. 1m, 5m, and 15m are recommended.")

float elevatedRvolThreshold = input.float(1.25, "Elevated bar RVOL", minval = 1.0, maxval = 5.0, step = 0.05, group = G_RVOL)
float extremeRvolThreshold = input.float(1.75, "Extreme bar RVOL", minval = 1.0, maxval = 10.0, step = 0.05, group = G_RVOL)
float elevatedCumulativeRvol = input.float(1.15, "Elevated cumulative RVOL", minval = 1.0, maxval = 5.0, step = 0.05, group = G_RVOL)
float quietRvolThreshold = input.float(0.75, "Quiet RVOL", minval = 0.1, maxval = 1.0, step = 0.05, group = G_RVOL)

int realizedVolLength = input.int(30, "Rolling return window", minval = 5, maxval = 300, group = G_VOL)
int realizedVolBaselineLength = input.int(150, "Volatility baseline length", minval = 20, maxval = 1000, group = G_VOL)
int realizedVolZLength = input.int(150, "Volatility z-score length", minval = 20, maxval = 1000, group = G_VOL)
float volatilityExpansionRatio = input.float(1.20, "Expansion ratio", minval = 1.01, maxval = 5.0, step = 0.05, group = G_VOL)
float volatilityContractionRatio = input.float(0.80, "Contraction ratio", minval = 0.1, maxval = 0.99, step = 0.05, group = G_VOL)

float vwapSource = input.source(hlc3, "VWAP source", group = G_VWAP)
int vwapSlopeLookback = input.int(5, "VWAP slope lookback", minval = 1, maxval = 100, group = G_VWAP)
int vwapCrossLookback = input.int(20, "VWAP crossing lookback", minval = 5, maxval = 200, group = G_VWAP)
float strongVwapSlopeAtr = input.float(0.12, "Strong VWAP slope (ATR)", minval = 0.01, maxval = 2.0, step = 0.01, group = G_VWAP)
float vwapRotationZ = input.float(0.55, "Rotation distance (σ)", minval = 0.1, maxval = 3.0, step = 0.05, group = G_VWAP)
int rotationCrossTarget = input.int(3, "Crosses indicating rotation", minval = 1, maxval = 20, group = G_VWAP)

float emaSource = input.source(close, "EMA source", group = G_EMA)
int emaFastLength = input.int(9, "Fast EMA", minval = 1, maxval = 200, inline = "emas", group = G_EMA)
int emaMidLength = input.int(20, "Mid EMA", minval = 2, maxval = 300, inline = "emas", group = G_EMA)
int emaSlowLength = input.int(50, "Slow EMA", minval = 3, maxval = 500, inline = "emas", group = G_EMA)
int emaSlopeLookback = input.int(5, "Slope lookback", minval = 1, maxval = 100, group = G_EMA)
float emaCompressionAtr = input.float(0.18, "Compression spread (ATR)", minval = 0.01, maxval = 3.0, step = 0.01, group = G_EMA)
float emaExpansionAtr = input.float(0.65, "Expansion spread (ATR)", minval = 0.05, maxval = 5.0, step = 0.05, group = G_EMA)

int atrLength = input.int(14, "ATR length", minval = 2, maxval = 200, group = G_TREND)
int diLength = input.int(14, "DI length", minval = 2, maxval = 100, inline = "dmi", group = G_TREND)
int adxSmoothing = input.int(14, "ADX smoothing", minval = 2, maxval = 100, inline = "dmi", group = G_TREND)
float weakAdx = input.float(17.0, "Weak ADX", minval = 1.0, maxval = 50.0, step = 0.5, inline = "adx", group = G_TREND)
float strongAdx = input.float(28.0, "Strong ADX", minval = 5.0, maxval = 80.0, step = 0.5, inline = "adx", group = G_TREND)
int efficiencyLength = input.int(20, "Directional efficiency length", minval = 2, maxval = 300, group = G_TREND)
float weakEfficiency = input.float(0.22, "Weak efficiency", minval = 0.01, maxval = 0.9, step = 0.01, inline = "eff", group = G_TREND)
float strongEfficiency = input.float(0.55, "Strong efficiency", minval = 0.05, maxval = 1.0, step = 0.01, inline = "eff", group = G_TREND)

bool enableRelativeStrength = input.bool(true, "Enable intermarket analysis", group = G_RS)
string relativeAnchorMode = input.string("Automatic", "Peer mode", options = ["Automatic", "Custom"], group = G_RS)
string customRelativeSymbol = input.symbol("CME_MINI:ES1!", "Custom primary peer", group = G_RS)
bool useSecondaryPeers = input.bool(true, "Use YM / RTY secondary peers", group = G_RS)
int relativeHorizon = input.int(5, "Return horizon (bars)", minval = 1, maxval = 100, group = G_RS)
int betaLength = input.int(100, "Rolling beta length", minval = 20, maxval = 1000, group = G_RS)
int relativeZLength = input.int(150, "Residual z-score length", minval = 20, maxval = 1000, group = G_RS)
float relativeDislocationZ = input.float(1.25, "Dislocation threshold (σ)", minval = 0.25, maxval = 5.0, step = 0.05, group = G_RS)
bool includeRelativeInRegime = input.bool(true, "Use intermarket state in regime", group = G_RS)

float directionThreshold = input.float(0.34, "Directional threshold", minval = 0.05, maxval = 0.9, step = 0.01, group = G_REGIME)
float trendThreshold = input.float(0.54, "Trend-quality threshold", minval = 0.05, maxval = 0.95, step = 0.01, group = G_REGIME)
float expansionThreshold = input.float(0.56, "Expansion threshold", minval = 0.05, maxval = 0.95, step = 0.01, group = G_REGIME)
float compressionThreshold = input.float(0.62, "Compression threshold", minval = 0.05, maxval = 0.95, step = 0.01, group = G_REGIME)
float balanceThreshold = input.float(0.58, "Balance threshold", minval = 0.05, maxval = 0.95, step = 0.01, group = G_REGIME)
float rangeExpansionRatio = input.float(1.18, "Range expansion ratio", minval = 1.01, maxval = 5.0, step = 0.05, group = G_REGIME)
float rangeCompressionRatio = input.float(0.78, "Range compression ratio", minval = 0.1, maxval = 0.99, step = 0.05, group = G_REGIME)

bool showBarRvol = input.bool(true, "Show bar RVOL histogram", group = G_PLOTS)
bool showCumulativeRvol = input.bool(true, "Show cumulative RVOL", group = G_PLOTS)
bool showRealizedVolRatio = input.bool(false, "Show realized-volatility ratio", group = G_PLOTS)
bool showRangeRatio = input.bool(false, "Show developing-range ratio", group = G_PLOTS)
float plotCap = input.float(4.0, "Visual plot cap", minval = 1.5, maxval = 20.0, step = 0.5, group = G_PLOTS,
     tooltip = "Caps extreme values visually without changing dashboard calculations or alerts.")
bool showPlotValues = input.bool(false, "Show plot values on scale/status", group = G_PLOTS)

color accentColor = input.color(#8B5CF6, "Brand / header", group = G_THEME)
color secondaryColor = input.color(#C4B5FD, "Secondary", group = G_THEME)
color participationColor = input.color(#2DD4BF, "Participation", group = G_THEME)
color volatilityColor = input.color(#FBBF24, "Volatility / warning", group = G_THEME)
color bullishColor = input.color(#2DD4BF, "Bullish", group = G_THEME)
color bearishColor = input.color(#F87171, "Bearish", group = G_THEME)
color informationColor = input.color(#38BDF8, "Information", group = G_THEME)
color pendingColor = input.color(#A1A1AA, "Pending / developing", group = G_THEME)
color warningColor = input.color(#FBBF24, "Transition / warning", group = G_THEME)
color neutralColor = input.color(#94A3B8, "Neutral", group = G_THEME)
color panelColor = input.color(#090E17, "Dashboard background", group = G_THEME)
color borderColor = input.color(#334155, "Dashboard border", group = G_THEME)
color textColor = input.color(#F8FAFC, "Dashboard text", group = G_THEME)
int cardTransparency = input.int(86, "Card transparency", minval = 70, maxval = 95, group = G_THEME)

bool showDashboard = input.bool(true, "Show context dashboard", group = G_DASH)
string dashboardTextSize = input.string("Small", "Dashboard text size", options = ["Tiny", "Small", "Normal"], group = G_DASH)
bool enableDynamicAlerts = input.bool(false, "Enable dynamic JSON alert() calls", group = G_DASH,
     tooltip = "Create an alert using Any alert() function call. Events are emitted only on confirmed bars.")
float alertRvolThreshold = input.float(2.0, "RVOL alert threshold", minval = 1.0, maxval = 20.0, step = 0.1, group = G_DASH)
float alertVolatilityRatio = input.float(1.40, "Volatility alert threshold", minval = 1.0, maxval = 10.0, step = 0.05, group = G_DASH)
float alertRelativeZ = input.float(1.75, "Relative-strength alert threshold", minval = 0.5, maxval = 10.0, step = 0.05, group = G_DASH)

f_clamp(float value, float lower, float upper) =>
    math.max(lower, math.min(upper, value))

f_safe_ratio(float numerator, float denominator) =>
    not na(numerator) and not na(denominator) and math.abs(denominator) > 1e-12 ? numerator / denominator : na

f_zscore(float source, int length) =>
    float meanValue = ta.sma(source, length)
    float deviation = ta.stdev(source, length)
    not na(deviation) and deviation > 1e-12 ? (source - meanValue) / deviation : na

f_ratio_text(float value) =>
    na(value) ? "—" : str.tostring(value, "#.##") + "×"

f_number_text(float value, string pattern) =>
    na(value) ? "—" : str.tostring(value, pattern)

f_signed_text(float value, string pattern) =>
    na(value) ? "—" : (value > 0.0 ? "+" : "") + str.tostring(value, pattern)

f_text_size(string name) =>
    switch name
        "Tiny"   => size.tiny
        "Normal" => size.normal
        "Large"  => size.large
        => size.small

f_alpha_from_strength(float strength, int lowAlpha, int highAlpha) =>
    int(math.round(f_clamp(lowAlpha - (lowAlpha - highAlpha) * strength, highAlpha, lowAlpha)))

f_histogram_color(float ratio, float quietThreshold, float elevatedThreshold, float extremeThreshold) =>
    if na(ratio)
        color.new(neutralColor, 70)
    else if ratio >= 1.0
        float denom = math.max(extremeThreshold - 1.0, 0.01)
        float strength = f_clamp((ratio - 1.0) / denom, 0.0, 1.0)
        color.new(participationColor, f_alpha_from_strength(strength, 72, 5))
    else
        float denom = math.max(1.0 - quietThreshold, 0.01)
        float strength = f_clamp((1.0 - ratio) / denom, 0.0, 1.0)
        color.new(pendingColor, f_alpha_from_strength(strength, 72, 8))

f_regime_text(int code) =>
    switch code
        2  => "Bull Expansion"
        1  => "Bull Trend"
        -1 => "Bear Trend"
        -2 => "Bear Expansion"
        3  => "Balanced Rotation"
        4  => "Compression"
        5  => "Volatility Expansion"
        9  => "Outside RTH"
        => "Transition"

f_regime_color(int code) =>
    switch code
        2  => bullishColor
        1  => bullishColor
        -1 => bearishColor
        -2 => bearishColor
        3  => accentColor
        4  => neutralColor
        5  => volatilityColor
        9  => neutralColor
        => warningColor

f_peer_metrics(float peerClose) =>
    float chartOneBar = close > 0.0 and close[1] > 0.0 ? math.log(close / close[1]) : na
    float peerOneBar = peerClose > 0.0 and peerClose[1] > 0.0 ? math.log(peerClose / peerClose[1]) : na
    float corr = ta.correlation(chartOneBar, peerOneBar, betaLength)
    float chartDev = ta.stdev(chartOneBar, betaLength)
    float peerDev = ta.stdev(peerOneBar, betaLength)
    float beta = not na(corr) and not na(peerDev) and peerDev > 1e-12 ? corr * chartDev / peerDev : na
    float chartHorizon = close > 0.0 and close[relativeHorizon] > 0.0 ? math.log(close / close[relativeHorizon]) : na
    float peerHorizon = peerClose > 0.0 and peerClose[relativeHorizon] > 0.0 ? math.log(peerClose / peerClose[relativeHorizon]) : na
    float residual = not na(beta) and not na(chartHorizon) and not na(peerHorizon) ? chartHorizon - beta * peerHorizon : na
    float residualZ = f_zscore(residual, relativeZLength)
    [residualZ, corr, beta, peerHorizon]

bool inRTH = not na(time(timeframe.period, rthSession, sessionTimezone))
int localYear = year(time, sessionTimezone)
int localMonth = month(time, sessionTimezone)
int localDay = dayofmonth(time, sessionTimezone)
int rthDateKey = localYear * 10000 + localMonth * 100 + localDay
bool newRTH = inRTH and (not inRTH[1] or rthDateKey != rthDateKey[1])

float timeframeSeconds = timeframe.in_seconds()
int timeframeMinutes = timeframe.isintraday ? int(math.round(timeframeSeconds / 60.0)) : 0
bool supportedTimeframe = timeframe.isintraday and timeframeSeconds >= 60.0 and timeframeMinutes <= maximumSupportedMinutes
int timeframeMilliseconds = supportedTimeframe ? int(timeframeSeconds * 1000.0) : 0
int rthStartTimestamp = timestamp(sessionTimezone, localYear, localMonth, localDay, rthOpenHour, rthOpenMinute)
int currentSlot = inRTH and supportedTimeframe and timeframeMilliseconds > 0 ? int(math.floor((time - rthStartTimestamp) / timeframeMilliseconds)) : -1

const int MAX_RTH_SLOTS = 500
bool validSlot = currentSlot >= 0 and currentSlot < MAX_RTH_SLOTS

var float[] slotVolumeHistory = array.new_float(MAX_RTH_SLOTS * baselineSessions, na)
var float[] slotRangeHistory = array.new_float(MAX_RTH_SLOTS * baselineSessions, na)
var float[] slotVolumeSums = array.new_float(MAX_RTH_SLOTS, 0.0)
var float[] slotRangeSums = array.new_float(MAX_RTH_SLOTS, 0.0)
var int[] slotSampleCounts = array.new_int(MAX_RTH_SLOTS, 0)
var int[] slotWriteIndices = array.new_int(MAX_RTH_SLOTS, 0)
var float[] pendingSessionVolumes = array.new_float(MAX_RTH_SLOTS, na)
var float[] pendingSessionRanges = array.new_float(MAX_RTH_SLOTS, na)
var int pendingLastSlot = -1
var bool havePendingSession = false

int rthOpenMinutes = rthOpenHour * 60 + rthOpenMinute
int rthCloseMinutes = rthCloseHour * 60 + rthCloseMinute
int rthDurationMinutes = rthCloseMinutes > rthOpenMinutes ? rthCloseMinutes - rthOpenMinutes : 24 * 60 - rthOpenMinutes + rthCloseMinutes
int expectedRthSlots = supportedTimeframe and timeframeMinutes > 0 ? int(math.ceil(float(rthDurationMinutes) / timeframeMinutes)) : 0
int expectedLastSlot = math.min(math.max(expectedRthSlots - 1, 0), MAX_RTH_SLOTS - 1)

f_commit_pending_session() =>
    bool completeEnough = pendingLastSlot >= math.max(expectedLastSlot - 1, 0)
    bool commitSession = havePendingSession and (not excludeIncompleteSessions or completeEnough)
    if commitSession
        int upperSlot = math.min(pendingLastSlot, MAX_RTH_SLOTS - 1)
        if upperSlot >= 0
            for slot = 0 to upperSlot
                float pendingVolume = array.get(pendingSessionVolumes, slot)
                float pendingRange = array.get(pendingSessionRanges, slot)
                if not na(pendingVolume) and not na(pendingRange)
                    int writeIndex = array.get(slotWriteIndices, slot)
                    int historyIndex = slot * baselineSessions + writeIndex
                    int existingCount = array.get(slotSampleCounts, slot)
                    float oldVolume = array.get(slotVolumeHistory, historyIndex)
                    float oldRange = array.get(slotRangeHistory, historyIndex)
                    if existingCount < baselineSessions
                        array.set(slotVolumeHistory, historyIndex, pendingVolume)
                        array.set(slotRangeHistory, historyIndex, pendingRange)
                        array.set(slotVolumeSums, slot, array.get(slotVolumeSums, slot) + pendingVolume)
                        array.set(slotRangeSums, slot, array.get(slotRangeSums, slot) + pendingRange)
                        array.set(slotSampleCounts, slot, existingCount + 1)
                    else
                        array.set(slotVolumeHistory, historyIndex, pendingVolume)
                        array.set(slotRangeHistory, historyIndex, pendingRange)
                        array.set(slotVolumeSums, slot, array.get(slotVolumeSums, slot) - nz(oldVolume, 0.0) + pendingVolume)
                        array.set(slotRangeSums, slot, array.get(slotRangeSums, slot) - nz(oldRange, 0.0) + pendingRange)
                    array.set(slotWriteIndices, slot, (writeIndex + 1) % baselineSessions)
    for slot = 0 to MAX_RTH_SLOTS - 1
        array.set(pendingSessionVolumes, slot, na)
        array.set(pendingSessionRanges, slot, na)
    commitSession

if newRTH
    f_commit_pending_session()
    pendingLastSlot := -1
    havePendingSession := false

int slotSamples = validSlot ? array.get(slotSampleCounts, currentSlot) : 0
float slotVolumeAverage = validSlot and slotSamples > 0 ? array.get(slotVolumeSums, currentSlot) / slotSamples : na
float slotRangeAverage = validSlot and slotSamples > 0 ? array.get(slotRangeSums, currentSlot) / slotSamples : na
bool baselineReady = validSlot and slotSamples >= minimumBaselineSamples

var float rthOpen = na
var float rthHigh = na
var float rthLow = na
var float sessionCumulativeVolume = 0.0
var float expectedCumulativeVolume = 0.0

if newRTH
    rthOpen := open
    rthHigh := high
    rthLow := low
    sessionCumulativeVolume := nz(volume, 0.0)
    expectedCumulativeVolume := nz(slotVolumeAverage, 0.0)
else if inRTH
    rthHigh := na(rthHigh) ? high : math.max(rthHigh, high)
    rthLow := na(rthLow) ? low : math.min(rthLow, low)
    sessionCumulativeVolume += nz(volume, 0.0)
    expectedCumulativeVolume += nz(slotVolumeAverage, 0.0)

float developingRange = inRTH and not na(rthHigh) and not na(rthLow) ? rthHigh - rthLow : na
float barRvol = inRTH and baselineReady ? f_safe_ratio(nz(volume, 0.0), slotVolumeAverage) : na
float cumulativeRvol = inRTH and baselineReady and expectedCumulativeVolume > 0.0 ? sessionCumulativeVolume / expectedCumulativeVolume : na
float developingRangeRatio = inRTH and baselineReady ? f_safe_ratio(developingRange, slotRangeAverage) : na

if barstate.isconfirmed and inRTH and validSlot
    array.set(pendingSessionVolumes, currentSlot, nz(volume, 0.0))
    array.set(pendingSessionRanges, currentSlot, nz(developingRange, 0.0))
    pendingLastSlot := math.max(pendingLastSlot, currentSlot)
    havePendingSession := true

var float vwapCumulativeVolume = 0.0
var float vwapCumulativePV = 0.0
var float vwapCumulativeP2V = 0.0

if newRTH
    vwapCumulativeVolume := 0.0
    vwapCumulativePV := 0.0
    vwapCumulativeP2V := 0.0

if inRTH
    float vwapBarVolume = nz(volume, 0.0)
    vwapCumulativeVolume += vwapBarVolume
    vwapCumulativePV += vwapSource * vwapBarVolume
    vwapCumulativeP2V += vwapSource * vwapSource * vwapBarVolume

float rthVwap = vwapCumulativeVolume > 0.0 ? vwapCumulativePV / vwapCumulativeVolume : na
float vwapVariance = vwapCumulativeVolume > 0.0 and not na(rthVwap) ? math.max(vwapCumulativeP2V / vwapCumulativeVolume - rthVwap * rthVwap, 0.0) : na
float vwapDeviation = math.sqrt(vwapVariance)
float vwapZ = inRTH and not na(vwapDeviation) and vwapDeviation > syminfo.mintick ? (close - rthVwap) / vwapDeviation : na

float atr = ta.atr(atrLength)
float emaFast = ta.ema(emaSource, emaFastLength)
float emaMid = ta.ema(emaSource, emaMidLength)
float emaSlow = ta.ema(emaSource, emaSlowLength)
float emaMaximum = math.max(emaFast, math.max(emaMid, emaSlow))
float emaMinimum = math.min(emaFast, math.min(emaMid, emaSlow))
float emaSpreadAtrValue = not na(atr) and atr > 0.0 ? (emaMaximum - emaMinimum) / atr : na
float emaFastSlopeAtr = not na(atr) and atr > 0.0 ? ((emaFast - emaFast[emaSlopeLookback]) / math.max(float(emaSlopeLookback), 1.0)) / atr : na

bool emaBullStack = emaFast > emaMid and emaMid > emaSlow
bool emaBearStack = emaFast < emaMid and emaMid < emaSlow
bool emaCompressed = not na(emaSpreadAtrValue) and emaSpreadAtrValue <= emaCompressionAtr
bool emaExpanded = not na(emaSpreadAtrValue) and emaSpreadAtrValue >= emaExpansionAtr

string emaState = na(emaFast) or na(emaMid) or na(emaSlow) ? "Warming Up" :
     emaCompressed ? "Compressed" :
     emaBullStack and emaExpanded and emaFastSlopeAtr > 0.0 ? "Bull Expansion" :
     emaBearStack and emaExpanded and emaFastSlopeAtr < 0.0 ? "Bear Expansion" :
     emaBullStack and close < emaFast and close >= emaMid ? "Bull Pullback" :
     emaBearStack and close > emaFast and close <= emaMid ? "Bear Pullback" :
     emaBullStack ? "Bull Stack" :
     emaBearStack ? "Bear Stack" : "Tangled"

[plusDI, minusDI, adx] = ta.dmi(diLength, adxSmoothing)
float totalPath = ta.sma(math.abs(ta.change(close)), efficiencyLength) * efficiencyLength
float directionalEfficiency = not na(totalPath) and totalPath > 0.0 ? math.abs(close - close[efficiencyLength]) / totalPath : na

float logReturn = close > 0.0 and close[1] > 0.0 ? math.log(close / close[1]) : 0.0
float realizedVariance = ta.sma(logReturn * logReturn, realizedVolLength) * realizedVolLength
float realizedVolatility = math.sqrt(math.max(nz(realizedVariance, 0.0), 0.0))
float realizedVolBaseline = ta.ema(realizedVolatility, realizedVolBaselineLength)
float realizedVolRatio = f_safe_ratio(realizedVolatility, realizedVolBaseline)
float realizedVolZ = f_zscore(realizedVolatility, realizedVolZLength)

float vwapSlopeAtrValue = inRTH and validSlot and currentSlot >= vwapSlopeLookback and not na(atr) and atr > 0.0 ? ((rthVwap - rthVwap[vwapSlopeLookback]) / math.max(float(vwapSlopeLookback), 1.0)) / atr : na
bool rawVwapCross = ta.cross(close, rthVwap)
float vwapCrossSeries = inRTH and rawVwapCross ? 1.0 : 0.0
float vwapCrossCount = ta.sma(vwapCrossSeries, vwapCrossLookback) * vwapCrossLookback

bool vwapRotating = inRTH and not na(vwapZ) and math.abs(vwapZ) <= vwapRotationZ and vwapCrossCount >= rotationCrossTarget
string vwapState = not inRTH or na(rthVwap) ? "Inactive" :
     vwapRotating ? "Rotating" :
     close > rthVwap and nz(vwapSlopeAtrValue) > 0.0 ? "Above · Rising" :
     close < rthVwap and nz(vwapSlopeAtrValue) < 0.0 ? "Below · Falling" :
     close > rthVwap ? "Above VWAP" : "Below VWAP"

bool chartIsNasdaqFuture = syminfo.root == "NQ" or syminfo.root == "MNQ"
bool chartIsSpFuture = syminfo.root == "ES" or syminfo.root == "MES"
bool chartIsDowFuture = syminfo.root == "YM" or syminfo.root == "MYM"
bool chartIsRtyFuture = syminfo.root == "RTY" or syminfo.root == "M2K"
string primaryAutoPeer = chartIsNasdaqFuture ? "CME_MINI:ES1!" : chartIsSpFuture ? "CME_MINI:NQ1!" : chartIsDowFuture ? "CME_MINI:ES1!" : chartIsRtyFuture ? "CME_MINI:ES1!" : customRelativeSymbol
string secondaryAutoPeer = chartIsNasdaqFuture or chartIsSpFuture ? "CBOT_MINI:YM1!" : "CME_MINI:NQ1!"
string tertiaryAutoPeer = chartIsRtyFuture ? "CBOT_MINI:YM1!" : "CME_MINI:RTY1!"
string primaryPeer = relativeAnchorMode == "Automatic" ? primaryAutoPeer : customRelativeSymbol

float primaryClose = request.security(primaryPeer, timeframe.period, close, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off, ignore_invalid_symbol = true)
float secondaryClose = request.security(secondaryAutoPeer, timeframe.period, close, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off, ignore_invalid_symbol = true)
float tertiaryClose = request.security(tertiaryAutoPeer, timeframe.period, close, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off, ignore_invalid_symbol = true)

[primaryZ, primaryCorrelation, rollingBeta, primaryHorizonReturn] = f_peer_metrics(primaryClose)
[secondaryZRaw, secondaryCorrelation, _, secondaryHorizonReturn] = f_peer_metrics(secondaryClose)
[tertiaryZRaw, tertiaryCorrelation, _, tertiaryHorizonReturn] = f_peer_metrics(tertiaryClose)
float secondaryZ = useSecondaryPeers ? secondaryZRaw : na
float tertiaryZ = useSecondaryPeers ? tertiaryZRaw : na
float chartHorizonReturn = close > 0.0 and close[relativeHorizon] > 0.0 ? math.log(close / close[relativeHorizon]) : na

float intermarketNumerator = 0.0
float intermarketDenominator = 0.0
float correlationNumerator = 0.0
float correlationDenominator = 0.0
if not na(primaryZ)
    float quality = f_clamp(math.abs(nz(primaryCorrelation)), 0.0, 1.0)
    intermarketNumerator += primaryZ * math.max(quality, 0.25)
    intermarketDenominator += math.max(quality, 0.25)
    correlationNumerator += math.abs(nz(primaryCorrelation))
    correlationDenominator += 1.0
if not na(secondaryZ)
    float quality = f_clamp(math.abs(nz(secondaryCorrelation)), 0.0, 1.0)
    intermarketNumerator += secondaryZ * math.max(quality, 0.25)
    intermarketDenominator += math.max(quality, 0.25)
    correlationNumerator += math.abs(nz(secondaryCorrelation))
    correlationDenominator += 1.0
if not na(tertiaryZ)
    float quality = f_clamp(math.abs(nz(tertiaryCorrelation)), 0.0, 1.0)
    intermarketNumerator += tertiaryZ * math.max(quality, 0.25)
    intermarketDenominator += math.max(quality, 0.25)
    correlationNumerator += math.abs(nz(tertiaryCorrelation))
    correlationDenominator += 1.0
float relativeZ = intermarketDenominator > 0.0 ? intermarketNumerator / intermarketDenominator : na
float averagePeerCorrelation = correlationDenominator > 0.0 ? correlationNumerator / correlationDenominator : na
bool relativeReady = enableRelativeStrength and not na(relativeZ)

int positiveMarkets = (not na(chartHorizonReturn) and chartHorizonReturn > 0.0 ? 1 : 0) + (not na(primaryHorizonReturn) and primaryHorizonReturn > 0.0 ? 1 : 0) + (useSecondaryPeers and not na(secondaryHorizonReturn) and secondaryHorizonReturn > 0.0 ? 1 : 0) + (useSecondaryPeers and not na(tertiaryHorizonReturn) and tertiaryHorizonReturn > 0.0 ? 1 : 0)
int negativeMarkets = (not na(chartHorizonReturn) and chartHorizonReturn < 0.0 ? 1 : 0) + (not na(primaryHorizonReturn) and primaryHorizonReturn < 0.0 ? 1 : 0) + (useSecondaryPeers and not na(secondaryHorizonReturn) and secondaryHorizonReturn < 0.0 ? 1 : 0) + (useSecondaryPeers and not na(tertiaryHorizonReturn) and tertiaryHorizonReturn < 0.0 ? 1 : 0)
int availableMarkets = (not na(chartHorizonReturn) ? 1 : 0) + (not na(primaryHorizonReturn) ? 1 : 0) + (useSecondaryPeers and not na(secondaryHorizonReturn) ? 1 : 0) + (useSecondaryPeers and not na(tertiaryHorizonReturn) ? 1 : 0)
bool broadRiskOn = availableMarkets >= 3 and positiveMarkets >= math.ceil(availableMarkets * 0.75)
bool broadRiskOff = availableMarkets >= 3 and negativeMarkets >= math.ceil(availableMarkets * 0.75)
bool meaningfulDivergence = relativeReady and math.abs(relativeZ) >= relativeDislocationZ and nz(averagePeerCorrelation) >= 0.45
string relativeState = not enableRelativeStrength ? "Disabled" : not relativeReady ? "Warming Up" : broadRiskOn ? "Broad Risk-On" : broadRiskOff ? "Broad Risk-Off" : meaningfulDivergence and relativeZ > 0.0 ? "Leading / Diverging" : meaningfulDivergence ? "Lagging / Diverging" : relativeZ >= relativeDislocationZ ? "Leading" : relativeZ <= -relativeDislocationZ ? "Lagging" : "In Line"

float emaDirection = emaBullStack ? 1.0 : emaBearStack ? -1.0 : close >= emaMid ? 0.25 : -0.25
float vwapDirection = inRTH and not na(rthVwap) ?
     (close >= rthVwap ? 0.60 : -0.60) + (nz(vwapSlopeAtrValue) > 0.02 ? 0.40 : nz(vwapSlopeAtrValue) < -0.02 ? -0.40 : 0.0) : 0.0
float diDirection = plusDI > minusDI ? 0.70 : plusDI < minusDI ? -0.70 : 0.0
float efficiencyDirection = not na(directionalEfficiency) ?
     directionalEfficiency * (close >= close[efficiencyLength] ? 0.70 : -0.70) : 0.0
float relativeDirection = includeRelativeInRegime and relativeReady ? f_clamp(relativeZ / 2.5, -0.45, 0.45) : 0.0
float openDirection = inRTH and not na(rthOpen) ? (close >= rthOpen ? 0.35 : -0.35) : 0.0
float directionRaw = emaDirection + vwapDirection + diDirection + efficiencyDirection + relativeDirection + openDirection
float directionMaximum = 4.20
float directionScore = f_clamp(directionRaw / directionMaximum, -1.0, 1.0)

float adxScore = f_clamp((adx - weakAdx) / math.max(strongAdx - weakAdx, 0.1), 0.0, 1.0)
float efficiencyScore = f_clamp((nz(directionalEfficiency) - weakEfficiency) / math.max(strongEfficiency - weakEfficiency, 0.01), 0.0, 1.0)
float emaSeparationScore = f_clamp(nz(emaSpreadAtrValue) / math.max(emaExpansionAtr, 0.01), 0.0, 1.0)
float vwapSlopeScore = f_clamp(math.abs(nz(vwapSlopeAtrValue)) / math.max(strongVwapSlopeAtr, 0.01), 0.0, 1.0)
float trendQualityScore = adxScore * 0.35 + efficiencyScore * 0.30 + emaSeparationScore * 0.20 + vwapSlopeScore * 0.15

float volatilityExpansionScore = f_clamp((nz(realizedVolRatio) - 1.0) / math.max(volatilityExpansionRatio - 1.0, 0.01), 0.0, 1.0)
float rangeExpansionScore = f_clamp((nz(developingRangeRatio) - 1.0) / math.max(rangeExpansionRatio - 1.0, 0.01), 0.0, 1.0)
float cumulativeParticipationScore = f_clamp((nz(cumulativeRvol) - 1.0) / math.max(elevatedCumulativeRvol - 1.0, 0.01), 0.0, 1.0)
float barParticipationScore = f_clamp((nz(barRvol) - 1.0) / math.max(elevatedRvolThreshold - 1.0, 0.01), 0.0, 1.0)

float expansionNumerator = volatilityExpansionScore * 0.35
float expansionDenominator = 0.35
if not na(developingRangeRatio)
    expansionNumerator += rangeExpansionScore * 0.30
    expansionDenominator += 0.30
if not na(cumulativeRvol)
    expansionNumerator += cumulativeParticipationScore * 0.20
    expansionDenominator += 0.20
if not na(barRvol)
    expansionNumerator += barParticipationScore * 0.15
    expansionDenominator += 0.15
float expansionScore = expansionDenominator > 0.0 ? expansionNumerator / expansionDenominator : 0.0

float emaCompressionScore = 1.0 - f_clamp(nz(emaSpreadAtrValue) / math.max(emaCompressionAtr, 0.01), 0.0, 1.0)
float adxCompressionScore = 1.0 - f_clamp(adx / math.max(weakAdx, 0.1), 0.0, 1.0)
float volatilityCompressionScore = 1.0 - f_clamp((nz(realizedVolRatio) - 0.55) / math.max(1.0 - 0.55, 0.01), 0.0, 1.0)
float rangeCompressionScore = not na(developingRangeRatio) ? 1.0 - f_clamp((developingRangeRatio - 0.45) / math.max(rangeCompressionRatio - 0.45, 0.01), 0.0, 1.0) : 0.0
float efficiencyCompressionScore = 1.0 - f_clamp(nz(directionalEfficiency) / math.max(weakEfficiency, 0.01), 0.0, 1.0)
float compressionScore = emaCompressionScore * 0.30 + adxCompressionScore * 0.20 + volatilityCompressionScore * 0.20 + rangeCompressionScore * 0.15 + efficiencyCompressionScore * 0.15

float crossRotationScore = f_clamp(vwapCrossCount / math.max(rotationCrossTarget, 1), 0.0, 1.0)
float vwapNearScore = 1.0 - f_clamp(math.abs(nz(vwapZ)) / 1.50, 0.0, 1.0)
float lowEfficiencyScore = 1.0 - f_clamp(nz(directionalEfficiency) / math.max(strongEfficiency, 0.01), 0.0, 1.0)
float lowDirectionScore = 1.0 - math.abs(directionScore)
float balanceScore = crossRotationScore * 0.30 + vwapNearScore * 0.25 + lowEfficiencyScore * 0.25 + lowDirectionScore * 0.20

bool directionalEnvironment = math.abs(directionScore) >= directionThreshold
bool trendingEnvironment = trendQualityScore >= trendThreshold and directionalEnvironment
bool expandingEnvironment = expansionScore >= expansionThreshold
bool compressingEnvironment = compressionScore >= compressionThreshold
bool balancedEnvironment = balanceScore >= balanceThreshold

int regimeCode = 0
if not inRTH and not classifyOutsideRTH
    regimeCode := 9
else if compressingEnvironment and not expandingEnvironment
    regimeCode := 4
else if expandingEnvironment and directionalEnvironment
    regimeCode := directionScore > 0.0 ? 2 : -2
else if trendingEnvironment
    regimeCode := directionScore > 0.0 ? 1 : -1
else if balancedEnvironment
    regimeCode := 3
else if expandingEnvironment
    regimeCode := 5
else
    regimeCode := 0

float maximumCompetingScore = math.max(trendQualityScore, math.max(expansionScore, math.max(compressionScore, balanceScore)))
float regimeAgreement = switch regimeCode
    2  => (expansionScore + trendQualityScore + math.abs(directionScore)) / 3.0
    -2 => (expansionScore + trendQualityScore + math.abs(directionScore)) / 3.0
    1  => (trendQualityScore + math.abs(directionScore) + (1.0 - balanceScore)) / 3.0
    -1 => (trendQualityScore + math.abs(directionScore) + (1.0 - balanceScore)) / 3.0
    3  => balanceScore
    4  => compressionScore
    5  => expansionScore
    9  => 0.0
    => f_clamp(1.20 - maximumCompetingScore, 0.35, 0.72)
int regimeConfidence = int(math.round(f_clamp(regimeAgreement, 0.0, 1.0) * 100.0))
string regimeText = f_regime_text(regimeCode)
color regimeColor = f_regime_color(regimeCode)

string participationState = not inRTH ? "Inactive" :
     not baselineReady ? "Warming Up" :
     nz(barRvol) >= extremeRvolThreshold ? "Extreme" :
     nz(barRvol) >= elevatedRvolThreshold or nz(cumulativeRvol) >= elevatedCumulativeRvol ? "Elevated" :
     nz(barRvol) <= quietRvolThreshold and nz(cumulativeRvol) <= quietRvolThreshold ? "Quiet" : "Normal"

string volatilityState = na(realizedVolRatio) ? "Warming Up" :
     realizedVolRatio >= volatilityExpansionRatio ? "Expanding" :
     realizedVolRatio <= volatilityContractionRatio ? "Contracting" : "Normal"

string trendStrengthState = na(adx) ? "Warming Up" : adx >= strongAdx ? "Strong" : adx <= weakAdx ? "Weak" : "Moderate"
string rangeState = not inRTH ? "Inactive" :
     not baselineReady ? "Warming Up" :
     nz(developingRangeRatio) >= rangeExpansionRatio ? "Expanded" :
     nz(developingRangeRatio) <= rangeCompressionRatio ? "Compressed" : "Normal"

int localMinutes = hour(time, sessionTimezone) * 60 + minute(time, sessionTimezone)
string sessionPhase = not inRTH ? "Outside RTH" :
     localMinutes < 600 ? "Opening Drive" :
     localMinutes < 660 ? "Price Discovery" :
     localMinutes < 840 ? "Midday" :
     localMinutes < 900 ? "Late Session" : "Power Hour"

string relativeDetail = not relativeReady ? "No baseline" : f_signed_text(relativeZ, "#.##") + "σ · Corr " + f_number_text(averagePeerCorrelation, "#.##")
string participationDetail = "Bar " + f_ratio_text(barRvol) + " · Cum " + f_ratio_text(cumulativeRvol)
string volatilityDetail = "RV " + f_ratio_text(realizedVolRatio) + " · Z " + f_signed_text(realizedVolZ, "#.##")
string trendDetail = "ADX " + f_number_text(adx, "#.0") + " · Eff " + f_number_text(directionalEfficiency, "#.##")
string locationDetail = "VWAP Z " + f_signed_text(vwapZ, "#.##") + " · Range " + f_ratio_text(developingRangeRatio)
string regimeDetail = "Bias " + f_signed_text(directionScore * 100.0, "#.0") + " · Confidence " + str.tostring(regimeConfidence) + "%"

float cappedBarRvol = na(barRvol) ? na : math.min(barRvol, plotCap)
float cappedCumulativeRvol = na(cumulativeRvol) ? na : math.min(cumulativeRvol, plotCap)
float cappedRealizedVolRatio = na(realizedVolRatio) ? na : math.min(realizedVolRatio, plotCap)
float cappedRangeRatio = na(developingRangeRatio) ? na : math.min(developingRangeRatio, plotCap)

color barRvolColor = f_histogram_color(barRvol, quietRvolThreshold, elevatedRvolThreshold, extremeRvolThreshold)
contextPlotDisplay = showPlotValues ? display.all : display.all - display.price_scale - display.status_line
plot(indicatorEnabled and showBarRvol ? cappedBarRvol : na, "Same-time Bar RVOL", color = barRvolColor, linewidth = 1, style = plot.style_columns, histbase = 0.0, display = contextPlotDisplay)
plot(indicatorEnabled and showCumulativeRvol ? cappedCumulativeRvol : na, "Cumulative RVOL", color = participationColor, linewidth = 2, style = plot.style_linebr, display = contextPlotDisplay)
plot(indicatorEnabled and showRealizedVolRatio ? cappedRealizedVolRatio : na, "Realized Volatility Ratio", color = volatilityColor, linewidth = 2, style = plot.style_linebr, display = contextPlotDisplay)
plot(indicatorEnabled and showRangeRatio ? cappedRangeRatio : na, "Developing Range Ratio", color = secondaryColor, linewidth = 1, style = plot.style_linebr, display = contextPlotDisplay)
hline(1.0, "Normal Baseline", color = color.new(neutralColor, 55), linestyle = hline.style_dotted, linewidth = 1)

plot(directionScore, "Regime Direction Score", color = na, display = display.data_window)
plot(trendQualityScore, "Trend Quality Score", color = na, display = display.data_window)
plot(expansionScore, "Expansion Score", color = na, display = display.data_window)
plot(compressionScore, "Compression Score", color = na, display = display.data_window)
plot(balanceScore, "Balance Score", color = na, display = display.data_window)
plot(relativeZ, "Intermarket Residual Z", color = na, display = display.data_window)
plot(primaryZ, "Primary Peer Residual Z", color = na, display = display.data_window)
plot(secondaryZ, "Secondary Peer Residual Z", color = na, display = display.data_window)
plot(tertiaryZ, "Tertiary Peer Residual Z", color = na, display = display.data_window)
plot(averagePeerCorrelation, "Average Peer Correlation", color = na, display = display.data_window)
plot(vwapZ, "VWAP Z", color = na, display = display.data_window)
plot(emaSpreadAtrValue, "EMA Spread / ATR", color = na, display = display.data_window)
plot(directionalEfficiency, "Directional Efficiency", color = na, display = display.data_window)
plot(adx, "ADX", color = na, display = display.data_window)

var table dashboard = table.new(
     position.top_right,
     2,
     8,
     bgcolor = color.new(panelColor, 10),
     frame_color = color.new(borderColor, 35),
     frame_width = 1,
     border_color = color.new(borderColor, 72),
     border_width = 1)

if barstate.isfirst
    table.merge_cells(dashboard, 0, 0, 1, 0)

if barstate.islast
    bool dashboardVisible = indicatorEnabled and showDashboard
    color invisible = color.new(panelColor, 100)
    color rowBackground = dashboardVisible ? color.new(panelColor, 10) : invisible
    color alternateBackground = dashboardVisible ? color.new(panelColor, 4) : invisible
    color visibleText = dashboardVisible ? textColor : color.new(textColor, 100)
    color mutedText = dashboardVisible ? neutralColor : color.new(neutralColor, 100)
    int freshnessThresholdMs = math.max(int(timeframe.in_seconds() * 3000.0), 15 * 60 * 1000)
    bool dataStale = timenow > time_close and timenow - time_close > freshnessThresholdMs
    string dataState = barstate.isrealtime ? "LIVE" : dataStale ? "STALE / CLOSED" : "CONFIRMED"
    color dataColor = dataStale ? warningColor : barstate.isrealtime ? informationColor : neutralColor
    string participationCompact = participationState + (not na(barRvol) ? " · " + f_ratio_text(barRvol) : "")
    string trendCompact = emaState + " · " + trendStrengthState
    string locationCompact = not inRTH ? "Inactive" : vwapState + " · " + rangeState
    string regimeCompact = regimeCode == 9 ? regimeText : regimeText + " · " + str.tostring(regimeConfidence)

    table.cell(dashboard, 0, 0, "Meridian -- Futures Context", bgcolor = dashboardVisible ? color.new(accentColor, 18) : color.new(accentColor, 100), text_color = visibleText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 0, 1, "Regime", bgcolor = rowBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 1, regimeCompact, bgcolor = rowBackground, text_color = dashboardVisible ? regimeColor : color.new(regimeColor, 100), text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 2, "Participation", bgcolor = alternateBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 2, participationCompact, bgcolor = alternateBackground, text_color = dashboardVisible ? participationColor : color.new(participationColor, 100), text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 3, "Trend", bgcolor = rowBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 3, trendCompact, bgcolor = rowBackground, text_color = dashboardVisible ? (str.contains(emaState, "Bull") ? bullishColor : str.contains(emaState, "Bear") ? bearishColor : neutralColor) : invisible, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 4, "Volatility", bgcolor = alternateBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 4, volatilityState, bgcolor = alternateBackground, text_color = dashboardVisible ? (volatilityState == "Expanding" ? warningColor : neutralColor) : invisible, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 5, "Intermarket", bgcolor = rowBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 5, relativeState, bgcolor = rowBackground, text_color = dashboardVisible ? (str.contains(relativeState, "Leading") or relativeState == "Broad Risk-On" ? bullishColor : str.contains(relativeState, "Lagging") or relativeState == "Broad Risk-Off" ? bearishColor : informationColor) : invisible, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 6, "Location", bgcolor = alternateBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 6, locationCompact, bgcolor = alternateBackground, text_color = dashboardVisible ? informationColor : invisible, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 7, "Data", bgcolor = rowBackground, text_color = mutedText, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 7, dataState, bgcolor = rowBackground, text_color = dashboardVisible ? dataColor : invisible, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)

bool regimeChanged = barstate.isconfirmed and inRTH and regimeCode != regimeCode[1]
bool rvolSpike = barstate.isconfirmed and inRTH and not na(barRvol) and barRvol >= alertRvolThreshold and nz(barRvol[1], 0.0) < alertRvolThreshold
bool volatilityExpansionEvent = barstate.isconfirmed and not na(realizedVolRatio) and realizedVolRatio >= alertVolatilityRatio and nz(realizedVolRatio[1], 0.0) < alertVolatilityRatio
bool relativeDislocationEvent = barstate.isconfirmed and relativeReady and math.abs(relativeZ) >= alertRelativeZ and math.abs(nz(relativeZ[1], 0.0)) < alertRelativeZ

string dynamicEvent = regimeChanged ? "regime_change" : rvolSpike ? "rvol_spike" : volatilityExpansionEvent ? "volatility_expansion" : relativeDislocationEvent ? "relative_dislocation" : ""

if enableDynamicAlerts and str.length(dynamicEvent) > 0
    string jsonMessage = "{" +
         "\"source\":\"meridian_futures_context\"," +
         "\"version\":\"0.2.0\"," +
         "\"event\":\"" + dynamicEvent + "\"," +
         "\"symbol\":\"" + syminfo.tickerid + "\"," +
         "\"timeframe\":\"" + timeframe.period + "\"," +
         "\"regime\":\"" + regimeText + "\"," +
         "\"confidence\":" + str.tostring(regimeConfidence) + "," +
         "\"direction_score\":" + str.tostring(directionScore, "#.####") + "," +
         "\"bar_rvol\":" + str.tostring(nz(barRvol), "#.####") + "," +
         "\"cumulative_rvol\":" + str.tostring(nz(cumulativeRvol), "#.####") + "," +
         "\"realized_vol_ratio\":" + str.tostring(nz(realizedVolRatio), "#.####") + "," +
         "\"relative_z\":" + str.tostring(nz(relativeZ), "#.####") + "," +
         "\"bar_time\":" + str.tostring(time) + "}"
    alert(jsonMessage, alert.freq_once_per_bar_close)

alertcondition(regimeChanged, "Meridian — Futures regime changed", "Meridian Futures Context: the confirmed RTH regime changed.")
alertcondition(rvolSpike, "Meridian — Same-time RVOL spike", "Meridian Futures Context: same-time bar RVOL crossed the configured alert threshold.")
alertcondition(volatilityExpansionEvent, "Meridian — Realized volatility expansion", "Meridian Futures Context: rolling realized volatility crossed the configured expansion alert threshold.")
alertcondition(relativeDislocationEvent, "Meridian — Relative-strength dislocation", "Meridian Futures Context: the beta-adjusted relative residual crossed the configured z-score threshold.")

Overview

Meridian - Futures Context measures volume participation using relative volume, calculates rolling realized volatility, and evaluates trend context through VWAP slope and EMA structures. Displayed in a separate pane with an integrated contextual dashboard, it classifies market regimes and trend quality across session timeframes. Custom settings enable adjusting regular trading hours, historical session baselines, volume thresholds, and volatility parameters.

Details

Published by

sukesan7 on GitHub

Type

Indicator, Pine Script v6

Draws

Separate pane below the chart

Category

Volume, Volatility & bands, Trend, Screeners & dashboards

Markets

Indices

Timeframes

M1-M5, M15-H1

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

fair-value-gapfutures-tradingindex-futuresmarket-profilemarket-structureoptions-tradingorder-blockspine-scriptpine-script-v6pinescript

Settings

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

SettingTypeDefaultVariable
Enable indicatorbooltrueindicatorEnabled
Analysis time zonestringAmerica/New_YorksessionTimezone
Classify outside RTHboolfalseclassifyOutsideRTH
RTH sessionsession0930-1600:23456rthSession
RTH open hourint9rthOpenHour
Minuteint30rthOpenMinute
RTH close hourint16rthCloseHour
Minuteint0rthCloseMinute
Exclude incomplete RTH sessions from baselinesbooltrueexcludeIncompleteSessions
Historical sessionsint40baselineSessions
Minimum samples before displayint10minimumBaselineSamples
Maximum supported chart timeframeint30maximumSupportedMinutes
Show all 40 settings
SettingTypeDefaultVariable
Elevated bar RVOLfloat1.25elevatedRvolThreshold
Extreme bar RVOLfloat1.75extremeRvolThreshold
Elevated cumulative RVOLfloat1.15elevatedCumulativeRvol
Quiet RVOLfloat0.75quietRvolThreshold
Rolling return windowint30realizedVolLength
Volatility baseline lengthint150realizedVolBaselineLength
Volatility z-score lengthint150realizedVolZLength
Expansion ratiofloat1.20volatilityExpansionRatio
Contraction ratiofloat0.80volatilityContractionRatio
VWAP sourcesourcehlc3vwapSource
VWAP slope lookbackint5vwapSlopeLookback
VWAP crossing lookbackint20vwapCrossLookback
Strong VWAP slope (ATR)float0.12strongVwapSlopeAtr
Rotation distance (σ)float0.55vwapRotationZ
Crosses indicating rotationint3rotationCrossTarget
EMA sourcesourcecloseemaSource
Fast EMAint9emaFastLength
Mid EMAint20emaMidLength
Slow EMAint50emaSlowLength
Slope lookbackint5emaSlopeLookback
Compression spread (ATR)float0.18emaCompressionAtr
Expansion spread (ATR)float0.65emaExpansionAtr
ATR lengthint14atrLength
DI lengthint14diLength
ADX smoothingint14adxSmoothing
Weak ADXfloat17.0weakAdx
Strong ADXfloat28.0strongAdx
Directional efficiency lengthint20efficiencyLength
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

Stats Table

by degenview
3 stars

Displays a statistical summary table pinned to the top right of the price chart. It calculates key metrics including ATR percentage relative to price, high-low range percentage over a configurable bar count, relative volume compared to its simple moving averag

Indicator v6 Screeners & dashboardsTrend
Updated today · GPL-3.0 GitHub repository of Stats Table
1 stars

This overlay indicator is a companion dashboard for Bursa Malaysia stocks. It builds a table that shows Minervini trend-template conditions, VCP squeeze tightness, volume surge and contraction, price and average-volume liquidity filters, and an MCDX banker or

Indicator v6 TrendVolatility & bands
Updated 4 months ago · None stated GitHub repository of Minervini VCP Dashboard v8 KLSE

MTF Confluence Trend

by aretephi
1 stars

MTF Confluence Trend plots trend signals when conditions align across timeframes. It detects Supertrend direction flips on the chart timeframe and confirms them against a higher timeframe Supertrend. Optional filters require an ADX strength threshold and a vol

Indicator v6 TrendVolatility & bands
Updated 3 months ago · None stated GitHub repository of MTF Confluence Trend

This indicator plots a Supertrend-based trend-following setup in a separate pane and skips conditions it treats as a sideways market. A volatility range is built from an ATR value, with RMA, SMA, EMA, or WMA smoothing, or from a fixed point distance. Optional

Indicator v5 TrendVolatility & bands
Updated 1 year ago · MPL-2.0 GitHub repository of Trend Following Setup - Sideways Market Skipper

This indicator combines Chaikin Money Flow (CMF), a SuperTrend baseline, and an Average True Range (ATR) calculation inside a separate sub-pane. It calculates a standard 20-period CMF plotted as a line and colored histogram columns around a zero baseline, trac

Indicator v6 TrendVolume
Updated 6 months ago · Apache-2.0 GitHub repository of Institutional View: CMF & SuperTrend

This indicator calculates the Relative Strength Index (RSI) alongside a simple moving average to generate directional momentum signals in a separate pane. It supports Break Flip and Break Continuous scanning modes for long and short setups. A wide array of opt

Indicator v5 Oscillators & momentumVolatility & bands
Updated 1 year ago · None stated GitHub repository of Momentum Setup: RSI Direction Confirmation

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