Indicator · Pine Script v6 · On the chart

Meridian - Futures Profile

by sukesan7

Calculates and renders Time Price Opportunity (TPO) market profiles, Initial Balance (IB) ranges, and value distributions directly on the price chart.

View on GitHub

GitHub stars4on the repository
Pine versionv6current
Lines of code1,164
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,164 lines of Pine Script v6 from src/futures/meridian-futures-profile.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

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

indicator(
     title = "Meridian - Futures Profile",
     shorttitle = "Meridian Futures Profile",
     overlay = true,
     format = format.price,
     precision = 2,
     max_bars_back = 5000,
     max_boxes_count = 500,
     max_lines_count = 250,
     max_labels_count = 300,
     explicit_plot_zorder = true)

const string G_GENERAL = "01 — General"
const string G_SESSION = "02 — Session + TPO Blocks"
const string G_ROWS    = "03 — Profile Rows"
const string G_DISPLAY = "04 — Profile Display"
const string G_LEVELS  = "05 — Levels + Structure"
const string G_VOLUME  = "06 — Estimated Volume Profile"
const string G_AMT     = "07 — Auction Classification"
const string G_IBQUAL  = "08 — Initial Balance Quality"
const string G_THEME   = "09 — Meridian Theme"
const string G_DASH    = "10 — Dashboard"
const string G_ALERTS  = "11 — Alerts + Telemetry"

bool indicatorEnabled = input.bool(true, "Enable indicator", group = G_GENERAL)
string sessionTimezone = input.string("America/New_York", "Analysis time zone", group = G_GENERAL)
bool requireStandardCandles = input.bool(true, "Warn on non-standard charts", group = G_GENERAL,
     tooltip = "Profiles are intended for standard time-based candles. Heikin Ashi, Renko, and other synthetic charts can distort ranges and volume.")

string rthSession = input.session("0930-1600:23456", "RTH session", group = G_SESSION)
int rthOpenHour = input.int(9, "RTH open hour", minval = 0, maxval = 23, inline = "open", group = G_SESSION)
int rthOpenMinute = input.int(30, "Minute", minval = 0, maxval = 59, inline = "open", group = G_SESSION)
string blockSizeInput = input.string("30 Minutes", "TPO block size",
     options = ["5 Minutes", "10 Minutes", "15 Minutes", "30 Minutes", "60 Minutes"], group = G_SESSION)
int initialBalanceBlocks = input.int(2, "Initial Balance blocks", minval = 1, maxval = 12, group = G_SESSION,
     tooltip = "Two 30-minute blocks produce the conventional 60-minute Initial Balance.")
int maximumChartMinutes = input.int(30, "Maximum chart timeframe", minval = 1, maxval = 60, group = G_SESSION,
     tooltip = "The chart timeframe should not exceed the selected TPO block size. 1m, 5m, and 15m charts are recommended.")

string rowSizeMode = input.string("Auto", "Row-size mode", options = ["Auto", "Manual"], group = G_ROWS)
int manualTicksPerRow = input.int(8, "Manual ticks per row", minval = 1, maxval = 1000, group = G_ROWS)
int automaticTargetRows = input.int(70, "Automatic target rows", minval = 20, maxval = 150, group = G_ROWS)
int minimumAutomaticTicks = input.int(1, "Minimum automatic ticks", minval = 1, maxval = 100, group = G_ROWS)
int maximumRenderedRows = input.int(100, "Maximum rendered rows", minval = 30, maxval = 140, group = G_ROWS,
     tooltip = "Profiles with more rows remain mathematically intact but are visually grouped to respect Pine drawing limits.")
int maximumRowsPerBlock = input.int(250, "Maximum rows per TPO block", minval = 50, maxval = 1000, group = G_ROWS,
     tooltip = "Protects the script when a manual row size is too small for a volatile bar or gap.")
float valueAreaPercent = input.float(70.0, "Value area percentage", minval = 50.0, maxval = 95.0, step = 1.0, group = G_ROWS)

bool showCurrentProfile = input.bool(true, "Show current profile", group = G_DISPLAY)
bool showPreviousProfile = input.bool(true, "Show previous profile", group = G_DISPLAY)
int profileOffsetBars = input.int(5, "Profile offset from latest bar", minval = 1, maxval = 100, group = G_DISPLAY)
int maximumProfileWidthBars = input.int(20, "Maximum TPO width (bars)", minval = 6, maxval = 50, group = G_DISPLAY)
int profileGapBars = input.int(3, "Gap between profiles", minval = 1, maxval = 20, group = G_DISPLAY)
bool showRowCounts = input.bool(false, "Show TPO count on rows", group = G_DISPLAY)
bool showProfileNames = input.bool(true, "Show Current / Previous labels", group = G_DISPLAY)
bool showValueAreaShading = input.bool(true, "Highlight value-area rows", group = G_DISPLAY)
bool showProfileMidpoint = input.bool(true, "Show profile midpoint", group = G_DISPLAY)

bool showCurrentLevels = input.bool(true, "Show current POC / VAH / VAL", group = G_LEVELS)
bool extendPreviousLevels = input.bool(true, "Extend previous POC / VAH / VAL", group = G_LEVELS)
bool showInitialBalance = input.bool(true, "Show Initial Balance", group = G_LEVELS)
bool showIbExtensions = input.bool(true, "Show IB range extensions", group = G_LEVELS)
float firstIbExtension = input.float(0.5, "First IB extension", minval = 0.1, maxval = 5.0, step = 0.1, inline = "ibext", group = G_LEVELS)
float secondIbExtension = input.float(1.0, "Second", minval = 0.1, maxval = 10.0, step = 0.1, inline = "ibext", group = G_LEVELS)
bool showSessionOpenClose = input.bool(false, "Show session open / close", group = G_LEVELS)
int acceptanceCloses = input.int(2, "Closes for prior-value acceptance", minval = 1, maxval = 5, group = G_LEVELS)
bool showSinglePrintZones = input.bool(true, "Show previous single-print zones", group = G_LEVELS)
bool showPoorHighLow = input.bool(true, "Show previous poor high / low", group = G_LEVELS)
int levelExtensionBars = input.int(20, "Level extension beyond latest bar", minval = 0, maxval = 200, group = G_LEVELS)
int maximumSinglePrintZones = input.int(12, "Maximum single-print zones", minval = 1, maxval = 30, group = G_LEVELS)
int levelLineWidth = input.int(1, "Level line width", minval = 1, maxval = 4, group = G_LEVELS)
string levelLineStyle = input.string("Dashed", "Previous level style", options = ["Solid", "Dashed", "Dotted"], group = G_LEVELS)

bool showEstimatedVolume = input.bool(false, "Show estimated volume profile", group = G_VOLUME,
     tooltip = "Distributes each confirmed chart bar's volume uniformly across every profile row touched by that bar. This is an estimate, not exchange-native volume at price.")
int estimatedVolumeWidthBars = input.int(12, "Maximum volume width (bars)", minval = 4, maxval = 30, group = G_VOLUME)
bool showEstimatedVolumeLevels = input.bool(true, "Show estimated VPOC / VAH / VAL", group = G_VOLUME)
float estimatedVolumeValueArea = input.float(70.0, "Estimated volume value area", minval = 50.0, maxval = 95.0, step = 1.0, group = G_VOLUME)

float doublePeakMinimumRatio = input.float(0.62, "Double-distribution secondary peak", minval = 0.30, maxval = 1.0, step = 0.01, group = G_AMT)
float doublePeakValleyRatio = input.float(0.55, "Double-distribution valley ratio", minval = 0.10, maxval = 0.90, step = 0.01, group = G_AMT)
int doublePeakMinimumSeparation = input.int(5, "Minimum peak separation (rows)", minval = 2, maxval = 30, group = G_AMT)
float shapeSkewThreshold = input.float(0.45, "P / b shape skew threshold", minval = 0.10, maxval = 2.0, step = 0.05, group = G_AMT)
float trendRangeExtension = input.float(1.75, "Trend-profile range / IB", minval = 1.1, maxval = 5.0, step = 0.05, group = G_AMT)
float strongPocMigrationRows = input.float(2.0, "Strong POC migration (rows)", minval = 0.5, maxval = 10.0, step = 0.5, group = G_AMT)

int ibQualityHistorySessions = input.int(40, "IB quality history", minval = 10, maxval = 120, group = G_IBQUAL)
float ibNarrowPercentile = input.float(25.0, "Narrow threshold percentile", minval = 5.0, maxval = 45.0, step = 1.0, inline = "ibclass", group = G_IBQUAL)
float ibWidePercentile = input.float(75.0, "Wide", minval = 55.0, maxval = 95.0, step = 1.0, inline = "ibclass", group = G_IBQUAL)
float ibExtremePercentile = input.float(90.0, "Extreme", minval = 70.0, maxval = 99.0, step = 1.0, inline = "ibclass2", group = G_IBQUAL)

color accentColor = input.color(#8B5CF6, "Brand / header", group = G_THEME)
color secondaryColor = input.color(#C4B5FD, "Secondary", group = G_THEME)
color valueAreaColor = input.color(#8B5CF6, "Current value area", group = G_THEME)
color outsideValueColor = input.color(#334155, "Current outside value", group = G_THEME)
color previousValueColor = input.color(#C4B5FD, "Previous value area", group = G_THEME)
color previousOutsideColor = input.color(#334155, "Previous outside value", group = G_THEME)
color pocColor = input.color(#38BDF8, "TPO POC / information", group = G_THEME)
color developingColor = input.color(#A1A1AA, "Developing", group = G_THEME)
color singlePrintColor = input.color(#38BDF8, "Single prints", group = G_THEME)
color poorColor = input.color(#FBBF24, "Warning / poor high-low", group = G_THEME)
color bullishColor = input.color(#2DD4BF, "Bullish migration", group = G_THEME)
color bearishColor = input.color(#F87171, "Bearish migration", group = G_THEME)
color neutralColor = input.color(#94A3B8, "Neutral", group = G_THEME)
color panelColor = input.color(#090E17, "Dashboard background", group = G_THEME)
color borderColor = input.color(#334155, "Dashboard border", group = G_THEME)
color textColor = input.color(#F8FAFC, "Text", group = G_THEME)
int profileTransparency = input.int(84, "Current profile transparency", minval = 70, maxval = 95, group = G_THEME)
int previousProfileTransparency = input.int(90, "Previous profile transparency", minval = 75, maxval = 98, group = G_THEME)

bool showDashboard = input.bool(true, "Show dashboard", group = G_DASH)
string dashboardTextSize = input.string("Small", "Dashboard text size", options = ["Tiny", "Small", "Normal"], group = G_DASH)

bool enableDynamicAlerts = input.bool(false, "Enable dynamic JSON alert() calls", group = G_ALERTS)
bool alertPreviousLevels = input.bool(true, "Alert on previous POC / VAH / VAL", group = G_ALERTS)
bool alertSinglePrints = input.bool(true, "Alert on previous single prints", group = G_ALERTS)
bool alertPoorHighLow = input.bool(true, "Alert on poor high / low repair", group = G_ALERTS)
bool alertAuctionChanges = input.bool(true, "Alert on auction-state changes", group = G_ALERTS)

f_line_style(string name) =>
    switch name
        "Solid"  => line.style_solid
        "Dotted" => line.style_dotted
        => line.style_dashed

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

f_percentile_rank(array<float> history, float value) =>
    float result = na
    int count = history.size()
    if count >= 5 and not na(value)
        int atOrBelow = 0
        for i = 0 to count - 1
            if history.get(i) <= value
                atOrBelow += 1
        result := 100.0 * atOrBelow / count
    result

f_block_minutes(string name) =>
    switch name
        "5 Minutes"  => 5
        "10 Minutes" => 10
        "15 Minutes" => 15
        "60 Minutes" => 60
        => 30

f_default_row_ticks() =>
    bool nasdaqFuture = syminfo.root == "NQ" or syminfo.root == "MNQ"
    bool spFuture = syminfo.root == "ES" or syminfo.root == "MES"
    nasdaqFuture ? 8 : spFuture ? 4 : 4

f_choose_ticks(float previousRange) =>
    int fallbackTicks = math.max(f_default_row_ticks(), minimumAutomaticTicks)
    int autoTicks = not na(previousRange) and previousRange > syminfo.mintick ?
         int(math.ceil(previousRange / syminfo.mintick / automaticTargetRows)) : fallbackTicks
    rowSizeMode == "Manual" ? manualTicksPerRow : math.max(autoTicks, minimumAutomaticTicks)

f_row_low(int row, float rowSize) =>
    row * rowSize

f_row_high(int row, float rowSize) =>
    (row + 1) * rowSize

f_row_center(int row, float rowSize) =>
    (row + 0.5) * rowSize

f_int_get(map<int, int> sourceMap, int key) =>
    sourceMap.contains(key) ? sourceMap.get(key) : 0

f_float_get(map<int, float> sourceMap, int key) =>
    sourceMap.contains(key) ? sourceMap.get(key) : 0.0

f_tpo_count(map<int, int> sourceMap, int row, bool includeActive, int activeLowRow, int activeHighRow) =>
    f_int_get(sourceMap, row) + (includeActive and row >= activeLowRow and row <= activeHighRow ? 1 : 0)

f_add_tpo_block(
     map<int, int> countMap,
     int lowRow,
     int highRow,
     int existingMinRow,
     int existingMaxRow) =>
    int newMinRow = existingMinRow
    int newMaxRow = existingMaxRow
    bool overflow = false
    int rowsTouched = highRow - lowRow + 1
    if rowsTouched > 0 and rowsTouched <= maximumRowsPerBlock
        for row = lowRow to highRow
            countMap.put(row, f_int_get(countMap, row) + 1)
        newMinRow := na(existingMinRow) ? lowRow : math.min(existingMinRow, lowRow)
        newMaxRow := na(existingMaxRow) ? highRow : math.max(existingMaxRow, highRow)
    else if rowsTouched > maximumRowsPerBlock
        overflow := true
    [newMinRow, newMaxRow, overflow]

f_add_estimated_volume(
     map<int, float> volumeMap,
     int lowRow,
     int highRow,
     float barVolume) =>
    bool overflow = false
    int rowsTouched = highRow - lowRow + 1
    if rowsTouched > 0 and rowsTouched <= maximumRowsPerBlock
        float volumePerRow = nz(barVolume, 0.0) / rowsTouched
        for row = lowRow to highRow
            volumeMap.put(row, f_float_get(volumeMap, row) + volumePerRow)
    else if rowsTouched > maximumRowsPerBlock
        overflow := true
    overflow

f_tpo_stats(
     map<int, int> countMap,
     int minRow,
     int maxRow,
     float rowSize,
     bool includeActive,
     int activeLowRow,
     int activeHighRow,
     float vaPercent) =>
    bool valid = not na(minRow) and not na(maxRow) and maxRow >= minRow and rowSize > 0.0
    int pocRow = int(na)
    int vahRow = int(na)
    int valRow = int(na)
    int totalTpo = 0
    int tpoAbove = 0
    int tpoBelow = 0
    int singleRows = 0
    int maximumCount = 0
    bool poorHigh = false
    bool poorLow = false
    float weightedMeanRow = na
    float skewness = na

    if valid
        float midpointRow = (minRow + maxRow) * 0.5
        float weightedSum = 0.0
        for row = minRow to maxRow
            int rowCount = f_tpo_count(countMap, row, includeActive, activeLowRow, activeHighRow)
            totalTpo += rowCount
            weightedSum += row * rowCount
            bool betterPoc = rowCount > maximumCount
            bool equalAndCloser = rowCount == maximumCount and rowCount > 0 and (na(pocRow) or math.abs(row - midpointRow) < math.abs(pocRow - midpointRow))
            bool equalDistanceHigher = rowCount == maximumCount and rowCount > 0 and not na(pocRow) and math.abs(row - midpointRow) == math.abs(pocRow - midpointRow) and row > pocRow
            if betterPoc or equalAndCloser or equalDistanceHigher
                maximumCount := rowCount
                pocRow := row

        if totalTpo > 0 and not na(pocRow)
            weightedMeanRow := weightedSum / totalTpo
            float secondMoment = 0.0
            float thirdMoment = 0.0
            for row = minRow to maxRow
                int rowCount = f_tpo_count(countMap, row, includeActive, activeLowRow, activeHighRow)
                float centered = row - weightedMeanRow
                secondMoment += centered * centered * rowCount
                thirdMoment += centered * centered * centered * rowCount
                if row > pocRow
                    tpoAbove += rowCount
                else if row < pocRow
                    tpoBelow += rowCount
                if row > minRow and row < maxRow and rowCount == 1
                    singleRows += 1

            float varianceRows = secondMoment / totalTpo
            float standardDeviationRows = math.sqrt(math.max(varianceRows, 0.0))
            skewness := standardDeviationRows > 1e-9 ? (thirdMoment / totalTpo) / math.pow(standardDeviationRows, 3.0) : 0.0

            int targetTpo = int(math.ceil(totalTpo * vaPercent / 100.0))
            int includedTpo = f_tpo_count(countMap, pocRow, includeActive, activeLowRow, activeHighRow)
            int valueLowRow = pocRow
            int valueHighRow = pocRow
            int safetyCounter = 0
            while includedTpo < targetTpo and (valueLowRow > minRow or valueHighRow < maxRow) and safetyCounter <= maxRow - minRow + 2
                int aboveCount = valueHighRow < maxRow ? f_tpo_count(countMap, valueHighRow + 1, includeActive, activeLowRow, activeHighRow) : -1
                int belowCount = valueLowRow > minRow ? f_tpo_count(countMap, valueLowRow - 1, includeActive, activeLowRow, activeHighRow) : -1
                if aboveCount >= belowCount
                    valueHighRow += 1
                    includedTpo += math.max(aboveCount, 0)
                else
                    valueLowRow -= 1
                    includedTpo += math.max(belowCount, 0)
                safetyCounter += 1
            vahRow := valueHighRow
            valRow := valueLowRow
            poorHigh := f_tpo_count(countMap, maxRow, includeActive, activeLowRow, activeHighRow) > 1
            poorLow := f_tpo_count(countMap, minRow, includeActive, activeLowRow, activeHighRow) > 1

    float profileHigh = valid ? f_row_high(maxRow, rowSize) : na
    float profileLow = valid ? f_row_low(minRow, rowSize) : na
    float profileMid = valid ? (profileHigh + profileLow) * 0.5 : na
    float pocPrice = valid and not na(pocRow) ? f_row_center(pocRow, rowSize) : na
    float vahPrice = valid and not na(vahRow) ? f_row_high(vahRow, rowSize) : na
    float valPrice = valid and not na(valRow) ? f_row_low(valRow, rowSize) : na
    [valid, pocRow, vahRow, valRow, totalTpo, tpoAbove, tpoBelow, singleRows, maximumCount, poorHigh, poorLow, weightedMeanRow, skewness, profileHigh, profileLow, profileMid, pocPrice, vahPrice, valPrice]

f_volume_stats(
     map<int, float> volumeMap,
     int minRow,
     int maxRow,
     float rowSize,
     float vaPercent) =>
    bool valid = not na(minRow) and not na(maxRow) and maxRow >= minRow and rowSize > 0.0 and volumeMap.size() > 0
    int pocRow = int(na)
    int vahRow = int(na)
    int valRow = int(na)
    float totalVolume = 0.0
    float maximumVolume = 0.0
    if valid
        float midpointRow = (minRow + maxRow) * 0.5
        for row = minRow to maxRow
            float rowVolume = f_float_get(volumeMap, row)
            totalVolume += rowVolume
            bool betterPoc = rowVolume > maximumVolume
            bool equalAndCloser = rowVolume == maximumVolume and rowVolume > 0.0 and (na(pocRow) or math.abs(row - midpointRow) < math.abs(pocRow - midpointRow))
            if betterPoc or equalAndCloser
                maximumVolume := rowVolume
                pocRow := row
        if totalVolume > 0.0 and not na(pocRow)
            float targetVolume = totalVolume * vaPercent / 100.0
            float includedVolume = f_float_get(volumeMap, pocRow)
            int valueLowRow = pocRow
            int valueHighRow = pocRow
            int safetyCounter = 0
            while includedVolume < targetVolume and (valueLowRow > minRow or valueHighRow < maxRow) and safetyCounter <= maxRow - minRow + 2
                float aboveVolume = valueHighRow < maxRow ? f_float_get(volumeMap, valueHighRow + 1) : -1.0
                float belowVolume = valueLowRow > minRow ? f_float_get(volumeMap, valueLowRow - 1) : -1.0
                if aboveVolume >= belowVolume
                    valueHighRow += 1
                    includedVolume += math.max(aboveVolume, 0.0)
                else
                    valueLowRow -= 1
                    includedVolume += math.max(belowVolume, 0.0)
                safetyCounter += 1
            vahRow := valueHighRow
            valRow := valueLowRow
    float pocPrice = valid and not na(pocRow) ? f_row_center(pocRow, rowSize) : na
    float vahPrice = valid and not na(vahRow) ? f_row_high(vahRow, rowSize) : na
    float valPrice = valid and not na(valRow) ? f_row_low(valRow, rowSize) : na
    [valid, pocRow, vahRow, valRow, totalVolume, maximumVolume, pocPrice, vahPrice, valPrice]

f_profile_shape(
     map<int, int> countMap,
     int minRow,
     int maxRow,
     bool includeActive,
     int activeLowRow,
     int activeHighRow,
     int pocRow,
     float skewness,
     float profileRange,
     float initialBalanceRange) =>
    int shapeCode = 0
    string shapeText = "Developing"
    bool doubleDistribution = false
    if not na(minRow) and not na(maxRow) and not na(pocRow) and maxRow > minRow
        int primaryPeakRow = pocRow
        int primaryPeakCount = f_tpo_count(countMap, primaryPeakRow, includeActive, activeLowRow, activeHighRow)
        int secondaryPeakRow = int(na)
        int secondaryPeakCount = 0
        for row = minRow to maxRow
            int rowCount = f_tpo_count(countMap, row, includeActive, activeLowRow, activeHighRow)
            if math.abs(row - primaryPeakRow) >= doublePeakMinimumSeparation and rowCount > secondaryPeakCount
                secondaryPeakCount := rowCount
                secondaryPeakRow := row
        if not na(secondaryPeakRow) and secondaryPeakCount >= primaryPeakCount * doublePeakMinimumRatio
            int valleyStart = math.min(primaryPeakRow, secondaryPeakRow)
            int valleyEnd = math.max(primaryPeakRow, secondaryPeakRow)
            int valleyCount = 1000000
            if valleyEnd - valleyStart > 1
                for row = valleyStart + 1 to valleyEnd - 1
                    valleyCount := math.min(valleyCount, f_tpo_count(countMap, row, includeActive, activeLowRow, activeHighRow))
            doubleDistribution := valleyCount <= math.min(primaryPeakCount, secondaryPeakCount) * doublePeakValleyRatio

        float pocLocation = (pocRow - minRow) / math.max(maxRow - minRow, 1.0)
        bool trendProfile = not na(initialBalanceRange) and initialBalanceRange > 0.0 and profileRange / initialBalanceRange >= trendRangeExtension
        if doubleDistribution
            shapeCode := 4
            shapeText := "Double Distribution"
        else if trendProfile and (pocLocation >= 0.70 or pocLocation <= 0.30 or math.abs(nz(skewness)) >= shapeSkewThreshold)
            shapeCode := 5
            shapeText := "Trend Profile"
        else if nz(skewness) <= -shapeSkewThreshold and pocLocation > 0.52
            shapeCode := 2
            shapeText := "P-shaped"
        else if nz(skewness) >= shapeSkewThreshold and pocLocation < 0.48
            shapeCode := 3
            shapeText := "b-shaped"
        else if math.abs(nz(skewness)) < shapeSkewThreshold and pocLocation >= 0.35 and pocLocation <= 0.65
            shapeCode := 1
            shapeText := "D-shaped"
        else
            shapeCode := 0
            shapeText := "Neutral / Mixed"
    [shapeCode, shapeText, doubleDistribution]

f_trim_boxes(array<box> objects, int used) =>
    while objects.size() > used
        box.delete(objects.pop())

f_trim_labels(array<label> objects, int used) =>
    while objects.size() > used
        label.delete(objects.pop())

f_set_box(
     array<box> objects,
     int index,
     int left,
     float top,
     int right,
     float bottom,
     color borderColor,
     color fillColor) =>
    box object = index < objects.size() ? objects.get(index) : na
    if na(object)
        object := box.new(left, top, right, bottom, xloc = xloc.bar_index, border_color = borderColor, border_width = 1, bgcolor = fillColor)
        if index < objects.size()
            objects.set(index, object)
        else
            objects.push(object)
    else
        box.set_left(object, left)
        box.set_top(object, top)
        box.set_right(object, right)
        box.set_bottom(object, bottom)
        box.set_border_color(object, borderColor)
        box.set_bgcolor(object, fillColor)

f_set_label(
     array<label> objects,
     int index,
     int x,
     float y,
     string labelText,
     color background,
     color foreground,
     string styleName,
     string sizeName) =>
    label object = index < objects.size() ? objects.get(index) : na
    string styleValue = styleName == "Left" ? label.style_label_left : styleName == "Right" ? label.style_label_right : label.style_none
    if na(object)
        object := label.new(x, y, labelText, xloc = xloc.bar_index, style = styleValue, color = background, textcolor = foreground, size = f_text_size(sizeName))
        if index < objects.size()
            objects.set(index, object)
        else
            objects.push(object)
    else
        label.set_xy(object, x, y)
        label.set_text(object, labelText)
        label.set_style(object, styleValue)
        label.set_color(object, background)
        label.set_textcolor(object, foreground)
        label.set_size(object, f_text_size(sizeName))

int blockMinutes = f_block_minutes(blockSizeInput)
int blockMilliseconds = blockMinutes * 60 * 1000
float chartSeconds = timeframe.in_seconds()
int chartMinutes = timeframe.isintraday ? int(math.round(chartSeconds / 60.0)) : 0
bool supportedTimeframe = timeframe.isintraday and chartMinutes >= 1 and chartMinutes <= maximumChartMinutes and chartMinutes <= blockMinutes
bool standardChart = chart.is_standard
bool chartWarning = not supportedTimeframe or (requireStandardCandles and not standardChart)

bool inRTH = not na(time(timeframe.period, rthSession, sessionTimezone))
int localYear = year(time, sessionTimezone)
int localMonth = month(time, sessionTimezone)
int localDay = dayofmonth(time, sessionTimezone)
int dateKey = localYear * 10000 + localMonth * 100 + localDay
bool newRTH = inRTH and (bar_index == 0 or not inRTH[1] or dateKey != dateKey[1])
bool endRTH = not inRTH and inRTH[1]
int rthStartTimestamp = timestamp(sessionTimezone, localYear, localMonth, localDay, rthOpenHour, rthOpenMinute)
int currentBlockId = inRTH ? int(math.floor((time - rthStartTimestamp) / blockMilliseconds)) : -1

var map<int, int> currentTpo = map.new<int, int>()
var map<int, int> previousTpo = map.new<int, int>()
var map<int, float> currentEstimatedVolume = map.new<int, float>()
var map<int, float> previousEstimatedVolume = map.new<int, float>()

var float currentRowSize = na
var float previousRowSize = na
var int currentMinRow = int(na)
var int currentMaxRow = int(na)
var int previousMinRow = int(na)
var int previousMaxRow = int(na)
var bool currentHasData = false
var bool previousHasData = false
var bool currentComplete = false
var bool currentOverflow = false
var bool previousOverflow = false

var int currentSessionStartBar = int(na)
var int currentSessionEndBar = int(na)
var int previousSessionStartBar = int(na)
var int previousSessionEndBar = int(na)
var float currentOpen = na
var float currentClose = na
var float previousOpen = na
var float previousClose = na
var float currentHigh = na
var float currentLow = na
var float previousHigh = na
var float previousLow = na
var float currentIbHigh = na
var float currentIbLow = na
var float previousIbHigh = na
var float previousIbLow = na
var float currentRotationFactor = 0.0
var float previousRotationFactor = 0.0
var int currentCommittedBlocks = 0
var int previousCommittedBlocks = 0

var array<float> ibRangeHistory = array.new_float()
var array<float> ibVolumeHistory = array.new_float()
var float currentIbVolume = 0.0
var float currentIbRangePercentile = na
var float currentIbVolumePercentile = na
var float currentIbEfficiency = na
var bool currentIbRecorded = false

var int activeBlockId = -1
var float activeBlockHigh = na
var float activeBlockLow = na
var float lastCommittedBlockHigh = na
var float lastCommittedBlockLow = na

if newRTH
    if currentHasData
        if not currentComplete
            if activeBlockId >= 0 and not na(activeBlockHigh) and not na(activeBlockLow) and not na(currentRowSize)
                int finalLowRow = int(math.floor(activeBlockLow / currentRowSize))
                int finalHighRow = int(math.floor(activeBlockHigh / currentRowSize))
                [newMinRow, newMaxRow, blockOverflow] = f_add_tpo_block(currentTpo, finalLowRow, finalHighRow, currentMinRow, currentMaxRow)
                currentMinRow := newMinRow
                currentMaxRow := newMaxRow
                currentOverflow := currentOverflow or blockOverflow
                if not na(lastCommittedBlockHigh)
                    currentRotationFactor += activeBlockHigh > lastCommittedBlockHigh ? 1.0 : activeBlockHigh < lastCommittedBlockHigh ? -1.0 : 0.0
                    currentRotationFactor += activeBlockLow > lastCommittedBlockLow ? 1.0 : activeBlockLow < lastCommittedBlockLow ? -1.0 : 0.0
                currentCommittedBlocks += 1
                lastCommittedBlockHigh := activeBlockHigh
                lastCommittedBlockLow := activeBlockLow
            currentClose := bar_index > 0 ? close[1] : close
            currentSessionEndBar := bar_index > 0 ? bar_index - 1 : bar_index
            currentComplete := true

        previousTpo := currentTpo.copy()
        previousEstimatedVolume := currentEstimatedVolume.copy()
        previousRowSize := currentRowSize
        previousMinRow := currentMinRow
        previousMaxRow := currentMaxRow
        previousSessionStartBar := currentSessionStartBar
        previousSessionEndBar := currentSessionEndBar
        previousOpen := currentOpen
        previousClose := currentClose
        previousHigh := currentHigh
        previousLow := currentLow
        previousIbHigh := currentIbHigh
        previousIbLow := currentIbLow
        previousRotationFactor := currentRotationFactor
        previousCommittedBlocks := currentCommittedBlocks
        previousOverflow := currentOverflow
        previousHasData := currentTpo.size() > 0

    float previousRangeForSizing = previousHasData and not na(previousHigh) and not na(previousLow) ? previousHigh - previousLow : na
    currentRowSize := f_choose_ticks(previousRangeForSizing) * syminfo.mintick
    currentTpo := map.new<int, int>()
    currentEstimatedVolume := map.new<int, float>()
    currentMinRow := int(na)
    currentMaxRow := int(na)
    currentHasData := true
    currentComplete := false
    currentOverflow := false
    currentSessionStartBar := bar_index
    currentSessionEndBar := int(na)
    currentOpen := open
    currentClose := na
    currentHigh := high
    currentLow := low
    currentIbHigh := na
    currentIbLow := na
    currentIbVolume := 0.0
    currentIbRangePercentile := na
    currentIbVolumePercentile := na
    currentIbEfficiency := na
    currentIbRecorded := false
    currentRotationFactor := 0.0
    currentCommittedBlocks := 0
    activeBlockId := -1
    activeBlockHigh := na
    activeBlockLow := na
    lastCommittedBlockHigh := na
    lastCommittedBlockLow := na

if inRTH and currentHasData and not na(currentRowSize)
    currentHigh := na(currentHigh) ? high : math.max(currentHigh, high)
    currentLow := na(currentLow) ? low : math.min(currentLow, low)

    if currentBlockId >= 0 and currentBlockId < initialBalanceBlocks
        currentIbHigh := na(currentIbHigh) ? high : math.max(currentIbHigh, high)
        currentIbLow := na(currentIbLow) ? low : math.min(currentIbLow, low)
        if barstate.isconfirmed
            currentIbVolume += nz(volume, 0.0)

    if currentBlockId >= initialBalanceBlocks and not currentIbRecorded and not na(currentIbHigh) and not na(currentIbLow)
        float completedIbRange = currentIbHigh - currentIbLow
        currentIbRangePercentile := f_percentile_rank(ibRangeHistory, completedIbRange)
        currentIbVolumePercentile := f_percentile_rank(ibVolumeHistory, currentIbVolume)
        float ibEndClose = bar_index > 0 ? close[1] : close
        currentIbEfficiency := completedIbRange > syminfo.mintick ? math.min(1.0, math.abs(ibEndClose - currentOpen) / completedIbRange) * 100.0 : 0.0
        ibRangeHistory.push(completedIbRange)
        ibVolumeHistory.push(currentIbVolume)
        while ibRangeHistory.size() > ibQualityHistorySessions
            ibRangeHistory.shift()
        while ibVolumeHistory.size() > ibQualityHistorySessions
            ibVolumeHistory.shift()
        currentIbRecorded := true

    if activeBlockId < 0
        activeBlockId := currentBlockId
        activeBlockHigh := high
        activeBlockLow := low
    else if currentBlockId != activeBlockId
        int completedLowRow = int(math.floor(activeBlockLow / currentRowSize))
        int completedHighRow = int(math.floor(activeBlockHigh / currentRowSize))
        [newMinRow, newMaxRow, blockOverflow] = f_add_tpo_block(currentTpo, completedLowRow, completedHighRow, currentMinRow, currentMaxRow)
        currentMinRow := newMinRow
        currentMaxRow := newMaxRow
        currentOverflow := currentOverflow or blockOverflow
        if not na(lastCommittedBlockHigh)
            currentRotationFactor += activeBlockHigh > lastCommittedBlockHigh ? 1.0 : activeBlockHigh < lastCommittedBlockHigh ? -1.0 : 0.0
            currentRotationFactor += activeBlockLow > lastCommittedBlockLow ? 1.0 : activeBlockLow < lastCommittedBlockLow ? -1.0 : 0.0
        currentCommittedBlocks += 1
        lastCommittedBlockHigh := activeBlockHigh
        lastCommittedBlockLow := activeBlockLow
        activeBlockId := currentBlockId
        activeBlockHigh := high
        activeBlockLow := low
    else
        activeBlockHigh := math.max(activeBlockHigh, high)
        activeBlockLow := math.min(activeBlockLow, low)

    if barstate.isconfirmed and showEstimatedVolume
        int barLowRow = int(math.floor(low / currentRowSize))
        int barHighRow = int(math.floor(high / currentRowSize))
        bool volumeOverflow = f_add_estimated_volume(currentEstimatedVolume, barLowRow, barHighRow, volume)
        currentOverflow := currentOverflow or volumeOverflow

if endRTH and currentHasData and not currentComplete and activeBlockId >= 0 and not na(currentRowSize)
    int finalLowRow = int(math.floor(activeBlockLow / currentRowSize))
    int finalHighRow = int(math.floor(activeBlockHigh / currentRowSize))
    [newMinRow, newMaxRow, blockOverflow] = f_add_tpo_block(currentTpo, finalLowRow, finalHighRow, currentMinRow, currentMaxRow)
    currentMinRow := newMinRow
    currentMaxRow := newMaxRow
    currentOverflow := currentOverflow or blockOverflow
    if not na(lastCommittedBlockHigh)
        currentRotationFactor += activeBlockHigh > lastCommittedBlockHigh ? 1.0 : activeBlockHigh < lastCommittedBlockHigh ? -1.0 : 0.0
        currentRotationFactor += activeBlockLow > lastCommittedBlockLow ? 1.0 : activeBlockLow < lastCommittedBlockLow ? -1.0 : 0.0
    currentCommittedBlocks += 1
    lastCommittedBlockHigh := activeBlockHigh
    lastCommittedBlockLow := activeBlockLow
    currentClose := close[1]
    currentSessionEndBar := bar_index - 1
    currentComplete := true
    activeBlockId := -1
    activeBlockHigh := na
    activeBlockLow := na

int activeLowRow = inRTH and activeBlockId >= 0 and not na(activeBlockLow) and not na(currentRowSize) ? int(math.floor(activeBlockLow / currentRowSize)) : int(na)
int activeHighRow = inRTH and activeBlockId >= 0 and not na(activeBlockHigh) and not na(currentRowSize) ? int(math.floor(activeBlockHigh / currentRowSize)) : int(na)
bool includeActiveBlock = inRTH and activeBlockId >= 0 and not na(activeLowRow) and not na(activeHighRow)
int displayCurrentMinRow = includeActiveBlock ? (na(currentMinRow) ? activeLowRow : math.min(currentMinRow, activeLowRow)) : currentMinRow
int displayCurrentMaxRow = includeActiveBlock ? (na(currentMaxRow) ? activeHighRow : math.max(currentMaxRow, activeHighRow)) : currentMaxRow

[curValid, curPocRow, curVahRow, curValRow, curTotalTpo, curTpoAbove, curTpoBelow, curSingleRows, curMaxCount, curPoorHigh, curPoorLow, curMeanRow, curSkewness, curProfileHigh, curProfileLow, curProfileMid, curPoc, curVah, curVal] =
     f_tpo_stats(currentTpo, displayCurrentMinRow, displayCurrentMaxRow, currentRowSize, includeActiveBlock, activeLowRow, activeHighRow, valueAreaPercent)

[prevValid, prevPocRow, prevVahRow, prevValRow, prevTotalTpo, prevTpoAbove, prevTpoBelow, prevSingleRows, prevMaxCount, prevPoorHigh, prevPoorLow, prevMeanRow, prevSkewness, prevProfileHigh, prevProfileLow, prevProfileMid, prevPoc, prevVah, prevVal] =
     f_tpo_stats(previousTpo, previousMinRow, previousMaxRow, previousRowSize, false, int(na), int(na), valueAreaPercent)

[curVolumeValid, curVolumePocRow, curVolumeVahRow, curVolumeValRow, curTotalEstimatedVolume, curMaxEstimatedVolume, curVolumePoc, curVolumeVah, curVolumeVal] =
     f_volume_stats(currentEstimatedVolume, displayCurrentMinRow, displayCurrentMaxRow, currentRowSize, estimatedVolumeValueArea)

float currentIbRange = not na(currentIbHigh) and not na(currentIbLow) ? currentIbHigh - currentIbLow : na
float currentProfileRange = curValid ? curProfileHigh - curProfileLow : na

[curShapeCode, curShapeText, curDoubleDistribution] = f_profile_shape(
     currentTpo, displayCurrentMinRow, displayCurrentMaxRow, includeActiveBlock, activeLowRow, activeHighRow,
     curPocRow, curSkewness, currentProfileRange, currentIbRange)

string valueRelationship = not curValid or not prevValid ? "No Comparison" :
     curVal > prevVah ? "Higher Value" :
     curVah < prevVal ? "Lower Value" :
     curVah > prevVah and curVal > prevVal ? "Overlapping Higher" :
     curVah < prevVah and curVal < prevVal ? "Overlapping Lower" :
     curVah <= prevVah and curVal >= prevVal ? "Inside Prior Value" :
     curVah >= prevVah and curVal <= prevVal ? "Outside Prior Value" : "Overlapping Value"

float pocMigrationRows = curValid and prevValid ? (curPoc - prevPoc) / math.max(currentRowSize, syminfo.mintick) : na
string pocMigration = na(pocMigrationRows) ? "No Comparison" :
     pocMigrationRows >= strongPocMigrationRows ? "Strongly Higher" :
     pocMigrationRows > 0.5 ? "Higher" :
     pocMigrationRows <= -strongPocMigrationRows ? "Strongly Lower" :
     pocMigrationRows < -0.5 ? "Lower" : "Stable"

string openingLocation = not prevValid or na(currentOpen) ? "No Prior Profile" :
     currentOpen > prevProfileHigh ? "Above Prior Range" :
     currentOpen > prevVah ? "Above Prior Value" :
     currentOpen >= prevVal ? "Inside Prior Value" :
     currentOpen >= prevProfileLow ? "Below Prior Value" : "Below Prior Range"

string currentLocation = not curValid ? "No Profile" :
     close > curProfileHigh ? "Above Profile" :
     close > curVah ? "Above Value" :
     math.abs(close - curPoc) <= currentRowSize ? "At POC" :
     close >= curVal ? "Inside Value" :
     close >= curProfileLow ? "Below Value" : "Below Profile"

bool upwardValue = str.contains(valueRelationship, "Higher")
bool downwardValue = str.contains(valueRelationship, "Lower")
bool pocUp = str.contains(pocMigration, "Higher")
bool pocDown = str.contains(pocMigration, "Lower")
bool aboveIb = not na(currentIbHigh) and close > currentIbHigh
bool belowIb = not na(currentIbLow) and close < currentIbLow
bool returnedInsidePriorValue = prevValid and close <= prevVah and close >= prevVal
bool openedAbovePriorValue = prevValid and currentOpen > prevVah
bool openedBelowPriorValue = prevValid and currentOpen < prevVal

string participantBehavior = curShapeCode == 1 and currentLocation == "Inside Value" ? "Two-sided Trade" :
     openedAbovePriorValue and returnedInsidePriorValue ? "Responsive Selling" :
     openedBelowPriorValue and returnedInsidePriorValue ? "Responsive Buying" :
     upwardValue and pocUp and (aboveIb or close > curVah) ? "Initiative Buying" :
     downwardValue and pocDown and (belowIb or close < curVal) ? "Initiative Selling" :
     "Indeterminate"

int auctionCode = 0
string auctionState = "Transition"
if curDoubleDistribution
    auctionCode := 4
    auctionState := "Double Distribution"
else if openedAbovePriorValue and returnedInsidePriorValue and not pocUp
    auctionCode := -3
    auctionState := "Failed Upward Auction"
else if openedBelowPriorValue and returnedInsidePriorValue and not pocDown
    auctionCode := 3
    auctionState := "Failed Downward Auction"
else if curShapeCode == 5 and upwardValue and pocUp
    auctionCode := 2
    auctionState := "Upward Trend Auction"
else if curShapeCode == 5 and downwardValue and pocDown
    auctionCode := -2
    auctionState := "Downward Trend Auction"
else if upwardValue and pocUp and close >= curPoc
    auctionCode := 1
    auctionState := "Upward Imbalance"
else if downwardValue and pocDown and close <= curPoc
    auctionCode := -1
    auctionState := "Downward Imbalance"
else if curShapeCode == 1 and currentLocation == "Inside Value"
    auctionCode := 5
    auctionState := "Balanced Auction"
else if curValid and close >= curVal and close <= curVah
    auctionCode := 6
    auctionState := "Developing Balance"

color auctionColor = auctionCode == 1 or auctionCode == 2 or auctionCode == 3 ? bullishColor :
     auctionCode == -1 or auctionCode == -2 or auctionCode == -3 ? bearishColor :
     auctionCode == 4 ? accentColor : auctionCode == 5 or auctionCode == 6 ? secondaryColor : neutralColor

color migrationColor = upwardValue or pocUp ? bullishColor : downwardValue or pocDown ? bearishColor : neutralColor

var int[] previousSingleZoneStarts = array.new_int()
var int[] previousSingleZoneEnds = array.new_int()
array.clear(previousSingleZoneStarts)
array.clear(previousSingleZoneEnds)

if prevValid
    int zoneStart = int(na)
    for row = previousMinRow to previousMaxRow
        int rowCount = f_int_get(previousTpo, row)
        bool isSingle = row > previousMinRow and row < previousMaxRow and rowCount == 1
        if isSingle and na(zoneStart)
            zoneStart := row
        bool closesZone = not isSingle and not na(zoneStart)
        if closesZone
            previousSingleZoneStarts.push(zoneStart)
            previousSingleZoneEnds.push(row - 1)
            zoneStart := int(na)
    if not na(zoneStart)
        previousSingleZoneStarts.push(zoneStart)
        previousSingleZoneEnds.push(previousMaxRow - 1)

bool previousPocTouched = inRTH and prevValid and high >= prevPoc and low <= prevPoc
bool previousVahTouched = inRTH and prevValid and high >= prevVah and low <= prevVah
bool previousValTouched = inRTH and prevValid and high >= prevVal and low <= prevVal

var int previousPocState = 0
var int previousVahState = 0
var int previousValState = 0
if newRTH
    previousPocState := 0
    previousVahState := 0
    previousValState := 0
float closesAbovePocCount = math.sum(prevValid and close > prevPoc ? 1.0 : 0.0, acceptanceCloses)
float closesBelowPocCount = math.sum(prevValid and close < prevPoc ? 1.0 : 0.0, acceptanceCloses)
float closesAboveVahCount = math.sum(prevValid and close > prevVah ? 1.0 : 0.0, acceptanceCloses)
float closesBelowValCount = math.sum(prevValid and close < prevVal ? 1.0 : 0.0, acceptanceCloses)
bool closesAbovePoc = prevValid and closesAbovePocCount >= acceptanceCloses
bool closesBelowPoc = prevValid and closesBelowPocCount >= acceptanceCloses
bool closesAboveVah = prevValid and closesAboveVahCount >= acceptanceCloses
bool closesBelowVal = prevValid and closesBelowValCount >= acceptanceCloses
if barstate.isconfirmed and inRTH and prevValid
    if closesAbovePoc
        previousPocState := 1
    else if closesBelowPoc
        previousPocState := -1
    else if previousPocTouched
        previousPocState := close >= prevPoc ? 2 : -2
    if closesAboveVah
        previousVahState := 1
    else if previousVahTouched and close < prevVah
        previousVahState := -2
    if closesBelowVal
        previousValState := -1
    else if previousValTouched and close > prevVal
        previousValState := 2

bool poorHighRepaired = inRTH and prevValid and prevPoorHigh and high > prevProfileHigh
bool poorLowRepaired = inRTH and prevValid and prevPoorLow and low < prevProfileLow
bool previousSinglePrintOverlap = false
if inRTH and prevValid and previousSingleZoneStarts.size() > 0
    for zoneIndex = 0 to previousSingleZoneStarts.size() - 1
        int startRow = previousSingleZoneStarts.get(zoneIndex)
        int endRow = previousSingleZoneEnds.get(zoneIndex)
        float zoneLow = f_row_low(startRow, previousRowSize)
        float zoneHigh = f_row_high(endRow, previousRowSize)
        previousSinglePrintOverlap := previousSinglePrintOverlap or (high >= zoneLow and low <= zoneHigh)

var array<box> currentProfileBoxes = array.new<box>()
var array<box> previousProfileBoxes = array.new<box>()
var array<box> estimatedVolumeBoxes = array.new<box>()
var array<box> singlePrintBoxes = array.new<box>()
var array<label> rowCountLabels = array.new<label>()
var array<label> profileNameLabels = array.new<label>()

if barstate.islast
    int currentLeft = bar_index + profileOffsetBars
    int estimatedVolumeLeft = currentLeft + maximumProfileWidthBars + 1
    int previousLeft = currentLeft + maximumProfileWidthBars + (showEstimatedVolume ? estimatedVolumeWidthBars + 2 : 0) + profileGapBars

    int currentUsed = 0
    int rowLabelsUsed = 0
    if indicatorEnabled and showCurrentProfile and curValid
        int currentRows = displayCurrentMaxRow - displayCurrentMinRow + 1
        int currentStride = math.max(1, int(math.ceil(currentRows / maximumRenderedRows)))
        int groupCount = int(math.ceil(currentRows / currentStride))
        int groupedMaxCount = 1
        for groupIndex = 0 to groupCount - 1
            int groupLowRow = displayCurrentMinRow + groupIndex * currentStride
            int groupHighRow = math.min(groupLowRow + currentStride - 1, displayCurrentMaxRow)
            int groupMaximum = 0
            for row = groupLowRow to groupHighRow
                groupMaximum := math.max(groupMaximum, f_tpo_count(currentTpo, row, includeActiveBlock, activeLowRow, activeHighRow))
            groupedMaxCount := math.max(groupedMaxCount, groupMaximum)

        for groupIndex = 0 to groupCount - 1
            int groupLowRow = displayCurrentMinRow + groupIndex * currentStride
            int groupHighRow = math.min(groupLowRow + currentStride - 1, displayCurrentMaxRow)
            int groupMaximum = 0
            bool groupSinglePrint = false
            for row = groupLowRow to groupHighRow
                int rowCount = f_tpo_count(currentTpo, row, includeActiveBlock, activeLowRow, activeHighRow)
                groupMaximum := math.max(groupMaximum, rowCount)
                groupSinglePrint := groupSinglePrint or (row > displayCurrentMinRow and row < displayCurrentMaxRow and rowCount == 1)
            int profileWidth = math.max(1, int(math.round(groupMaximum / math.max(groupedMaxCount, 1.0) * maximumProfileWidthBars)))
            bool isValue = groupHighRow >= curValRow and groupLowRow <= curVahRow
            bool isPoc = groupHighRow >= curPocRow and groupLowRow <= curPocRow
            color baseColor = isPoc ? pocColor : groupSinglePrint ? singlePrintColor : isValue and showValueAreaShading ? valueAreaColor : outsideValueColor
            int transparency = isPoc ? math.max(profileTransparency - 12, 0) : profileTransparency
            f_set_box(currentProfileBoxes, currentUsed, currentLeft, f_row_high(groupHighRow, currentRowSize), currentLeft + profileWidth, f_row_low(groupLowRow, currentRowSize), color.new(baseColor, math.min(transparency + 10, 100)), color.new(baseColor, transparency))
            currentUsed += 1
            if showRowCounts
                f_set_label(rowCountLabels, rowLabelsUsed, currentLeft + profileWidth + 1, (f_row_high(groupHighRow, currentRowSize) + f_row_low(groupLowRow, currentRowSize)) * 0.5, str.tostring(groupMaximum), color.new(panelColor, 100), color.new(textColor, 12), "None", "Tiny")
                rowLabelsUsed += 1
    f_trim_boxes(currentProfileBoxes, currentUsed)

    int previousUsed = 0
    if indicatorEnabled and showPreviousProfile and prevValid
        int previousRows = previousMaxRow - previousMinRow + 1
        int previousStride = math.max(1, int(math.ceil(previousRows / maximumRenderedRows)))
        int groupCount = int(math.ceil(previousRows / previousStride))
        int groupedMaxCount = 1
        for groupIndex = 0 to groupCount - 1
            int groupLowRow = previousMinRow + groupIndex * previousStride
            int groupHighRow = math.min(groupLowRow + previousStride - 1, previousMaxRow)
            int groupMaximum = 0
            for row = groupLowRow to groupHighRow
                groupMaximum := math.max(groupMaximum, f_int_get(previousTpo, row))
            groupedMaxCount := math.max(groupedMaxCount, groupMaximum)
        for groupIndex = 0 to groupCount - 1
            int groupLowRow = previousMinRow + groupIndex * previousStride
            int groupHighRow = math.min(groupLowRow + previousStride - 1, previousMaxRow)
            int groupMaximum = 0
            bool groupSinglePrint = false
            for row = groupLowRow to groupHighRow
                int rowCount = f_int_get(previousTpo, row)
                groupMaximum := math.max(groupMaximum, rowCount)
                groupSinglePrint := groupSinglePrint or (row > previousMinRow and row < previousMaxRow and rowCount == 1)
            int profileWidth = math.max(1, int(math.round(groupMaximum / math.max(groupedMaxCount, 1.0) * maximumProfileWidthBars)))
            bool isValue = groupHighRow >= prevValRow and groupLowRow <= prevVahRow
            bool isPoc = groupHighRow >= prevPocRow and groupLowRow <= prevPocRow
            color baseColor = isPoc ? color.new(pocColor, 15) : groupSinglePrint ? color.new(singlePrintColor, 12) : isValue and showValueAreaShading ? previousValueColor : previousOutsideColor
            f_set_box(previousProfileBoxes, previousUsed, previousLeft, f_row_high(groupHighRow, previousRowSize), previousLeft + profileWidth, f_row_low(groupLowRow, previousRowSize), color.new(baseColor, math.min(previousProfileTransparency + 10, 100)), color.new(baseColor, previousProfileTransparency))
            previousUsed += 1
    f_trim_boxes(previousProfileBoxes, previousUsed)

    int volumeUsed = 0
    if indicatorEnabled and showEstimatedVolume and curVolumeValid
        int currentRows = displayCurrentMaxRow - displayCurrentMinRow + 1
        int currentStride = math.max(1, int(math.ceil(currentRows / maximumRenderedRows)))
        int groupCount = int(math.ceil(currentRows / currentStride))
        float groupedMaximumVolume = 1.0
        for groupIndex = 0 to groupCount - 1
            int groupLowRow = displayCurrentMinRow + groupIndex * currentStride
            int groupHighRow = math.min(groupLowRow + currentStride - 1, displayCurrentMaxRow)
            float groupVolume = 0.0
            for row = groupLowRow to groupHighRow
                groupVolume += f_float_get(currentEstimatedVolume, row)
            groupedMaximumVolume := math.max(groupedMaximumVolume, groupVolume)
        for groupIndex = 0 to groupCount - 1
            int groupLowRow = displayCurrentMinRow + groupIndex * currentStride
            int groupHighRow = math.min(groupLowRow + currentStride - 1, displayCurrentMaxRow)
            float groupVolume = 0.0
            for row = groupLowRow to groupHighRow
                groupVolume += f_float_get(currentEstimatedVolume, row)
            int volumeWidth = math.max(1, int(math.round(groupVolume / groupedMaximumVolume * estimatedVolumeWidthBars)))
            bool isValue = groupHighRow >= curVolumeValRow and groupLowRow <= curVolumeVahRow
            bool isPoc = groupHighRow >= curVolumePocRow and groupLowRow <= curVolumePocRow
            color volumeColor = isPoc ? pocColor : isValue ? secondaryColor : neutralColor
            f_set_box(estimatedVolumeBoxes, volumeUsed, estimatedVolumeLeft, f_row_high(groupHighRow, currentRowSize), estimatedVolumeLeft + volumeWidth, f_row_low(groupLowRow, currentRowSize), color.new(volumeColor, 65), color.new(volumeColor, isPoc ? 24 : 56))
            volumeUsed += 1
    f_trim_boxes(estimatedVolumeBoxes, volumeUsed)
    f_trim_labels(rowCountLabels, rowLabelsUsed)

    int nameLabelsUsed = 0
    if indicatorEnabled and showProfileNames and curValid and showCurrentProfile
        f_set_label(profileNameLabels, nameLabelsUsed, currentLeft, curProfileHigh + currentRowSize, "CURRENT TPO", color.new(accentColor, 68), textColor, "Left", "Tiny")
        nameLabelsUsed += 1
    if indicatorEnabled and showProfileNames and prevValid and showPreviousProfile
        f_set_label(profileNameLabels, nameLabelsUsed, previousLeft, prevProfileHigh + previousRowSize, "PREVIOUS TPO", color.new(previousValueColor, 66), textColor, "Left", "Tiny")
        nameLabelsUsed += 1
    if indicatorEnabled and showProfileNames and showEstimatedVolume and curVolumeValid
        f_set_label(profileNameLabels, nameLabelsUsed, estimatedVolumeLeft, curProfileHigh + currentRowSize, "EST. VOLUME", color.new(secondaryColor, 68), textColor, "Left", "Tiny")
        nameLabelsUsed += 1
    f_trim_labels(profileNameLabels, nameLabelsUsed)

    int singleZonesUsed = 0
    if indicatorEnabled and showSinglePrintZones and prevValid and inRTH and previousSingleZoneStarts.size() > 0
        int zonesToDraw = math.min(previousSingleZoneStarts.size(), maximumSinglePrintZones)
        for zoneIndex = 0 to zonesToDraw - 1
            int startRow = previousSingleZoneStarts.get(zoneIndex)
            int endRow = previousSingleZoneEnds.get(zoneIndex)
            float zoneLow = f_row_low(startRow, previousRowSize)
            float zoneHigh = f_row_high(endRow, previousRowSize)
            bool touched = currentHigh >= zoneLow and currentLow <= zoneHigh
            bool repaired = currentHigh >= zoneHigh and currentLow <= zoneLow
            color zoneColor = repaired ? neutralColor : touched ? poorColor : singlePrintColor
            int zoneLeft = na(previousSessionEndBar) ? bar_index - 1 : previousSessionEndBar
            int zoneRight = bar_index + levelExtensionBars
            f_set_box(singlePrintBoxes, singleZonesUsed, zoneLeft, zoneHigh, zoneRight, zoneLow, color.new(zoneColor, 42), color.new(zoneColor, repaired ? 92 : touched ? 86 : 78))
            singleZonesUsed += 1
    f_trim_boxes(singlePrintBoxes, singleZonesUsed)

var array<line> keyLines = array.new<line>()
var array<label> keyLabels = array.new<label>()

f_set_level(
     array<line> lineObjects,
     array<label> labelObjects,
     int index,
     bool enabled,
     int startX,
     int endX,
     float price,
     string name,
     color levelColor,
     string styleValue,
     int widthValue) =>
    line levelLine = index < lineObjects.size() ? lineObjects.get(index) : na
    label levelLabel = index < labelObjects.size() ? labelObjects.get(index) : na
    if enabled and not na(price)
        if na(levelLine)
            levelLine := line.new(startX, price, endX, price, xloc = xloc.bar_index, extend = extend.none, color = levelColor, style = styleValue, width = widthValue)
            if index < lineObjects.size()
                lineObjects.set(index, levelLine)
            else
                lineObjects.push(levelLine)
        else
            line.set_xy1(levelLine, startX, price)
            line.set_xy2(levelLine, endX, price)
            line.set_color(levelLine, levelColor)
            line.set_style(levelLine, styleValue)
            line.set_width(levelLine, widthValue)
        if na(levelLabel)
            levelLabel := label.new(endX, price, name, xloc = xloc.bar_index, style = label.style_label_left, color = color.new(levelColor, 82), textcolor = textColor, size = size.tiny)
            if index < labelObjects.size()
                labelObjects.set(index, levelLabel)
            else
                labelObjects.push(levelLabel)
        else
            label.set_xy(levelLabel, endX, price)
            label.set_text(levelLabel, name)
            label.set_color(levelLabel, color.new(levelColor, 82))
            label.set_textcolor(levelLabel, textColor)
    else
        if not na(levelLine)
            line.delete(levelLine)
            lineObjects.set(index, line(na))
        if not na(levelLabel)
            label.delete(levelLabel)
            labelObjects.set(index, label(na))

if barstate.islast
    while keyLines.size() < 20
        keyLines.push(line(na))
    while keyLabels.size() < 20
        keyLabels.push(label(na))

    int currentLevelEnd = bar_index + profileOffsetBars
    int previousLevelEnd = bar_index + levelExtensionBars
    int currentStart = na(currentSessionStartBar) ? bar_index : currentSessionStartBar
    int previousStart = na(previousSessionEndBar) ? bar_index : previousSessionEndBar
    string previousStyle = f_line_style(levelLineStyle)

    f_set_level(keyLines, keyLabels, 0, indicatorEnabled and showCurrentLevels and curValid, currentStart, currentLevelEnd, curPoc, "TPO POC", pocColor, line.style_solid, 2)
    f_set_level(keyLines, keyLabels, 1, indicatorEnabled and showCurrentLevels and curValid, currentStart, currentLevelEnd, curVah, "TPO VAH", developingColor, line.style_dashed, levelLineWidth)
    f_set_level(keyLines, keyLabels, 2, indicatorEnabled and showCurrentLevels and curValid, currentStart, currentLevelEnd, curVal, "TPO VAL", developingColor, line.style_dashed, levelLineWidth)
    f_set_level(keyLines, keyLabels, 3, indicatorEnabled and showProfileMidpoint and curValid, currentStart, currentLevelEnd, curProfileMid, "TPO MID", color.new(accentColor, 18), line.style_dotted, 1)
    f_set_level(keyLines, keyLabels, 4, indicatorEnabled and showInitialBalance and not na(currentIbHigh), currentStart, currentLevelEnd, currentIbHigh, "IBH", color.new(secondaryColor, currentIbRecorded ? 8 : 28), currentIbRecorded ? line.style_solid : line.style_dashed, levelLineWidth)
    f_set_level(keyLines, keyLabels, 5, indicatorEnabled and showInitialBalance and not na(currentIbLow), currentStart, currentLevelEnd, currentIbLow, "IBL", color.new(secondaryColor, currentIbRecorded ? 8 : 28), currentIbRecorded ? line.style_solid : line.style_dashed, levelLineWidth)

    color prevPocColor = previousPocState == 1 or previousPocState == 2 ? bullishColor : previousPocState == -1 or previousPocState == -2 ? bearishColor : previousPocTouched ? poorColor : previousValueColor
    color prevVahColor = previousVahState == 1 ? bullishColor : previousVahState == -2 ? bearishColor : previousVahTouched ? poorColor : previousValueColor
    color prevValColor = previousValState == 2 ? bullishColor : previousValState == -1 ? bearishColor : previousValTouched ? poorColor : previousValueColor
    string prevPocText = previousPocState == 1 ? "PRIOR POC · ACCEPTED ABOVE" : previousPocState == -1 ? "PRIOR POC · ACCEPTED BELOW" : previousPocState == 2 ? "PRIOR POC · RECLAIMED" : previousPocState == -2 ? "PRIOR POC · LOST" : "PRIOR POC"
    string prevVahText = previousVahState == 1 ? "PRIOR VAH · ACCEPTED ABOVE" : previousVahState == -2 ? "PRIOR VAH · REJECTED" : "PRIOR VAH"
    string prevValText = previousValState == -1 ? "PRIOR VAL · ACCEPTED BELOW" : previousValState == 2 ? "PRIOR VAL · RECLAIMED" : "PRIOR VAL"
    f_set_level(keyLines, keyLabels, 6, indicatorEnabled and extendPreviousLevels and prevValid, previousStart, previousLevelEnd, prevPoc, prevPocText, prevPocColor, previousStyle, levelLineWidth)
    f_set_level(keyLines, keyLabels, 7, indicatorEnabled and extendPreviousLevels and prevValid, previousStart, previousLevelEnd, prevVah, prevVahText, prevVahColor, previousStyle, levelLineWidth)
    f_set_level(keyLines, keyLabels, 8, indicatorEnabled and extendPreviousLevels and prevValid, previousStart, previousLevelEnd, prevVal, prevValText, prevValColor, previousStyle, levelLineWidth)
    f_set_level(keyLines, keyLabels, 9, indicatorEnabled and showPoorHighLow and prevValid and prevPoorHigh, previousStart, previousLevelEnd, prevProfileHigh, poorHighRepaired ? "POOR HIGH · REPAIRED" : "POOR HIGH", poorHighRepaired ? neutralColor : poorColor, line.style_dotted, levelLineWidth)
    f_set_level(keyLines, keyLabels, 10, indicatorEnabled and showPoorHighLow and prevValid and prevPoorLow, previousStart, previousLevelEnd, prevProfileLow, poorLowRepaired ? "POOR LOW · REPAIRED" : "POOR LOW", poorLowRepaired ? neutralColor : poorColor, line.style_dotted, levelLineWidth)
    f_set_level(keyLines, keyLabels, 11, indicatorEnabled and showEstimatedVolume and showEstimatedVolumeLevels and curVolumeValid, currentStart, currentLevelEnd, curVolumePoc, "EST. VPOC", secondaryColor, line.style_solid, 1)
    f_set_level(keyLines, keyLabels, 12, indicatorEnabled and showEstimatedVolume and showEstimatedVolumeLevels and curVolumeValid, currentStart, currentLevelEnd, curVolumeVah, "EST. VVAH", color.new(secondaryColor, 18), line.style_dashed, 1)
    f_set_level(keyLines, keyLabels, 13, indicatorEnabled and showEstimatedVolume and showEstimatedVolumeLevels and curVolumeValid, currentStart, currentLevelEnd, curVolumeVal, "EST. VVAL", color.new(secondaryColor, 18), line.style_dashed, 1)
    float liveSessionClose = inRTH ? close : currentClose
    f_set_level(keyLines, keyLabels, 14, indicatorEnabled and showSessionOpenClose and not na(currentOpen), currentStart, currentLevelEnd, currentOpen, "RTH OPEN", color.new(textColor, 28), line.style_solid, 1)
    f_set_level(keyLines, keyLabels, 15, indicatorEnabled and showSessionOpenClose and not na(liveSessionClose), currentStart, currentLevelEnd, liveSessionClose, inRTH ? "LIVE CLOSE" : "RTH CLOSE", color.new(neutralColor, 22), line.style_dotted, 1)
    float ibRangeForExtensions = not na(currentIbHigh) and not na(currentIbLow) ? currentIbHigh - currentIbLow : na
    f_set_level(keyLines, keyLabels, 16, indicatorEnabled and showIbExtensions and not na(ibRangeForExtensions), currentStart, currentLevelEnd, currentIbHigh + ibRangeForExtensions * firstIbExtension, "IB +" + str.tostring(firstIbExtension, "#.##"), color.new(secondaryColor, 24), line.style_dashed, 1)
    f_set_level(keyLines, keyLabels, 17, indicatorEnabled and showIbExtensions and not na(ibRangeForExtensions), currentStart, currentLevelEnd, currentIbLow - ibRangeForExtensions * firstIbExtension, "IB -" + str.tostring(firstIbExtension, "#.##"), color.new(secondaryColor, 24), line.style_dashed, 1)
    f_set_level(keyLines, keyLabels, 18, indicatorEnabled and showIbExtensions and not na(ibRangeForExtensions), currentStart, currentLevelEnd, currentIbHigh + ibRangeForExtensions * secondIbExtension, "IB +" + str.tostring(secondIbExtension, "#.##"), color.new(secondaryColor, 42), line.style_dotted, 1)
    f_set_level(keyLines, keyLabels, 19, indicatorEnabled and showIbExtensions and not na(ibRangeForExtensions), currentStart, currentLevelEnd, currentIbLow - ibRangeForExtensions * secondIbExtension, "IB -" + str.tostring(secondIbExtension, "#.##"), color.new(secondaryColor, 42), line.style_dotted, 1)

string structureState = not curValid ? "No Profile" :
     curPoorHigh and curPoorLow ? "Poor High + Low" :
     curPoorHigh ? "Poor High" :
     curPoorLow ? "Poor Low" :
     curSingleRows > 0 ? str.tostring(curSingleRows) + " Single Rows" : "Clean Extremes"

string ibState = not curValid or na(currentIbHigh) or na(currentIbLow) ? "IB Developing" :
     close > currentIbHigh ? "Above IB" : close < currentIbLow ? "Below IB" : "Inside IB"

string ibQuality = not currentIbRecorded or na(currentIbRangePercentile) ? "BUILDING" :
     currentIbRangePercentile >= ibExtremePercentile ? "EXTREME" :
     currentIbRangePercentile >= ibWidePercentile ? "WIDE" :
     currentIbRangePercentile <= ibNarrowPercentile ? "NARROW" : "NORMAL"
string ibDetail = not currentIbRecorded ? "Developing" :
     ibQuality + " · " + str.tostring(currentIbRangePercentile, "#") + "p" +
     (na(currentIbVolumePercentile) ? "" : " · Vol " + str.tostring(currentIbVolumePercentile, "#") + "p")

bool dataStale = barstate.islast and timenow > time_close + math.max(60 * 60 * 1000, int(timeframe.in_seconds() * 1000.0 * 4.0))
string dataState = dataStale ? "STALE / CLOSED" : barstate.isrealtime and not barstate.isconfirmed ? "LIVE" : "CONFIRMED"
string profileState = curValid ? curShapeText : "BUILDING"
string valueState = valueRelationship + " · " + pocMigration + " POC"
string locationState = currentLocation + " · " + ibState
string warningState = chartWarning ? "CHECK CHART" : currentOverflow or previousOverflow ? "ROW DENSITY" : dataState

var table dashboard = table.new(
     position.top_right, 2, 7,
     bgcolor = color.new(panelColor, 8),
     frame_color = color.new(borderColor, 25), frame_width = 1,
     border_color = color.new(borderColor, 78), border_width = 1)

if barstate.islast
    bool visible = indicatorEnabled and showDashboard
    color invisible = color.new(panelColor, 100)
    color bg = visible ? color.new(panelColor, 8) : invisible
    color fg = visible ? textColor : color.new(textColor, 100)
    color muted = visible ? neutralColor : color.new(neutralColor, 100)
    table.set_bgcolor(dashboard, bg)
    table.set_frame_color(dashboard, visible ? color.new(borderColor, 25) : color.new(borderColor, 100))
    table.set_border_color(dashboard, visible ? color.new(borderColor, 78) : color.new(borderColor, 100))
    table.cell(dashboard, 0, 0, "Meridian -- Futures Profile", text_color = fg, bgcolor = visible ? color.new(accentColor, 72) : invisible, text_size = f_text_size(dashboardTextSize), text_halign = text.align_left)
    table.cell(dashboard, 1, 0, "", text_color = fg, bgcolor = visible ? color.new(accentColor, 72) : invisible, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 0, 1, "Auction", text_color = muted, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 1, 1, auctionState, text_color = visible ? auctionColor : color.new(auctionColor, 100), text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 2, "Value", text_color = muted, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 1, 2, valueState, text_color = fg, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 3, "Profile", text_color = muted, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 1, 3, profileState, text_color = fg, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 4, "IB", text_color = muted, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 1, 4, ibDetail, text_color = visible ? (ibQuality == "EXTREME" ? poorColor : ibQuality == "NARROW" ? developingColor : fg) : color.new(textColor, 100), text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 5, "Location", text_color = muted, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 1, 5, locationState, text_color = fg, text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)
    table.cell(dashboard, 0, 6, "Data", text_color = muted, text_size = f_text_size(dashboardTextSize))
    table.cell(dashboard, 1, 6, warningState, text_color = visible ? (chartWarning or currentOverflow or previousOverflow ? poorColor : dataStale ? poorColor : textColor) : color.new(textColor, 100), text_size = f_text_size(dashboardTextSize), text_halign = text.align_right)

bool previousPocEvent = barstate.isconfirmed and alertPreviousLevels and previousPocTouched and not previousPocTouched[1]
bool previousVahEvent = barstate.isconfirmed and alertPreviousLevels and previousVahTouched and not previousVahTouched[1]
bool previousValEvent = barstate.isconfirmed and alertPreviousLevels and previousValTouched and not previousValTouched[1]
bool singlePrintEvent = barstate.isconfirmed and alertSinglePrints and previousSinglePrintOverlap and not previousSinglePrintOverlap[1]
bool poorHighEvent = barstate.isconfirmed and alertPoorHighLow and poorHighRepaired and not poorHighRepaired[1]
bool poorLowEvent = barstate.isconfirmed and alertPoorHighLow and poorLowRepaired and not poorLowRepaired[1]
bool auctionChangeEvent = barstate.isconfirmed and alertAuctionChanges and inRTH and auctionCode != auctionCode[1]

string dynamicEvent = previousPocEvent ? "previous_poc_test" :
     previousVahEvent ? "previous_vah_test" :
     previousValEvent ? "previous_val_test" :
     singlePrintEvent ? "single_print_test" :
     poorHighEvent ? "poor_high_repair" :
     poorLowEvent ? "poor_low_repair" :
     auctionChangeEvent ? "auction_state_change" : ""

if enableDynamicAlerts and str.length(dynamicEvent) > 0
    string jsonMessage = "{" +
         "\"source\":\"meridian_futures_profile\"," +
         "\"version\":\"0.2.0\"," +
         "\"event\":\"" + dynamicEvent + "\"," +
         "\"symbol\":\"" + syminfo.tickerid + "\"," +
         "\"timeframe\":\"" + timeframe.period + "\"," +
         "\"auction_state\":\"" + auctionState + "\"," +
         "\"profile_shape\":\"" + curShapeText + "\"," +
         "\"value_relationship\":\"" + valueRelationship + "\"," +
         "\"poc\":" + str.tostring(nz(curPoc), format.mintick) + "," +
         "\"vah\":" + str.tostring(nz(curVah), format.mintick) + "," +
         "\"val\":" + str.tostring(nz(curVal), format.mintick) + "," +
         "\"previous_poc\":" + str.tostring(nz(prevPoc), format.mintick) + "," +
         "\"rotation_factor\":" + str.tostring(currentRotationFactor, "#.##") + "," +
         "\"bar_time\":" + str.tostring(time) + "}"
    alert(jsonMessage, alert.freq_once_per_bar_close)

alertcondition(previousPocEvent, "Meridian — Previous TPO POC tested", "Meridian Futures Profile: price tested the previous completed RTH TPO POC.")
alertcondition(previousVahEvent, "Meridian — Previous TPO VAH tested", "Meridian Futures Profile: price tested the previous completed RTH TPO VAH.")
alertcondition(previousValEvent, "Meridian — Previous TPO VAL tested", "Meridian Futures Profile: price tested the previous completed RTH TPO VAL.")
alertcondition(singlePrintEvent, "Meridian — Previous single-print zone tested", "Meridian Futures Profile: price entered a previous completed RTH single-print zone.")
alertcondition(poorHighEvent, "Meridian — Poor high repaired", "Meridian Futures Profile: price traded above the previous poor high.")
alertcondition(poorLowEvent, "Meridian — Poor low repaired", "Meridian Futures Profile: price traded below the previous poor low.")
alertcondition(auctionChangeEvent, "Meridian — Auction state changed", "Meridian Futures Profile: the confirmed Auction Market Theory state changed.")

Overview

Meridian - Futures Profile determines key auction levels including the Point of Control (POC), Value Area High (VAH), Value Area Low (VAL), and profile midpoints for current and previous sessions. Settings configure regular trading hours (RTH), TPO block intervals, automatic or manual row-size tick counts, and value area percentages. Current session profiles update as intraday bars complete, and the tool is restricted to standard timeframes below the selected block size.

Details

Published by

sukesan7 on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Support & resistance, Sessions & time, Volume

Markets

Indices, Oil & commodities

Timeframes

M1-M5, M15-H1

Risk Notes

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

Topics

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

Settings

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

SettingTypeDefaultVariable
Enable indicatorbooltrueindicatorEnabled
Analysis time zonestringAmerica/New_YorksessionTimezone
Warn on non-standard chartsbooltruerequireStandardCandles
RTH sessionsession0930-1600:23456rthSession
RTH open hourint9rthOpenHour
Minuteint30rthOpenMinute
TPO block sizestring30 MinutesblockSizeInput
Initial Balance blocksint2initialBalanceBlocks
Maximum chart timeframeint30maximumChartMinutes
Row-size modestringAutorowSizeMode
Manual ticks per rowint8manualTicksPerRow
Automatic target rowsint70automaticTargetRows
Show all 40 settings
SettingTypeDefaultVariable
Minimum automatic ticksint1minimumAutomaticTicks
Maximum rendered rowsint100maximumRenderedRows
Maximum rows per TPO blockint250maximumRowsPerBlock
Value area percentagefloat70.0valueAreaPercent
Show current profilebooltrueshowCurrentProfile
Show previous profilebooltrueshowPreviousProfile
Profile offset from latest barint5profileOffsetBars
Maximum TPO width (bars)int20maximumProfileWidthBars
Gap between profilesint3profileGapBars
Show TPO count on rowsboolfalseshowRowCounts
Show Current / Previous labelsbooltrueshowProfileNames
Highlight value-area rowsbooltrueshowValueAreaShading
Show profile midpointbooltrueshowProfileMidpoint
Show current POC / VAH / VALbooltrueshowCurrentLevels
Extend previous POC / VAH / VALbooltrueextendPreviousLevels
Show Initial BalancebooltrueshowInitialBalance
Show IB range extensionsbooltrueshowIbExtensions
First IB extensionfloat0.5firstIbExtension
Secondfloat1.0secondIbExtension
Show session open / closeboolfalseshowSessionOpenClose
Closes for prior-value acceptanceint2acceptanceCloses
Show previous single-print zonesbooltrueshowSinglePrintZones
Show previous poor high / lowbooltrueshowPoorHighLow
Level extension beyond latest barint20levelExtensionBars
Maximum single-print zonesint12maximumSinglePrintZones
Level line widthint1levelLineWidth
Previous level stylestringDashedlevelLineStyle
Show estimated volume profileboolfalseshowEstimatedVolume
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

VP

by dearvn
257 stars

This indicator calculates and plots a session-based volume profile directly on the price chart. It maps volume distribution across price levels, highlighting key structural reference points including the Point of Control, Value Area High, and Value Area Low. C

Indicator v5 VolumeSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of VP

Midnight VWAP

by TradersPost
218 stars

The Midnight VWAP indicator calculates and overlays two distinct Volume Weighted Average Price (VWAP) lines on the price chart based on customizable session timeframes. It tracks an overnight or pre-market session alongside regular trading hours, resetting cal

Indicator v6 Moving averagesSessions & time
Updated 3 months ago · None stated GitHub repository of Midnight VWAP

Moon Dev GEX Arrows

by moondevonyt
92 stars

This overlay indicator plots option strike walls and an optional session VWAP on the price chart for SPY or ES. It spaces major strikes by the selected instrument, draws a set number of strikes on each side, and marks wall-trap and sweep events after a cooldow

Indicator v5 Support & resistanceSessions & time
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev GEX Arrows

This overlay indicator draws a volume profile for a chosen trading session directly on the price chart. It plots the point of control, value area high and low, an optional value area box, a live zone, and a session box with labels. Users select Tokyo, London,

Indicator v5 Sessions & timeVolume
Updated 7 weeks ago · None stated GitHub repository of Sessions & VP with prev session VP & daily/weekly opens

LoD Intraday Entry

by ChartsLector
41 stars

This overlay indicator marks the current day's low and a suggested entry zone a fraction of daily ATR below that low. It compares daily volume with a volume moving average, plots daily SMA and EMA distance, and draws an optional ATR-width price cloud. Labels c

Indicator v6 Support & resistanceVolatility & bands
Updated 2 days ago · None stated GitHub repository of LoD Intraday Entry

RS: Market Profile

by agejevasv
12 stars

This indicator plots a Market Profile on the price chart using time-price opportunity letters or symbols. It marks the point of control, a configurable value area, and the initial balance, and it can highlight a volume-profile point of control and session VWAP

Indicator v5 VolumeSupport & resistance
Updated 2 years ago · None stated GitHub repository of RS: Market Profile

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