Indicator · Pine Script v6 · On the chart

Meridian - Options Levels

by sukesan7

Meridian Options Levels plots clustered reaction zones on the price chart for liquid options underlyings.

View on GitHub

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

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

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

indicator(
     title = "Meridian - Options Levels",
     shorttitle = "Meridian Options Levels",
     overlay = true,
     max_bars_back = 1000,
     max_boxes_count = 8,
     behind_chart = true
)

string GROUP_SESSIONS = "01. Sessions"
string GROUP_DISPLAY = "02. Display"
string GROUP_ENGINE = "03. Reaction Zones"
string GROUP_SOURCES = "04. Candidate Sources"
string GROUP_REACTION = "05. Reaction Model"
string GROUP_EVENT = "06. Event AVWAP"
string GROUP_VWAP = "07. VWAP"
string GROUP_ALERTS = "08. Dashboard & Alerts"
string GROUP_THEME = "09. Theme"
string GROUP_RESEARCH = "10. Research / Debug"

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

string displayMode = input.string(
     "Reaction Zones",
     "Display mode",
     options = ["Reaction Zones", "Zones + Key References", "Legacy / Debug"],
     group = GROUP_DISPLAY
)
bool showZoneText = input.bool(true, "Show zone text", group = GROUP_DISPLAY)
bool showDashboard = input.bool(true, "Show dashboard", group = GROUP_DISPLAY)
string dashboardSizeInput = input.string("Small", "Dashboard text size", options = ["Tiny", "Small", "Normal"], group = GROUP_DISPLAY)
int zoneBackBars = input.int(12, "Zone history bars", minval = 1, maxval = 250, group = GROUP_DISPLAY)
int zoneForwardBars = input.int(40, "Zone projection bars", minval = 5, maxval = 250, group = GROUP_DISPLAY)
bool showEdgeSignals = input.bool(true, "Show buy / sell edge zones", group = GROUP_DISPLAY)
float edgeSignalMinimumActionability = input.float(55.0, "Edge zone minimum actionability", minval = 0.0, maxval = 100.0, step = 1.0, group = GROUP_DISPLAY)
float edgeSignalHeightPct = input.float(0.20, "Edge zone height (% of parent zone)", minval = 0.05, maxval = 0.50, step = 0.01, group = GROUP_DISPLAY)
bool showOpeningRangeBox = input.bool(true, "Highlight opening-range window", group = GROUP_DISPLAY)

int atrLength = input.int(14, "Confirmed daily ATR length", minval = 5, maxval = 100, group = GROUP_ENGINE)
float maxDistanceAtr = input.float(2.5, "Maximum candidate distance (daily ATR)", minval = 0.5, maxval = 10.0, step = 0.25, group = GROUP_ENGINE)
float clusterAtrFraction = input.float(0.08, "Cluster tolerance (daily ATR)", minval = 0.02, maxval = 0.25, step = 0.01, group = GROUP_ENGINE)
int clusterMinimumTicks = input.int(4, "Minimum cluster tolerance (ticks)", minval = 1, maxval = 100, group = GROUP_ENGINE)
float clusterMinimumPct = input.float(0.0005, "Minimum cluster tolerance (% as decimal)", minval = 0.0, maxval = 0.01, step = 0.0001, group = GROUP_ENGINE)
float minZoneAtr = input.float(0.02, "Minimum zone half-width (daily ATR)", minval = 0.005, maxval = 0.10, step = 0.005, group = GROUP_ENGINE)
float maxZoneAtr = input.float(0.12, "Maximum zone half-width (daily ATR)", minval = 0.03, maxval = 0.40, step = 0.01, group = GROUP_ENGINE)
int minZoneTicks = input.int(4, "Minimum zone half-width (ticks)", minval = 1, maxval = 100, group = GROUP_ENGINE)
float dispersionMultiplier = input.float(1.25, "Cluster dispersion multiplier", minval = 0.5, maxval = 3.0, step = 0.05, group = GROUP_ENGINE)
float actionabilityDecay = input.float(0.45, "Distance decay", minval = 0.05, maxval = 2.0, step = 0.05, group = GROUP_ENGINE)
float switchMargin = input.float(10.0, "Zone switch margin", minval = 1.0, maxval = 30.0, step = 1.0, group = GROUP_ENGINE)
float minimumZoneStrength = input.float(45.0, "Minimum zone strength", minval = 0.0, maxval = 100.0, step = 1.0, group = GROUP_ENGINE)
int refreshBars = input.int(3, "Cluster refresh bars", minval = 1, maxval = 20, group = GROUP_ENGINE)
float approachingAtr = input.float(0.20, "Approaching distance (daily ATR)", minval = 0.02, maxval = 1.0, step = 0.01, group = GROUP_ENGINE)
float rejectionDisplacementAtr = input.float(0.03, "Rejection displacement (daily ATR)", minval = 0.0, maxval = 0.25, step = 0.01, group = GROUP_ENGINE)
float breakDisplacementAtr = input.float(0.04, "Break displacement (daily ATR)", minval = 0.0, maxval = 0.30, step = 0.01, group = GROUP_ENGINE)
int breakClosesRequired = input.int(2, "Closes beyond zone for break", minval = 1, maxval = 5, group = GROUP_ENGINE)
int acceptanceClosesRequired = input.int(2, "Closes in / through zone for acceptance", minval = 1, maxval = 5, group = GROUP_ENGINE)
int stateHoldBars = input.int(3, "Event-state hold bars", minval = 1, maxval = 20, group = GROUP_ENGINE)
int flipWatchMinutes = input.int(30, "Broken-zone flip watch (minutes)", minval = 5, maxval = 240, group = GROUP_ENGINE)
float flipRetestAtr = input.float(0.04, "Flip retest tolerance (daily ATR)", minval = 0.0, maxval = 0.25, step = 0.01, group = GROUP_ENGINE)

bool usePriorDay = input.bool(true, "Previous day / close", group = GROUP_SOURCES)
bool usePriorWeek = input.bool(true, "Previous week", group = GROUP_SOURCES)
bool usePremarket = input.bool(true, "Premarket high / low", group = GROUP_SOURCES)
bool useOpeningRange = input.bool(true, "Finalized opening range", group = GROUP_SOURCES)
bool useRthOpen = input.bool(true, "RTH open", group = GROUP_SOURCES)
bool usePivots = input.bool(true, "Confirmed swing pivots", group = GROUP_SOURCES)
bool useEqualHighLow = input.bool(true, "Confirmed equal highs / lows", group = GROUP_SOURCES)
float equalLevelToleranceAtr = input.float(0.025, "Equal-level tolerance (daily ATR)", minval = 0.005, maxval = 0.10, step = 0.005, group = GROUP_SOURCES)
int equalLevelMinimumTicks = input.int(3, "Equal-level minimum tolerance (ticks)", minval = 1, maxval = 20, group = GROUP_SOURCES)
int equalLevelMaxAgeBars = input.int(240, "Equal-level maximum age (bars)", minval = 20, maxval = 1500, group = GROUP_SOURCES)
int pivotLeftBars = input.int(3, "Pivot left bars", minval = 1, maxval = 20, group = GROUP_SOURCES)
int pivotRightBars = input.int(3, "Pivot right bars", minval = 1, maxval = 20, group = GROUP_SOURCES)
int maxPivotRecords = input.int(36, "Maximum pivot records", minval = 10, maxval = 60, group = GROUP_SOURCES)
int pivotMaxAgeBars = input.int(600, "Pivot maximum age (bars)", minval = 50, maxval = 3000, group = GROUP_SOURCES)
int pivotDecayBars = input.int(240, "Pivot recency decay (bars)", minval = 20, maxval = 1500, group = GROUP_SOURCES)
bool useRoundNumbers = input.bool(true, "Adaptive round numbers", group = GROUP_SOURCES)
bool useAtrProjections = input.bool(false, "ATR projections", group = GROUP_SOURCES)
bool useGapStructure = input.bool(true, "Gap structure", group = GROUP_SOURCES)
float gapMinimumAtr = input.float(0.15, "Minimum gap (daily ATR)", minval = 0.02, maxval = 2.0, step = 0.01, group = GROUP_SOURCES)
bool useVwapCandidate = input.bool(true, "RTH VWAP as candidate", group = GROUP_SOURCES)
bool useVwapBandCandidates = input.bool(false, "VWAP bands as candidates", group = GROUP_SOURCES)

int reactionLookback = input.int(120, "Reaction lookback bars", minval = 30, maxval = 400, group = GROUP_REACTION)
int reactionWindow = input.int(6, "Reaction observation bars", minval = 2, maxval = 20, group = GROUP_REACTION)
int touchCooldownBars = input.int(8, "Touch cooldown bars", minval = 2, maxval = 50, group = GROUP_REACTION)
int maxReactionTouches = input.int(6, "Maximum historical touches scored", minval = 1, maxval = 12, group = GROUP_REACTION)
int touchMinimumTicks = input.int(2, "Touch tolerance (ticks)", minval = 1, maxval = 50, group = GROUP_REACTION)
float touchAtrFraction = input.float(0.025, "Touch tolerance (daily ATR)", minval = 0.005, maxval = 0.15, step = 0.005, group = GROUP_REACTION)

string eventAnchorMode = input.string(
     "Disabled",
     "Event AVWAP anchor",
     options = ["Disabled", "Manual timestamp", "Automatic earnings"],
     group = GROUP_EVENT
)
int manualEventTime = input.time(0, "Manual event timestamp", group = GROUP_EVENT)
int eventMaxAgeDays = input.int(20, "Maximum event age (days)", minval = 1, maxval = 90, group = GROUP_EVENT)
int eventMinBars = input.int(5, "Minimum bars before AVWAP contributes", minval = 1, maxval = 100, group = GROUP_EVENT)
float eventMaxDistanceAtr = input.float(2.0, "Maximum AVWAP distance (daily ATR)", minval = 0.25, maxval = 10.0, step = 0.25, group = GROUP_EVENT)

bool showVwap = input.bool(true, "Show regular-session VWAP", group = GROUP_VWAP)
float vwapSource = input.source(hlc3, "VWAP source", group = GROUP_VWAP)
bool showVwapBand1 = input.bool(false, "Show +/-1 stdev band", group = GROUP_VWAP)
bool showVwapBand2 = input.bool(false, "Show +/-2 stdev band", group = GROUP_VWAP)
float vwapBand1Multiplier = input.float(1.0, "Band 1 multiplier", minval = 0.1, step = 0.1, group = GROUP_VWAP)
float vwapBand2Multiplier = input.float(2.0, "Band 2 multiplier", minval = 0.1, step = 0.1, group = GROUP_VWAP)
bool showEmas = input.bool(false, "Show 9 / 21 EMA in Legacy / Debug", group = GROUP_VWAP)

bool enableAlerts = input.bool(true, "Enable transition alerts", group = GROUP_ALERTS)

color brandColor = input.color(#8B5CF6, "Brand / header", group = GROUP_THEME)
color supportColor = input.color(#2DD4BF, "Support / bullish", group = GROUP_THEME)
color resistanceColor = input.color(#F87171, "Resistance / bearish", group = GROUP_THEME)
color informationColor = input.color(#38BDF8, "Information", group = GROUP_THEME)
color neutralColor = input.color(#94A3B8, "Neutral", group = GROUP_THEME)
color pendingColor = input.color(#A1A1AA, "Pending / developing", group = GROUP_THEME)
color warningColor = input.color(#FBBF24, "Warning", group = GROUP_THEME)
color panelColor = input.color(#090E17, "HUD background", group = GROUP_THEME)
color borderColor = input.color(#334155, "HUD border", group = GROUP_THEME)
color textColor = input.color(#F8FAFC, "HUD text", group = GROUP_THEME)

bool showResearchValues = input.bool(false, "Show research values in Data Window", group = GROUP_RESEARCH)
bool showPotentialZones = input.bool(false, "Show potential reaction zones", group = GROUP_RESEARCH)
float potentialZoneMinimumStrength = input.float(35.0, "Potential-zone minimum strength", minval = 0.0, maxval = 100.0, step = 1.0, group = GROUP_RESEARCH)

int SRC_PDH = 1
int SRC_PDL = 2
int SRC_PDC = 3
int SRC_PMH = 4
int SRC_PML = 5
int SRC_PWH = 6
int SRC_PWL = 7
int SRC_ORH = 8
int SRC_ORL = 9
int SRC_RTH_OPEN = 10
int SRC_PIVOT_HIGH = 11
int SRC_PIVOT_LOW = 12
int SRC_GAP = 13
int SRC_ROUND = 14
int SRC_VWAP = 15
int SRC_VWAP_BAND = 16
int SRC_EVENT_AVWAP = 17
int SRC_ATR_PROJECTION = 18
int SRC_EQH = 19
int SRC_EQL = 20

int STATE_DISTANT = 1
int STATE_APPROACHING = 2
int STATE_INSIDE = 3
int STATE_REJECTING = 4
int STATE_ACCEPTING = 5
int STATE_BROKEN = 6
int STATE_FLIPPED = 7

string sessionDays = weekdaysOnly ? ":23456" : ":1234567"
string rthSession = rthInput + sessionDays
string premarketSession = premarketInput + sessionDays
bool inRth = not na(time(timeframe.period, rthSession, sessionTimezone))
bool inPremarket = not na(time(timeframe.period, premarketSession, 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 (newCalendarDay or not inRth[1])
bool premarketStart = inPremarket and (newCalendarDay or not inPremarket[1])

if barstate.isfirst and not timeframe.isintraday
    runtime.error("Meridian Options Levels is designed for intraday charts.")

float chartAtr = ta.atr(atrLength)
float confirmedDailyAtr = request.security(
     syminfo.tickerid,
     "1D",
     ta.atr(atrLength)[1],
     gaps = barmerge.gaps_off,
     lookahead = barmerge.lookahead_on
)
float normalizationAtr = not na(confirmedDailyAtr) and confirmedDailyAtr > 0.0 ? confirmedDailyAtr : chartAtr
float volumeBaseline = ta.sma(volume, 20)
float clusterTolerance = math.max(clusterMinimumTicks * syminfo.mintick, math.max(normalizationAtr * clusterAtrFraction, close * clusterMinimumPct))
float maximumCandidateDistance = normalizationAtr * maxDistanceAtr

type PivotRecord
    float price
    int polarity
    int pivotBar
    int knownBar
    float baseWeight
    float originImpulse
    float originVolume

type LevelCandidate
    float price
    float baseWeight
    float score
    float reaction
    float impulse
    float touchQuality
    float recency
    float volumeEvidence
    int sourceType
    string name
    string family
    int polarity

type Cluster
    float center
    float low
    float high
    float strength
    float actionability
    float weightedMean
    float maxScore
    float reactionQuality
    float weightSum
    float priceWeightSum
    float squareWeightSum
    float scoreWeightSum
    float reactionWeightSum
    float reactionWeight
    float supportWeight
    float resistanceWeight
    int memberCount
    int diversityCount
    string families
    string sourceTokens
    string sources
    int sourceNameCount

type ActiveZone
    bool valid
    float center
    float low
    float high
    float strength
    float actionability
    float weightedMean
    float reactionQuality
    int diversityCount
    string sources
    int polarity
    bool polarityFlipped
    int state
    int stateBar
    int selectedBar
    int insideCloses
    int beyondCloses
    int missedRefreshes

type FlipWatch
    bool valid
    float center
    float low
    float high
    float strength
    float weightedMean
    float reactionQuality
    int diversityCount
    string sources
    int brokenTime

f_clamp(float x, float lo, float hi) =>
    math.max(lo, math.min(hi, x))

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

f_positiveSat(float x) =>
    x <= 0.0 ? 0.0 : 100.0 * x / (x + 0.5)

f_participationScore(float rvol) =>
    na(rvol) ? na : f_clamp((rvol - 1.0) * 50.0, 0.0, 100.0)

f_stateText(int state) =>
    switch state
        STATE_APPROACHING => "APPROACHING"
        STATE_INSIDE => "INSIDE"
        STATE_REJECTING => "REJECTING"
        STATE_ACCEPTING => "ACCEPTING"
        STATE_BROKEN => "BROKEN"
        STATE_FLIPPED => "FLIPPED"
        => "DISTANT"

f_sourceName(int sourceType) =>
    switch sourceType
        SRC_PDH => "PDH"
        SRC_PDL => "PDL"
        SRC_PDC => "PDC"
        SRC_PMH => "PMH"
        SRC_PML => "PML"
        SRC_PWH => "PWH"
        SRC_PWL => "PWL"
        SRC_ORH => "ORH"
        SRC_ORL => "ORL"
        SRC_RTH_OPEN => "RTH OPEN"
        SRC_PIVOT_HIGH => "PIV H"
        SRC_PIVOT_LOW => "PIV L"
        SRC_GAP => "GAP"
        SRC_ROUND => "ROUND"
        SRC_VWAP => "VWAP"
        SRC_VWAP_BAND => "VWAP BAND"
        SRC_EVENT_AVWAP => "EVENT AVWAP"
        SRC_ATR_PROJECTION => "ATR PROJ"
        SRC_EQH => "EQH"
        SRC_EQL => "EQL"
        => "LEVEL"

f_sourceFamily(int sourceType) =>
    switch sourceType
        SRC_PDH => "DAY"
        SRC_PDL => "DAY"
        SRC_PDC => "DAY"
        SRC_PMH => "PREMARKET"
        SRC_PML => "PREMARKET"
        SRC_PWH => "WEEK"
        SRC_PWL => "WEEK"
        SRC_ORH => "OR"
        SRC_ORL => "OR"
        SRC_RTH_OPEN => "SESSION"
        SRC_PIVOT_HIGH => "PIVOT"
        SRC_PIVOT_LOW => "PIVOT"
        SRC_GAP => "GAP"
        SRC_ROUND => "ROUND"
        SRC_VWAP => "VWAP"
        SRC_VWAP_BAND => "VWAP"
        SRC_EVENT_AVWAP => "EVENT"
        SRC_ATR_PROJECTION => "PROJECTION"
        SRC_EQH => "LIQUIDITY"
        SRC_EQL => "LIQUIDITY"
        => "OTHER"

f_sourceBaseRecency(int sourceType) =>
    switch sourceType
        SRC_PWH => 78.0
        SRC_PWL => 78.0
        SRC_PDH => 88.0
        SRC_PDL => 88.0
        SRC_PDC => 88.0
        SRC_EVENT_AVWAP => 100.0
        SRC_PIVOT_HIGH => 100.0
        SRC_PIVOT_LOW => 100.0
        SRC_EQH => 92.0
        SRC_EQL => 92.0
        => 100.0

f_latestEqualLevel(array<PivotRecord> source, int polarity, float tolerance, int maxAgeBars) =>
    float level = na
    int knownBar = na
    int originBar = na
    int count = source.size()
    if count >= 2
        for i = count - 1 to 1
            PivotRecord recent = source.get(i)
            if recent.polarity == polarity and bar_index - recent.knownBar <= maxAgeBars
                for j = i - 1 to 0
                    PivotRecord older = source.get(j)
                    if older.polarity == polarity and bar_index - older.knownBar <= maxAgeBars and math.abs(recent.price - older.price) <= tolerance
                        level := (recent.price + older.price) * 0.5
                        knownBar := math.max(recent.knownBar, older.knownBar)
                        originBar := math.min(recent.pivotBar, older.pivotBar)
                        break
            if not na(level)
                break
    [level, knownBar, originBar]

f_reactionMetrics(float level, int knownBar) =>
    float reactionSum = 0.0
    float impulseSum = 0.0
    float volumeSum = 0.0
    int volumeSamples = 0
    int touches = 0
    int lastAcceptedOffset = na
    int mostRecentTouchAge = na
    int historyLimit = not na(knownBar) ? math.min(reactionLookback, math.max(bar_index - knownBar - 1, 0)) : reactionLookback
    for i = reactionWindow to reactionLookback
        if touches < maxReactionTouches and i <= historyLimit
            float localAtr = normalizationAtr[i]
            float fallbackAtr = normalizationAtr
            float tolerance = math.max(touchMinimumTicks * syminfo.mintick, nz(localAtr, fallbackAtr) * touchAtrFraction)
            bool touched = not na(low[i]) and not na(high[i]) and low[i] <= level + tolerance and high[i] >= level - tolerance
            bool cooled = na(lastAcceptedOffset) or i - lastAcceptedOffset >= touchCooldownBars
            if touched and cooled
                int direction = not na(close[i + 1]) and close[i + 1] > level + tolerance * 0.25 ? 1 : not na(close[i + 1]) and close[i + 1] < level - tolerance * 0.25 ? -1 : close[i] >= level ? 1 : -1
                float mfe = 0.0
                float mae = 0.0
                int persistenceCount = 0
                for j = 1 to reactionWindow
                    int offset = i - j
                    if direction == 1
                        mfe := math.max(mfe, high[offset] - level)
                        mae := math.max(mae, level - low[offset])
                        persistenceCount += close[offset] >= level ? 1 : 0
                    else
                        mfe := math.max(mfe, level - low[offset])
                        mae := math.max(mae, high[offset] - level)
                        persistenceCount += close[offset] <= level ? 1 : 0
                float ratio = mfe / math.max(mfe + mae, syminfo.mintick)
                float persistence = 100.0 * persistenceCount / reactionWindow
                float reaction = 75.0 * ratio + 0.25 * persistence
                float impulse = not na(localAtr) and localAtr > 0.0 ? f_positiveSat(mfe / localAtr) : na
                reactionSum += reaction
                impulseSum += nz(impulse, 0.0)
                float baseline = volumeBaseline[i]
                float rvol = not na(volume[i]) and not na(baseline) and baseline > 0.0 ? volume[i] / baseline : na
                float volumeScore = f_participationScore(rvol)
                if not na(volumeScore)
                    volumeSum += volumeScore
                    volumeSamples += 1
                touches += 1
                lastAcceptedOffset := i
                mostRecentTouchAge := na(mostRecentTouchAge) ? i : math.min(mostRecentTouchAge, i)
    float reactionQuality = touches > 0 ? reactionSum / touches : na
    float impulseQuality = touches > 0 ? impulseSum / touches : na
    float countScore = touches > 0 ? 100.0 * touches / (touches + 2.0) : na
    float touchQuality = touches > 0 ? countScore * (0.50 + 0.50 * nz(reactionQuality, 50.0) / 100.0) : na
    float volumeEvidence = volumeSamples > 0 ? volumeSum / volumeSamples : na
    [reactionQuality, impulseQuality, touchQuality, volumeEvidence, touches, mostRecentTouchAge]

f_candidateScore(float sourceQuality, float reactionQuality, float impulseQuality, float touchQuality, float recency, float volumeEvidence) =>
    float weighted = 0.30 * sourceQuality + 0.10 * recency
    float weights = 0.40
    if not na(reactionQuality)
        weighted += 0.25 * reactionQuality
        weights += 0.25
    if not na(impulseQuality)
        weighted += 0.18 * impulseQuality
        weights += 0.18
    if not na(touchQuality)
        weighted += 0.12 * touchQuality
        weights += 0.12
    if not na(volumeEvidence)
        weighted += 0.05 * volumeEvidence
        weights += 0.05
    f_clamp(weighted / math.max(weights, 0.001), 0.0, 100.0)

f_addCandidate(array<LevelCandidate> registry, float level, float baseWeight, int sourceType, int polarity, int originBar, int knownBar, float originImpulse, float originVolume, float recencyOverride, bool scanReactions, float atr, float maxDistance) =>
    if not na(level) and level > 0.0 and not na(atr) and atr > 0.0 and math.abs(level - close) <= maxDistance
        bool duplicate = false
        if registry.size() > 0
            for existing in registry
                if existing.sourceType == sourceType and math.abs(existing.price - level) <= syminfo.mintick * 0.5
                    duplicate := true
        if not duplicate
            float reactionQuality = na
            float reactionImpulse = na
            float touchQuality = na
            float reactionVolume = na
            int mostRecentTouchAge = na
            if scanReactions
                [reactionQuality, reactionImpulse, touchQuality, reactionVolume, _, mostRecentTouchAge] = f_reactionMetrics(level, knownBar)
            float impulseQuality = na(originImpulse) ? reactionImpulse : na(reactionImpulse) ? originImpulse : math.max(originImpulse, reactionImpulse)
            float volumeEvidence = na(originVolume) ? reactionVolume : na(reactionVolume) ? originVolume : math.max(originVolume, reactionVolume)
            float baseRecency = f_sourceBaseRecency(sourceType)
            float originRecency = not na(originBar) ? 100.0 / (1.0 + math.max(bar_index - originBar, 0) / math.max(float(pivotDecayBars), 1.0)) : baseRecency
            float touchRecency = not na(mostRecentTouchAge) ? 100.0 / (1.0 + mostRecentTouchAge / math.max(float(pivotDecayBars), 1.0)) : na
            float recency = not na(recencyOverride) ? recencyOverride : sourceType == SRC_PIVOT_HIGH or sourceType == SRC_PIVOT_LOW ? math.max(originRecency, nz(touchRecency, 0.0)) : math.max(baseRecency, nz(touchRecency, 0.0))
            float finalScore = f_candidateScore(baseWeight, reactionQuality, impulseQuality, touchQuality, recency, volumeEvidence)
            registry.push(LevelCandidate.new(
                 price = level,
                 baseWeight = baseWeight,
                 score = finalScore,
                 reaction = reactionQuality,
                 impulse = impulseQuality,
                 touchQuality = touchQuality,
                 recency = recency,
                 volumeEvidence = volumeEvidence,
                 sourceType = sourceType,
                 name = f_sourceName(sourceType),
                 family = f_sourceFamily(sourceType),
                 polarity = polarity
            ))

f_newCluster(LevelCandidate candidate) =>
    float weight = math.max(candidate.score, 1.0)
    string familyToken = "|" + candidate.family + "|"
    string sourceToken = "|" + candidate.name + "|"
    Cluster.new(
         center = candidate.price,
         low = candidate.price,
         high = candidate.price,
         strength = candidate.score,
         actionability = candidate.score,
         weightedMean = candidate.score,
         maxScore = candidate.score,
         reactionQuality = candidate.reaction,
         weightSum = weight,
         priceWeightSum = candidate.price * weight,
         squareWeightSum = candidate.price * candidate.price * weight,
         scoreWeightSum = candidate.score * weight,
         reactionWeightSum = na(candidate.reaction) ? 0.0 : candidate.reaction * weight,
         reactionWeight = na(candidate.reaction) ? 0.0 : weight,
         supportWeight = candidate.polarity == 1 ? weight : 0.0,
         resistanceWeight = candidate.polarity == -1 ? weight : 0.0,
         memberCount = 1,
         diversityCount = 1,
         families = familyToken,
         sourceTokens = sourceToken,
         sources = candidate.name,
         sourceNameCount = 1
    )

f_clusterAdd(Cluster cluster, LevelCandidate candidate, float duplicateTolerance) =>
    bool sameFamily = str.contains(cluster.families, "|" + candidate.family + "|")
    bool nearDuplicate = math.abs(candidate.price - cluster.center) <= duplicateTolerance
    float redundancyPenalty = sameFamily ? (nearDuplicate ? 0.55 : 0.78) : 1.0
    float weight = math.max(candidate.score * redundancyPenalty, 1.0)
    cluster.weightSum += weight
    cluster.priceWeightSum += candidate.price * weight
    cluster.squareWeightSum += candidate.price * candidate.price * weight
    cluster.scoreWeightSum += candidate.score * weight
    cluster.maxScore := math.max(cluster.maxScore, candidate.score)
    if not na(candidate.reaction)
        cluster.reactionWeightSum += candidate.reaction * weight
        cluster.reactionWeight += weight
    if candidate.polarity == 1
        cluster.supportWeight += weight
    else if candidate.polarity == -1
        cluster.resistanceWeight += weight
    cluster.memberCount += 1
    if not sameFamily
        cluster.diversityCount += 1
        cluster.families += candidate.family + "|"
    string sourceToken = "|" + candidate.name + "|"
    if not str.contains(cluster.sourceTokens, sourceToken)
        cluster.sourceTokens += candidate.name + "|"
        if cluster.sourceNameCount < 3
            cluster.sources += " + " + candidate.name
            cluster.sourceNameCount += 1
    cluster.center := cluster.priceWeightSum / cluster.weightSum
    cluster

f_finalizeCluster(Cluster cluster, float atr) =>
    cluster.center := cluster.priceWeightSum / math.max(cluster.weightSum, 0.001)
    float variance = math.max(cluster.squareWeightSum / math.max(cluster.weightSum, 0.001) - cluster.center * cluster.center, 0.0)
    float weightedStd = math.sqrt(variance)
    float minimumWidth = math.max(minZoneTicks * syminfo.mintick, atr * minZoneAtr)
    float maximumWidth = math.max(minimumWidth, atr * maxZoneAtr)
    float halfWidth = f_clamp(math.max(weightedStd * dispersionMultiplier, minimumWidth), minimumWidth, maximumWidth)
    cluster.low := cluster.center - halfWidth
    cluster.high := cluster.center + halfWidth
    cluster.weightedMean := cluster.scoreWeightSum / math.max(cluster.weightSum, 0.001)
    cluster.reactionQuality := cluster.reactionWeight > 0.0 ? cluster.reactionWeightSum / cluster.reactionWeight : na
    float redundantMembers = math.max(cluster.memberCount - cluster.diversityCount, 0)
    float diversityScore = f_clamp(25.0 + math.max(cluster.diversityCount - 1, 0) * 20.0 + math.min(redundantMembers, 3) * 5.0, 0.0, 100.0)
    float raw = 0.45 * cluster.weightedMean + 0.25 * cluster.maxScore + 0.20 * diversityScore
    float weights = 0.90
    if not na(cluster.reactionQuality)
        raw += 0.10 * cluster.reactionQuality
        weights += 0.10
    cluster.strength := f_clamp(raw / weights, 0.0, 100.0)
    float distanceToZone = close < cluster.low ? cluster.low - close : close > cluster.high ? close - cluster.high : 0.0
    float distanceAtr = distanceToZone / math.max(atr, syminfo.mintick)
    cluster.actionability := cluster.strength / (1.0 + distanceAtr * actionabilityDecay)
    cluster

f_buildClusters(array<LevelCandidate> registry, float tolerance, float atr) =>
    array<Cluster> result = array.new<Cluster>()
    if registry.size() > 0
        registry.sort(order = order.ascending, sort_field = "price")
        float duplicateTolerance = math.max(syminfo.mintick * 2.0, tolerance * 0.20)
        Cluster current = na
        bool hasCurrent = false
        for candidate in registry
            if not hasCurrent
                current := f_newCluster(candidate)
                hasCurrent := true
            else if math.abs(candidate.price - current.center) <= tolerance
                current := f_clusterAdd(current, candidate, duplicateTolerance)
            else
                result.push(f_finalizeCluster(current, atr))
                current := f_newCluster(candidate)
        if hasCurrent
            result.push(f_finalizeCluster(current, atr))
    result

f_clusterPolarityMatch(Cluster cluster, int polarity) =>
    bool hasSupportAuthority = cluster.supportWeight > 0.0
    bool hasResistanceAuthority = cluster.resistanceWeight > 0.0
    polarity == 1 ? (not hasResistanceAuthority or (hasSupportAuthority and cluster.supportWeight >= cluster.resistanceWeight)) : (not hasSupportAuthority or (hasResistanceAuthority and cluster.resistanceWeight >= cluster.supportWeight))

f_bestClusterIndex(array<Cluster> clusters, int polarity, FlipWatch blockedWatch, float tolerance) =>
    int bestIndex = -1
    float bestActionability = -1.0
    if clusters.size() > 0
        for [i, cluster] in clusters
            bool sideMatch = polarity == 1 ? cluster.center < close : cluster.center > close
            bool polarityMatch = f_clusterPolarityMatch(cluster, polarity)
            bool blocked = blockedWatch.valid and (cluster.high >= blockedWatch.low and cluster.low <= blockedWatch.high or math.abs(cluster.center - blockedWatch.center) <= tolerance * 1.5)
            if sideMatch and polarityMatch and not blocked and cluster.strength >= minimumZoneStrength and cluster.actionability > bestActionability
                bestActionability := cluster.actionability
                bestIndex := i
    bestIndex

f_bestPotentialClusterIndex(array<Cluster> clusters, int polarity) =>
    int bestIndex = -1
    float bestActionability = -1.0
    if clusters.size() > 0
        for [i, cluster] in clusters
            bool sideMatch = polarity == 1 ? cluster.center < close : cluster.center > close
            bool polarityMatch = f_clusterPolarityMatch(cluster, polarity)
            bool potentialStrength = cluster.strength >= potentialZoneMinimumStrength and cluster.strength < minimumZoneStrength
            if sideMatch and polarityMatch and potentialStrength and cluster.actionability > bestActionability
                bestActionability := cluster.actionability
                bestIndex := i
    bestIndex

f_matchingClusterIndex(array<Cluster> clusters, ActiveZone zone, float tolerance) =>
    int matchIndex = -1
    float nearest = 1e12
    if zone.valid and clusters.size() > 0
        for [i, cluster] in clusters
            float distance = math.abs(cluster.center - zone.center)
            bool overlap = cluster.high >= zone.low and cluster.low <= zone.high
            bool polarityMatch = zone.polarityFlipped or f_clusterPolarityMatch(cluster, zone.polarity)
            if cluster.strength >= minimumZoneStrength and polarityMatch and (overlap or distance <= tolerance * 1.5) and distance < nearest
                nearest := distance
                matchIndex := i
    matchIndex

f_zoneFromCluster(Cluster cluster, int polarity, int state, int selectedBar) =>
    ActiveZone.new(
         valid = true,
         center = cluster.center,
         low = cluster.low,
         high = cluster.high,
         strength = cluster.strength,
         actionability = cluster.actionability,
         weightedMean = cluster.weightedMean,
         reactionQuality = cluster.reactionQuality,
         diversityCount = cluster.diversityCount,
         sources = cluster.sources,
         polarity = polarity,
         polarityFlipped = false,
         state = state,
         stateBar = bar_index,
         selectedBar = selectedBar,
         insideCloses = 0,
         beyondCloses = 0,
         missedRefreshes = 0
    )

f_refreshZone(ActiveZone zone, Cluster cluster) =>
    ActiveZone.new(
         valid = zone.valid,
         center = cluster.center,
         low = cluster.low,
         high = cluster.high,
         strength = cluster.strength,
         actionability = cluster.actionability,
         weightedMean = cluster.weightedMean,
         reactionQuality = cluster.reactionQuality,
         diversityCount = cluster.diversityCount,
         sources = cluster.sources,
         polarity = zone.polarity,
         polarityFlipped = zone.polarityFlipped,
         state = zone.state,
         stateBar = zone.stateBar,
         selectedBar = zone.selectedBar,
         insideCloses = zone.insideCloses,
         beyondCloses = zone.beyondCloses,
         missedRefreshes = 0
    )

f_emptyZone() =>
    ActiveZone.new(
         valid = false,
         center = na,
         low = na,
         high = na,
         strength = na,
         actionability = na,
         weightedMean = na,
         reactionQuality = na,
         diversityCount = 0,
         sources = "",
         polarity = 0,
         polarityFlipped = false,
         state = STATE_DISTANT,
         stateBar = na,
         selectedBar = na,
         insideCloses = 0,
         beyondCloses = 0,
         missedRefreshes = 0
    )

f_emptyFlipWatch() =>
    FlipWatch.new(
         valid = false,
         center = na,
         low = na,
         high = na,
         strength = na,
         weightedMean = na,
         reactionQuality = na,
         diversityCount = 0,
         sources = "",
         brokenTime = na
    )

f_zoneToFlipWatch(ActiveZone zone) =>
    FlipWatch.new(
         valid = zone.valid,
         center = zone.center,
         low = zone.low,
         high = zone.high,
         strength = zone.strength,
         weightedMean = zone.weightedMean,
         reactionQuality = zone.reactionQuality,
         diversityCount = zone.diversityCount,
         sources = zone.sources,
         brokenTime = time
    )

f_zoneFromFlipWatch(FlipWatch watch, int polarity) =>
    ActiveZone.new(
         valid = true,
         center = watch.center,
         low = watch.low,
         high = watch.high,
         strength = watch.strength,
         actionability = watch.strength,
         weightedMean = watch.weightedMean,
         reactionQuality = watch.reactionQuality,
         diversityCount = watch.diversityCount,
         sources = watch.sources,
         polarity = polarity,
         polarityFlipped = true,
         state = STATE_FLIPPED,
         stateBar = bar_index,
         selectedBar = bar_index,
         insideCloses = 0,
         beyondCloses = 0,
         missedRefreshes = 0
    )

f_roundSteps(float price) =>
    float minor = price < 5.0 ? 0.10 : price < 20.0 ? 0.25 : price < 100.0 ? 0.50 : price < 500.0 ? 1.0 : 5.0
    float major = price < 5.0 ? 0.25 : price < 20.0 ? 0.50 : price < 100.0 ? 1.0 : price < 500.0 ? 5.0 : 10.0
    [minor, major]

f_addRoundCandidates(array<LevelCandidate> registry, float atr) =>
    [minorStep, majorStep] = f_roundSteps(close)
    float minorBelow = math.floor(close / minorStep) * minorStep
    float minorAbove = minorBelow + minorStep
    float majorBelow = math.floor(close / majorStep) * majorStep
    float majorAbove = majorBelow + majorStep
    float maxDistance = atr * maxDistanceAtr
    f_addCandidate(registry, minorBelow, 25.0, SRC_ROUND, 0, na, na, na, na, 100.0, true, atr, maxDistance)
    f_addCandidate(registry, minorAbove, 25.0, SRC_ROUND, 0, na, na, na, na, 100.0, true, atr, maxDistance)
    f_addCandidate(registry, majorBelow, 25.0, SRC_ROUND, 0, na, na, na, na, 100.0, true, atr, maxDistance)
    f_addCandidate(registry, majorAbove, 25.0, SRC_ROUND, 0, na, na, na, na, 100.0, true, atr, maxDistance)

f_zoneFillColor(int polarity, int state) =>
    color base = polarity == 1 ? supportColor : resistanceColor
    int transparency = state == STATE_INSIDE ? 80 : state == STATE_REJECTING or state == STATE_FLIPPED ? 82 : state == STATE_ACCEPTING ? 84 : state == STATE_APPROACHING ? 88 : state == STATE_BROKEN ? 93 : 92
    color.new(state == STATE_BROKEN ? neutralColor : base, transparency)

f_zoneBorderColor(int polarity, int state) =>
    color base = polarity == 1 ? supportColor : resistanceColor
    int transparency = state == STATE_INSIDE or state == STATE_REJECTING or state == STATE_FLIPPED ? 5 : state == STATE_ACCEPTING ? 15 : state == STATE_APPROACHING ? 35 : state == STATE_BROKEN ? 50 : 65
    color.new(state == STATE_BROKEN ? neutralColor : base, transparency)

f_zoneText(ActiveZone zone) =>
    string side = zone.polarity == 1 ? "SUPPORT" : "RESISTANCE"
    side + " | " + f_stateText(zone.state)

var float currentDayHigh = na
var float currentDayLow = na
var float currentDayClose = na
var float previousDayHigh = na
var float previousDayLow = na
var float previousDayClose = na
var int previousDayKnownBar = na
var float currentSessionOpen = na
var int currentSessionOpenKnownBar = na

bool dailyLevelRoll = premarketStart or (rthStart and not inPremarket[1])
if dailyLevelRoll
    if not na(currentDayHigh)
        previousDayHigh := currentDayHigh
        previousDayLow := currentDayLow
        previousDayClose := currentDayClose
        previousDayKnownBar := bar_index
    currentDayHigh := na
    currentDayLow := na
    currentDayClose := na
    currentSessionOpen := na
    currentSessionOpenKnownBar := na

if rthStart
    currentSessionOpen := open
    currentSessionOpenKnownBar := bar_index
    currentDayHigh := high
    currentDayLow := low
    currentDayClose := close
else if inRth
    currentDayHigh := na(currentDayHigh) ? high : math.max(currentDayHigh, high)
    currentDayLow := na(currentDayLow) ? low : math.min(currentDayLow, low)
    currentDayClose := close

int currentWeekKey = time("1W")
var int trackedWeekKey = na
var float currentWeekHigh = na
var float currentWeekLow = na
var float previousWeekHigh = na
var float previousWeekLow = na
var int previousWeekKnownBar = na
if na(trackedWeekKey)
    trackedWeekKey := currentWeekKey
bool weekChanged = not na(trackedWeekKey) and currentWeekKey != trackedWeekKey
if weekChanged
    if not na(currentWeekHigh)
        previousWeekHigh := currentWeekHigh
        previousWeekLow := currentWeekLow
        previousWeekKnownBar := bar_index
    trackedWeekKey := currentWeekKey
    currentWeekHigh := na
    currentWeekLow := na
if inRth
    currentWeekHigh := na(currentWeekHigh) ? high : math.max(currentWeekHigh, high)
    currentWeekLow := na(currentWeekLow) ? low : math.min(currentWeekLow, low)

var float premarketHigh = na
var float premarketLow = na
var int premarketDayKey = na
var int premarketHighKnownBar = na
var int premarketLowKnownBar = na
if premarketStart
    premarketHigh := high
    premarketLow := low
    premarketDayKey := calendarDayKey
    premarketHighKnownBar := bar_index
    premarketLowKnownBar := bar_index
else if inPremarket
    if na(premarketHigh) or high > premarketHigh
        premarketHigh := high
        premarketHighKnownBar := bar_index
    if na(premarketLow) or low < premarketLow
        premarketLow := low
        premarketLowKnownBar := bar_index
    premarketDayKey := calendarDayKey
if rthStart and (premarketDayKey != calendarDayKey or na(premarketHigh) or na(premarketLow))
    premarketHigh := na
    premarketLow := na
    premarketHighKnownBar := na
    premarketLowKnownBar := na

var int openingRangeEndTime = na
var int openingRangeStartBar = na
var float openingRangeHigh = na
var float openingRangeLow = na
var bool openingRangeFinalized = false
var int openingRangeKnownBar = na
if premarketStart
    openingRangeEndTime := na
    openingRangeStartBar := na
    openingRangeHigh := na
    openingRangeLow := na
    openingRangeFinalized := false
    openingRangeKnownBar := na
if rthStart
    openingRangeEndTime := time + openingRangeMinutes * 60 * 1000
    openingRangeStartBar := bar_index
    openingRangeHigh := high
    openingRangeLow := low
    openingRangeFinalized := false
    openingRangeKnownBar := na
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
    openingRangeKnownBar := bar_index

var bool gapActive = false
var bool gapFilled = false
var int gapDirection = 0
var float gapHigh = na
var float gapLow = na
var float gapMid = na
var float gapUnfilledBoundary = na
var int gapKnownBar = na
if premarketStart
    gapActive := false
    gapFilled := false
    gapDirection := 0
    gapHigh := na
    gapLow := na
    gapMid := na
    gapUnfilledBoundary := na
    gapKnownBar := na
if rthStart
    gapActive := useGapStructure and not na(previousDayClose) and not na(normalizationAtr) and normalizationAtr > 0.0 and math.abs(open - previousDayClose) >= normalizationAtr * gapMinimumAtr
    gapFilled := false
    gapDirection := gapActive ? (open > previousDayClose ? 1 : -1) : 0
    gapHigh := gapActive ? math.max(open, previousDayClose) : na
    gapLow := gapActive ? math.min(open, previousDayClose) : na
    gapMid := gapActive ? (open + previousDayClose) * 0.5 : na
    gapUnfilledBoundary := gapActive ? open : na
    gapKnownBar := gapActive ? bar_index : na
if gapActive and not gapFilled and inRth
    gapUnfilledBoundary := gapDirection == 1 ? math.min(gapUnfilledBoundary, low) : math.max(gapUnfilledBoundary, high)
    gapFilled := gapDirection == 1 ? gapUnfilledBoundary <= gapLow : gapDirection == -1 ? gapUnfilledBoundary >= gapHigh : true

bool dailyVwapAnchor = rthStart
float sessionVwap = ta.vwap(vwapSource, dailyVwapAnchor)
[_, upperBand1, lowerBand1] = ta.vwap(vwapSource, dailyVwapAnchor, vwapBand1Multiplier)
[_, upperBand2, lowerBand2] = ta.vwap(vwapSource, dailyVwapAnchor, vwapBand2Multiplier)
float fastEma = ta.ema(close, 9)
float slowEma = ta.ema(close, 21)

float earningsActual = request.earnings(
     syminfo.tickerid,
     earnings.actual,
     barmerge.gaps_on,
     barmerge.lookahead_off,
     true
)
var int lastEarningsDayKey = na
var bool pendingEventAnchor = false
var bool manualAnchorConsumed = false
var int eventAnchorTime = na
var int eventAnchorBar = na
var float eventCumPV = 0.0
var float eventCumVolume = 0.0
var int eventBars = 0

bool autoEventObserved = eventAnchorMode == "Automatic earnings" and not na(earningsActual) and (na(lastEarningsDayKey) or calendarDayKey != lastEarningsDayKey)
bool manualEventObserved = eventAnchorMode == "Manual timestamp" and manualEventTime > 0 and not manualAnchorConsumed and time >= manualEventTime and (na(time[1]) or time[1] < manualEventTime)
bool startEventAnchorNow = false
if autoEventObserved
    lastEarningsDayKey := calendarDayKey
    if inRth
        startEventAnchorNow := true
    else
        pendingEventAnchor := true
if manualEventObserved
    manualAnchorConsumed := true
    if inRth
        startEventAnchorNow := true
    else
        pendingEventAnchor := true
if pendingEventAnchor and rthStart
    startEventAnchorNow := true
    pendingEventAnchor := false
if startEventAnchorNow
    eventAnchorTime := time
    eventAnchorBar := bar_index
    eventCumPV := 0.0
    eventCumVolume := 0.0
    eventBars := 0
if eventAnchorMode != "Disabled" and not na(eventAnchorTime) and time >= eventAnchorTime
    if not na(volume) and volume > 0.0
        eventCumPV += hlc3 * volume
        eventCumVolume += volume
    eventBars += 1
float eventAvwap = eventCumVolume > 0.0 ? eventCumPV / eventCumVolume : na
float eventAgeDays = not na(eventAnchorTime) ? math.max(time - eventAnchorTime, 0) / 86400000.0 : na
float eventRecency = not na(eventAgeDays) ? 100.0 / (1.0 + eventAgeDays / math.max(eventMaxAgeDays * 0.5, 1.0)) : na
bool eventCandidateValid = eventAnchorMode != "Disabled" and not na(eventAvwap) and eventBars >= eventMinBars and not na(eventAgeDays) and eventAgeDays <= eventMaxAgeDays and not na(normalizationAtr) and math.abs(eventAvwap - close) <= normalizationAtr * eventMaxDistanceAtr

var array<PivotRecord> pivots = array.new<PivotRecord>()
float confirmedPivotHigh = ta.pivothigh(high, pivotLeftBars, pivotRightBars)
float confirmedPivotLow = ta.pivotlow(low, pivotLeftBars, pivotRightBars)
float pivotConfirmationLow = ta.lowest(low, pivotRightBars + 1)
float pivotConfirmationHigh = ta.highest(high, pivotRightBars + 1)
bool newPivotAdded = false

if barstate.isconfirmed and usePivots and not na(confirmedPivotHigh)
    float originAtr = normalizationAtr[pivotRightBars]
    float excursion = not na(originAtr) and originAtr > 0.0 ? (confirmedPivotHigh - pivotConfirmationLow) / originAtr : 0.0
    float baseWeight = f_clamp(55.0 + excursion * 10.0, 55.0, 75.0)
    float originImpulse = f_positiveSat(excursion)
    float originRvol = not na(volumeBaseline[pivotRightBars]) and volumeBaseline[pivotRightBars] > 0.0 ? volume[pivotRightBars] / volumeBaseline[pivotRightBars] : na
    float originVolume = f_participationScore(originRvol)
    bool merged = false
    if pivots.size() > 0
        for pivot in pivots
            if pivot.polarity == -1 and math.abs(pivot.price - confirmedPivotHigh) <= math.max(syminfo.mintick * 2.0, normalizationAtr * 0.015)
                pivot.pivotBar := bar_index - pivotRightBars
                pivot.baseWeight := math.max(pivot.baseWeight, baseWeight)
                pivot.originImpulse := math.max(pivot.originImpulse, originImpulse)
                pivot.originVolume := na(pivot.originVolume) ? originVolume : na(originVolume) ? pivot.originVolume : math.max(pivot.originVolume, originVolume)
                merged := true
                newPivotAdded := true
    if not merged
        pivots.push(PivotRecord.new(confirmedPivotHigh, -1, bar_index - pivotRightBars, bar_index, baseWeight, originImpulse, originVolume))
        newPivotAdded := true

if barstate.isconfirmed and usePivots and not na(confirmedPivotLow)
    float originAtr = normalizationAtr[pivotRightBars]
    float excursion = not na(originAtr) and originAtr > 0.0 ? (pivotConfirmationHigh - confirmedPivotLow) / originAtr : 0.0
    float baseWeight = f_clamp(55.0 + excursion * 10.0, 55.0, 75.0)
    float originImpulse = f_positiveSat(excursion)
    float originRvol = not na(volumeBaseline[pivotRightBars]) and volumeBaseline[pivotRightBars] > 0.0 ? volume[pivotRightBars] / volumeBaseline[pivotRightBars] : na
    float originVolume = f_participationScore(originRvol)
    bool merged = false
    if pivots.size() > 0
        for pivot in pivots
            if pivot.polarity == 1 and math.abs(pivot.price - confirmedPivotLow) <= math.max(syminfo.mintick * 2.0, normalizationAtr * 0.015)
                pivot.pivotBar := bar_index - pivotRightBars
                pivot.baseWeight := math.max(pivot.baseWeight, baseWeight)
                pivot.originImpulse := math.max(pivot.originImpulse, originImpulse)
                pivot.originVolume := na(pivot.originVolume) ? originVolume : na(originVolume) ? pivot.originVolume : math.max(pivot.originVolume, originVolume)
                merged := true
                newPivotAdded := true
    if not merged
        pivots.push(PivotRecord.new(confirmedPivotLow, 1, bar_index - pivotRightBars, bar_index, baseWeight, originImpulse, originVolume))
        newPivotAdded := true

int pivotIndex = 0
while pivotIndex < pivots.size()
    PivotRecord pivot = pivots.get(pivotIndex)
    if bar_index - pivot.pivotBar > pivotMaxAgeBars
        pivots.remove(pivotIndex)
    else
        pivotIndex += 1
while pivots.size() > maxPivotRecords
    pivots.shift()

var ActiveZone supportZone = f_emptyZone()
var ActiveZone resistanceZone = f_emptyZone()
var ActiveZone potentialSupportZone = f_emptyZone()
var ActiveZone potentialResistanceZone = f_emptyZone()
var FlipWatch supportBreakWatch = f_emptyFlipWatch()
var FlipWatch resistanceBreakWatch = f_emptyFlipWatch()

bool enteredSupport = false
bool enteredResistance = false
bool supportRejection = false
bool resistanceRejection = false
bool supportBreakAcceptance = false
bool resistanceBreakAcceptance = false
bool zoneFlip = false
bool higherQualityZoneSelected = false
bool zoneInvalidated = false

if barstate.isconfirmed and supportZone.valid and supportZone.state == STATE_BROKEN and bar_index > supportZone.stateBar
    supportZone := f_emptyZone()
if barstate.isconfirmed and resistanceZone.valid and resistanceZone.state == STATE_BROKEN and bar_index > resistanceZone.stateBar
    resistanceZone := f_emptyZone()

if barstate.isconfirmed and supportZone.valid
    bool overlap = high >= supportZone.low and low <= supportZone.high
    bool beyond = close < supportZone.low
    float distance = close > supportZone.high ? (close - supportZone.high) / math.max(normalizationAtr, syminfo.mintick) : 0.0
    int previousState = supportZone.state
    bool closeInside = close >= supportZone.low and close <= supportZone.high
    enteredSupport := overlap and previousState != STATE_INSIDE and previousState != STATE_REJECTING and previousState != STATE_ACCEPTING and previousState != STATE_FLIPPED
    supportZone.insideCloses := closeInside ? supportZone.insideCloses + 1 : 0
    supportZone.beyondCloses := beyond ? supportZone.beyondCloses + 1 : 0
    bool hardBreak = close < supportZone.low - normalizationAtr * breakDisplacementAtr
    bool confirmedBreak = hardBreak or supportZone.beyondCloses >= breakClosesRequired
    bool rejection = overlap and close > supportZone.high + normalizationAtr * rejectionDisplacementAtr
    bool accepting = (supportZone.insideCloses >= acceptanceClosesRequired or supportZone.beyondCloses > 0) and not rejection and not confirmedBreak
    if confirmedBreak
        supportZone.state := STATE_BROKEN
        supportZone.stateBar := bar_index
        supportBreakWatch := f_zoneToFlipWatch(supportZone)
        supportBreakAcceptance := previousState != STATE_BROKEN
        zoneInvalidated := true
    else if rejection
        supportZone.state := STATE_REJECTING
        supportZone.stateBar := bar_index
        supportRejection := previousState != STATE_REJECTING
    else if accepting
        supportZone.state := STATE_ACCEPTING
        supportZone.stateBar := bar_index
        supportBreakAcceptance := previousState != STATE_ACCEPTING
    else if overlap
        supportZone.state := STATE_INSIDE
        supportZone.stateBar := previousState != STATE_INSIDE ? bar_index : supportZone.stateBar
    else if previousState == STATE_REJECTING or previousState == STATE_ACCEPTING or previousState == STATE_FLIPPED
        if bar_index - supportZone.stateBar > stateHoldBars
            supportZone.state := distance <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT
    else
        supportZone.state := distance <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT

if barstate.isconfirmed and resistanceZone.valid
    bool overlap = high >= resistanceZone.low and low <= resistanceZone.high
    bool beyond = close > resistanceZone.high
    float distance = close < resistanceZone.low ? (resistanceZone.low - close) / math.max(normalizationAtr, syminfo.mintick) : 0.0
    int previousState = resistanceZone.state
    bool closeInside = close >= resistanceZone.low and close <= resistanceZone.high
    enteredResistance := overlap and previousState != STATE_INSIDE and previousState != STATE_REJECTING and previousState != STATE_ACCEPTING and previousState != STATE_FLIPPED
    resistanceZone.insideCloses := closeInside ? resistanceZone.insideCloses + 1 : 0
    resistanceZone.beyondCloses := beyond ? resistanceZone.beyondCloses + 1 : 0
    bool hardBreak = close > resistanceZone.high + normalizationAtr * breakDisplacementAtr
    bool confirmedBreak = hardBreak or resistanceZone.beyondCloses >= breakClosesRequired
    bool rejection = overlap and close < resistanceZone.low - normalizationAtr * rejectionDisplacementAtr
    bool accepting = (resistanceZone.insideCloses >= acceptanceClosesRequired or resistanceZone.beyondCloses > 0) and not rejection and not confirmedBreak
    if confirmedBreak
        resistanceZone.state := STATE_BROKEN
        resistanceZone.stateBar := bar_index
        resistanceBreakWatch := f_zoneToFlipWatch(resistanceZone)
        resistanceBreakAcceptance := previousState != STATE_BROKEN
        zoneInvalidated := true
    else if rejection
        resistanceZone.state := STATE_REJECTING
        resistanceZone.stateBar := bar_index
        resistanceRejection := previousState != STATE_REJECTING
    else if accepting
        resistanceZone.state := STATE_ACCEPTING
        resistanceZone.stateBar := bar_index
        resistanceBreakAcceptance := previousState != STATE_ACCEPTING
    else if overlap
        resistanceZone.state := STATE_INSIDE
        resistanceZone.stateBar := previousState != STATE_INSIDE ? bar_index : resistanceZone.stateBar
    else if previousState == STATE_REJECTING or previousState == STATE_ACCEPTING or previousState == STATE_FLIPPED
        if bar_index - resistanceZone.stateBar > stateHoldBars
            resistanceZone.state := distance <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT
    else
        resistanceZone.state := distance <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT

if barstate.isconfirmed and supportBreakWatch.valid
    bool expired = time - supportBreakWatch.brokenTime > flipWatchMinutes * 60 * 1000
    bool retested = high >= supportBreakWatch.low - normalizationAtr * flipRetestAtr and low <= supportBreakWatch.high + normalizationAtr * flipRetestAtr
    bool heldAsResistance = time > supportBreakWatch.brokenTime and retested and close < supportBreakWatch.low - normalizationAtr * rejectionDisplacementAtr
    if heldAsResistance
        ActiveZone flipped = f_zoneFromFlipWatch(supportBreakWatch, -1)
        bool activateFlip = not resistanceZone.valid or flipped.actionability >= resistanceZone.actionability or flipped.strength >= resistanceZone.strength + switchMargin
        if activateFlip
            resistanceZone := flipped
            zoneFlip := true
        supportBreakWatch := f_emptyFlipWatch()
    else if expired
        supportBreakWatch := f_emptyFlipWatch()

if barstate.isconfirmed and resistanceBreakWatch.valid
    bool expired = time - resistanceBreakWatch.brokenTime > flipWatchMinutes * 60 * 1000
    bool retested = high >= resistanceBreakWatch.low - normalizationAtr * flipRetestAtr and low <= resistanceBreakWatch.high + normalizationAtr * flipRetestAtr
    bool heldAsSupport = time > resistanceBreakWatch.brokenTime and retested and close > resistanceBreakWatch.high + normalizationAtr * rejectionDisplacementAtr
    if heldAsSupport
        ActiveZone flipped = f_zoneFromFlipWatch(resistanceBreakWatch, 1)
        bool activateFlip = not supportZone.valid or flipped.actionability >= supportZone.actionability or flipped.strength >= supportZone.strength + switchMargin
        if activateFlip
            supportZone := flipped
            zoneFlip := true
        resistanceBreakWatch := f_emptyFlipWatch()
    else if expired
        resistanceBreakWatch := f_emptyFlipWatch()

bool rebuildClusters = barstate.isconfirmed and not na(normalizationAtr) and normalizationAtr > 0.0 and (newPivotAdded or rthStart or premarketStart or zoneInvalidated or bar_index % refreshBars == 0)

if rebuildClusters
    array<LevelCandidate> registry = array.new<LevelCandidate>()
    if usePriorDay
        f_addCandidate(registry, previousDayHigh, 75.0, SRC_PDH, -1, na, previousDayKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, previousDayLow, 75.0, SRC_PDL, 1, na, previousDayKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, previousDayClose, 55.0, SRC_PDC, 0, na, previousDayKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
    if usePriorWeek
        f_addCandidate(registry, previousWeekHigh, 80.0, SRC_PWH, -1, na, previousWeekKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, previousWeekLow, 80.0, SRC_PWL, 1, na, previousWeekKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
    if usePremarket and not na(premarketHigh) and not na(premarketLow)
        float premarketWeight = inPremarket ? 55.0 : 65.0
        f_addCandidate(registry, premarketHigh, premarketWeight, SRC_PMH, -1, na, premarketHighKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, premarketLow, premarketWeight, SRC_PML, 1, na, premarketLowKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
    if useOpeningRange and openingRangeFinalized
        f_addCandidate(registry, openingRangeHigh, 65.0, SRC_ORH, -1, na, openingRangeKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, openingRangeLow, 65.0, SRC_ORL, 1, na, openingRangeKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
    if useRthOpen
        float rthOpenWeight = gapActive and not gapFilled ? 55.0 : 45.0
        f_addCandidate(registry, currentSessionOpen, rthOpenWeight, SRC_RTH_OPEN, 0, na, currentSessionOpenKnownBar, na, na, na, true, normalizationAtr, maximumCandidateDistance)
    if useGapStructure and gapActive and not gapFilled
        if not usePriorDay
            f_addCandidate(registry, previousDayClose, 55.0, SRC_GAP, 0, na, gapKnownBar, na, na, 100.0, true, normalizationAtr, maximumCandidateDistance)
        if not useRthOpen
            f_addCandidate(registry, currentSessionOpen, 55.0, SRC_GAP, 0, na, gapKnownBar, na, na, 100.0, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, gapMid, 35.0, SRC_GAP, 0, na, gapKnownBar, na, na, 100.0, true, normalizationAtr, maximumCandidateDistance)
        bool distinctGapBoundary = not na(gapUnfilledBoundary) and math.abs(gapUnfilledBoundary - gapMid) > clusterTolerance and math.abs(gapUnfilledBoundary - currentSessionOpen) > clusterTolerance and math.abs(gapUnfilledBoundary - previousDayClose) > clusterTolerance
        if distinctGapBoundary
            int gapBoundaryPolarity = gapDirection == 1 ? 1 : -1
            f_addCandidate(registry, gapUnfilledBoundary, 55.0, SRC_GAP, gapBoundaryPolarity, bar_index, bar_index, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
    if useVwapCandidate and inRth and not na(sessionVwap)
        f_addCandidate(registry, sessionVwap, 55.0, SRC_VWAP, 0, bar_index, bar_index, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
    if useVwapBandCandidates and inRth
        f_addCandidate(registry, upperBand1, 25.0, SRC_VWAP_BAND, -1, bar_index, bar_index, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, lowerBand1, 25.0, SRC_VWAP_BAND, 1, bar_index, bar_index, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
    if eventCandidateValid
        float eventSourceWeight = 75.0 * (0.60 + 0.40 * eventRecency / 100.0)
        f_addCandidate(registry, eventAvwap, eventSourceWeight, SRC_EVENT_AVWAP, 0, eventAnchorBar, eventAnchorBar, na, na, eventRecency, false, normalizationAtr, maximumCandidateDistance)
    if useAtrProjections and not na(currentSessionOpen)
        f_addCandidate(registry, currentSessionOpen + normalizationAtr * 0.5, 20.0, SRC_ATR_PROJECTION, -1, currentSessionOpenKnownBar, currentSessionOpenKnownBar, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, currentSessionOpen - normalizationAtr * 0.5, 20.0, SRC_ATR_PROJECTION, 1, currentSessionOpenKnownBar, currentSessionOpenKnownBar, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, currentSessionOpen + normalizationAtr, 20.0, SRC_ATR_PROJECTION, -1, currentSessionOpenKnownBar, currentSessionOpenKnownBar, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, currentSessionOpen - normalizationAtr, 20.0, SRC_ATR_PROJECTION, 1, currentSessionOpenKnownBar, currentSessionOpenKnownBar, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
        if not na(previousDayClose)
            f_addCandidate(registry, previousDayClose + normalizationAtr, 20.0, SRC_ATR_PROJECTION, -1, previousDayKnownBar, previousDayKnownBar, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
            f_addCandidate(registry, previousDayClose - normalizationAtr, 20.0, SRC_ATR_PROJECTION, 1, previousDayKnownBar, previousDayKnownBar, na, na, 100.0, false, normalizationAtr, maximumCandidateDistance)
    if useRoundNumbers
        f_addRoundCandidates(registry, normalizationAtr)
    if usePivots and pivots.size() > 0
        for pivot in pivots
            int sourceType = pivot.polarity == -1 ? SRC_PIVOT_HIGH : SRC_PIVOT_LOW
            f_addCandidate(registry, pivot.price, pivot.baseWeight, sourceType, pivot.polarity, pivot.pivotBar, pivot.knownBar, pivot.originImpulse, pivot.originVolume, na, true, normalizationAtr, maximumCandidateDistance)
    if useEqualHighLow and pivots.size() >= 2
        float equalTolerance = math.max(equalLevelMinimumTicks * syminfo.mintick, normalizationAtr * equalLevelToleranceAtr)
        [equalHigh, equalHighKnownBar, equalHighOriginBar] = f_latestEqualLevel(pivots, -1, equalTolerance, equalLevelMaxAgeBars)
        [equalLow, equalLowKnownBar, equalLowOriginBar] = f_latestEqualLevel(pivots, 1, equalTolerance, equalLevelMaxAgeBars)
        f_addCandidate(registry, equalHigh, 70.0, SRC_EQH, -1, equalHighOriginBar, equalHighKnownBar, na, na, 92.0, true, normalizationAtr, maximumCandidateDistance)
        f_addCandidate(registry, equalLow, 70.0, SRC_EQL, 1, equalLowOriginBar, equalLowKnownBar, na, na, 92.0, true, normalizationAtr, maximumCandidateDistance)

    array<Cluster> clusters = f_buildClusters(registry, clusterTolerance, normalizationAtr)
    int supportMatchIndex = f_matchingClusterIndex(clusters, supportZone, clusterTolerance)
    int resistanceMatchIndex = f_matchingClusterIndex(clusters, resistanceZone, clusterTolerance)
    if supportZone.valid
        if supportMatchIndex >= 0
            supportZone := f_refreshZone(supportZone, clusters.get(supportMatchIndex))
        else
            supportZone.missedRefreshes += 1
            if supportZone.missedRefreshes >= 3 or rthStart or premarketStart
                supportZone := f_emptyZone()
    if resistanceZone.valid
        if resistanceMatchIndex >= 0
            resistanceZone := f_refreshZone(resistanceZone, clusters.get(resistanceMatchIndex))
        else
            resistanceZone.missedRefreshes += 1
            if resistanceZone.missedRefreshes >= 3 or rthStart or premarketStart
                resistanceZone := f_emptyZone()

    int bestSupportIndex = f_bestClusterIndex(clusters, 1, resistanceBreakWatch, clusterTolerance)
    int bestResistanceIndex = f_bestClusterIndex(clusters, -1, supportBreakWatch, clusterTolerance)
    int potentialSupportIndex = f_bestPotentialClusterIndex(clusters, 1)
    int potentialResistanceIndex = f_bestPotentialClusterIndex(clusters, -1)
    potentialSupportZone := potentialSupportIndex >= 0 ? f_zoneFromCluster(clusters.get(potentialSupportIndex), 1, STATE_DISTANT, bar_index) : f_emptyZone()
    potentialResistanceZone := potentialResistanceIndex >= 0 ? f_zoneFromCluster(clusters.get(potentialResistanceIndex), -1, STATE_DISTANT, bar_index) : f_emptyZone()

    if bestSupportIndex >= 0
        Cluster bestSupport = clusters.get(bestSupportIndex)
        bool sameSupport = supportZone.valid and (bestSupport.high >= supportZone.low and bestSupport.low <= supportZone.high or math.abs(bestSupport.center - supportZone.center) <= clusterTolerance * 1.5)
        if not supportZone.valid
            int initialState = bestSupport.high >= low and bestSupport.low <= high ? STATE_INSIDE : math.abs(bestSupport.center - close) / normalizationAtr <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT
            supportZone := f_zoneFromCluster(bestSupport, 1, initialState, bar_index)
            enteredSupport := enteredSupport or initialState == STATE_INSIDE
        else if not sameSupport and (bestSupport.strength >= supportZone.strength + switchMargin or bestSupport.actionability >= supportZone.actionability + switchMargin)
            int replacementState = bestSupport.high >= low and bestSupport.low <= high ? STATE_INSIDE : math.abs(bestSupport.center - close) / normalizationAtr <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT
            supportZone := f_zoneFromCluster(bestSupport, 1, replacementState, bar_index)
            enteredSupport := enteredSupport or replacementState == STATE_INSIDE
            higherQualityZoneSelected := true

    if bestResistanceIndex >= 0
        Cluster bestResistance = clusters.get(bestResistanceIndex)
        bool sameResistance = resistanceZone.valid and (bestResistance.high >= resistanceZone.low and bestResistance.low <= resistanceZone.high or math.abs(bestResistance.center - resistanceZone.center) <= clusterTolerance * 1.5)
        if not resistanceZone.valid
            int initialState = bestResistance.high >= low and bestResistance.low <= high ? STATE_INSIDE : math.abs(bestResistance.center - close) / normalizationAtr <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT
            resistanceZone := f_zoneFromCluster(bestResistance, -1, initialState, bar_index)
            enteredResistance := enteredResistance or initialState == STATE_INSIDE
        else if not sameResistance and (bestResistance.strength >= resistanceZone.strength + switchMargin or bestResistance.actionability >= resistanceZone.actionability + switchMargin)
            int replacementState = bestResistance.high >= low and bestResistance.low <= high ? STATE_INSIDE : math.abs(bestResistance.center - close) / normalizationAtr <= approachingAtr ? STATE_APPROACHING : STATE_DISTANT
            resistanceZone := f_zoneFromCluster(bestResistance, -1, replacementState, bar_index)
            enteredResistance := enteredResistance or replacementState == STATE_INSIDE
            higherQualityZoneSelected := true

bool showKeyReferences = displayMode == "Zones + Key References" or displayMode == "Legacy / Debug"
bool showLegacy = displayMode == "Legacy / Debug"
bool showCurrentVwapBar = inRth

plot(showKeyReferences and usePriorDay ? previousDayHigh : na, "PDH", color.new(neutralColor, 35), 1, plot.style_linebr)
plot(showKeyReferences and usePriorDay ? previousDayLow : na, "PDL", color.new(neutralColor, 35), 1, plot.style_linebr)
plot(showKeyReferences and usePriorDay ? previousDayClose : na, "PDC", color.new(informationColor, 45), 1, plot.style_linebr)
plot(showKeyReferences and usePriorWeek ? previousWeekHigh : na, "PWH", color.new(neutralColor, 55), 1, plot.style_linebr)
plot(showKeyReferences and usePriorWeek ? previousWeekLow : na, "PWL", color.new(neutralColor, 55), 1, plot.style_linebr)
plot(showKeyReferences and usePremarket ? premarketHigh : na, "PMH", color.new(informationColor, 45), 1, plot.style_linebr)
plot(showKeyReferences and usePremarket ? premarketLow : na, "PML", color.new(informationColor, 45), 1, plot.style_linebr)
plot(showKeyReferences and useOpeningRange ? openingRangeHigh : na, "ORH", color.new(warningColor, 40), 1, plot.style_linebr)
plot(showKeyReferences and useOpeningRange ? openingRangeLow : na, "ORL", color.new(warningColor, 40), 1, plot.style_linebr)
plot(showKeyReferences and useRthOpen ? currentSessionOpen : na, "RTH Open", color.new(informationColor, 50), 1, plot.style_linebr)

plot(showVwap and showCurrentVwapBar ? sessionVwap : na, "RTH VWAP", #C4B5FD, 2, plot.style_linebr)
plot(showVwap and showVwapBand1 and showCurrentVwapBar ? upperBand1 : na, "VWAP +1 stdev", color.new(brandColor, 35), 1, plot.style_linebr)
plot(showVwap and showVwapBand1 and showCurrentVwapBar ? lowerBand1 : na, "VWAP -1 stdev", color.new(brandColor, 35), 1, plot.style_linebr)
plot(showVwap and showVwapBand2 and showCurrentVwapBar ? upperBand2 : na, "VWAP +2 stdev", color.new(brandColor, 55), 1, plot.style_linebr)
plot(showVwap and showVwapBand2 and showCurrentVwapBar ? lowerBand2 : na, "VWAP -2 stdev", color.new(brandColor, 55), 1, plot.style_linebr)
plot(showLegacy and showEmas ? fastEma : na, "EMA 9", color.new(#38BDF8, 25), 1)
plot(showLegacy and showEmas ? slowEma : na, "EMA 21", color.new(warningColor, 25), 1)
plot(showLegacy and eventCandidateValid ? eventAvwap : na, "Event AVWAP", color.new(brandColor, 15), 2, plot.style_linebr)
plot(showLegacy and gapActive and not gapFilled ? gapHigh : na, "Active Gap High", color.new(resistanceColor, 65), 1, plot.style_linebr)
plot(showLegacy and gapActive and not gapFilled ? gapLow : na, "Active Gap Low", color.new(supportColor, 65), 1, plot.style_linebr)
plot(showLegacy and gapActive and not gapFilled ? gapMid : na, "Active Gap Mid", color.new(#FBBF24, 70), 1, plot.style_linebr)
plot(showLegacy and gapActive and not gapFilled ? gapUnfilledBoundary : na, "Active Gap Unfilled Boundary", color.new(#F8FAFC, 55), 1, plot.style_linebr)

var box potentialSupportBox = na
var box potentialResistanceBox = na
var box openingRangeBox = na
var box supportBox = na
var box resistanceBox = na
var box buyEdgeBox = na
var box sellEdgeBox = na
if barstate.islast
    if showPotentialZones and potentialSupportZone.valid
        int leftIndex = math.max(potentialSupportZone.selectedBar, bar_index - zoneBackBars)
        int rightIndex = bar_index + zoneForwardBars
        if na(potentialSupportBox)
            potentialSupportBox := box.new(left = leftIndex, top = potentialSupportZone.high, right = rightIndex, bottom = potentialSupportZone.low, xloc = xloc.bar_index, border_color = color.new(pendingColor, 72), border_width = 1, border_style = line.style_dotted, bgcolor = color.new(pendingColor, 93), text = "POTENTIAL SUPPORT", text_size = size.tiny, text_color = color.new(pendingColor, 10), text_halign = text.align_right, text_valign = text.align_center, text_font_family = font.family_monospace)
        else
            potentialSupportBox.set_left(leftIndex)
            potentialSupportBox.set_right(rightIndex)
            potentialSupportBox.set_top(potentialSupportZone.high)
            potentialSupportBox.set_bottom(potentialSupportZone.low)
    else if not na(potentialSupportBox)
        potentialSupportBox.delete()
        potentialSupportBox := na

    if showPotentialZones and potentialResistanceZone.valid
        int leftIndex = math.max(potentialResistanceZone.selectedBar, bar_index - zoneBackBars)
        int rightIndex = bar_index + zoneForwardBars
        if na(potentialResistanceBox)
            potentialResistanceBox := box.new(left = leftIndex, top = potentialResistanceZone.high, right = rightIndex, bottom = potentialResistanceZone.low, xloc = xloc.bar_index, border_color = color.new(pendingColor, 72), border_width = 1, border_style = line.style_dotted, bgcolor = color.new(pendingColor, 93), text = "POTENTIAL RESISTANCE", text_size = size.tiny, text_color = color.new(pendingColor, 10), text_halign = text.align_right, text_valign = text.align_center, text_font_family = font.family_monospace)
        else
            potentialResistanceBox.set_left(leftIndex)
            potentialResistanceBox.set_right(rightIndex)
            potentialResistanceBox.set_top(potentialResistanceZone.high)
            potentialResistanceBox.set_bottom(potentialResistanceZone.low)
    else if not na(potentialResistanceBox)
        potentialResistanceBox.delete()
        potentialResistanceBox := na

    if showOpeningRangeBox and useOpeningRange and not na(openingRangeStartBar) and not na(openingRangeHigh) and not na(openingRangeLow)
        int orLeft = openingRangeStartBar
        int orRight = openingRangeFinalized and not na(openingRangeKnownBar) ? openingRangeKnownBar : bar_index
        if na(openingRangeBox)
            openingRangeBox := box.new(
                 left = orLeft,
                 top = openingRangeHigh,
                 right = orRight,
                 bottom = openingRangeLow,
                 xloc = xloc.bar_index,
                 border_color = color.new(warningColor, 55),
                 border_width = 1,
                 bgcolor = color.new(warningColor, 90)
            )
        else
            openingRangeBox.set_left(orLeft)
            openingRangeBox.set_right(orRight)
            openingRangeBox.set_top(openingRangeHigh)
            openingRangeBox.set_bottom(openingRangeLow)
            openingRangeBox.set_border_color(color.new(warningColor, 55))
            openingRangeBox.set_bgcolor(color.new(warningColor, 90))
    else if not na(openingRangeBox)
        openingRangeBox.delete()
        openingRangeBox := na
    if supportZone.valid
        int leftIndex = math.max(supportZone.selectedBar, bar_index - zoneBackBars)
        int rightIndex = bar_index + zoneForwardBars
        if na(supportBox)
            supportBox := box.new(
                 left = leftIndex,
                 top = supportZone.high,
                 right = rightIndex,
                 bottom = supportZone.low,
                 xloc = xloc.bar_index,
                 border_color = f_zoneBorderColor(1, supportZone.state),
                 border_width = supportZone.state == STATE_INSIDE or supportZone.state == STATE_REJECTING or supportZone.state == STATE_FLIPPED ? 2 : 1,
                 bgcolor = f_zoneFillColor(1, supportZone.state),
                 text = showZoneText ? f_zoneText(supportZone) : "",
                 text_size = size.tiny,
                 text_color = color.new(supportColor, 0),
                 text_halign = text.align_right,
                 text_valign = text.align_center,
                 text_wrap = text.wrap_none,
                 text_font_family = font.family_monospace
            )
        else
            supportBox.set_left(leftIndex)
            supportBox.set_right(rightIndex)
            supportBox.set_top(supportZone.high)
            supportBox.set_bottom(supportZone.low)
            supportBox.set_bgcolor(f_zoneFillColor(1, supportZone.state))
            supportBox.set_border_color(f_zoneBorderColor(1, supportZone.state))
            supportBox.set_border_width(supportZone.state == STATE_INSIDE or supportZone.state == STATE_REJECTING or supportZone.state == STATE_FLIPPED ? 2 : 1)
            supportBox.set_text(showZoneText ? f_zoneText(supportZone) : "")
            supportBox.set_text_color(color.new(supportColor, 0))
    else if not na(supportBox)
        supportBox.delete()
        supportBox := na

    bool showBuyEdge = showEdgeSignals and supportZone.valid and supportZone.state != STATE_BROKEN and supportZone.actionability >= edgeSignalMinimumActionability
    if showBuyEdge
        int leftIndex = math.max(supportZone.selectedBar, bar_index - zoneBackBars)
        int rightIndex = bar_index + zoneForwardBars
        float parentHeight = math.max(supportZone.high - supportZone.low, syminfo.mintick * 2.0)
        float edgeHeight = math.max(parentHeight * edgeSignalHeightPct, syminfo.mintick * 2.0)
        float edgeTop = math.min(supportZone.low + edgeHeight, supportZone.high)
        if na(buyEdgeBox)
            buyEdgeBox := box.new(
                 left = leftIndex,
                 top = edgeTop,
                 right = rightIndex,
                 bottom = supportZone.low,
                 xloc = xloc.bar_index,
                 border_color = color.new(supportColor, 0),
                 border_width = 1,
                 bgcolor = color.new(supportColor, 72),
                 text = showZoneText ? "BUY" : "",
                 text_size = size.tiny,
                 text_color = color.new(textColor, 0),
                 text_halign = text.align_right,
                 text_valign = text.align_center,
                 text_wrap = text.wrap_none,
                 text_font_family = font.family_monospace
            )
        else
            buyEdgeBox.set_left(leftIndex)
            buyEdgeBox.set_right(rightIndex)
            buyEdgeBox.set_top(edgeTop)
            buyEdgeBox.set_bottom(supportZone.low)
            buyEdgeBox.set_border_color(color.new(supportColor, 0))
            buyEdgeBox.set_bgcolor(color.new(supportColor, 72))
            buyEdgeBox.set_text(showZoneText ? "BUY" : "")
            buyEdgeBox.set_text_color(color.new(textColor, 0))
    else if not na(buyEdgeBox)
        buyEdgeBox.delete()
        buyEdgeBox := na

    if resistanceZone.valid
        int leftIndex = math.max(resistanceZone.selectedBar, bar_index - zoneBackBars)
        int rightIndex = bar_index + zoneForwardBars
        if na(resistanceBox)
            resistanceBox := box.new(
                 left = leftIndex,
                 top = resistanceZone.high,
                 right = rightIndex,
                 bottom = resistanceZone.low,
                 xloc = xloc.bar_index,
                 border_color = f_zoneBorderColor(-1, resistanceZone.state),
                 border_width = resistanceZone.state == STATE_INSIDE or resistanceZone.state == STATE_REJECTING or resistanceZone.state == STATE_FLIPPED ? 2 : 1,
                 bgcolor = f_zoneFillColor(-1, resistanceZone.state),
                 text = showZoneText ? f_zoneText(resistanceZone) : "",
                 text_size = size.tiny,
                 text_color = color.new(resistanceColor, 0),
                 text_halign = text.align_right,
                 text_valign = text.align_center,
                 text_wrap = text.wrap_none,
                 text_font_family = font.family_monospace
            )
        else
            resistanceBox.set_left(leftIndex)
            resistanceBox.set_right(rightIndex)
            resistanceBox.set_top(resistanceZone.high)
            resistanceBox.set_bottom(resistanceZone.low)
            resistanceBox.set_bgcolor(f_zoneFillColor(-1, resistanceZone.state))
            resistanceBox.set_border_color(f_zoneBorderColor(-1, resistanceZone.state))
            resistanceBox.set_border_width(resistanceZone.state == STATE_INSIDE or resistanceZone.state == STATE_REJECTING or resistanceZone.state == STATE_FLIPPED ? 2 : 1)
            resistanceBox.set_text(showZoneText ? f_zoneText(resistanceZone) : "")
            resistanceBox.set_text_color(color.new(resistanceColor, 0))
    else if not na(resistanceBox)
        resistanceBox.delete()
        resistanceBox := na

    bool showSellEdge = showEdgeSignals and resistanceZone.valid and resistanceZone.state != STATE_BROKEN and resistanceZone.actionability >= edgeSignalMinimumActionability
    if showSellEdge
        int leftIndex = math.max(resistanceZone.selectedBar, bar_index - zoneBackBars)
        int rightIndex = bar_index + zoneForwardBars
        float parentHeight = math.max(resistanceZone.high - resistanceZone.low, syminfo.mintick * 2.0)
        float edgeHeight = math.max(parentHeight * edgeSignalHeightPct, syminfo.mintick * 2.0)
        float edgeBottom = math.max(resistanceZone.high - edgeHeight, resistanceZone.low)
        if na(sellEdgeBox)
            sellEdgeBox := box.new(
                 left = leftIndex,
                 top = resistanceZone.high,
                 right = rightIndex,
                 bottom = edgeBottom,
                 xloc = xloc.bar_index,
                 border_color = color.new(resistanceColor, 0),
                 border_width = 1,
                 bgcolor = color.new(resistanceColor, 72),
                 text = showZoneText ? "SELL" : "",
                 text_size = size.tiny,
                 text_color = color.new(textColor, 0),
                 text_halign = text.align_right,
                 text_valign = text.align_center,
                 text_wrap = text.wrap_none,
                 text_font_family = font.family_monospace
            )
        else
            sellEdgeBox.set_left(leftIndex)
            sellEdgeBox.set_right(rightIndex)
            sellEdgeBox.set_top(resistanceZone.high)
            sellEdgeBox.set_bottom(edgeBottom)
            sellEdgeBox.set_border_color(color.new(resistanceColor, 0))
            sellEdgeBox.set_bgcolor(color.new(resistanceColor, 72))
            sellEdgeBox.set_text(showZoneText ? "SELL" : "")
            sellEdgeBox.set_text_color(color.new(textColor, 0))
    else if not na(sellEdgeBox)
        sellEdgeBox.delete()
        sellEdgeBox := na

var table dashboard = na
if barstate.isfirst and showDashboard
    dashboard := table.new(position.top_right, 2, 5, bgcolor = color.new(panelColor, 10), frame_color = color.new(borderColor, 35), frame_width = 1, border_color = color.new(borderColor, 70), border_width = 1)
    table.merge_cells(dashboard, 0, 0, 1, 0)
if barstate.islast and showDashboard and not na(dashboard)
    string resistanceText = resistanceZone.valid ? str.tostring(resistanceZone.low, format.mintick) + "–" + str.tostring(resistanceZone.high, format.mintick) : "n/a"
    string supportText = supportZone.valid ? str.tostring(supportZone.low, format.mintick) + "–" + str.tostring(supportZone.high, format.mintick) : "n/a"
    string nearestState = "n/a"
    color nearestColor = neutralColor
    if supportZone.valid and resistanceZone.valid
        bool supportNearest = math.abs(close - supportZone.center) <= math.abs(resistanceZone.center - close)
        nearestState := (supportNearest ? "SUPPORT · " + f_stateText(supportZone.state) : "RESISTANCE · " + f_stateText(resistanceZone.state))
        nearestColor := supportNearest ? supportColor : resistanceColor
    else if supportZone.valid
        nearestState := "SUPPORT · " + f_stateText(supportZone.state)
        nearestColor := supportColor
    else if resistanceZone.valid
        nearestState := "RESISTANCE · " + f_stateText(resistanceZone.state)
        nearestColor := resistanceColor
    string sessionText = inRth ? "RTH" : inPremarket ? "PREMARKET" : "OUTSIDE"
    color sessionColor = inRth ? supportColor : inPremarket ? informationColor : neutralColor
    color rowBackground = color.new(panelColor, 12)
    color alternateBackground = color.new(panelColor, 5)
    table.cell(dashboard, 0, 0, "Meridian -- Options Levels", bgcolor = color.new(brandColor, 18), text_color = textColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_left)
    table.cell(dashboard, 0, 1, "Resistance", bgcolor = rowBackground, text_color = resistanceColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_left)
    table.cell(dashboard, 1, 1, resistanceText, bgcolor = rowBackground, text_color = textColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_right)
    table.cell(dashboard, 0, 2, "Support", bgcolor = alternateBackground, text_color = supportColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_left)
    table.cell(dashboard, 1, 2, supportText, bgcolor = alternateBackground, text_color = textColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_right)
    table.cell(dashboard, 0, 3, "Nearest", bgcolor = rowBackground, text_color = neutralColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_left)
    table.cell(dashboard, 1, 3, nearestState, bgcolor = rowBackground, text_color = nearestColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_right)
    table.cell(dashboard, 0, 4, "Session", bgcolor = alternateBackground, text_color = neutralColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_left)
    table.cell(dashboard, 1, 4, sessionText, bgcolor = alternateBackground, text_color = sessionColor, text_size = f_textSize(dashboardSizeInput), text_halign = text.align_right)

float nearestZoneState = na
if supportZone.valid and resistanceZone.valid
    nearestZoneState := math.abs(close - supportZone.center) <= math.abs(resistanceZone.center - close) ? supportZone.state : -resistanceZone.state
else if supportZone.valid
    nearestZoneState := supportZone.state
else if resistanceZone.valid
    nearestZoneState := -resistanceZone.state

plot(supportZone.valid ? supportZone.low : na, "Support Zone Low", display = showResearchValues ? display.data_window : display.none)
plot(supportZone.valid ? supportZone.high : na, "Support Zone High", display = showResearchValues ? display.data_window : display.none)
plot(supportZone.valid ? supportZone.strength : na, "Support Zone Strength", display = showResearchValues ? display.data_window : display.none)
plot(resistanceZone.valid ? resistanceZone.low : na, "Resistance Zone Low", display = showResearchValues ? display.data_window : display.none)
plot(resistanceZone.valid ? resistanceZone.high : na, "Resistance Zone High", display = showResearchValues ? display.data_window : display.none)
plot(resistanceZone.valid ? resistanceZone.strength : na, "Resistance Zone Strength", display = showResearchValues ? display.data_window : display.none)
plot(nearestZoneState, "Zone State", display = showResearchValues ? display.data_window : display.none)
plot(supportZone.valid ? supportZone.actionability : na, "Support Zone Actionability", display = showResearchValues ? display.data_window : display.none)
plot(resistanceZone.valid ? resistanceZone.actionability : na, "Resistance Zone Actionability", display = showResearchValues ? display.data_window : display.none)
plot(potentialSupportZone.valid ? potentialSupportZone.strength : na, "Potential Support Strength", display = showResearchValues ? display.data_window : display.none)
plot(potentialResistanceZone.valid ? potentialResistanceZone.strength : na, "Potential Resistance Strength", display = showResearchValues ? display.data_window : display.none)
plot(gapActive and not gapFilled ? gapHigh : na, "Gap High", display = display.none)
plot(gapActive and not gapFilled ? gapLow : na, "Gap Low", display = display.none)
plot(gapActive and not gapFilled ? gapMid : na, "Gap Mid", display = display.none)
plot(gapActive and not gapFilled ? gapUnfilledBoundary : na, "Gap Unfilled Boundary", display = display.none)

alertcondition(enableAlerts and barstate.isconfirmed and enteredResistance, "Entered Resistance Zone", "{{ticker}} entered the active Meridian resistance reaction zone at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and enteredSupport, "Entered Support Zone", "{{ticker}} entered the active Meridian support reaction zone at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and resistanceRejection, "Resistance Rejection", "{{ticker}} rejected the active Meridian resistance reaction zone at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and supportRejection, "Support Rejection", "{{ticker}} rejected the active Meridian support reaction zone at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and resistanceBreakAcceptance, "Resistance Break / Acceptance", "{{ticker}} is accepting through or has broken the active Meridian resistance zone at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and supportBreakAcceptance, "Support Break / Acceptance", "{{ticker}} is accepting through or has broken the active Meridian support zone at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and zoneFlip, "Zone Flip", "{{ticker}} confirmed a Meridian reaction-zone flip at {{close}}.")
alertcondition(enableAlerts and barstate.isconfirmed and higherQualityZoneSelected, "New Higher-Quality Zone Selected", "{{ticker}} selected a materially stronger Meridian reaction zone at {{close}}.")

Overview

Meridian - Options Levels groups nearby candidate levels using confirmed daily ATR distance and tick or percent tolerances, then sizes each zone between minimum and maximum ATR half-widths. Optional buy and sell edge bands appear when actionability clears a threshold. Session settings define the New York regular and premarket windows, weekdays, and an opening-range box. Display mode, zone history, forward projection, and a small dashboard control what is shown. It is an indicator with no trade entries or exits.

Details

Published by

sukesan7 on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Support & resistance, Sessions & time, Screeners & dashboards

Markets

Stocks, Indices

Timeframes

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
Session timezonestringAmerica/New_YorksessionTimezone
Regular sessionsession0930-1600rthInput
Premarket sessionsession0400-0930premarketInput
Monday-Friday onlybooltrueweekdaysOnly
Opening-range minutesint15openingRangeMinutes
Display modestringReaction ZonesdisplayMode
Show zone textbooltrueshowZoneText
Show dashboardbooltrueshowDashboard
Dashboard text sizestringSmalldashboardSizeInput
Zone history barsint12zoneBackBars
Zone projection barsint40zoneForwardBars
Show buy / sell edge zonesbooltrueshowEdgeSignals
Show all 40 settings
SettingTypeDefaultVariable
Edge zone minimum actionabilityfloat55.0edgeSignalMinimumActionability
Edge zone height (% of parent zone)float0.20edgeSignalHeightPct
Highlight opening-range windowbooltrueshowOpeningRangeBox
Confirmed daily ATR lengthint14atrLength
Maximum candidate distance (daily ATR)float2.5maxDistanceAtr
Cluster tolerance (daily ATR)float0.08clusterAtrFraction
Minimum cluster tolerance (ticks)int4clusterMinimumTicks
Minimum cluster tolerance (% as decimal)float0.0005clusterMinimumPct
Minimum zone half-width (daily ATR)float0.02minZoneAtr
Maximum zone half-width (daily ATR)float0.12maxZoneAtr
Minimum zone half-width (ticks)int4minZoneTicks
Cluster dispersion multiplierfloat1.25dispersionMultiplier
Distance decayfloat0.45actionabilityDecay
Zone switch marginfloat10.0switchMargin
Minimum zone strengthfloat45.0minimumZoneStrength
Cluster refresh barsint3refreshBars
Approaching distance (daily ATR)float0.20approachingAtr
Rejection displacement (daily ATR)float0.03rejectionDisplacementAtr
Break displacement (daily ATR)float0.04breakDisplacementAtr
Closes beyond zone for breakint2breakClosesRequired
Closes in / through zone for acceptanceint2acceptanceClosesRequired
Event-state hold barsint3stateHoldBars
Broken-zone flip watch (minutes)int30flipWatchMinutes
Flip retest tolerance (daily ATR)float0.04flipRetestAtr
Previous day / closebooltrueusePriorDay
Previous weekbooltrueusePriorWeek
Premarket high / lowbooltrueusePremarket
Finalized opening rangebooltrueuseOpeningRange
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

575 stars

This indicator builds Market Profile Time Price Opportunity (TPO) structures across defined session timeframes directly on the main price chart. It visualizes the Point of Control (POC) and Value Area boundaries using dynamic box drawings. An integrated multi-

Indicator v5 Support & resistanceScreeners & dashboards
Updated 1 year ago · MPL-2.0 GitHub repository of Market Profile with Screener
257 stars

This indicator overlays an intraday SPX and ES chart with a midnight session range, regular-trading-hours opening levels, and a VWAP projection box. It draws the midnight high, mid, and low, flags edge rejections within an absolute tolerance, and plots optiona

Indicator v6 TrendSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of SPX Midnight Box + Snag Logic

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

Session Edge Probabilities

by TraderOracle
61 stars

Session Edge Probabilities tracks eight session-based trading rules using statistical probabilities evaluated on historical chart data. It utilizes a fixed calculation timeframe to evaluate opening ranges, midnight levels, afternoon reversal windows, and price

Indicator v6 Sessions & timeScreeners & dashboards
Updated 9 days ago · None stated GitHub repository of Session Edge Probabilities
56 stars

This indicator marks forex trading sessions and opening ranges on the price chart. It can draw Sydney, Tokyo, Frankfurt, London, and New York session boxes or five- and fifteen-minute opening ranges, plus an optional opening channel and a day-open range. Day r

Indicator v5 Sessions & timeSupport & resistance Repaints
Updated 2 months ago · None stated GitHub repository of Day Trading Booster by DGT

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