Initial Balance Statistical Mapping.txt
Copy
// 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)
Show all 679 lines