Indicator · Pine Script v6 · Own pane

Meridian - Options Context

by sukesan7

Meridian Options Context plots a separate-pane directional context for liquid options underlyings.

View on GitHub

Repaints

GitHub stars4on the repository
Pine versionv6current
Lines of code665
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

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

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

indicator(
     title = "Meridian - Options Context",
     shorttitle = "Meridian Options Context",
     overlay = false,
     format = format.price,
     precision = 1,
     max_bars_back = 1000
)

import TradingView/ta/12 as tvta

string GROUP_SESSION = "01. Session"
string GROUP_NORMALIZATION = "02. Normalization"
string GROUP_TREND = "03. Trend"
string GROUP_STRUCTURE = "04. Structure"
string GROUP_MOMENTUM = "05. Momentum"
string GROUP_PARTICIPATION = "06. Participation"
string GROUP_PERSISTENCE = "07. Persistence"
string GROUP_VOLATILITY = "08. Volatility"
string GROUP_HTF = "09. Higher Timeframes"
string GROUP_VISUALS = "10. Visuals"
string GROUP_DASHBOARD = "11. Dashboard"
string GROUP_ALERTS = "12. Alerts"
string GROUP_COLORS = "13. Colors"
string GROUP_RESEARCH = "14. Research / Debug"

string sessionTimezone = input.string("America/New_York", "Session timezone", group = GROUP_SESSION)
string rthInput = input.session("0930-1600", "Regular session", group = GROUP_SESSION)
bool weekdaysOnly = input.bool(true, "Monday-Friday only", group = GROUP_SESSION)
int openingRangeMinutes = input.int(15, "Opening-range minutes", minval = 1, maxval = 120, group = GROUP_SESSION)

int atrLength = input.int(14, "ATR length", minval = 5, maxval = 100, group = GROUP_NORMALIZATION)

int fastEmaLength = input.int(9, "Fast EMA", minval = 1, maxval = 100, group = GROUP_TREND)
int slowEmaLength = input.int(21, "Slow EMA", minval = 2, maxval = 200, group = GROUP_TREND)
int trendSlopeLookback = input.int(5, "Slope lookback", minval = 1, maxval = 30, group = GROUP_TREND)
float trendSpreadScale = input.float(0.25, "EMA spread scale (ATR)", minval = 0.05, maxval = 2.0, step = 0.05, group = GROUP_TREND)
float trendSlopeScale = input.float(0.20, "EMA slope scale (ATR)", minval = 0.05, maxval = 2.0, step = 0.05, group = GROUP_TREND)

float vwapSource = input.source(hlc3, "RTH VWAP source", group = GROUP_STRUCTURE)
float vwapDistanceScale = input.float(0.35, "VWAP displacement scale (ATR)", minval = 0.05, maxval = 2.0, step = 0.05, group = GROUP_STRUCTURE)
float openingRangeBreakScale = input.float(0.25, "Opening-range break scale (ATR)", minval = 0.05, maxval = 2.0, step = 0.05, group = GROUP_STRUCTURE)
float sessionOpenScale = input.float(0.50, "RTH-open displacement scale (ATR)", minval = 0.05, maxval = 3.0, step = 0.05, group = GROUP_STRUCTURE)

float rsiSource = input.source(close, "RSI source", group = GROUP_MOMENTUM)
int rsiLength = input.int(14, "RSI length", minval = 2, maxval = 100, group = GROUP_MOMENTUM)
int momentumLookback = input.int(5, "Normalized-return lookback", minval = 1, maxval = 30, group = GROUP_MOMENTUM)
float momentumScale = input.float(1.0, "Normalized-return scale", minval = 0.10, maxval = 5.0, step = 0.10, group = GROUP_MOMENTUM)
int momentumDeltaLookback = input.int(3, "Momentum-change lookback", minval = 1, maxval = 20, group = GROUP_MOMENTUM)
float momentumDeltaScale = input.float(0.50, "Momentum-change scale", minval = 0.10, maxval = 2.0, step = 0.05, group = GROUP_MOMENTUM)

int rvolLookbackDays = input.int(20, "RVOL historical days", minval = 2, maxval = 100, group = GROUP_PARTICIPATION)
bool adjustRealtimeRvol = input.bool(true, "Estimate unclosed real-time bar", group = GROUP_PARTICIPATION)
float participationReturnScale = input.float(0.50, "Directional-return scale (ATR)", minval = 0.10, maxval = 3.0, step = 0.10, group = GROUP_PARTICIPATION)
float rvolLogScale = input.float(0.70, "RVOL intensity scale", minval = 0.10, maxval = 3.0, step = 0.05, group = GROUP_PARTICIPATION)
float highRvolThreshold = input.float(1.5, "High RVOL threshold", minval = 1.0, maxval = 10.0, step = 0.1, group = GROUP_PARTICIPATION)
float extremeRvolThreshold = input.float(2.0, "Extreme RVOL threshold", minval = 1.0, maxval = 20.0, step = 0.1, group = GROUP_PARTICIPATION)
float rvolDisplayCap = input.float(3.0, "RVOL display cap", minval = 1.0, maxval = 20.0, step = 0.25, group = GROUP_PARTICIPATION)

int persistenceLength = input.int(12, "Persistence bars", minval = 3, maxval = 60, group = GROUP_PERSISTENCE)

int volatilityShortLength = input.int(14, "Short ATR length", minval = 5, maxval = 50, group = GROUP_VOLATILITY)
int volatilityLongLength = input.int(50, "Long ATR length", minval = 20, maxval = 200, group = GROUP_VOLATILITY)
int volatilityZLookback = input.int(100, "Volatility history", minval = 30, maxval = 500, group = GROUP_VOLATILITY)
float volatilityZScale = input.float(1.5, "Volatility z-score scale", minval = 0.25, maxval = 5.0, step = 0.25, group = GROUP_VOLATILITY)

bool includeHigherTimeframes = input.bool(true, "Include HTF trend in Context", group = GROUP_HTF)
bool showHigherTimeframeMatrix = input.bool(true, "Show 1H / 4H / 1D / 1W states", group = GROUP_HTF)
int htfFastEmaLength = input.int(20, "HTF fast EMA", minval = 2, maxval = 100, group = GROUP_HTF)
int htfSlowEmaLength = input.int(50, "HTF slow EMA", minval = 5, maxval = 300, group = GROUP_HTF)
int htfAtrLength = input.int(14, "HTF ATR length", minval = 5, maxval = 100, group = GROUP_HTF)
int htfSlopeLookback = input.int(3, "HTF EMA slope bars", minval = 1, maxval = 20, group = GROUP_HTF)
int htfPathLookback = input.int(5, "HTF recent-path bars", minval = 2, maxval = 20, group = GROUP_HTF)
int htfPersistenceLength = input.int(6, "HTF persistence bars", minval = 3, maxval = 20, group = GROUP_HTF)
float htfSpreadScale = input.float(0.30, "HTF EMA spread scale (ATR)", minval = 0.05, maxval = 3.0, step = 0.05, group = GROUP_HTF)
float htfSlopeScale = input.float(0.12, "HTF EMA slope scale (ATR/bar)", minval = 0.02, maxval = 1.0, step = 0.01, group = GROUP_HTF)
float htfReturnScale = input.float(0.85, "HTF path-return scale", minval = 0.10, maxval = 3.0, step = 0.05, group = GROUP_HTF)
float htfDirectionalThreshold = input.float(20.0, "HTF directional threshold", minval = 5.0, maxval = 50.0, step = 5.0, group = GROUP_HTF)

bool showContextLine = input.bool(true, "Show Context Direction", group = GROUP_VISUALS)
bool showConfidenceLine = input.bool(true, "Show Context Confidence", group = GROUP_VISUALS)
bool showRvol = input.bool(true, "Show RVOL columns", group = GROUP_VISUALS)
bool showRsiMomentum = input.bool(false, "Show RSI momentum score", group = GROUP_VISUALS)
bool showHtfComposite = input.bool(false, "Show HTF composite", group = GROUP_VISUALS)
bool showRegimeBackground = input.bool(true, "Subtle regime background", group = GROUP_VISUALS)
int regimeBackgroundTransparency = input.int(96, "Regime background transparency", minval = 85, maxval = 100, group = GROUP_VISUALS)

bool showDashboard = input.bool(true, "Show dashboard", group = GROUP_DASHBOARD)
string dashboardMode = input.string("Detailed", "Dashboard mode", options = ["Detailed", "Compact"], group = GROUP_DASHBOARD)
string dashboardSizeInput = input.string("Small", "Dashboard text size", options = ["Tiny", "Small", "Normal"], group = GROUP_DASHBOARD)

bool enableAlerts = input.bool(true, "Enable alert conditions", group = GROUP_ALERTS)
float highConfidenceDirectionThreshold = input.float(55.0, "High-confidence direction threshold", minval = 20.0, maxval = 100.0, step = 5.0, group = GROUP_ALERTS)
float highConfidenceThreshold = input.float(60.0, "High-confidence confidence threshold", minval = 20.0, maxval = 100.0, step = 5.0, group = GROUP_ALERTS)
int contextFailureWindow = input.int(12, "Strong-regime failure window (bars)", minval = 2, maxval = 50, group = GROUP_ALERTS)

color brandColor = input.color(#8B5CF6, "Brand / header", group = GROUP_COLORS)
color bullishColor = input.color(#2DD4BF, "Bullish / support", group = GROUP_COLORS)
color bearishColor = input.color(#F87171, "Bearish / resistance", group = GROUP_COLORS)
color informationColor = input.color(#38BDF8, "Information", group = GROUP_COLORS)
color neutralColor = input.color(#94A3B8, "Neutral", group = GROUP_COLORS)
color pendingColor = input.color(#A1A1AA, "Pending / developing", group = GROUP_COLORS)
color warningColor = input.color(#FBBF24, "Warning", group = GROUP_COLORS)
color secondaryColor = input.color(#C4B5FD, "Secondary / confidence", group = GROUP_COLORS)
color panelColor = input.color(#090E17, "Dashboard background", group = GROUP_COLORS)
color borderColor = input.color(#334155, "Dashboard border", group = GROUP_COLORS)
color textColor = input.color(#F8FAFC, "Dashboard text", group = GROUP_COLORS)

bool showResearchValues = input.bool(false, "Show research values in Data Window", group = GROUP_RESEARCH)
bool showLegacyComparison = input.bool(false, "Show v0.1 binary-score comparison", group = GROUP_RESEARCH)

int REGIME_STRONG_BEAR = -3
int REGIME_BEAR = -2
int REGIME_BEAR_LEAN = -1
int REGIME_BALANCED = 0
int REGIME_BULL_LEAN = 1
int REGIME_BULL = 2
int REGIME_STRONG_BULL = 3

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

f_sat(float value) =>
    value / (1.0 + math.abs(value))

f_bound(float value) =>
    100.0 * f_sat(value)

f_sign(float value) =>
    value > 0.0 ? 1.0 : value < 0.0 ? -1.0 : 0.0

f_weighted2(float v1, float w1, float v2, float w2) =>
    float numerator = 0.0
    float denominator = 0.0
    if not na(v1)
        numerator := numerator + v1 * w1
        denominator := denominator + w1
    if not na(v2)
        numerator := numerator + v2 * w2
        denominator := denominator + w2
    denominator > 0.0 ? numerator / denominator : na

f_weighted3(float v1, float w1, float v2, float w2, float v3, float w3) =>
    float numerator = 0.0
    float denominator = 0.0
    if not na(v1)
        numerator := numerator + v1 * w1
        denominator := denominator + w1
    if not na(v2)
        numerator := numerator + v2 * w2
        denominator := denominator + w2
    if not na(v3)
        numerator := numerator + v3 * w3
        denominator := denominator + w3
    denominator > 0.0 ? numerator / denominator : na

f_weighted4(float v1, float w1, float v2, float w2, float v3, float w3, float v4, float w4) =>
    float numerator = 0.0
    float denominator = 0.0
    if not na(v1)
        numerator := numerator + v1 * w1
        denominator := denominator + w1
    if not na(v2)
        numerator := numerator + v2 * w2
        denominator := denominator + w2
    if not na(v3)
        numerator := numerator + v3 * w3
        denominator := denominator + w3
    if not na(v4)
        numerator := numerator + v4 * w4
        denominator := denominator + w4
    denominator > 0.0 ? numerator / denominator : na

f_textSize(string sizeText) =>
    sizeText == "Normal" ? size.normal : sizeText == "Small" ? size.small : size.tiny

f_scoreText(float value) =>
    na(value) ? "n/a" : value > 0.0 ? "+" + str.tostring(math.round(value)) : str.tostring(math.round(value))

f_ratioText(float value) =>
    na(value) ? "n/a" : str.tostring(value, "#.00") + "x"

f_confidenceText(float value) =>
    na(value) ? "n/a" : value > 65.0 ? "HIGH" : value >= 35.0 ? "MOD" : "LOW"

f_volatilityText(float value) =>
    na(value) ? "n/a" : value >= 80.0 ? "EXTREME" : value >= 60.0 ? "EXPANDED" : value >= 40.0 ? "NORMAL" : value >= 20.0 ? "QUIET" : "CONTRACTED"

f_rawRegime(float score) =>
    score >= 75.0 ? REGIME_STRONG_BULL :
     score >= 45.0 ? REGIME_BULL :
     score >= 20.0 ? REGIME_BULL_LEAN :
     score <= -75.0 ? REGIME_STRONG_BEAR :
     score <= -45.0 ? REGIME_BEAR :
     score <= -20.0 ? REGIME_BEAR_LEAN :
     REGIME_BALANCED

f_updateRegime(int previousState, float score) =>
    int rawState = f_rawRegime(score)
    int nextState = previousState
    if previousState == REGIME_STRONG_BULL
        nextState := score >= 68.0 ? REGIME_STRONG_BULL : rawState
    else if previousState == REGIME_BULL
        nextState := score >= 75.0 ? REGIME_STRONG_BULL : score >= 38.0 ? REGIME_BULL : rawState
    else if previousState == REGIME_BULL_LEAN
        nextState := score >= 45.0 ? rawState : score >= 14.0 ? REGIME_BULL_LEAN : rawState
    else if previousState == REGIME_STRONG_BEAR
        nextState := score <= -68.0 ? REGIME_STRONG_BEAR : rawState
    else if previousState == REGIME_BEAR
        nextState := score <= -75.0 ? REGIME_STRONG_BEAR : score <= -38.0 ? REGIME_BEAR : rawState
    else if previousState == REGIME_BEAR_LEAN
        nextState := score <= -45.0 ? rawState : score <= -14.0 ? REGIME_BEAR_LEAN : rawState
    else
        nextState := rawState
    nextState

f_regimeText(int state) =>
    state == REGIME_STRONG_BULL ? "STRONG BULL" :
     state == REGIME_BULL ? "BULL" :
     state == REGIME_BULL_LEAN ? "BULL LEAN" :
     state == REGIME_STRONG_BEAR ? "STRONG BEAR" :
     state == REGIME_BEAR ? "BEAR" :
     state == REGIME_BEAR_LEAN ? "BEAR LEAN" :
     "BALANCED"

f_componentText(float score) =>
    na(score) ? "n/a" : score >= 75.0 ? "STRONG BULL" : score >= 45.0 ? "BULL" : score >= 20.0 ? "BULL LEAN" : score <= -75.0 ? "STRONG BEAR" : score <= -45.0 ? "BEAR" : score <= -20.0 ? "BEAR LEAN" : "BALANCED"

f_regimeColor(int state) =>
    state == REGIME_STRONG_BULL ? bullishColor :
     state == REGIME_BULL or state == REGIME_BULL_LEAN ? bullishColor :
     state == REGIME_STRONG_BEAR ? bearishColor :
     state == REGIME_BEAR or state == REGIME_BEAR_LEAN ? bearishColor :
     neutralColor

f_scoreColor(float score) =>
    na(score) ? neutralColor : score >= 75.0 ? bullishColor : score >= 20.0 ? bullishColor : score <= -75.0 ? bearishColor : score <= -20.0 ? bearishColor : neutralColor

f_levelsBias(float direction, float confidence) =>
    na(direction) or na(confidence) ? "n/a" : confidence < 35.0 ? "LOW CONVICTION" : direction >= 45.0 ? "PREFER SUPPORT" : direction <= -45.0 ? "PREFER RESISTANCE" : direction >= 20.0 ? "SUPPORT LEAN" : direction <= -20.0 ? "RESISTANCE LEAN" : "TWO-SIDED"

f_zonePreference(float direction, float confidence) =>
    na(direction) or na(confidence) or confidence < 35.0 or math.abs(direction) < 20.0 ? 0.0 : direction > 0.0 ? 1.0 : -1.0

f_tableCell(table tableId, int column, int row, string cellText, color textColor, color backgroundColor, string horizontalAlignment) =>
    table.cell(
         tableId,
         column,
         row,
         cellText,
         text_color = textColor,
         bgcolor = backgroundColor,
         text_size = f_textSize(dashboardSizeInput),
         text_halign = horizontalAlignment == "Left" ? text.align_left : text.align_right
    )

f_htfMetricsConfirmed() =>
    float htfAtr = ta.atr(htfAtrLength)
    float htfFast = ta.ema(close, htfFastEmaLength)
    float htfSlow = ta.ema(close, htfSlowEmaLength)
    float htfRsi = ta.rsi(close, rsiLength)
    float atrSafeHtf = math.max(nz(htfAtr, syminfo.mintick), syminfo.mintick)
    float spreadScoreRaw = f_bound(((htfFast - htfSlow) / atrSafeHtf) / htfSpreadScale)
    float slowSlopePerBar = ((htfSlow - htfSlow[htfSlopeLookback]) / math.max(float(htfSlopeLookback), 1.0)) / atrSafeHtf
    float fastSlopePerBar = ((htfFast - htfFast[htfSlopeLookback]) / math.max(float(htfSlopeLookback), 1.0)) / atrSafeHtf
    float slowSlopeScore = f_bound(slowSlopePerBar / htfSlopeScale)
    float fastSlopeScore = f_bound(fastSlopePerBar / htfSlopeScale)
    float slopeScoreRaw = f_weighted2(slowSlopeScore, 0.65, fastSlopeScore, 0.35)
    float pathMove = close - close[htfPathLookback]
    float pathReturnAtr = pathMove / (atrSafeHtf * math.sqrt(float(htfPathLookback)))
    float returnScoreRaw = f_bound(pathReturnAtr / htfReturnScale)
    float pathLength = ta.sma(math.abs(ta.change(close)), htfPathLookback) * htfPathLookback
    float efficiencyScoreRaw = not na(pathLength) and pathLength > syminfo.mintick ? f_clamp(100.0 * pathMove / pathLength, -100.0, 100.0) : na
    float persistenceScoreRaw = 100.0 * ta.sma(f_sign(close - htfFast), htfPersistenceLength)
    float rsiScoreRaw = f_bound((htfRsi - 50.0) / 20.0)
    float directionRaw = 0.28 * spreadScoreRaw + 0.22 * slopeScoreRaw + 0.22 * returnScoreRaw + 0.13 * efficiencyScoreRaw + 0.10 * persistenceScoreRaw + 0.05 * rsiScoreRaw
    float magnitudeRaw = 0.28 * math.abs(spreadScoreRaw) + 0.22 * math.abs(slopeScoreRaw) + 0.22 * math.abs(returnScoreRaw) + 0.13 * math.abs(efficiencyScoreRaw) + 0.10 * math.abs(persistenceScoreRaw) + 0.05 * math.abs(rsiScoreRaw)
    float coherenceRaw = magnitudeRaw > 0.0 ? f_clamp(math.abs(directionRaw) / magnitudeRaw, 0.0, 1.0) : 0.0
    float qualityRaw = f_clamp(magnitudeRaw * (0.60 + 0.40 * coherenceRaw), 0.0, 100.0)
    [f_clamp(directionRaw[1], -100.0, 100.0), qualityRaw[1]]

f_htfBreadthText(float score1, float score2, float score3, float score4, float threshold) =>
    int bullCount = (not na(score1) and score1 >= threshold ? 1 : 0) + (not na(score2) and score2 >= threshold ? 1 : 0) + (not na(score3) and score3 >= threshold ? 1 : 0) + (not na(score4) and score4 >= threshold ? 1 : 0)
    int bearCount = (not na(score1) and score1 <= -threshold ? 1 : 0) + (not na(score2) and score2 <= -threshold ? 1 : 0) + (not na(score3) and score3 <= -threshold ? 1 : 0) + (not na(score4) and score4 <= -threshold ? 1 : 0)
    int availableCount = (not na(score1) ? 1 : 0) + (not na(score2) ? 1 : 0) + (not na(score3) ? 1 : 0) + (not na(score4) ? 1 : 0)
    bullCount > bearCount ? str.tostring(bullCount) + "/" + str.tostring(availableCount) + " BULL" : bearCount > bullCount ? str.tostring(bearCount) + "/" + str.tostring(availableCount) + " BEAR" : availableCount > 0 ? "MIXED" : "n/a"

string sessionDays = weekdaysOnly ? ":23456" : ":1234567"
string rthSession = rthInput + sessionDays
bool inRth = not na(time(timeframe.period, rthSession, sessionTimezone))
int calendarDayKey = year(time, sessionTimezone) * 10000 + month(time, sessionTimezone) * 100 + dayofmonth(time, sessionTimezone)
bool newCalendarDay = na(calendarDayKey[1]) or calendarDayKey != calendarDayKey[1]
bool rthStart = inRth and (session.isfirstbar_regular or newCalendarDay or not inRth[1])
int staleThresholdMs = int(math.max(timeframe.in_seconds() * 1000.0 * 3.0, 30.0 * 60.0 * 1000.0))
bool staleData = barstate.islast and timenow > time_close + staleThresholdMs
string dataState = staleData ? "STALE / CLOSED" : barstate.isrealtime and not barstate.isconfirmed ? "LIVE" : "CONFIRMED"

if barstate.isfirst and (not timeframe.isintraday or timeframe.in_seconds() >= 3600)
    runtime.error("Meridian Options Context is designed for intraday charts below 60 minutes.")

float atr = ta.atr(atrLength)
float atrSafe = math.max(nz(atr, syminfo.mintick), syminfo.mintick)
float atrPct = atrSafe / math.max(close, syminfo.mintick)

var float currentSessionOpen = na
if rthStart
    currentSessionOpen := open

bool dailyVwapAnchor = rthStart
float sessionVwap = ta.vwap(vwapSource, dailyVwapAnchor)

var int openingRangeEndTime = na
var float openingRangeHigh = na
var float openingRangeLow = na
var bool openingRangeFinalized = false
if rthStart
    openingRangeEndTime := time + openingRangeMinutes * 60 * 1000
    openingRangeHigh := high
    openingRangeLow := low
    openingRangeFinalized := false
bool inOpeningRange = inRth and not na(openingRangeEndTime) and time < openingRangeEndTime
if inOpeningRange and not rthStart
    openingRangeHigh := math.max(openingRangeHigh, high)
    openingRangeLow := math.min(openingRangeLow, low)
if inRth and not inOpeningRange and not openingRangeFinalized and not na(openingRangeEndTime)
    openingRangeFinalized := true
bool openingRangeTimeframeWarning = timeframe.in_seconds() > openingRangeMinutes * 60

float fastEma = ta.ema(close, fastEmaLength)
float slowEma = ta.ema(close, slowEmaLength)
float spreadNorm = (fastEma - slowEma) / atrSafe
float slowSlopeNorm = ((slowEma - slowEma[trendSlopeLookback]) / math.max(float(trendSlopeLookback), 1.0)) / atrSafe
float fastSlopeNorm = ((fastEma - fastEma[trendSlopeLookback]) / math.max(float(trendSlopeLookback), 1.0)) / atrSafe
float trendSpreadScore = f_bound(spreadNorm / trendSpreadScale)
float trendSlowSlopeScore = f_bound(slowSlopeNorm / trendSlopeScale)
float trendFastSlopeScore = f_bound(fastSlopeNorm / trendSlopeScale)
float trendScore = f_clamp(0.55 * trendSpreadScore + 0.30 * trendSlowSlopeScore + 0.15 * trendFastSlopeScore, -100.0, 100.0)

float vwapStructureScore = inRth and not na(sessionVwap) ? f_bound(((close - sessionVwap) / atrSafe) / vwapDistanceScale) : na
float openingRangeScore = na
if inRth and openingRangeFinalized and not openingRangeTimeframeWarning and not na(openingRangeHigh) and not na(openingRangeLow)
    float openingRangeWidth = math.max(openingRangeHigh - openingRangeLow, syminfo.mintick)
    if close > openingRangeHigh
        openingRangeScore := 30.0 + 70.0 * f_sat(((close - openingRangeHigh) / atrSafe) / openingRangeBreakScale)
    else if close < openingRangeLow
        openingRangeScore := -30.0 - 70.0 * f_sat(((openingRangeLow - close) / atrSafe) / openingRangeBreakScale)
    else
        float position = 2.0 * (close - openingRangeLow) / openingRangeWidth - 1.0
        openingRangeScore := 30.0 * f_clamp(position, -1.0, 1.0)
float sessionOpenScore = inRth and not na(currentSessionOpen) ? f_bound(((close - currentSessionOpen) / atrSafe) / sessionOpenScale) : na
float structureScore = f_weighted3(vwapStructureScore, 0.60, openingRangeScore, 0.30, sessionOpenScore, 0.10)

float rsiValue = ta.rsi(rsiSource, rsiLength)
float rsiMomentumScore = not na(rsiValue) ? f_bound((rsiValue - 50.0) / 20.0) : na
float momentumReturn = close[momentumLookback] > 0.0 ? math.log(close / close[momentumLookback]) : na
float expectedMove = atrPct * math.sqrt(momentumLookback)
float momentumReturnNorm = not na(momentumReturn) and expectedMove > 0.0 ? momentumReturn / expectedMove : na
float returnMomentumScore = not na(momentumReturnNorm) ? f_bound(momentumReturnNorm / momentumScale) : na
float momentumDeltaRaw = not na(returnMomentumScore) and not na(returnMomentumScore[momentumDeltaLookback]) ? (returnMomentumScore - returnMomentumScore[momentumDeltaLookback]) / 100.0 : na
float momentumDeltaScore = not na(momentumDeltaRaw) ? f_bound(momentumDeltaRaw / momentumDeltaScale) : na
float momentumScore = f_weighted3(returnMomentumScore, 0.55, rsiMomentumScore, 0.35, momentumDeltaScore, 0.10)

string regularSessionTicker = ticker.new(syminfo.prefix, syminfo.ticker, session.regular)
[_, _, barRvolRaw] = request.security(
     regularSessionTicker,
     timeframe.period,
     tvta.relativeVolume(rvolLookbackDays, "D", false, adjustRealtimeRvol),
     gaps = barmerge.gaps_on,
     lookahead = barmerge.lookahead_off
)
[_, _, cumulativeRvolRaw] = request.security(
     regularSessionTicker,
     timeframe.period,
     tvta.relativeVolume(rvolLookbackDays, "D", true, adjustRealtimeRvol),
     gaps = barmerge.gaps_on,
     lookahead = barmerge.lookahead_off
)
float barRvol = inRth ? barRvolRaw : na
float cumulativeRvol = inRth ? cumulativeRvolRaw : na
float barRange = math.max(high - low, math.max(atrSafe * 0.02, syminfo.mintick))
float closeLocationValue = f_clamp((2.0 * close - high - low) / barRange, -1.0, 1.0)
float oneBarReturnNorm = close[1] > 0.0 ? math.log(close / close[1]) / atrPct : na
float returnDirection = not na(oneBarReturnNorm) ? f_sat(oneBarReturnNorm / participationReturnScale) : 0.0
float directionalProxy = f_clamp(0.65 * closeLocationValue + 0.35 * returnDirection, -1.0, 1.0)
float barIntensity = not na(barRvol) and barRvol > 0.0 ? f_sat(math.max(math.log(barRvol), 0.0) / rvolLogScale) : na
float cumulativeIntensity = not na(cumulativeRvol) and cumulativeRvol > 0.0 ? f_sat(math.max(math.log(cumulativeRvol), 0.0) / rvolLogScale) : na
float participationIntensity = f_weighted2(barIntensity, 0.65, cumulativeIntensity, 0.35)
float participationScore = not na(participationIntensity) ? 100.0 * directionalProxy * participationIntensity : na
float participationQuality = not na(participationIntensity) ? 100.0 * participationIntensity : na

var int rthBarCount = 0
if rthStart
    rthBarCount := 1
else if inRth
    rthBarCount := rthBarCount + 1
else
    rthBarCount := 0
float persistenceDivisor = math.max(math.min(persistenceLength, rthBarCount), 1)
float vwapPersistenceInput = inRth and not na(sessionVwap) ? f_sign(close - sessionVwap) : 0.0
float emaPersistenceInput = inRth ? f_sign(fastEma - slowEma) : 0.0
float returnPersistenceInput = inRth and close[1] > 0.0 ? f_sign(math.log(close / close[1])) : 0.0
float vwapPersistenceAverage = ta.sma(vwapPersistenceInput, persistenceLength)
float emaPersistenceAverage = ta.sma(emaPersistenceInput, persistenceLength)
float returnPersistenceAverage = ta.sma(returnPersistenceInput, persistenceLength)
float vwapPersistence = inRth ? vwapPersistenceAverage * persistenceLength / persistenceDivisor : na
float emaPersistence = inRth ? emaPersistenceAverage * persistenceLength / persistenceDivisor : na
float returnPersistence = inRth ? returnPersistenceAverage * persistenceLength / persistenceDivisor : na
float persistenceScore = inRth ? 100.0 * f_weighted3(vwapPersistence, 0.40, emaPersistence, 0.35, returnPersistence, 0.25) : na
persistenceScore := not na(persistenceScore) ? f_clamp(persistenceScore, -100.0, 100.0) : na

float volatilityShortAtr = ta.atr(volatilityShortLength)
float volatilityLongAtr = ta.atr(volatilityLongLength)
float volatilityRatio = not na(volatilityLongAtr) and volatilityLongAtr > 0.0 ? volatilityShortAtr / volatilityLongAtr : na
float volatilityMean = ta.sma(volatilityRatio, volatilityZLookback)
float volatilityDeviation = ta.stdev(volatilityRatio, volatilityZLookback)
float volatilityZ = not na(volatilityDeviation) and volatilityDeviation > 0.0 ? (volatilityRatio - volatilityMean) / volatilityDeviation : na
float volatilityScore = not na(volatilityZ) ? f_clamp(50.0 + 50.0 * f_sat(volatilityZ / volatilityZScale), 0.0, 100.0) : na
float volatilityRelevance = not na(volatilityScore) ? f_clamp(100.0 - 1.5 * math.abs(volatilityScore - 65.0), 0.0, 100.0) : na

[htf1hScore, htf1hQuality] = request.security(regularSessionTicker, "60", f_htfMetricsConfirmed(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_on)
[htf4hScore, htf4hQuality] = request.security(regularSessionTicker, "240", f_htfMetricsConfirmed(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_on)
[htf1dScore, htf1dQuality] = request.security(regularSessionTicker, "1D", f_htfMetricsConfirmed(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_on)
[htf1wScore, htf1wQuality] = request.security(regularSessionTicker, "1W", f_htfMetricsConfirmed(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_on)
float htfNumerator = 0.0
float htfDenominator = 0.0
float htfMagnitude = 0.0
float htfQualityNumerator = 0.0
float htfQualityDenominator = 0.0
if not na(htf1hScore)
    htfNumerator := htfNumerator + 0.35 * htf1hScore
    htfDenominator := htfDenominator + 0.35
    htfMagnitude := htfMagnitude + 0.35 * math.abs(htf1hScore)
if not na(htf1hQuality)
    htfQualityNumerator := htfQualityNumerator + 0.35 * htf1hQuality
    htfQualityDenominator := htfQualityDenominator + 0.35
if not na(htf4hScore)
    htfNumerator := htfNumerator + 0.30 * htf4hScore
    htfDenominator := htfDenominator + 0.30
    htfMagnitude := htfMagnitude + 0.30 * math.abs(htf4hScore)
if not na(htf4hQuality)
    htfQualityNumerator := htfQualityNumerator + 0.30 * htf4hQuality
    htfQualityDenominator := htfQualityDenominator + 0.30
if not na(htf1dScore)
    htfNumerator := htfNumerator + 0.22 * htf1dScore
    htfDenominator := htfDenominator + 0.22
    htfMagnitude := htfMagnitude + 0.22 * math.abs(htf1dScore)
if not na(htf1dQuality)
    htfQualityNumerator := htfQualityNumerator + 0.22 * htf1dQuality
    htfQualityDenominator := htfQualityDenominator + 0.22
if not na(htf1wScore)
    htfNumerator := htfNumerator + 0.13 * htf1wScore
    htfDenominator := htfDenominator + 0.13
    htfMagnitude := htfMagnitude + 0.13 * math.abs(htf1wScore)
if not na(htf1wQuality)
    htfQualityNumerator := htfQualityNumerator + 0.13 * htf1wQuality
    htfQualityDenominator := htfQualityDenominator + 0.13
float htfScore = htfDenominator > 0.0 ? htfNumerator / htfDenominator : na
float htfCoherence = htfMagnitude > 0.0 ? f_clamp(math.abs(htfNumerator) / htfMagnitude, 0.0, 1.0) : na
float htfAverageMagnitude = htfDenominator > 0.0 ? htfMagnitude / htfDenominator : na
float htfAverageQuality = htfQualityDenominator > 0.0 ? htfQualityNumerator / htfQualityDenominator : na
float htfConvictionBase = f_weighted2(htfAverageMagnitude, 0.45, htfAverageQuality, 0.55)
float htfConviction = not na(htfConvictionBase) and not na(htfCoherence) ? f_clamp(htfConvictionBase * htfCoherence, 0.0, 100.0) : na
float htfTacticalScore = f_weighted2(htf1hScore, 0.55, htf4hScore, 0.45)
float htfMacroScore = f_weighted2(htf1dScore, 0.65, htf1wScore, 0.35)
string htfBreadthText = f_htfBreadthText(htf1hScore, htf4hScore, htf1dScore, htf1wScore, htfDirectionalThreshold)

float localWeightScale = includeHigherTimeframes ? 0.85 : 1.0
float trendWeight = 0.26 * localWeightScale
float structureWeight = 0.24 * localWeightScale
float momentumWeight = 0.21 * localWeightScale
float participationWeight = 0.16 * localWeightScale
float persistenceWeight = 0.13 * localWeightScale
float htfWeight = includeHigherTimeframes ? 0.15 : 0.0
float contextNumerator = 0.0
float contextDenominator = 0.0
float contextMagnitude = 0.0
if not na(trendScore)
    contextNumerator := contextNumerator + trendWeight * trendScore
    contextDenominator := contextDenominator + trendWeight
    contextMagnitude := contextMagnitude + trendWeight * math.abs(trendScore)
if not na(structureScore)
    contextNumerator := contextNumerator + structureWeight * structureScore
    contextDenominator := contextDenominator + structureWeight
    contextMagnitude := contextMagnitude + structureWeight * math.abs(structureScore)
if not na(momentumScore)
    contextNumerator := contextNumerator + momentumWeight * momentumScore
    contextDenominator := contextDenominator + momentumWeight
    contextMagnitude := contextMagnitude + momentumWeight * math.abs(momentumScore)
if not na(participationScore)
    contextNumerator := contextNumerator + participationWeight * participationScore
    contextDenominator := contextDenominator + participationWeight
    contextMagnitude := contextMagnitude + participationWeight * math.abs(participationScore)
if not na(persistenceScore)
    contextNumerator := contextNumerator + persistenceWeight * persistenceScore
    contextDenominator := contextDenominator + persistenceWeight
    contextMagnitude := contextMagnitude + persistenceWeight * math.abs(persistenceScore)
if htfWeight > 0.0 and not na(htfScore)
    contextNumerator := contextNumerator + htfWeight * htfScore
    contextDenominator := contextDenominator + htfWeight
    contextMagnitude := contextMagnitude + htfWeight * math.abs(htfScore)
float contextDirection = contextDenominator > 0.0 ? f_clamp(contextNumerator / contextDenominator, -100.0, 100.0) : na
float contextCoherence = contextMagnitude > 0.0 ? f_clamp(math.abs(contextNumerator) / contextMagnitude, 0.0, 1.0) : 0.0
float contextAverageMagnitude = contextDenominator > 0.0 ? contextMagnitude / contextDenominator : 0.0
float agreementScore = f_clamp(contextAverageMagnitude * contextCoherence, 0.0, 100.0)
float contextConfidence = f_weighted4(agreementScore, 0.45, participationQuality, 0.30, volatilityRelevance, 0.15, not na(persistenceScore) ? math.abs(persistenceScore) : na, 0.10)
contextConfidence := not na(contextConfidence) ? f_clamp(contextConfidence, 0.0, 100.0) : na

var int regimeState = REGIME_BALANCED
if barstate.isconfirmed and not na(contextDirection)
    regimeState := f_updateRegime(regimeState, contextDirection)
string regimeText = f_regimeText(regimeState)
color regimeColor = f_regimeColor(regimeState)
string levelsBiasText = f_levelsBias(contextDirection, contextConfidence)
float zonePreference = f_zonePreference(contextDirection, contextConfidence)

int legacyVwapVote = not inRth or na(sessionVwap) ? 0 : close > sessionVwap ? 1 : close < sessionVwap ? -1 : 0
int legacyRsiVote = na(rsiValue) ? 0 : rsiValue >= 55.0 ? 1 : rsiValue <= 45.0 ? -1 : 0
int legacyEmaVote = fastEma > slowEma ? 1 : fastEma < slowEma ? -1 : 0
int legacyOrVote = not openingRangeFinalized or openingRangeTimeframeWarning ? 0 : close > openingRangeHigh ? 1 : close < openingRangeLow ? -1 : 0
int legacyParticipationVote = na(barRvol) or barRvol < highRvolThreshold ? 0 : close > open ? 1 : close < open ? -1 : 0
int legacyScore = legacyVwapVote + legacyRsiVote + legacyEmaVote + legacyOrVote + legacyParticipationVote

float scaledRvol = na(barRvol) ? na : math.min(barRvol / rvolDisplayCap, 1.0) * 100.0
color rvolColumnColor = na(participationScore) ? color.new(neutralColor, 88) : participationScore >= 0.0 ? color.new(bullishColor, 82) : color.new(bearishColor, 82)
plot(showRvol ? scaledRvol : na, "RVOL normalized", color = rvolColumnColor, style = plot.style_columns, linewidth = 1, histbase = 0)
zeroPlot = plot(0.0, "Context Fill Baseline", color = color.new(neutralColor, 100), linewidth = 1)
contextPlot = plot(showContextLine ? contextDirection : na, "Context Direction", color = regimeColor, linewidth = 3)
fill(contextPlot, zeroPlot, color = showContextLine ? color.new(regimeColor, 91) : na, title = "Context fill")
plot(showConfidenceLine ? contextConfidence : na, "Confidence Line", color = color.new(secondaryColor, 15), linewidth = 1)
plot(showRsiMomentum ? rsiMomentumScore : na, "RSI Momentum", color = color.new(brandColor, 25), linewidth = 1)
plot(showHtfComposite ? htfScore : na, "HTF Composite", color = color.new(#FBBF24, 20), linewidth = 2)
plot(showLegacyComparison ? legacyScore * 20.0 : na, "Legacy v0.1 Score x20", color = color.new(warningColor, 30), linewidth = 1)

hline(100.0, "Maximum", color = color.new(neutralColor, 100))
hline(75.0, "+75 Strong Bull", color = color.new(bullishColor, 70), linestyle = hline.style_dotted)
hline(45.0, "+45 Bull", color = color.new(bullishColor, 78), linestyle = hline.style_dotted)
hline(20.0, "+20 Bull Lean", color = color.new(bullishColor, 88), linestyle = hline.style_dotted)
hline(0.0, "Balanced", color = color.new(neutralColor, 55), linestyle = hline.style_dashed)
hline(-20.0, "-20 Bear Lean", color = color.new(bearishColor, 88), linestyle = hline.style_dotted)
hline(-45.0, "-45 Bear", color = color.new(bearishColor, 78), linestyle = hline.style_dotted)
hline(-75.0, "-75 Strong Bear", color = color.new(bearishColor, 70), linestyle = hline.style_dotted)
hline(-100.0, "Minimum", color = color.new(neutralColor, 100))

bgcolor(showRegimeBackground ? color.new(regimeColor, regimeBackgroundTransparency) : na, title = "Context regime background")

plot(contextDirection, "Context Score", display = showResearchValues ? display.data_window : display.none)
plot(contextConfidence, "Context Confidence", display = showResearchValues ? display.data_window : display.none)
plot(agreementScore, "Agreement Score", display = showResearchValues ? display.data_window : display.none)
plot(contextCoherence * 100.0, "Directional Coherence", display = showResearchValues ? display.data_window : display.none)
plot(trendScore, "Trend Score", display = showResearchValues ? display.data_window : display.none)
plot(structureScore, "Structure Score", display = showResearchValues ? display.data_window : display.none)
plot(momentumScore, "Momentum Score", display = showResearchValues ? display.data_window : display.none)
plot(participationScore, "Participation Score", display = showResearchValues ? display.data_window : display.none)
plot(persistenceScore, "Persistence Score", display = showResearchValues ? display.data_window : display.none)
plot(volatilityScore, "Volatility Score", display = showResearchValues ? display.data_window : display.none)
plot(barRvol, "Bar RVOL", display = showResearchValues ? display.data_window : display.none)
plot(cumulativeRvol, "Cumulative RVOL", display = showResearchValues ? display.data_window : display.none)
plot(htfScore, "HTF Score", display = showResearchValues ? display.data_window : display.none)
plot(htfConviction, "HTF Conviction", display = showResearchValues ? display.data_window : display.none)
plot(htfCoherence * 100.0, "HTF Directional Coherence", display = showResearchValues ? display.data_window : display.none)
plot(htfTacticalScore, "HTF Tactical Score", display = showResearchValues ? display.data_window : display.none)
plot(htfMacroScore, "HTF Macro Score", display = showResearchValues ? display.data_window : display.none)
plot(htf1hScore, "1H Trend Score", display = showResearchValues ? display.data_window : display.none)
plot(htf4hScore, "4H Trend Score", display = showResearchValues ? display.data_window : display.none)
plot(htf1dScore, "1D Trend Score", display = showResearchValues ? display.data_window : display.none)
plot(htf1wScore, "1W Trend Score", display = showResearchValues ? display.data_window : display.none)
plot(htf1hQuality, "1H Trend Quality", display = showResearchValues ? display.data_window : display.none)
plot(htf4hQuality, "4H Trend Quality", display = showResearchValues ? display.data_window : display.none)
plot(htf1dQuality, "1D Trend Quality", display = showResearchValues ? display.data_window : display.none)
plot(htf1wQuality, "1W Trend Quality", display = showResearchValues ? display.data_window : display.none)
plot(zonePreference, "Zone Preference", display = showResearchValues ? display.data_window : display.none)
plot(legacyScore, "Legacy Context Score", display = showResearchValues ? display.data_window : display.none)

var table contextTable = table.new(
     position.top_right,
     3,
     10,
     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(contextTable, 0, 0, 2, 0)

if barstate.islast
    color invisible = color.new(panelColor, 100)
    color rowBackground = showDashboard ? color.new(panelColor, 12) : invisible
    color alternateBackground = showDashboard ? color.new(panelColor, 5) : invisible
    color headerBackground = showDashboard ? color.new(brandColor, 18) : invisible
    color hiddenText = color.new(textColor, 100)
    if showDashboard
        f_tableCell(contextTable, 0, 0, "Meridian -- Options Context", textColor, headerBackground, "Left")
        f_tableCell(contextTable, 0, 1, "Direction", neutralColor, rowBackground, "Left")
        f_tableCell(contextTable, 1, 1, f_scoreText(contextDirection), f_scoreColor(contextDirection), rowBackground, "Right")
        f_tableCell(contextTable, 2, 1, regimeText, regimeColor, rowBackground, "Right")
        f_tableCell(contextTable, 0, 2, "Confidence", neutralColor, alternateBackground, "Left")
        f_tableCell(contextTable, 1, 2, na(contextConfidence) ? "n/a" : str.tostring(math.round(contextConfidence)), secondaryColor, alternateBackground, "Right")
        f_tableCell(contextTable, 2, 2, f_confidenceText(contextConfidence), secondaryColor, alternateBackground, "Right")
        f_tableCell(contextTable, 0, 3, "Levels Bias", neutralColor, rowBackground, "Left")
        f_tableCell(contextTable, 1, 3, "", neutralColor, rowBackground, "Right")
        f_tableCell(contextTable, 2, 3, levelsBiasText, zonePreference > 0.0 ? bullishColor : zonePreference < 0.0 ? bearishColor : neutralColor, rowBackground, "Right")
        f_tableCell(contextTable, 0, 4, "HTF", neutralColor, alternateBackground, "Left")
        f_tableCell(contextTable, 1, 4, f_scoreText(htfScore), f_scoreColor(htfScore), alternateBackground, "Right")
        f_tableCell(contextTable, 2, 4, htfBreadthText, f_scoreColor(htfScore), alternateBackground, "Right")
        if dashboardMode == "Detailed"
            f_tableCell(contextTable, 0, 5, "Trend", neutralColor, rowBackground, "Left")
            f_tableCell(contextTable, 1, 5, f_scoreText(trendScore), f_scoreColor(trendScore), rowBackground, "Right")
            f_tableCell(contextTable, 2, 5, f_componentText(trendScore), f_scoreColor(trendScore), rowBackground, "Right")
            f_tableCell(contextTable, 0, 6, "Structure", neutralColor, alternateBackground, "Left")
            f_tableCell(contextTable, 1, 6, f_scoreText(structureScore), f_scoreColor(structureScore), alternateBackground, "Right")
            f_tableCell(contextTable, 2, 6, f_componentText(structureScore), f_scoreColor(structureScore), alternateBackground, "Right")
            f_tableCell(contextTable, 0, 7, "Participation", neutralColor, rowBackground, "Left")
            f_tableCell(contextTable, 1, 7, f_ratioText(barRvol), informationColor, rowBackground, "Right")
            f_tableCell(contextTable, 2, 7, f_componentText(participationScore), f_scoreColor(participationScore), rowBackground, "Right")
            f_tableCell(contextTable, 0, 8, "Volatility", neutralColor, alternateBackground, "Left")
            f_tableCell(contextTable, 1, 8, na(volatilityScore) ? "n/a" : str.tostring(math.round(volatilityScore)), warningColor, alternateBackground, "Right")
            f_tableCell(contextTable, 2, 8, f_volatilityText(volatilityScore), volatilityScore >= 60.0 ? warningColor : neutralColor, alternateBackground, "Right")
            f_tableCell(contextTable, 0, 9, "Data", neutralColor, rowBackground, "Left")
            f_tableCell(contextTable, 1, 9, inRth ? "RTH" : "OUTSIDE", inRth ? bullishColor : neutralColor, rowBackground, "Right")
            f_tableCell(contextTable, 2, 9, dataState, staleData ? warningColor : dataState == "LIVE" ? informationColor : neutralColor, rowBackground, "Right")
        else
            for row = 5 to 8
                f_tableCell(contextTable, 0, row, "", hiddenText, invisible, "Left")
                f_tableCell(contextTable, 1, row, "", hiddenText, invisible, "Right")
                f_tableCell(contextTable, 2, row, "", hiddenText, invisible, "Right")
            f_tableCell(contextTable, 0, 9, "Data", neutralColor, rowBackground, "Left")
            f_tableCell(contextTable, 1, 9, inRth ? "RTH" : "OUTSIDE", inRth ? bullishColor : neutralColor, rowBackground, "Right")
            f_tableCell(contextTable, 2, 9, dataState, staleData ? warningColor : dataState == "LIVE" ? informationColor : neutralColor, rowBackground, "Right")
    else
        for row = 0 to 9
            for col = 0 to 2
                table.cell(contextTable, col, row, "", bgcolor = invisible, text_color = hiddenText)

bool confirmedBar = barstate.isconfirmed
bool enteredStrongBull = enableAlerts and confirmedBar and regimeState == REGIME_STRONG_BULL and regimeState[1] != REGIME_STRONG_BULL
bool enteredStrongBear = enableAlerts and confirmedBar and regimeState == REGIME_STRONG_BEAR and regimeState[1] != REGIME_STRONG_BEAR
bool enteredBullish = enableAlerts and confirmedBar and regimeState > REGIME_BALANCED and regimeState[1] <= REGIME_BALANCED
bool enteredBearish = enableAlerts and confirmedBar and regimeState < REGIME_BALANCED and regimeState[1] >= REGIME_BALANCED
bool highConfidenceDirectionalRaw = not na(contextDirection) and not na(contextConfidence) and math.abs(contextDirection) >= highConfidenceDirectionThreshold and contextConfidence >= highConfidenceThreshold
bool highConfidenceDirectional = enableAlerts and confirmedBar and highConfidenceDirectionalRaw and not highConfidenceDirectionalRaw[1]
bool alignedParticipationRaw = not na(barRvol) and barRvol >= highRvolThreshold and not na(participationScore) and not na(contextDirection) and math.abs(contextDirection) >= 20.0 and f_sign(participationScore) == f_sign(contextDirection)
bool alignedParticipationSurge = enableAlerts and confirmedBar and alignedParticipationRaw and not alignedParticipationRaw[1]
var int lastStrongBullBar = na
var int lastStrongBearBar = na
if enteredStrongBull
    lastStrongBullBar := bar_index
if enteredStrongBear
    lastStrongBearBar := bar_index
bool bullContextFailure = enableAlerts and confirmedBar and not na(lastStrongBullBar) and bar_index - lastStrongBullBar <= contextFailureWindow and contextDirection < 20.0 and contextDirection[1] >= 20.0
bool bearContextFailure = enableAlerts and confirmedBar and not na(lastStrongBearBar) and bar_index - lastStrongBearBar <= contextFailureWindow and contextDirection > -20.0 and contextDirection[1] <= -20.0

alertcondition(enteredStrongBull, "Strong Bull Context", "{{ticker}} entered Strong Bull context. Context: {{plot(\"Context Score\")}}.")
alertcondition(enteredStrongBear, "Strong Bear Context", "{{ticker}} entered Strong Bear context. Context: {{plot(\"Context Score\")}}.")
alertcondition(enteredBullish, "Bullish Context Transition", "{{ticker}} transitioned into bullish context. Context: {{plot(\"Context Score\")}}.")
alertcondition(enteredBearish, "Bearish Context Transition", "{{ticker}} transitioned into bearish context. Context: {{plot(\"Context Score\")}}.")
alertcondition(highConfidenceDirectional, "High-Confidence Directional Context", "{{ticker}} reached a high-confidence directional context. Context: {{plot(\"Context Score\")}} | Confidence: {{plot(\"Context Confidence\")}}.")
alertcondition(alignedParticipationSurge, "Aligned RVOL Surge", "{{ticker}} printed a high-RVOL bar aligned with Context Direction. RVOL: {{plot(\"Bar RVOL\")}}.")
alertcondition(bullContextFailure, "Bull Context Failure", "{{ticker}} lost a recent Strong Bull context within the configured failure window.")
alertcondition(bearContextFailure, "Bear Context Failure", "{{ticker}} lost a recent Strong Bear context within the configured failure window.")

Overview

Meridian - Options Context normalizes trend from fast and slow EMAs, RTH VWAP and opening-range displacement, RSI and short-return momentum, relative volume participation, persistence, and short versus long ATR volatility. Session inputs set the New York regular hours, weekday filter, and opening-range length, while ATR scales set how large each displacement must be. A dashboard and alerts group is present. Higher-timeframe state is requested with lookahead, so those readings can use future bars.

Details

Published by

sukesan7 on GitHub

Type

Indicator, Pine Script v6

Draws

Separate pane below the chart

Category

Oscillators & momentum, Moving averages, Volume, Sessions & time

Markets

Stocks, Indices

Timeframes

M15-H1

Risk Notes

  • Reads higher-timeframe data with lookahead on, so the history can show signals that were not there in real time

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
Session timezonestringAmerica/New_YorksessionTimezone
Regular sessionsession0930-1600rthInput
Monday-Friday onlybooltrueweekdaysOnly
Opening-range minutesint15openingRangeMinutes
ATR lengthint14atrLength
Fast EMAint9fastEmaLength
Slow EMAint21slowEmaLength
Slope lookbackint5trendSlopeLookback
EMA spread scale (ATR)float0.25trendSpreadScale
EMA slope scale (ATR)float0.20trendSlopeScale
RTH VWAP sourcesourcehlc3vwapSource
VWAP displacement scale (ATR)float0.35vwapDistanceScale
Show all 40 settings
SettingTypeDefaultVariable
Opening-range break scale (ATR)float0.25openingRangeBreakScale
RTH-open displacement scale (ATR)float0.50sessionOpenScale
RSI sourcesourceclosersiSource
RSI lengthint14rsiLength
Normalized-return lookbackint5momentumLookback
Normalized-return scalefloat1.0momentumScale
Momentum-change lookbackint3momentumDeltaLookback
Momentum-change scalefloat0.50momentumDeltaScale
RVOL historical daysint20rvolLookbackDays
Estimate unclosed real-time barbooltrueadjustRealtimeRvol
Directional-return scale (ATR)float0.50participationReturnScale
RVOL intensity scalefloat0.70rvolLogScale
High RVOL thresholdfloat1.5highRvolThreshold
Extreme RVOL thresholdfloat2.0extremeRvolThreshold
RVOL display capfloat3.0rvolDisplayCap
Persistence barsint12persistenceLength
Short ATR lengthint14volatilityShortLength
Long ATR lengthint50volatilityLongLength
Volatility historyint100volatilityZLookback
Volatility z-score scalefloat1.5volatilityZScale
Include HTF trend in ContextbooltrueincludeHigherTimeframes
Show 1H / 4H / 1D / 1W statesbooltrueshowHigherTimeframeMatrix
HTF fast EMAint20htfFastEmaLength
HTF slow EMAint50htfSlowEmaLength
HTF ATR lengthint14htfAtrLength
HTF EMA slope barsint3htfSlopeLookback
HTF recent-path barsint5htfPathLookback
HTF persistence barsint6htfPersistenceLength
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

Aggregate Signal (v.2)

by capissimo
114 stars

Aggregate Signal (v.2) is an oscillator indicator that combines multiple technical factors into a single composite score. It scales OHLCV data using minimax normalization and evaluates indicators such as MACD, RSI, Stochastic, CCI, Momentum, On-Balance Volume,

Indicator v4 Oscillators & momentumMoving averages Repaints
Updated 1 year ago · None stated GitHub repository of Aggregate Signal (v.2)

Volume-Weighted MACD

by mihakralj
99 stars

This indicator plots a volume-weighted MACD in a separate pane. It subtracts a slow volume-weighted moving average of close from a faster one, then smooths that line with an exponential moving average to form the signal. A column histogram shows the gap betwee

Indicator v5 Oscillators & momentumMoving averages
Updated 4 weeks ago · Apache-2.0 GitHub repository of Volume-Weighted MACD

PPO ATR Bands

by cryptorife
75 stars

PPO ATR Bands plots a percentage price oscillator in a separate pane. It compares a Hull average of typical price with a volume-weighted exponential average of Parabolic SAR, then scales three true-range bands around the slow average into matching oscillator l

Indicator v4 Oscillators & momentumMoving averages
Updated 3 years ago · GPL-3.0 GitHub repository of PPO ATR Bands

RSI-VWMA

by Zettt
43 stars

This indicator calculates the Relative Strength Index using a volume-weighted moving average of the closing price as its source. It plots the resulting oscillator in a separate pane alongside configurable overbought and oversold threshold lines. The plot line

Indicator v4 Oscillators & momentumMoving averages
Updated 1 year ago · None stated GitHub repository of RSI-VWMA

Trap Shorts - Hamza Naveed

by ProfHamzaNaveed
43 stars

This indicator tracks and visualizes cryptocurrency net positioning across multiple derivative exchanges, including Binance, BitMEX, and Kraken. It calculates aggregated net longs, net shorts, net delta, and ratio metrics, or an optional Position RSI, renderin

Indicator v5 VolumeOscillators & momentum
Updated 9 months ago · None stated GitHub repository of Trap Shorts - Hamza Naveed

NFI v6

by koradiyakaushal
12 stars

This Pine v4 study ports early NostalgiaForInfinity V6 conditions onto the price chart. It computes two Bollinger setups, several EMAs and SMAs, RSI, MFI, a percent SMA spread labeled EWO, a 14-period choppiness value, and short and longer volume medians. Fixe

Indicator v4 Moving averagesVolatility & bands
Updated 5 years ago · MIT GitHub repository of NFI v6

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