Indicator · Pine Script v5 · On the chart

Initial Balance Statistical Mapping

by TamaraKGreen· code credited to notprofessorgreen

Initial Balance Statistical Mapping plots the high, low, and midpoint of a defined initial balance session, such as the first hour of trading, directly on the main price chart.

View on GitHub

GitHub stars46on the repository
Pine versionv5runs as is
Lines of code679
Settings20inputs
LicenceNone statedfile: MPL-2.0
Last update7 weeks ago4 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

679 lines of Pine Script v5 from Initial Balance Statistical Mapping.txt, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

Initial Balance Statistical Mapping.txt
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © notprofessorgreen

//@version=5
indicator("Initial Balance Statistical Mapping", shorttitle="IB", overlay=true, max_bars_back=5000)

//-------------------------------------------------------------------------------------------------
// Inputs
//-------------------------------------------------------------------------------------------------
ib_session = input.session("0930-1030", title="IB Period (9:30-10:30 AM ET)", group="Calculation Period")
rth_session = input.session("0930-1600", title="Regular Trading Hours (9:30-16:00 ET)", group="Calculation Period", 
     tooltip="Lines will only extend during these hours")
fill_ib_areas = input.bool(true, "Show IB Box", group="Display")
only_current_zone = input.bool(true, "Only Show Current IB", group="Display")
show_ext_25th = input.bool(false, "Show 25th Percentile Extensions", group="Display")
show_ext_median = input.bool(true, "Show Median (50th) Extensions", group="Display")
show_ext_75th = input.bool(false, "Show 75th Percentile Extensions", group="Display")
show_ext_90th = input.bool(false, "Show 90th Percentile Extensions", group="Display")
show_ext_labels = input.bool(true, "Show Extension Labels", group="Display")

fill_ib_color = input.color(color.rgb(33, 149, 243, 80), "IB Box Color", group="Colors")
border_color = input.color(color.gray, "Box Border Color", group="Colors")
midpoint_color = input.color(color.gray, "Midpoint Color", group="Colors")
ext_color = input.color(color.rgb(33, 149, 243), "Extension Color", group="Colors")

line_style = input.string("Dotted", "Line Style", options=["Solid", "Dashed", "Dotted"], group="Styling")
ext_line_style = input.string("Dotted", "Extension Line Style", options=["Solid", "Dashed", "Dotted"], group="Styling")
ext_line_width = input.int(1, "Extension Line Width", minval=1, maxval=4, group="Styling")
ext_label_size = input.string("Tiny", "Extension Label Size", 
     options=["Tiny", "Small", "Normal", "Large", "Huge"], group="Styling")

table_position = input.string("Top Right", "Table Position", 
     options=["Bottom Center", "Bottom Left", "Bottom Right", "Middle Center", 
              "Middle Left", "Middle Right", "Top Center", "Top Left", "Top Right"], group="Stats Table")
table_size = input.string("Small", "Table Size", 
     options=["Auto", "Tiny", "Small", "Normal", "Large", "Huge"], group="Stats Table")
ib_stats_lookback = input.int(50, "Sessions for IB Stats", minval=5, maxval=250, group="Stats Table")

//-------------------------------------------------------------------------------------------------
// Constants
//-------------------------------------------------------------------------------------------------
string time_zone = "America/New_York"

EXT_HIGH_25TH   = 0.140
EXT_HIGH_MEDIAN = 0.314
EXT_HIGH_75TH   = 0.601
EXT_HIGH_90TH   = 0.997

EXT_LOW_25TH    = 0.163
EXT_LOW_MEDIAN  = 0.380
EXT_LOW_75TH    = 0.794
EXT_LOW_90TH    = 1.366

// IB Combination Statistics
// Format: [sample_size, break_high_pct, break_low_pct, neither_pct, midpoint_retest_pct, median_retest_mins, bullish_close_pct]
BULL_HIGH_ABOVE = array.from(151,  70.9, 25.8, 3.3, 89.4, 3.0, 72.2)
BULL_HIGH_BELOW = array.from(55,   25.5, 69.1, 5.5, 94.5, 3.0, 63.6)
BULL_LOW_ABOVE  = array.from(1281, 80.2, 17.0, 2.8, 76.6, 10.0, 76.7)
BULL_LOW_BELOW  = array.from(141,  42.6, 54.6, 2.8, 96.5, 1.0, 64.5)

BEAR_HIGH_ABOVE = array.from(225,  64.4, 30.7, 4.9, 96.0, 2.0, 46.2)
BEAR_HIGH_BELOW = array.from(1127, 19.3, 73.9, 6.7, 81.0, 6.0, 28.0)
BEAR_LOW_ABOVE  = array.from(45,   64.4, 33.3, 2.2, 91.1, 2.0, 46.7)
BEAR_LOW_BELOW  = array.from(105,  21.0, 77.1, 1.9, 97.1, 2.0, 50.5)

// Post-Break Midpoint Retest Statistics
// Format: [total_breaks, retest_pct, confirm_pct, reverse_pct, stays_pct]
BULL_ABOVE_HIGH_BREAKS = array.from(1134, 44.9, 41.3, 39.1, 19.6)
BULL_ABOVE_LOW_BREAKS  = array.from(257,  56.0, 40.3, 34.7, 25.0)
BULL_BELOW_HIGH_BREAKS = array.from(74,   41.9, 41.9, 45.2, 12.9)
BULL_BELOW_LOW_BREAKS  = array.from(115,  60.0, 26.1, 47.8, 26.1)
BEAR_ABOVE_HIGH_BREAKS = array.from(174,  55.7, 44.3, 41.2, 14.4)
BEAR_ABOVE_LOW_BREAKS  = array.from(84,   66.7, 32.1, 44.6, 23.2)
BEAR_BELOW_HIGH_BREAKS = array.from(240,  45.8, 42.7, 32.7, 24.5)
BEAR_BELOW_LOW_BREAKS  = array.from(914,  52.7, 38.2, 36.9, 24.9)

//-------------------------------------------------------------------------------------------------
// 1-Minute LTF Data
// This is the KEY fix. Rather than trying to detect the first extreme
// bar-by-bar on the chart timeframe (which fails when a single chart bar
// contains both the session high and low), we collect every 1-minute bar
// during the IB session and walk them in order at session end.
// The first 1-min bar whose high equals the session high  → high was first.
// The first 1-min bar whose low  equals the session low   → low  was first.
// Whichever index is smaller wins — simple, accurate, and reliable.
//-------------------------------------------------------------------------------------------------
[high_1m, low_1m] = request.security_lower_tf(ticker.standard(syminfo.tickerid), "1", [high, low])

//-------------------------------------------------------------------------------------------------
// Variables
//-------------------------------------------------------------------------------------------------
var box   ib_area             = na
var line  ib_midpoint         = na
var line  ib_ext_high_25th    = na
var line  ib_ext_high_median  = na
var line  ib_ext_high_75th    = na
var line  ib_ext_high_90th    = na
var line  ib_ext_low_25th     = na
var line  ib_ext_low_median   = na
var line  ib_ext_low_75th     = na
var line  ib_ext_low_90th     = na
var label ib_ext_high_25th_label   = na
var label ib_ext_high_median_label = na
var label ib_ext_high_75th_label   = na
var label ib_ext_high_90th_label   = na
var label ib_ext_low_25th_label    = na
var label ib_ext_low_median_label  = na
var label ib_ext_low_75th_label    = na
var label ib_ext_low_90th_label    = na
var table stats_table = na
var int   offset      = 0
var float ib_high     = na
var float ib_low      = na
var float ib_mid_price = na
var float ib_open     = na
var float ib_close    = na
var bool  ib_formed   = false
var string ib_extreme_first = "none"
var bool  ib_high_broken = false
var bool  ib_low_broken  = false
var bool  table_updated_for_break = false

// Midpoint retest tracking
var bool midpoint_retested = false
var bool left_midpoint     = false
var int  retest_bar_index  = 0

// IB Range tracking
var ib_ranges = array.new_float(0)

// LTF accumulation arrays — collected during session, scanned at session end
var array<float> ltf_highs_frozen = array.new<float>()
var array<float> ltf_lows_frozen  = array.new<float>()
var bool ltf_frozen = false

// Store IB characteristics for post-break table updates
var bool stored_is_bullish     = false
var bool stored_close_above_mid = false
var bool stored_high_first     = false

//-------------------------------------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------------------------------------
inSession(sess) =>
    not na(time(timeframe.period, sess, time_zone))

get_levels(n) =>
    h = high[1]
    l = low[1]
    o = open[n]
    c = close[1]
    for i = 1 to n
        if low[i]  < l
            l := low[i]
        if high[i] > h
            h := high[i]
    [h, l, (h + l) / 2, o, c]

get_line_style(s) =>
    s == "Solid" ? line.style_solid : s == "Dashed" ? line.style_dashed : line.style_dotted

get_label_size(s) =>
    s == "Tiny" ? size.tiny : s == "Small" ? size.small : s == "Normal" ? size.normal : s == "Large" ? size.large : size.huge

get_table_position(pos) =>
    switch pos
        "Bottom Center" => position.bottom_center
        "Bottom Left"   => position.bottom_left
        "Bottom Right"  => position.bottom_right
        "Middle Center" => position.middle_center
        "Middle Left"   => position.middle_left
        "Middle Right"  => position.middle_right
        "Top Center"    => position.top_center
        "Top Left"      => position.top_left
        "Top Right"     => position.top_right

get_table_text_size(sz) =>
    switch sz
        "Auto"   => size.auto
        "Tiny"   => size.tiny
        "Small"  => size.small
        "Normal" => size.normal
        "Large"  => size.large
        "Huge"   => size.huge

get_ordinal_suffix(num) =>
    int last_digit      = math.floor(num) % 10
    int last_two_digits = math.floor(num) % 100
    string suffix = "th"
    if last_two_digits == 11 or last_two_digits == 12 or last_two_digits == 13
        suffix := "th"
    else if last_digit == 1
        suffix := "st"
    else if last_digit == 2
        suffix := "nd"
    else if last_digit == 3
        suffix := "rd"
    suffix

array_mean(arr) =>
    float sum = 0.0
    int sz = array.size(arr)
    if sz > 0
        for i = 0 to sz - 1
            sum += array.get(arr, i)
        sum / sz
    else
        na

calc_percentile_rank(current_value, arr) =>
    int sz = array.size(arr)
    if sz > 0 and not na(current_value)
        int count_below = 0
        for i = 0 to sz - 1
            if array.get(arr, i) < current_value
                count_below += 1
        (count_below / sz) * 100
    else
        na

get_ib_stats(is_bullish, high_first, close_above_mid) =>
    if is_bullish
        if high_first
            close_above_mid ? BULL_HIGH_ABOVE : BULL_HIGH_BELOW
        else
            close_above_mid ? BULL_LOW_ABOVE : BULL_LOW_BELOW
    else
        if high_first
            close_above_mid ? BEAR_HIGH_ABOVE : BEAR_HIGH_BELOW
        else
            close_above_mid ? BEAR_LOW_ABOVE : BEAR_LOW_BELOW

get_retest_stats(is_bullish, close_above_mid, high_broke) =>
    if is_bullish
        if close_above_mid
            high_broke ? BULL_ABOVE_HIGH_BREAKS : BULL_ABOVE_LOW_BREAKS
        else
            high_broke ? BULL_BELOW_HIGH_BREAKS : BULL_BELOW_LOW_BREAKS
    else
        if close_above_mid
            high_broke ? BEAR_ABOVE_HIGH_BREAKS : BEAR_ABOVE_LOW_BREAKS
        else
            high_broke ? BEAR_BELOW_HIGH_BREAKS : BEAR_BELOW_LOW_BREAKS

//-------------------------------------------------------------------------------------------------
// Main Logic
//-------------------------------------------------------------------------------------------------
ins    = inSession(ib_session)
in_rth = inSession(rth_session)

// Reset at IB session start
if ins and not ins[1]
    offset           := 0
    ltf_frozen       := false
    ib_extreme_first := "none"
    ltf_highs_frozen.clear()
    ltf_lows_frozen.clear()

// Accumulate offset and LTF data during session
if ins
    offset += 1
    // Collect all 1-min bars delivered on this chart bar
    if not ltf_frozen and not na(high_1m) and array.size(high_1m) > 0
        for i = 0 to array.size(high_1m) - 1
            ltf_highs_frozen.push(array.get(high_1m, i))
            ltf_lows_frozen.push(array.get(low_1m, i))

// Track breakouts after IB forms
if ib_formed and not na(ib_high) and not na(ib_low)
    if not ib_high_broken and high > ib_high
        ib_high_broken        := true
        table_updated_for_break := false
    if not ib_low_broken and low < ib_low
        ib_low_broken         := true
        table_updated_for_break := false

// Track midpoint retest
if ib_formed and not na(ib_mid_price)
    threshold = ib_mid_price * 0.001
    if not left_midpoint
        if math.abs(close - ib_mid_price) > threshold
            left_midpoint := true
    if left_midpoint and not midpoint_retested
        if math.abs(low - ib_mid_price) <= threshold or math.abs(high - ib_mid_price) <= threshold
            midpoint_retested := true
            retest_bar_index  := bar_index

// End of RTH — clear table and reset
if in_rth[1] and not in_rth
    table.delete(stats_table)
    stats_table             := na
    ib_formed               := false
    table_updated_for_break := false
    midpoint_retested       := false
    left_midpoint           := false

// ============================================================
// SESSION END: determine extreme-first from 1-min LTF data,
// then draw all visuals and build the stats table
// ============================================================
if ins[1] and not ins
    ltf_frozen := true

    // Get IB levels
    [h, l, m, o, c] = get_levels(offset)
    ib_high      := h
    ib_low       := l
    ib_mid_price := m
    ib_open      := o
    ib_close     := c
    ib_formed    := true
    ib_high_broken          := false
    ib_low_broken           := false
    table_updated_for_break := false
    midpoint_retested       := false
    left_midpoint           := false

    // ----------------------------------------------------------------
    // Extreme-first detection via 1-minute data.
    //
    // Walk all accumulated 1-min bars chronologically. Find the earliest
    // index where the bar's high reached the final session high (high_index)
    // and the earliest index where the bar's low reached the final session
    // low (low_index). Whichever index is smaller came first.
    //
    // Using >= / <= rather than == handles floating-point edge cases where
    // a 1-min bar slightly exceeds the OHLC value reported by the HTF bar.
    // ----------------------------------------------------------------
    int high_index = -1
    int low_index  = -1
    int ltf_size   = array.size(ltf_highs_frozen)

    if ltf_size > 0
        for i = 0 to ltf_size - 1
            if high_index == -1 and array.get(ltf_highs_frozen, i) >= h
                high_index := i
            if low_index == -1 and array.get(ltf_lows_frozen, i) <= l
                low_index := i
            if high_index != -1 and low_index != -1
                break

    if high_index != -1 and low_index != -1
        ib_extreme_first := high_index <= low_index ? "high" : "low"
    else if high_index != -1
        ib_extreme_first := "high"
    else if low_index != -1
        ib_extreme_first := "low"
    else
        ib_extreme_first := "none"

    // Store for post-break table updates
    stored_is_bullish      := c >= o
    stored_close_above_mid := c > m
    stored_high_first      := ib_extreme_first == "high"

    // Store IB range
    ib_range = h - l
    array.push(ib_ranges, ib_range)
    if array.size(ib_ranges) > ib_stats_lookback
        array.shift(ib_ranges)

    // ---- Draw visuals ----
    if fill_ib_areas
        if only_current_zone
            box.delete(ib_area)
            line.delete(ib_midpoint)
            line.delete(ib_ext_high_25th)
            line.delete(ib_ext_high_median)
            line.delete(ib_ext_high_75th)
            line.delete(ib_ext_high_90th)
            line.delete(ib_ext_low_25th)
            line.delete(ib_ext_low_median)
            line.delete(ib_ext_low_75th)
            line.delete(ib_ext_low_90th)
            label.delete(ib_ext_high_25th_label)
            label.delete(ib_ext_high_median_label)
            label.delete(ib_ext_high_75th_label)
            label.delete(ib_ext_high_90th_label)
            label.delete(ib_ext_low_25th_label)
            label.delete(ib_ext_low_median_label)
            label.delete(ib_ext_low_75th_label)
            label.delete(ib_ext_low_90th_label)

        ib_area := box.new(
             left=bar_index[offset], top=h, right=bar_index, bottom=l,
             bgcolor=fill_ib_color, border_color=border_color,
             border_style=get_line_style(line_style), border_width=1)
        ib_midpoint := line.new(
             x1=bar_index[offset], y1=m, x2=bar_index, y2=m,
             color=midpoint_color, style=get_line_style(line_style), width=1)

        ext_style  = get_line_style(ext_line_style)
        label_size = get_label_size(ext_label_size)

        if show_ext_25th
            ep25h = h * (1 + EXT_HIGH_25TH / 100)
            ep25l = l * (1 - EXT_LOW_25TH  / 100)
            ib_ext_high_25th := line.new(bar_index[offset], ep25h, bar_index, ep25h, color=ext_color, style=ext_style, width=ext_line_width)
            ib_ext_low_25th  := line.new(bar_index[offset], ep25l, bar_index, ep25l, color=ext_color, style=ext_style, width=ext_line_width)
            if show_ext_labels
                ib_ext_high_25th_label := label.new(bar_index[offset], ep25h, "25th +0.14%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)
                ib_ext_low_25th_label  := label.new(bar_index[offset], ep25l, "25th -0.16%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)

        if show_ext_median
            epmh = h * (1 + EXT_HIGH_MEDIAN / 100)
            epml = l * (1 - EXT_LOW_MEDIAN  / 100)
            ib_ext_high_median := line.new(bar_index[offset], epmh, bar_index, epmh, color=ext_color, style=ext_style, width=ext_line_width)
            ib_ext_low_median  := line.new(bar_index[offset], epml, bar_index, epml, color=ext_color, style=ext_style, width=ext_line_width)
            if show_ext_labels
                ib_ext_high_median_label := label.new(bar_index[offset], epmh, "Median +0.31%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)
                ib_ext_low_median_label  := label.new(bar_index[offset], epml, "Median -0.38%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)

        if show_ext_75th
            ep75h = h * (1 + EXT_HIGH_75TH / 100)
            ep75l = l * (1 - EXT_LOW_75TH  / 100)
            ib_ext_high_75th := line.new(bar_index[offset], ep75h, bar_index, ep75h, color=ext_color, style=ext_style, width=ext_line_width)
            ib_ext_low_75th  := line.new(bar_index[offset], ep75l, bar_index, ep75l, color=ext_color, style=ext_style, width=ext_line_width)
            if show_ext_labels
                ib_ext_high_75th_label := label.new(bar_index[offset], ep75h, "75th +0.60%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)
                ib_ext_low_75th_label  := label.new(bar_index[offset], ep75l, "75th -0.79%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)

        if show_ext_90th
            ep90h = h * (1 + EXT_HIGH_90TH / 100)
            ep90l = l * (1 - EXT_LOW_90TH  / 100)
            ib_ext_high_90th := line.new(bar_index[offset], ep90h, bar_index, ep90h, color=ext_color, style=ext_style, width=ext_line_width)
            ib_ext_low_90th  := line.new(bar_index[offset], ep90l, bar_index, ep90l, color=ext_color, style=ext_style, width=ext_line_width)
            if show_ext_labels
                ib_ext_high_90th_label := label.new(bar_index[offset], ep90h, "90th +1.00%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)
                ib_ext_low_90th_label  := label.new(bar_index[offset], ep90l, "90th -1.37%", style=label.style_label_right, color=color.new(ext_color, 100), textcolor=ext_color, size=label_size)

    // ---- Build stats table ----
    if only_current_zone
        table.delete(stats_table)

    table_pos = get_table_position(table_position)
    ts        = get_table_text_size(table_size)

    header_bg    = color.rgb(64, 64, 64)
    header_text  = color.white
    cell_bg      = color.white
    cell_text    = color.rgb(64, 64, 64)
    highlight_bg = color.rgb(46, 125, 50)
    highlight_text = color.white

    ib_range_current     = h - l
    ib_mean_range        = array_mean(ib_ranges)
    ib_percentile        = calc_percentile_rank(ib_range_current, ib_ranges)
    ib_sessions_captured = array.size(ib_ranges)

    is_bullish      = c >= o
    close_above_mid = c > m
    high_first      = ib_extreme_first == "high"

    stats               = get_ib_stats(is_bullish, high_first, close_above_mid)
    sample_size         = array.get(stats, 0)
    break_high_pct      = array.get(stats, 1)
    break_low_pct       = array.get(stats, 2)
    neither_pct         = array.get(stats, 3)
    midpoint_retest_pct = array.get(stats, 4)
    bullish_close_pct   = array.get(stats, 6)

    stats_table := table.new(table_pos, 3, 20, border_width=1, border_color=color.rgb(64, 64, 64), bgcolor=cell_bg)
    row = 0

    // Header
    table.cell(stats_table, 0, row, "━━━ IB STATISTICS ━━━", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    // IB Direction
    table.cell(stats_table, 0, row, "IB Direction", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    dir_color = is_bullish ? color.green : color.red
    table.cell(stats_table, 1, row, is_bullish ? "Bullish" : "Bearish", bgcolor=cell_bg, text_color=dir_color, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Extreme First
    table.cell(stats_table, 0, row, "Extreme First", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    extreme_text = ib_extreme_first == "high" ? "High First" : ib_extreme_first == "low" ? "Low First" : "Unknown"
    table.cell(stats_table, 1, row, extreme_text, bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Close Position
    table.cell(stats_table, 0, row, "Close Position", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, close_above_mid ? "Above Midpoint" : "Below Midpoint", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Separator
    table.cell(stats_table, 0, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    // Historical Pattern
    table.cell(stats_table, 0, row, "Historical Pattern", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(sample_size, "#") + " sessions", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Next Break
    string break_dir  = ""
    float  break_prob = 0.0
    if break_high_pct > break_low_pct and break_high_pct > neither_pct
        break_dir  := "HIGH"
        break_prob := break_high_pct
    else if break_low_pct > break_high_pct and break_low_pct > neither_pct
        break_dir  := "LOW"
        break_prob := break_low_pct
    else
        break_dir  := "Neither"
        break_prob := neither_pct

    table.cell(stats_table, 0, row, "Next Break", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, break_dir + " " + str.tostring(break_prob, "#.#") + "%", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Midpoint Retest (row 7 — used by live update below)
    table.cell(stats_table, 0, row, "Midpoint Retest", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(midpoint_retest_pct, "#.#") + "%", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "PENDING", bgcolor=cell_bg, text_color=color.rgb(255, 152, 0), text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Session Close (row 8)
    session_dir  = bullish_close_pct > (100 - bullish_close_pct) ? "Bullish" : "Bearish"
    session_prob = math.max(bullish_close_pct, 100 - bullish_close_pct)
    table.cell(stats_table, 0, row, "Session Close", bgcolor=highlight_bg, text_color=highlight_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, session_dir + " " + str.tostring(session_prob, "#.#") + "%", bgcolor=highlight_bg, text_color=highlight_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=highlight_bg, text_color=highlight_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    // Separator (row 9 — break section starts here)
    table.cell(stats_table, 0, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    // IB Range section
    table.cell(stats_table, 0, row, "━━━ IB RANGE ━━━", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    table.cell(stats_table, 0, row, "Current Range", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.format("{0,number,#.##}", ib_range_current), bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    table.cell(stats_table, 0, row, str.format("Mean ({0}/{1}d)", ib_sessions_captured, ib_stats_lookback), bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, na(ib_mean_range) ? "N/A" : str.format("{0,number,#.##}", ib_mean_range), bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    pct_color_ib = na(ib_percentile) ? cell_bg :
         ib_percentile > 80 ? color.rgb(255, 200, 200) :
         ib_percentile > 60 ? color.rgb(220, 220, 220) :
         ib_percentile < 20 ? color.rgb(200, 220, 255) : cell_bg
    table.cell(stats_table, 2, row, na(ib_percentile) ? "" : str.format("{0,number,#.#}", ib_percentile) + get_ordinal_suffix(ib_percentile), bgcolor=pct_color_ib, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)

    offset := 0

// UPDATE TABLE: Midpoint retest confirmed (row 7)
if ib_formed and midpoint_retested and not na(stats_table)
    table.cell(stats_table, 2, 7, "✓ CONFIRMED", bgcolor=color.white, text_color=color.green, text_size=get_table_text_size(table_size), text_font_family=font.family_monospace)

// UPDATE TABLE: Break occurred — fills rows 9 onward (row 9 was the separator)
if ib_formed and (ib_high_broken or ib_low_broken) and not table_updated_for_break and not na(stats_table)
    table_updated_for_break := true

    retest_stats = get_retest_stats(stored_is_bullish, stored_close_above_mid, ib_high_broken)
    total_breaks = array.get(retest_stats, 0)
    retest_pct   = array.get(retest_stats, 1)
    confirm_pct  = array.get(retest_stats, 2)
    reverse_pct  = array.get(retest_stats, 3)
    stays_pct    = array.get(retest_stats, 4)

    ts          = get_table_text_size(table_size)
    cell_bg     = color.white
    cell_text   = color.rgb(64, 64, 64)
    header_bg   = color.rgb(64, 64, 64)
    header_text = color.white
    break_bg    = color.rgb(255, 152, 0)
    break_text  = color.white

    row = 9

    // Separator
    table.cell(stats_table, 0, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    // Break Alert Header
    break_label = ib_high_broken ? "━ IB HIGH BROKEN ━" : "━ IB LOW BROKEN ━"
    table.cell(stats_table, 0, row, break_label, bgcolor=break_bg, text_color=break_text, text_size=ts, text_font_family=font.family_monospace)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    table.cell(stats_table, 0, row, "Historical Breaks", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(total_breaks, "#") + " times", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    table.cell(stats_table, 0, row, "Mid Retest Prob", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(retest_pct, "#.#") + "%", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    table.cell(stats_table, 0, row, "If Retest Occurs:", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    table.merge_cells(stats_table, 0, row, 2, row)
    row += 1

    confirm_label = ib_high_broken ? "Bounce Back UP" : "Bounce Back DOWN"
    table.cell(stats_table, 0, row, confirm_label, bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(confirm_pct, "#.#") + "%", bgcolor=cell_bg, text_color=color.green, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    reverse_label = ib_high_broken ? "Reverse to LOW" : "Reverse to HIGH"
    table.cell(stats_table, 0, row, reverse_label, bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(reverse_pct, "#.#") + "%", bgcolor=cell_bg, text_color=color.red, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    row += 1

    table.cell(stats_table, 0, row, "Stays in Range", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 1, row, str.tostring(stays_pct, "#.#") + "%", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
    table.cell(stats_table, 2, row, "", bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)

// Pending table while IB hasn't formed yet
if in_rth and not ib_formed and na(stats_table)
    table_pos     = get_table_position(table_position)
    ts            = get_table_text_size(table_size)
    header_bg     = color.rgb(64, 64, 64)
    header_text   = color.white
    cell_bg       = color.white
    cell_text     = color.rgb(64, 64, 64)
    pending_color = color.rgb(255, 152, 0)

    stats_table := table.new(table_pos, 2, 10, border_width=1, border_color=color.rgb(64, 64, 64), bgcolor=cell_bg)
    row = 0

    table.cell(stats_table, 0, row, "━━━ IB STATISTICS ━━━", bgcolor=header_bg, text_color=header_text, text_size=ts, text_font_family=font.family_monospace)
    table.merge_cells(stats_table, 0, row, 1, row)
    row += 1

    for label_str in array.from("IB Direction", "Extreme First", "Close Position", "", "Next Break", "Midpoint Retest", "Session Close")
        table.cell(stats_table, 0, row, label_str, bgcolor=cell_bg, text_color=cell_text, text_size=ts, text_font_family=font.family_monospace)
        table.cell(stats_table, 1, row, "PENDING", bgcolor=cell_bg, text_color=pending_color, text_size=ts, text_font_family=font.family_monospace)
        row += 1

// Extend lines during RTH
if ib_formed and in_rth
    if not na(ib_ext_high_25th)
        line.set_x2(ib_ext_high_25th, bar_index)
    if not na(ib_ext_low_25th)
        line.set_x2(ib_ext_low_25th, bar_index)
    if not na(ib_ext_high_25th_label)
        label.set_x(ib_ext_high_25th_label, bar_index)
    if not na(ib_ext_low_25th_label)
        label.set_x(ib_ext_low_25th_label, bar_index)
    if not na(ib_ext_high_median)
        line.set_x2(ib_ext_high_median, bar_index)
    if not na(ib_ext_low_median)
        line.set_x2(ib_ext_low_median, bar_index)
    if not na(ib_ext_high_median_label)
        label.set_x(ib_ext_high_median_label, bar_index)
    if not na(ib_ext_low_median_label)
        label.set_x(ib_ext_low_median_label, bar_index)
    if not na(ib_ext_high_75th)
        line.set_x2(ib_ext_high_75th, bar_index)
    if not na(ib_ext_low_75th)
        line.set_x2(ib_ext_low_75th, bar_index)
    if not na(ib_ext_high_75th_label)
        label.set_x(ib_ext_high_75th_label, bar_index)
    if not na(ib_ext_low_75th_label)
        label.set_x(ib_ext_low_75th_label, bar_index)
    if not na(ib_ext_high_90th)
        line.set_x2(ib_ext_high_90th, bar_index)
    if not na(ib_ext_low_90th)
        line.set_x2(ib_ext_low_90th, bar_index)
    if not na(ib_ext_high_90th_label)
        label.set_x(ib_ext_high_90th_label, bar_index)
    if not na(ib_ext_low_90th_label)
        label.set_x(ib_ext_low_90th_label, bar_index)

Overview

Initial Balance Statistical Mapping projects statistical extension levels using fixed percentile multipliers (25th, median, 75th, and 90th) and presents historical session performance data in an on-screen table. Custom inputs allow traders to configure session hours, select active extension levels, adjust table lookbacks, and change styling.

Details

Published by

TamaraKGreen on GitHub

Code credit

notprofessorgreen, as written in the script's header

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Support & resistance, Sessions & time, Screeners & dashboards

Markets

Stocks, Indices

Timeframes

M1-M5, M15-H1

Risk Notes

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

Settings

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

SettingTypeDefaultVariable
IB Period (9:30-10:30 AM ET)session0930-1030ib_session
Regular Trading Hours (9:30-16:00 ET)session0930-1600rth_session
Show IB Boxbooltruefill_ib_areas
Only Show Current IBbooltrueonly_current_zone
Show 25th Percentile Extensionsboolfalseshow_ext_25th
Show Median (50th) Extensionsbooltrueshow_ext_median
Show 75th Percentile Extensionsboolfalseshow_ext_75th
Show 90th Percentile Extensionsboolfalseshow_ext_90th
Show Extension Labelsbooltrueshow_ext_labels
IB Box Colorcolorcolor.rgb(33, 149, 243, 80)fill_ib_color
Box Border Colorcolorcolor.grayborder_color
Midpoint Colorcolorcolor.graymidpoint_color
Show all 20 settings
SettingTypeDefaultVariable
Extension Colorcolorcolor.rgb(33, 149, 243)ext_color
Line StylestringDottedline_style
Extension Line StylestringDottedext_line_style
Extension Line Widthint1ext_line_width
Extension Label SizestringTinyext_label_size
Table PositionstringTop Righttable_position
Table SizestringSmalltable_size
Sessions for IB Statsint50ib_stats_lookback
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