NQ Hourly Retracement Levels.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/
// © npg
//@version=5
indicator("NQ Hourly Retracement Levels", "Hourly Levels", true, max_lines_count=500, max_labels_count=500)
// ---------------------------------------------- Inputs --------------------------------------------------
var g_SESS = "========= Trading Sessions =========="
show_sesa = input(true, 'Show NY Session', inline='sesa', group=g_SESS)
sesa_css = input.color(color.rgb(255, 0, 55), '', inline='sesa', group=g_SESS)
show_sesb = input(true, 'Show London Session', inline='sesb', group=g_SESS)
sesb_css = input.color(color.rgb(33, 87, 243), '', inline='sesb', group=g_SESS)
show_sesc = input(true, 'Show Asia Session', inline='sesc', group=g_SESS)
sesc_css = input.color(color.rgb(255, 152, 0), '', inline='sesc', group=g_SESS)
bg_transp = input.float(90, 'Range Area Transparency', group=g_SESS)
show_outline = input(true, 'Range Outline', group=g_SESS)
var g_LEVELS = "========= Hourly Levels =========="
show_hourly_levels = input(true, "Show Hourly Levels at NY Open", group=g_LEVELS)
show_opens = input(true, "Show Hourly Opens", group=g_LEVELS)
show_midpoints = input(true, "Show Hourly Midpoints", group=g_LEVELS)
min_hit_rate = input.float(75.0, "Minimum Hit Rate % to Display", minval=0, maxval=100, step=5, group=g_LEVELS)
line_width = input.int(1, "Line Width", minval=1, maxval=5, group=g_LEVELS)
line_style_input = input.string("Dashed", "Retracement Line Style", options=["Solid", "Dashed", "Dotted"], group=g_LEVELS)
label_size_input = input.string("small", "Label Size", options=["tiny", "small", "normal", "large"], group=g_LEVELS)
remove_on_hit = input(true, "Remove Level When Hit", group=g_LEVELS)
var g_TABLES = "========= Table Settings ========="
show_stats_table = input(true, "Show Statistics Table", group=g_TABLES)
stats_table_pos = input.string("Bottom Right", "Stats Table Position",
options=["Bottom Center", "Bottom Left", "Bottom Right", "Middle Center",
"Middle Left", "Middle Right", "Top Center", "Top Left", "Top Right"], group=g_TABLES)
stats_table_size = input.string("Small", "Stats Table Size",
options=["Auto", "Tiny", "Small", "Normal", "Large", "Huge"], group=g_TABLES)
// ---------------------------------------------- Helper Functions --------------------------------------------------
get_line_style(style_string) =>
switch style_string
"Solid" => line.style_solid
"Dashed" => line.style_dashed
"Dotted" => line.style_dotted
=> line.style_solid
get_label_size(size_string) =>
switch size_string
"tiny" => size.tiny
"small" => size.small
"normal" => size.normal
"large" => size.large
=> size.small
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(size) =>
switch size
'Auto' => size.auto
'Tiny' => size.tiny
'Small' => size.small
'Normal' => size.normal
'Large' => size.large
'Huge' => size.huge
// Get color based on MAE percentage
get_color_for_mae(mae_pct) =>
color result = color.gray
if mae_pct <= 0.25
result := color.rgb(33, 150, 243) // Blue
else if mae_pct <= 0.30
result := color.rgb(156, 39, 176) // Purple
else if mae_pct <= 0.35
result := color.rgb(255, 87, 34) // Deep Orange
else if mae_pct <= 0.40
result := color.rgb(0, 150, 136) // Teal
else if mae_pct <= 0.45
result := color.rgb(255, 152, 0) // Orange
else if mae_pct <= 0.50
result := color.rgb(233, 30, 99) // Pink
else
result := color.rgb(96, 125, 139) // Blue Grey
result
// Get color for the live distance cell
// Green = price is within MAE (level very much in play)
// Yellow = price is within 2× MAE (approaching, worth watching)
// Orange = price is within 3× MAE (possible but stretched)
// Gray = price is beyond 3× MAE (unlikely / far away)
get_distance_color(dist_pct, mae_pct) =>
color result = color.rgb(180, 180, 180) // default gray
if dist_pct <= mae_pct
result := color.rgb(76, 175, 80) // Green – within MAE
else if dist_pct <= mae_pct * 2
result := color.rgb(255, 235, 59) // Yellow – within 2× MAE
else if dist_pct <= mae_pct * 3
result := color.rgb(255, 152, 0) // Orange – within 3× MAE
result
get_distance_text_color(dist_pct, mae_pct) =>
color result = color.rgb(64, 64, 64)
if dist_pct <= mae_pct
result := color.white
else if dist_pct <= mae_pct * 2
result := color.rgb(64, 64, 64)
else if dist_pct <= mae_pct * 3
result := color.rgb(64, 64, 64)
result
// ---------------------------------------------- Session Detection --------------------------------------------------
tf = timeframe.period
var tz = "America/New_York"
is_sesa = math.sign(nz(time(tf, "0800-1600", tz)))
is_sesb = math.sign(nz(time(tf, "0200-0800", tz)))
is_sesc = math.sign(nz(time(tf, "2000-0200", tz)))
// Hourly tracking
is_6pm = math.sign(nz(time(tf, "1800-1900", tz)))
is_7pm = math.sign(nz(time(tf, "1900-2000", tz)))
is_8pm = math.sign(nz(time(tf, "2000-2100", tz)))
is_9pm = math.sign(nz(time(tf, "2100-2200", tz)))
is_10pm = math.sign(nz(time(tf, "2200-2300", tz)))
is_11pm = math.sign(nz(time(tf, "2300-0000", tz)))
is_12am = math.sign(nz(time(tf, "0000-0100", tz)))
is_1am = math.sign(nz(time(tf, "0100-0200", tz)))
is_2am = math.sign(nz(time(tf, "0200-0300", tz)))
is_3am = math.sign(nz(time(tf, "0300-0400", tz)))
is_4am = math.sign(nz(time(tf, "0400-0500", tz)))
is_5am = math.sign(nz(time(tf, "0500-0600", tz)))
is_6am = math.sign(nz(time(tf, "0600-0700", tz)))
is_7am = math.sign(nz(time(tf, "0700-0800", tz)))
// ---------------------------------------------- Session Ranges --------------------------------------------------
get_range(session, session_css)=>
var max = high
var min = low
var sess_open = open
var box bx = na
if session > session[1]
max := high
min := low
sess_open := open
bx := box.new(bar_index, max, bar_index, min,
bgcolor=color.new(session_css, bg_transp),
border_color=show_outline ? session_css : na,
border_style=line.style_dotted)
if session and session == session[1]
max := math.max(high, max)
min := math.min(low, min)
box.set_top(bx, max)
box.set_rightbottom(bx, bar_index, min)
[session ? na : max, session ? na : min, sess_open]
// Session variables
var float max_sesa = na
var float min_sesa = na
var float open_sesa = na
var float max_sesb = na
var float min_sesb = na
var float open_sesb = na
var float max_sesc = na
var float min_sesc = na
var float open_sesc = na
// Track hourly opens and ranges
track_hourly(is_hour, prev_is_hour) =>
var float hour_open = na
var float hour_max = na
var float hour_min = na
if is_hour and is_hour > prev_is_hour
hour_open := open
hour_max := high
hour_min := low
else if is_hour
hour_max := math.max(high, hour_max)
hour_min := math.min(low, hour_min)
[hour_open, hour_max, hour_min]
// Call track_hourly for each hour
[open_6pm, max_6pm, min_6pm] = track_hourly(is_6pm, is_6pm[1])
[open_7pm, max_7pm, min_7pm] = track_hourly(is_7pm, is_7pm[1])
[open_8pm, max_8pm, min_8pm] = track_hourly(is_8pm, is_8pm[1])
[open_9pm, max_9pm, min_9pm] = track_hourly(is_9pm, is_9pm[1])
[open_10pm, max_10pm, min_10pm] = track_hourly(is_10pm, is_10pm[1])
[open_11pm, max_11pm, min_11pm] = track_hourly(is_11pm, is_11pm[1])
[open_12am, max_12am, min_12am] = track_hourly(is_12am, is_12am[1])
[open_1am, max_1am, min_1am] = track_hourly(is_1am, is_1am[1])
[open_2am, max_2am, min_2am] = track_hourly(is_2am, is_2am[1])
[open_3am, max_3am, min_3am] = track_hourly(is_3am, is_3am[1])
[open_4am, max_4am, min_4am] = track_hourly(is_4am, is_4am[1])
[open_5am, max_5am, min_5am] = track_hourly(is_5am, is_5am[1])
[open_6am, max_6am, min_6am] = track_hourly(is_6am, is_6am[1])
[open_7am, max_7am, min_7am] = track_hourly(is_7am, is_7am[1])
// Track which levels have been hit during NY session
var bool hit_7to8_mid = false
var bool hit_7am_open = false
var bool hit_6to7_mid = false
var bool hit_6am_open = false
var bool hit_5to6_mid = false
var bool hit_5am_open = false
var bool hit_4to5_mid = false
var bool hit_4am_open = false
var bool hit_3to4_mid = false
var bool hit_3am_open = false
var bool hit_2to3_mid = false
var bool hit_2am_open = false
var bool hit_1to2_mid = false
var bool hit_1am_open = false
var bool hit_12to1_mid = false
var bool hit_12am_open = false
// Session ranges
if show_sesa
[max, min, sess_open] = get_range(is_sesa, sesa_css)
max_sesa := max
min_sesa := min
open_sesa := sess_open
if show_sesb
[max, min, sess_open] = get_range(is_sesb, sesb_css)
max_sesb := max
min_sesb := min
open_sesb := sess_open
if show_sesc
[max, min, sess_open] = get_range(is_sesc, sesc_css)
max_sesc := max
min_sesc := min
open_sesc := sess_open
// ---------------------------------------------- Pattern Detection --------------------------------------------------
get_session_pattern() =>
var string pattern = "unknown"
if not na(max_sesb) and not na(min_sesb) and not na(max_sesc) and not na(min_sesc)
if max_sesb > max_sesc and min_sesb < min_sesc
pattern := "london_engulfs_asia"
else if max_sesc >= max_sesb and min_sesc <= min_sesb
pattern := "asia_engulfs_london"
else if max_sesb > max_sesc and min_sesb >= min_sesc
pattern := "london_partial_up"
else if max_sesb <= max_sesc and min_sesb < min_sesc
pattern := "london_partial_down"
else
pattern := "none"
pattern
var string current_pattern = "unknown"
if not na(max_sesb) and not na(min_sesb) and not na(max_sesc) and not na(min_sesc)
current_pattern := get_session_pattern()
// ---------------------------------------------- Hit Rate Data --------------------------------------------------
get_overall_hit_rate(level_name) =>
rate = 0.0
rate := switch level_name
"07:00am_midpoint" => 94.19
"07:00am_open" => 88.46
"06:00am_midpoint" => 87.25
"06:00am_open" => 84.42
"05:00am_midpoint" => 84.35
"05:00am_open" => 82.71
"04:00am_midpoint" => 81.73
"04:00am_open" => 79.58
"03:00am_midpoint" => 78.85
"03:00am_open" => 77.14
"02:00am_midpoint" => 76.68
"02:00am_open" => 75.65
"01:00am_midpoint" => 75.21
"01:00am_open" => 74.52
"12:00am_midpoint" => 74.09
"12:00am_open" => 73.67
=> 0.0
rate
get_pattern_hit_rate(level_name, pattern) =>
rate = 0.0
if pattern == "london_engulfs_asia"
rate := switch level_name
"07:00am_midpoint" => 94.14
"07:00am_open" => 87.31
"06:00am_midpoint" => 86.50
"06:00am_open" => 83.94
"05:00am_midpoint" => 83.27
"05:00am_open" => 80.95
"04:00am_midpoint" => 78.27
"04:00am_open" => 75.81
"03:00am_midpoint" => 75.30
"03:00am_open" => 76.25
"02:00am_midpoint" => 75.71
"02:00am_open" => 78.38
"01:00am_midpoint" => 78.68
"01:00am_open" => 79.09
"12:00am_midpoint" => 79.08
"12:00am_open" => 79.53
=> 0.0
else if pattern == "asia_engulfs_london"
rate := switch level_name
"07:00am_midpoint" => 97.52
"07:00am_open" => 91.54
"06:00am_midpoint" => 92.08
"06:00am_open" => 90.55
"05:00am_midpoint" => 90.10
"05:00am_open" => 91.50
"04:00am_midpoint" => 89.60
"04:00am_open" => 88.12
"03:00am_midpoint" => 87.62
"03:00am_open" => 89.05
"02:00am_midpoint" => 90.10
"02:00am_open" => 89.05
"01:00am_midpoint" => 87.13
"01:00am_open" => 84.65
"12:00am_midpoint" => 86.14
"12:00am_open" => 85.57
=> 0.0
else if pattern == "london_partial_up"
rate := switch level_name
"07:00am_midpoint" => 94.31
"07:00am_open" => 89.01
"06:00am_midpoint" => 87.82
"06:00am_open" => 85.16
"05:00am_midpoint" => 85.10
"05:00am_open" => 83.17
"04:00am_midpoint" => 82.45
"04:00am_open" => 79.97
"03:00am_midpoint" => 78.45
"03:00am_open" => 76.58
"02:00am_midpoint" => 75.32
"02:00am_open" => 73.11
"01:00am_midpoint" => 71.71
"01:00am_open" => 70.86
"12:00am_midpoint" => 69.95
"12:00am_open" => 69.46
=> 0.0
else if pattern == "london_partial_down"
rate := switch level_name
"07:00am_midpoint" => 93.40
"07:00am_open" => 87.95
"06:00am_midpoint" => 86.05
"06:00am_open" => 82.48
"05:00am_midpoint" => 82.96
"05:00am_open" => 81.62
"04:00am_midpoint" => 81.79
"04:00am_open" => 80.19
"03:00am_midpoint" => 80.30
"03:00am_open" => 76.04
"02:00am_midpoint" => 76.36
"02:00am_open" => 73.99
"01:00am_midpoint" => 74.55
"01:00am_open" => 73.59
"12:00am_midpoint" => 73.06
"12:00am_open" => 72.13
=> 0.0
rate
get_mae_pct(level_name) =>
mae = 0.0
mae := switch level_name
"07:00am_midpoint" => 0.25
"07:00am_open" => 0.25
"06:00am_midpoint" => 0.25
"06:00am_open" => 0.30
"05:00am_midpoint" => 0.30
"05:00am_open" => 0.35
"04:00am_midpoint" => 0.35
"04:00am_open" => 0.40
"03:00am_midpoint" => 0.40
"03:00am_open" => 0.45
"02:00am_midpoint" => 0.45
"02:00am_open" => 0.45
"01:00am_midpoint" => 0.50
"01:00am_open" => 0.50
"12:00am_midpoint" => 0.50
"12:00am_open" => 0.50
=> 0.50
mae
// ---------------------------------------------- Draw Hourly Levels at NY Open --------------------------------------------------
var array<line> hourly_lines = array.new<line>()
var array<label> hourly_labels = array.new<label>()
var array<float> hourly_prices = array.new<float>()
// Clear old lines and labels
clear_hourly_levels() =>
if array.size(hourly_lines) > 0
for i = 0 to array.size(hourly_lines) - 1
line.delete(array.get(hourly_lines, i))
array.clear(hourly_lines)
if array.size(hourly_labels) > 0
for i = 0 to array.size(hourly_labels) - 1
label.delete(array.get(hourly_labels, i))
array.clear(hourly_labels)
if array.size(hourly_prices) > 0
array.clear(hourly_prices)
// Draw a level
draw_level(price, level_name, hit_rate, mae_pct, ny_open_bar, ny_open_price, is_midpoint) =>
if hit_rate >= min_hit_rate
style = get_line_style(line_style_input)
lbl_size = get_label_size(label_size_input)
level_color = get_color_for_mae(mae_pct)
new_line = line.new(ny_open_bar, price, ny_open_bar, price,
color=level_color, style=style, width=line_width)
array.push(hourly_lines, new_line)
label_text = str.format("{0}: {1,number,#.#}%", level_name, hit_rate)
new_label = label.new(ny_open_bar, price, label_text,
style=label.style_none, textcolor=level_color, size=lbl_size,
textalign=text.align_left, text_font_family=font.family_monospace)
array.push(hourly_labels, new_label)
array.push(hourly_prices, price)
// Check if levels have been hit and remove them
check_and_remove_hit_levels() =>
if remove_on_hit and array.size(hourly_prices) > 0
i = array.size(hourly_prices) - 1
while i >= 0
level_price = array.get(hourly_prices, i)
if low <= level_price and level_price <= high
line.delete(array.get(hourly_lines, i))
label.delete(array.get(hourly_labels, i))
array.remove(hourly_lines, i)
array.remove(hourly_labels, i)
array.remove(hourly_prices, i)
i := i - 1
// At NY open (8am), draw all relevant levels
if show_hourly_levels and is_sesa and is_sesa > is_sesa[1]
clear_hourly_levels()
hit_7to8_mid := false
hit_7am_open := false
hit_6to7_mid := false
hit_6am_open := false
hit_5to6_mid := false
hit_5am_open := false
hit_4to5_mid := false
hit_4am_open := false
hit_3to4_mid := false
hit_3am_open := false
hit_2to3_mid := false
hit_2am_open := false
hit_1to2_mid := false
hit_1am_open := false
hit_12to1_mid := false
hit_12am_open := false
ny_open_bar = bar_index
ny_open_price = open
pattern = current_pattern
if not na(max_7am) and not na(min_7am) and show_midpoints
mid = (max_7am + min_7am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("07:00am_midpoint", pattern) : get_overall_hit_rate("07:00am_midpoint")
mae = get_mae_pct("07:00am_midpoint")
draw_level(mid, "7-8am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_7am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("07:00am_open", pattern) : get_overall_hit_rate("07:00am_open")
mae = get_mae_pct("07:00am_open")
draw_level(open_7am, "7am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_6am) and not na(min_6am) and show_midpoints
mid = (max_6am + min_6am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("06:00am_midpoint", pattern) : get_overall_hit_rate("06:00am_midpoint")
mae = get_mae_pct("06:00am_midpoint")
draw_level(mid, "6-7am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_6am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("06:00am_open", pattern) : get_overall_hit_rate("06:00am_open")
mae = get_mae_pct("06:00am_open")
draw_level(open_6am, "6am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_5am) and not na(min_5am) and show_midpoints
mid = (max_5am + min_5am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("05:00am_midpoint", pattern) : get_overall_hit_rate("05:00am_midpoint")
mae = get_mae_pct("05:00am_midpoint")
draw_level(mid, "5-6am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_5am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("05:00am_open", pattern) : get_overall_hit_rate("05:00am_open")
mae = get_mae_pct("05:00am_open")
draw_level(open_5am, "5am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_4am) and not na(min_4am) and show_midpoints
mid = (max_4am + min_4am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("04:00am_midpoint", pattern) : get_overall_hit_rate("04:00am_midpoint")
mae = get_mae_pct("04:00am_midpoint")
draw_level(mid, "4-5am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_4am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("04:00am_open", pattern) : get_overall_hit_rate("04:00am_open")
mae = get_mae_pct("04:00am_open")
draw_level(open_4am, "4am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_3am) and not na(min_3am) and show_midpoints
mid = (max_3am + min_3am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("03:00am_midpoint", pattern) : get_overall_hit_rate("03:00am_midpoint")
mae = get_mae_pct("03:00am_midpoint")
draw_level(mid, "3-4am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_3am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("03:00am_open", pattern) : get_overall_hit_rate("03:00am_open")
mae = get_mae_pct("03:00am_open")
draw_level(open_3am, "3am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_2am) and not na(min_2am) and show_midpoints
mid = (max_2am + min_2am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("02:00am_midpoint", pattern) : get_overall_hit_rate("02:00am_midpoint")
mae = get_mae_pct("02:00am_midpoint")
draw_level(mid, "2-3am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_2am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("02:00am_open", pattern) : get_overall_hit_rate("02:00am_open")
mae = get_mae_pct("02:00am_open")
draw_level(open_2am, "2am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_1am) and not na(min_1am) and show_midpoints
mid = (max_1am + min_1am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("01:00am_midpoint", pattern) : get_overall_hit_rate("01:00am_midpoint")
mae = get_mae_pct("01:00am_midpoint")
draw_level(mid, "1-2am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_1am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("01:00am_open", pattern) : get_overall_hit_rate("01:00am_open")
mae = get_mae_pct("01:00am_open")
draw_level(open_1am, "1am Open", rate, mae, ny_open_bar, ny_open_price, false)
if not na(max_12am) and not na(min_12am) and show_midpoints
mid = (max_12am + min_12am) / 2
rate = pattern != "unknown" ? get_pattern_hit_rate("12:00am_midpoint", pattern) : get_overall_hit_rate("12:00am_midpoint")
mae = get_mae_pct("12:00am_midpoint")
draw_level(mid, "12-1am Mid", rate, mae, ny_open_bar, ny_open_price, true)
if not na(open_12am) and show_opens
rate = pattern != "unknown" ? get_pattern_hit_rate("12:00am_open", pattern) : get_overall_hit_rate("12:00am_open")
mae = get_mae_pct("12:00am_open")
draw_level(open_12am, "12am Open", rate, mae, ny_open_bar, ny_open_price, false)
// Track hits during NY session
if is_sesa
if not na(max_7am) and not na(min_7am)
mid = (max_7am + min_7am) / 2
if low <= mid and mid <= high
hit_7to8_mid := true
if not na(open_7am) and low <= open_7am and open_7am <= high
hit_7am_open := true
if not na(max_6am) and not na(min_6am)
mid = (max_6am + min_6am) / 2
if low <= mid and mid <= high
hit_6to7_mid := true
if not na(open_6am) and low <= open_6am and open_6am <= high
hit_6am_open := true
if not na(max_5am) and not na(min_5am)
mid = (max_5am + min_5am) / 2
if low <= mid and mid <= high
hit_5to6_mid := true
if not na(open_5am) and low <= open_5am and open_5am <= high
hit_5am_open := true
if not na(max_4am) and not na(min_4am)
mid = (max_4am + min_4am) / 2
if low <= mid and mid <= high
hit_4to5_mid := true
if not na(open_4am) and low <= open_4am and open_4am <= high
hit_4am_open := true
if not na(max_3am) and not na(min_3am)
mid = (max_3am + min_3am) / 2
if low <= mid and mid <= high
hit_3to4_mid := true
if not na(open_3am) and low <= open_3am and open_3am <= high
hit_3am_open := true
if not na(max_2am) and not na(min_2am)
mid = (max_2am + min_2am) / 2
if low <= mid and mid <= high
hit_2to3_mid := true
if not na(open_2am) and low <= open_2am and open_2am <= high
hit_2am_open := true
if not na(max_1am) and not na(min_1am)
mid = (max_1am + min_1am) / 2
if low <= mid and mid <= high
hit_1to2_mid := true
if not na(open_1am) and low <= open_1am and open_1am <= high
hit_1am_open := true
if not na(max_12am) and not na(min_12am)
mid = (max_12am + min_12am) / 2
if low <= mid and mid <= high
hit_12to1_mid := true
if not na(open_12am) and low <= open_12am and open_12am <= high
hit_12am_open := true
// Extend lines during NY session and check for hits
if is_sesa and array.size(hourly_lines) > 0
curr_bar = bar_index
check_and_remove_hit_levels()
remaining = array.size(hourly_lines)
if remaining > 0
for i = 0 to remaining - 1
line.set_x2(array.get(hourly_lines, i), curr_bar)
label.set_x(array.get(hourly_labels, i), curr_bar)
// ---------------------------------------------- Statistics Table --------------------------------------------------
get_pattern_label(pattern) =>
switch pattern
"london_engulfs_asia" => "London Engulfs Asia"
"asia_engulfs_london" => "Asia Engulfs London"
"london_partial_up" => "London Partial Up"
"london_partial_down" => "London Partial Down"
=> "Unknown"
// Helper: compute live distance % from current close to a level price
// Returns na if level price is na
live_dist(level_price) =>
not na(level_price) ? math.abs(close - level_price) / close * 100 : float(na)
// Helper: format distance for table display
// Shows "Hit ✓" if already hit, the % distance if pending, or "—" if level not available
fmt_dist(dist_pct, was_hit) =>
if was_hit
"Hit ✓"
else if na(dist_pct)
"—"
else
str.format("{0,number,#.##}%", dist_pct)
var table stats_table = na
if show_stats_table
table.delete(stats_table)
table_pos = get_table_position(stats_table_pos)
table_size = get_table_text_size(stats_table_size)
header_bg = color.rgb(64, 64, 64)
subheader_bg = color.rgb(96, 96, 96)
header_text = color.white
cell_bg = color.white
cell_text = color.rgb(64, 64, 64)
hit_bg = color.teal
hit_text = color.white
pending_bg = color.yellow
pending_text = color.rgb(64, 64, 64)
// ---- WAITING STATE ----
if not is_sesa
stats_table := table.new(table_pos, 5, 6, border_width=1, border_color=color.rgb(64, 64, 64), bgcolor=cell_bg)
table.cell(stats_table, 0, 0, "━━━ NY SESSION STATS ━━━", bgcolor=header_bg, text_color=header_text,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 0, 0, 4, 0)
table.cell(stats_table, 0, 1, "Active Session", bgcolor=cell_bg, text_color=cell_text,
text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, 1, "Closed", bgcolor=color.rgb(96, 96, 96), text_color=color.white,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 1, 1, 4, 1)
table.cell(stats_table, 0, 2, "Pattern", bgcolor=cell_bg, text_color=cell_text,
text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, 2, "Waiting for next NY session", bgcolor=cell_bg, text_color=color.rgb(150, 150, 150),
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 1, 2, 4, 2)
table.cell(stats_table, 0, 3, "━━━ TOP 11 LEVELS ━━━", bgcolor=header_bg, text_color=header_text,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 0, 3, 4, 3)
table.cell(stats_table, 0, 4, "Level", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, 4, "Hit %", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, 4, "MAE %", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, 4, "Dist Now", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 4, 4, "Status", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 0, 5, "Levels reset at 8am NY", bgcolor=cell_bg, text_color=color.rgb(150, 150, 150),
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 0, 5, 4, 5)
// ---- ACTIVE STATE ----
if is_sesa
// Pre-calculate all level prices for live distance
mid_7to8 = (not na(max_7am) and not na(min_7am)) ? (max_7am + min_7am) / 2 : float(na)
mid_6to7 = (not na(max_6am) and not na(min_6am)) ? (max_6am + min_6am) / 2 : float(na)
mid_5to6 = (not na(max_5am) and not na(min_5am)) ? (max_5am + min_5am) / 2 : float(na)
mid_4to5 = (not na(max_4am) and not na(min_4am)) ? (max_4am + min_4am) / 2 : float(na)
mid_3to4 = (not na(max_3am) and not na(min_3am)) ? (max_3am + min_3am) / 2 : float(na)
mid_2to3 = (not na(max_2am) and not na(min_2am)) ? (max_2am + min_2am) / 2 : float(na)
mid_1to2 = (not na(max_1am) and not na(min_1am)) ? (max_1am + min_1am) / 2 : float(na)
mid_12to1 = (not na(max_12am) and not na(min_12am)) ? (max_12am + min_12am) / 2 : float(na)
// Live distances
d_7to8_mid = live_dist(mid_7to8)
d_7am_open = live_dist(open_7am)
d_6to7_mid = live_dist(mid_6to7)
d_6am_open = live_dist(open_6am)
d_5to6_mid = live_dist(mid_5to6)
d_5am_open = live_dist(open_5am)
d_4to5_mid = live_dist(mid_4to5)
d_4am_open = live_dist(open_4am)
d_3to4_mid = live_dist(mid_3to4)
d_3am_open = live_dist(open_3am)
d_2to3_mid = live_dist(mid_2to3)
d_2am_open = live_dist(open_2am)
d_1to2_mid = live_dist(mid_1to2)
d_1am_open = live_dist(open_1am)
d_12to1_mid = live_dist(mid_12to1)
d_12am_open = live_dist(open_12am)
stats_table := table.new(table_pos, 5, 27, border_width=1, border_color=color.rgb(64, 64, 64), bgcolor=cell_bg)
row = 0
// Header
table.cell(stats_table, 0, row, "━━━ NY SESSION STATS ━━━", bgcolor=header_bg, text_color=header_text,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 0, row, 4, row)
row := row + 1
// Session active
table.cell(stats_table, 0, row, "Active Session", bgcolor=cell_bg, text_color=cell_text,
text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, "New York", bgcolor=sesa_css, text_color=color.white,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 1, row, 4, row)
row := row + 1
// Pattern
pattern_label = get_pattern_label(current_pattern)
table.cell(stats_table, 0, row, "Pattern", bgcolor=cell_bg, text_color=cell_text,
text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, pattern_label, bgcolor=cell_bg, text_color=cell_text,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 1, row, 4, row)
row := row + 1
// Active levels count
active_count = array.size(hourly_lines)
table.cell(stats_table, 0, row, "Active Levels", bgcolor=cell_bg, text_color=cell_text,
text_size=table_size, text_font_family=font.family_monospace)
active_bg = active_count > 0 ? pending_bg : cell_bg
active_txt = active_count > 0 ? pending_text : cell_text
table.cell(stats_table, 1, row, str.tostring(active_count), bgcolor=active_bg, text_color=active_txt,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 1, row, 4, row)
row := row + 1
if current_pattern != "unknown"
// Section header
table.cell(stats_table, 0, row, "━━━ TOP 11 LEVELS ━━━", bgcolor=header_bg, text_color=header_text,
text_size=table_size, text_font_family=font.family_monospace)
table.merge_cells(stats_table, 0, row, 4, row)
row := row + 1
// Column headers
table.cell(stats_table, 0, row, "Level", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, "Hit %", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, "MAE %", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, "Distance from level", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 4, row, "Status", bgcolor=subheader_bg, text_color=header_text, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// ---- Macro to write one level row ----
// (Pine doesn't have macros so we inline each row)
// Level 1: 7-8am Mid
rate = get_pattern_hit_rate("07:00am_midpoint", current_pattern)
mae = get_mae_pct("07:00am_midpoint")
dist = hit_7to8_mid ? float(na) : d_7to8_mid
dist_bg = hit_7to8_mid ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt = hit_7to8_mid ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "7-8am Mid", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_7to8_mid), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg = hit_7to8_mid ? hit_bg : pending_bg
val_txt = hit_7to8_mid ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_7to8_mid ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 2: 7am Open
rate := get_pattern_hit_rate("07:00am_open", current_pattern)
mae := get_mae_pct("07:00am_open")
dist := hit_7am_open ? float(na) : d_7am_open
dist_bg := hit_7am_open ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_7am_open ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "7am Open", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_7am_open), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_7am_open ? hit_bg : pending_bg
val_txt := hit_7am_open ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_7am_open ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 3: 6-7am Mid
rate := get_pattern_hit_rate("06:00am_midpoint", current_pattern)
mae := get_mae_pct("06:00am_midpoint")
dist := hit_6to7_mid ? float(na) : d_6to7_mid
dist_bg := hit_6to7_mid ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_6to7_mid ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "6-7am Mid", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_6to7_mid), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_6to7_mid ? hit_bg : pending_bg
val_txt := hit_6to7_mid ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_6to7_mid ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 4: 6am Open
rate := get_pattern_hit_rate("06:00am_open", current_pattern)
mae := get_mae_pct("06:00am_open")
dist := hit_6am_open ? float(na) : d_6am_open
dist_bg := hit_6am_open ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_6am_open ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "6am Open", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_6am_open), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_6am_open ? hit_bg : pending_bg
val_txt := hit_6am_open ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_6am_open ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 5: 5-6am Mid
rate := get_pattern_hit_rate("05:00am_midpoint", current_pattern)
mae := get_mae_pct("05:00am_midpoint")
dist := hit_5to6_mid ? float(na) : d_5to6_mid
dist_bg := hit_5to6_mid ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_5to6_mid ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "5-6am Mid", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_5to6_mid), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_5to6_mid ? hit_bg : pending_bg
val_txt := hit_5to6_mid ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_5to6_mid ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 6: 5am Open
rate := get_pattern_hit_rate("05:00am_open", current_pattern)
mae := get_mae_pct("05:00am_open")
dist := hit_5am_open ? float(na) : d_5am_open
dist_bg := hit_5am_open ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_5am_open ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "5am Open", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_5am_open), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_5am_open ? hit_bg : pending_bg
val_txt := hit_5am_open ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_5am_open ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 7: 4-5am Mid
rate := get_pattern_hit_rate("04:00am_midpoint", current_pattern)
mae := get_mae_pct("04:00am_midpoint")
dist := hit_4to5_mid ? float(na) : d_4to5_mid
dist_bg := hit_4to5_mid ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_4to5_mid ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "4-5am Mid", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_4to5_mid), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_4to5_mid ? hit_bg : pending_bg
val_txt := hit_4to5_mid ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_4to5_mid ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 8: 4am Open
rate := get_pattern_hit_rate("04:00am_open", current_pattern)
mae := get_mae_pct("04:00am_open")
dist := hit_4am_open ? float(na) : d_4am_open
dist_bg := hit_4am_open ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_4am_open ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "4am Open", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_4am_open), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_4am_open ? hit_bg : pending_bg
val_txt := hit_4am_open ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_4am_open ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 9: 3-4am Mid
rate := get_pattern_hit_rate("03:00am_midpoint", current_pattern)
mae := get_mae_pct("03:00am_midpoint")
dist := hit_3to4_mid ? float(na) : d_3to4_mid
dist_bg := hit_3to4_mid ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_3to4_mid ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "3-4am Mid", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_3to4_mid), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_3to4_mid ? hit_bg : pending_bg
val_txt := hit_3to4_mid ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_3to4_mid ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 10: 3am Open
rate := get_pattern_hit_rate("03:00am_open", current_pattern)
mae := get_mae_pct("03:00am_open")
dist := hit_3am_open ? float(na) : d_3am_open
dist_bg := hit_3am_open ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_3am_open ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "3am Open", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_3am_open), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_3am_open ? hit_bg : pending_bg
val_txt := hit_3am_open ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_3am_open ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
row := row + 1
// Level 11: 2-3am Mid
rate := get_pattern_hit_rate("02:00am_midpoint", current_pattern)
mae := get_mae_pct("02:00am_midpoint")
dist := hit_2to3_mid ? float(na) : d_2to3_mid
dist_bg := hit_2to3_mid ? hit_bg : get_distance_color(nz(dist, 999.0), mae)
dist_txt := hit_2to3_mid ? hit_text : get_distance_text_color(nz(dist, 999.0), mae)
table.cell(stats_table, 0, row, "2-3am Mid", bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 1, row, str.format("{0,number,#.#}%", rate), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 2, row, str.format("{0,number,#.##}%", mae), bgcolor=cell_bg, text_color=cell_text, text_size=table_size, text_font_family=font.family_monospace)
table.cell(stats_table, 3, row, fmt_dist(dist, hit_2to3_mid), bgcolor=dist_bg, text_color=dist_txt, text_size=table_size, text_font_family=font.family_monospace)
val_bg := hit_2to3_mid ? hit_bg : pending_bg
val_txt := hit_2to3_mid ? hit_text : pending_text
table.cell(stats_table, 4, row, hit_2to3_mid ? "Hit ✓" : "Pending", bgcolor=val_bg, text_color=val_txt, text_size=table_size, text_font_family=font.family_monospace)
Show all 947 lines