Indicator · Pine Script v5 · On the chart

NY vs Asia Statistical Levels

by TamaraKGreen· code credited to lucymatos

This overlay indicator maps Asia, London, and New York session ranges on the price chart and compares later price action with the Asia range.

View on GitHub

GitHub stars46on the repository
Pine versionv5runs as is
Lines of code791
Settings24inputs
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

791 lines of Pine Script v5 from NY vs Asia Statistical Levels.txt, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

NY vs Asia Statistical Levels.txt
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © lucymatos

//@version=5
indicator(title="NY vs Asia Statistical Levels", shorttitle="NY/Asia Stats", overlay=true, max_lines_count=500, max_labels_count=500, max_boxes_count=300)

// ============================================================
// SETTINGS
// ============================================================
var g_GL = "General"
hist_sessions   = input.int(100,  title="Lookback Sessions", minval=10, group=g_GL)
label_sz_input  = input.string("Small", title="Label Size", options=["Tiny","Small","Normal"], group=g_GL)
show_table      = input.bool(true, title="Show Stats Table", group=g_GL)
table_pos_input = input.string("Bottom Right", title="Table Position",
                   options=["Top Left","Top Right","Bottom Left","Bottom Right"], group=g_GL)
table_sz_input  = input.string("Tiny", title="Table Text Size", options=["Tiny","Small","Normal"], group=g_GL)

var g_SESS = "Session Ranges"
show_asia_box   = input.bool(true,  title="Show Asia Range Box",   group=g_SESS)
show_london_box = input.bool(true,  title="Show London Range Box", group=g_SESS)
show_ny_box     = input.bool(true,  title="Show NY Range Box",     group=g_SESS)
bg_transp       = input.float(90,   title="Box Fill Transparency", minval=50, maxval=99, group=g_SESS)
show_outline    = input.bool(true,  title="Box Outline",           group=g_SESS)
col_asia_box    = input.color(color.rgb(255,152,0),  title="Asia Box Color",   group=g_SESS)
col_london_box  = input.color(color.rgb(33,87,243),  title="London Box Color", group=g_SESS)
col_ny_box      = input.color(color.rgb(255,0,55),   title="NY Box Color",     group=g_SESS)

var g_GRID = "Asia Range Grid (from NY Open)"
show_grid       = input.bool(true,  title="Show Asia Range Grid",         group=g_GRID)
grid_multiples  = input.int(3,      title="Multiples above & below open", minval=1, maxval=10, group=g_GRID)
col_grid        = input.color(color.rgb(120,180,220), title="Grid Level Color",  group=g_GRID)
grid_line_style = input.string("Dashed", title="Grid Line Style", options=["Solid","Dashed","Dotted"], group=g_GRID)
grid_line_width = input.int(1, title="Grid Line Width", minval=1, maxval=4, group=g_GRID)

var g_LV = "Statistical Levels"
show_ext_levels = input.bool(true, title="Show Extension Levels (P50/75/90 above & below Asia range)", group=g_LV)
show_rev_levels = input.bool(true, title="Show Reversal Levels (P50/75 from extension)", group=g_LV)
col_ext_up      = input.color(color.rgb(80,200,80),  title="Extension Above Color", group=g_LV)
col_ext_dn      = input.color(color.rgb(200,80,80),  title="Extension Below Color", group=g_LV)
col_rev         = input.color(color.rgb(200,160,40), title="Reversal Level Color",  group=g_LV)
col_hit         = input.color(color.rgb(130,130,130),title="Hit Level Color (✓)",   group=g_LV)

// ============================================================
// HELPERS
// ============================================================
TZ = "America/New_York"

f_lsz() =>
    label_sz_input == "Tiny" ? size.tiny : label_sz_input == "Small" ? size.small : size.normal

f_pct(arr, p) =>
    array.size(arr) >= 4 ? array.percentile_nearest_rank(arr, p) : na

f_mean(arr) =>
    s = array.size(arr)
    s >= 2 ? array.sum(arr) / s : na

f_stdev(arr) =>
    s = array.size(arr)
    if s < 4
        na
    else
        m = array.sum(arr) / s
        variance = 0.0
        for i = 0 to s - 1
            d = array.get(arr, i) - m
            variance += d * d
        math.sqrt(variance / s)

f_zscore(arr, val) =>
    sd = f_stdev(arr)
    m  = f_mean(arr)
    not na(sd) and sd > 0 and not na(m) ? (val - m) / sd : na

f_pctrank(arr, val) =>
    s = array.size(arr)
    if s < 2
        na
    else
        cnt = 0
        for i = 0 to s - 1
            if array.get(arr, i) <= val
                cnt += 1
        100.0 * cnt / s

f_line(x1, x2, y, c, ls, lw) =>
    line.new(x1, y, x2, y, color=c,
             style=ls == "dashed" ? line.style_dashed :
                   ls == "solid"  ? line.style_solid  : line.style_dotted,
             width=lw)

f_lbl(x, y, txt, c, sz) =>
    label.new(x, y, " " + txt,
              style=label.style_none, textcolor=c,
              color=color.new(color.white, 100),
              size=sz, text_font_family=font.family_monospace,
              textalign=text.align_left)

f_box(x1, x2, top, bot, c, transp, outline) =>
    box.new(x1, top, x2, bot,
             bgcolor=color.new(c, transp),
             border_color=outline ? c : na,
             border_style=line.style_dotted,
             border_width=1)

// ============================================================
// SESSION DETECTION
// ============================================================
tf = timeframe.period

is_asia   = math.sign(nz(time(tf, "1900-0200", TZ)))
is_london = math.sign(nz(time(tf, "0200-0800", TZ)))
is_ny     = math.sign(nz(time(tf, "0800-1600", TZ)))

asia_start   = is_asia   > is_asia[1]
asia_end     = is_asia   < is_asia[1]
london_start = is_london > is_london[1]
ny_start     = is_ny     > is_ny[1]
ny_end       = is_ny     < is_ny[1]

// ============================================================
// SESSION RANGE TRACKING
// ============================================================
var float asia_h         = na
var float asia_l         = na
var float asia_o         = na
var float asia_mid       = na
var float asia_rng       = na
var int   asia_start_bar = na

var float london_h         = na
var float london_l         = na
var float london_o         = na
var int   london_start_bar = na

var float ny_o             = na
var float ny_h             = na
var float ny_l             = na
var int   ny_start_bar_idx = na

if asia_start
    asia_o         := open
    asia_h         := high
    asia_l         := low
    asia_start_bar := bar_index
else if is_asia
    asia_h := math.max(asia_h, high)
    asia_l := math.min(asia_l, low)

if asia_end
    asia_mid := (asia_h + asia_l) / 2
    asia_rng := asia_h - asia_l

if london_start
    london_o         := open
    london_h         := high
    london_l         := low
    london_start_bar := bar_index
else if is_london
    london_h := math.max(london_h, high)
    london_l := math.min(london_l, low)

if ny_start
    ny_o             := open
    ny_h             := high
    ny_l             := low
    ny_start_bar_idx := bar_index
else if is_ny
    ny_h := math.max(ny_h, high)
    ny_l := math.min(ny_l, low)

var float ny_ext_hi = na
var float ny_ext_lo = na

if ny_start
    ny_ext_hi := 0.0
    ny_ext_lo := 0.0
else if is_ny
    if not na(asia_h)
        ny_ext_hi := math.max(ny_ext_hi, math.max(0.0, high - asia_h))
    if not na(asia_l)
        ny_ext_lo := math.max(ny_ext_lo, math.max(0.0, asia_l - low))

var int ny_bucket = -1

if ny_start and not na(asia_h) and not na(asia_l) and not na(asia_mid)
    if ny_o > asia_h
        ny_bucket := 2
    else if ny_o < asia_l
        ny_bucket := 3
    else if ny_o >= asia_mid
        ny_bucket := 1
    else
        ny_bucket := 0

// ============================================================
// SESSION BOXES
// ============================================================
var box box_asia   = na
var box box_london = na
var box box_ny     = na

if asia_start and show_asia_box
    box.delete(box_asia)
    box_asia := f_box(bar_index, bar_index + 1, high, low, col_asia_box, bg_transp, show_outline)

if is_asia and show_asia_box and not na(box_asia)
    box.set_top(box_asia, asia_h)
    box.set_rightbottom(box_asia, bar_index, asia_l)

if london_start and show_london_box
    box.delete(box_london)
    box_london := f_box(bar_index, bar_index + 1, high, low, col_london_box, bg_transp, show_outline)

if is_london and show_london_box and not na(box_london)
    box.set_top(box_london, london_h)
    box.set_rightbottom(box_london, bar_index, london_l)

if ny_start and show_ny_box
    box.delete(box_ny)
    box_ny := f_box(bar_index, bar_index + 1, high, low, col_ny_box, bg_transp, show_outline)

if is_ny and show_ny_box and not na(box_ny)
    box.set_top(box_ny, ny_h)
    box.set_rightbottom(box_ny, bar_index, ny_l)

// ============================================================
// STAT ARRAYS — 4 BUCKETS
// ============================================================
var float[] asia_ranges = array.new_float(0)

var float[] mult_0 = array.new_float(0)
var float[] mult_1 = array.new_float(0)
var float[] mult_2 = array.new_float(0)
var float[] mult_3 = array.new_float(0)

var float[] ext_hi_0 = array.new_float(0)
var float[] ext_hi_1 = array.new_float(0)
var float[] ext_hi_2 = array.new_float(0)
var float[] ext_hi_3 = array.new_float(0)

var float[] ext_lo_0 = array.new_float(0)
var float[] ext_lo_1 = array.new_float(0)
var float[] ext_lo_2 = array.new_float(0)
var float[] ext_lo_3 = array.new_float(0)

var float[] rev_hi_0 = array.new_float(0)
var float[] rev_hi_1 = array.new_float(0)
var float[] rev_hi_2 = array.new_float(0)
var float[] rev_hi_3 = array.new_float(0)

var float[] rev_lo_0 = array.new_float(0)
var float[] rev_lo_1 = array.new_float(0)
var float[] rev_lo_2 = array.new_float(0)
var float[] rev_lo_3 = array.new_float(0)

f_push_trim(arr0, arr1, arr2, arr3, bucket, val) =>
    if bucket == 0
        array.push(arr0, val)
        if array.size(arr0) > hist_sessions
            array.shift(arr0)
    else if bucket == 1
        array.push(arr1, val)
        if array.size(arr1) > hist_sessions
            array.shift(arr1)
    else if bucket == 2
        array.push(arr2, val)
        if array.size(arr2) > hist_sessions
            array.shift(arr2)
    else if bucket == 3
        array.push(arr3, val)
        if array.size(arr3) > hist_sessions
            array.shift(arr3)

if ny_end and ny_bucket >= 0 and not na(asia_rng) and asia_rng > 0
    array.push(asia_ranges, asia_rng)
    if array.size(asia_ranges) > hist_sessions
        array.shift(asia_ranges)

    ny_rng_val = ny_h - ny_l
    f_push_trim(mult_0, mult_1, mult_2, mult_3, ny_bucket, ny_rng_val / asia_rng)
    f_push_trim(ext_hi_0, ext_hi_1, ext_hi_2, ext_hi_3, ny_bucket, ny_ext_hi / asia_rng)
    f_push_trim(ext_lo_0, ext_lo_1, ext_lo_2, ext_lo_3, ny_bucket, ny_ext_lo / asia_rng)

    if ny_ext_hi > 0
        rev_hi_val = (asia_h + ny_ext_hi) - ny_l
        f_push_trim(rev_hi_0, rev_hi_1, rev_hi_2, rev_hi_3, ny_bucket, rev_hi_val)

    if ny_ext_lo > 0
        rev_lo_val = ny_h - (asia_l - ny_ext_lo)
        f_push_trim(rev_lo_0, rev_lo_1, rev_lo_2, rev_lo_3, ny_bucket, rev_lo_val)

// ============================================================
// CURRENT SESSION COMPUTED LEVELS
// ============================================================
var float cur_asia_h   = na
var float cur_asia_l   = na
var float cur_asia_rng = na
var float cur_asia_mid = na
var int   cur_bucket   = -1

var float lv_ext_hi_p50 = na
var float lv_ext_hi_p75 = na
var float lv_ext_hi_p90 = na
var float lv_ext_lo_p50 = na
var float lv_ext_lo_p75 = na
var float lv_ext_lo_p90 = na

var float lv_rev_hi_p50 = na
var float lv_rev_hi_p75 = na
var float lv_rev_lo_p50 = na
var float lv_rev_lo_p75 = na

var float hr_ext_hi_p50 = na
var float hr_ext_hi_p75 = na
var float hr_ext_hi_p90 = na
var float hr_ext_lo_p50 = na
var float hr_ext_lo_p75 = na
var float hr_ext_lo_p90 = na
var float hr_rev_hi_p50 = na
var float hr_rev_hi_p75 = na
var float hr_rev_lo_p50 = na
var float hr_rev_lo_p75 = na

// Grid hit rates: index 0 = 1x multiple, index 9 = 10x multiple
var float[] hr_grid = array.new_float(10, na)

if ny_start and not na(asia_h) and not na(asia_rng) and asia_rng > 0
    cur_asia_h   := asia_h
    cur_asia_l   := asia_l
    cur_asia_rng := asia_rng
    cur_asia_mid := asia_mid
    cur_bucket   := ny_bucket

    eh = cur_bucket == 0 ? ext_hi_0 : cur_bucket == 1 ? ext_hi_1 : cur_bucket == 2 ? ext_hi_2 : ext_hi_3
    el = cur_bucket == 0 ? ext_lo_0 : cur_bucket == 1 ? ext_lo_1 : cur_bucket == 2 ? ext_lo_2 : ext_lo_3
    rh = cur_bucket == 0 ? rev_hi_0 : cur_bucket == 1 ? rev_hi_1 : cur_bucket == 2 ? rev_hi_2 : rev_hi_3
    rl = cur_bucket == 0 ? rev_lo_0 : cur_bucket == 1 ? rev_lo_1 : cur_bucket == 2 ? rev_lo_2 : rev_lo_3
    m  = cur_bucket == 0 ? mult_0   : cur_bucket == 1 ? mult_1   : cur_bucket == 2 ? mult_2   : mult_3

    lv_ext_hi_p50 := not na(f_pct(eh, 50)) ? cur_asia_h + f_pct(eh, 50) * cur_asia_rng : na
    lv_ext_hi_p75 := not na(f_pct(eh, 75)) ? cur_asia_h + f_pct(eh, 75) * cur_asia_rng : na
    lv_ext_hi_p90 := not na(f_pct(eh, 90)) ? cur_asia_h + f_pct(eh, 90) * cur_asia_rng : na
    lv_ext_lo_p50 := not na(f_pct(el, 50)) ? cur_asia_l - f_pct(el, 50) * cur_asia_rng : na
    lv_ext_lo_p75 := not na(f_pct(el, 75)) ? cur_asia_l - f_pct(el, 75) * cur_asia_rng : na
    lv_ext_lo_p90 := not na(f_pct(el, 90)) ? cur_asia_l - f_pct(el, 90) * cur_asia_rng : na

    ext_hi_p50_price = not na(f_pct(eh, 50)) ? cur_asia_h + f_pct(eh, 50) * cur_asia_rng : na
    ext_lo_p50_price = not na(f_pct(el, 50)) ? cur_asia_l - f_pct(el, 50) * cur_asia_rng : na

    lv_rev_hi_p50 := not na(ext_hi_p50_price) and not na(f_pct(rh, 50)) ? ext_hi_p50_price - f_pct(rh, 50) : na
    lv_rev_hi_p75 := not na(ext_hi_p50_price) and not na(f_pct(rh, 75)) ? ext_hi_p50_price - f_pct(rh, 75) : na
    lv_rev_lo_p50 := not na(ext_lo_p50_price) and not na(f_pct(rl, 50)) ? ext_lo_p50_price + f_pct(rl, 50) : na
    lv_rev_lo_p75 := not na(ext_lo_p50_price) and not na(f_pct(rl, 75)) ? ext_lo_p50_price + f_pct(rl, 75) : na

    // Extension hit rates
    tot_eh = array.size(eh)
    if tot_eh >= 4
        p50t = f_pct(eh, 50)
        p75t = f_pct(eh, 75)
        p90t = f_pct(eh, 90)
        c50 = 0
        c75 = 0
        c90 = 0
        for i = 0 to tot_eh - 1
            v = array.get(eh, i)
            if v >= p50t
                c50 += 1
            if v >= p75t
                c75 += 1
            if v >= p90t
                c90 += 1
        hr_ext_hi_p50 := 100.0 * c50 / tot_eh
        hr_ext_hi_p75 := 100.0 * c75 / tot_eh
        hr_ext_hi_p90 := 100.0 * c90 / tot_eh

    tot_el = array.size(el)
    if tot_el >= 4
        p50tl = f_pct(el, 50)
        p75tl = f_pct(el, 75)
        p90tl = f_pct(el, 90)
        c50l = 0
        c75l = 0
        c90l = 0
        for i = 0 to tot_el - 1
            v = array.get(el, i)
            if v >= p50tl
                c50l += 1
            if v >= p75tl
                c75l += 1
            if v >= p90tl
                c90l += 1
        hr_ext_lo_p50 := 100.0 * c50l / tot_el
        hr_ext_lo_p75 := 100.0 * c75l / tot_el
        hr_ext_lo_p90 := 100.0 * c90l / tot_el

    tot_rh = array.size(rh)
    if tot_rh >= 4
        rh50t = f_pct(rh, 50)
        rh75t = f_pct(rh, 75)
        crh50 = 0
        crh75 = 0
        for i = 0 to tot_rh - 1
            v = array.get(rh, i)
            if v >= rh50t
                crh50 += 1
            if v >= rh75t
                crh75 += 1
        hr_rev_hi_p50 := 100.0 * crh50 / tot_rh
        hr_rev_hi_p75 := 100.0 * crh75 / tot_rh

    tot_rl = array.size(rl)
    if tot_rl >= 4
        rl50t = f_pct(rl, 50)
        rl75t = f_pct(rl, 75)
        crl50 = 0
        crl75 = 0
        for i = 0 to tot_rl - 1
            v = array.get(rl, i)
            if v >= rl50t
                crl50 += 1
            if v >= rl75t
                crl75 += 1
        hr_rev_lo_p50 := 100.0 * crl50 / tot_rl
        hr_rev_lo_p75 := 100.0 * crl75 / tot_rl

    // Grid hit rates: % of sessions where NY range mult >= k (i.e. session covered k Asia ranges)
    tot_m = array.size(m)
    if tot_m >= 4
        for k = 1 to 10
            cnt_k = 0
            for i = 0 to tot_m - 1
                if array.get(m, i) >= float(k)
                    cnt_k += 1
            array.set(hr_grid, k - 1, 100.0 * cnt_k / tot_m)

// ============================================================
// LEVEL DRAWING + HIT TRACKING
// ============================================================

// -- Extension above Asia H --
var line  ln_ehi50 = na
var label lb_ehi50 = na
var bool  ht_ehi50 = false
var line  ln_ehi75 = na
var label lb_ehi75 = na
var bool  ht_ehi75 = false
var line  ln_ehi90 = na
var label lb_ehi90 = na
var bool  ht_ehi90 = false

// -- Extension below Asia L --
var line  ln_elo50 = na
var label lb_elo50 = na
var bool  ht_elo50 = false
var line  ln_elo75 = na
var label lb_elo75 = na
var bool  ht_elo75 = false
var line  ln_elo90 = na
var label lb_elo90 = na
var bool  ht_elo90 = false

// -- Reversal from high --
var line  ln_rhi50 = na
var label lb_rhi50 = na
var bool  ht_rhi50 = false
var line  ln_rhi75 = na
var label lb_rhi75 = na
var bool  ht_rhi75 = false

// -- Reversal from low --
var line  ln_rlo50 = na
var label lb_rlo50 = na
var bool  ht_rlo50 = false
var line  ln_rlo75 = na
var label lb_rlo75 = na
var bool  ht_rlo75 = false

// -- Asia range grid --
var line[]  ln_grid_up = array.new_line()
var label[] lb_grid_up = array.new_label()
var bool[]  ht_grid_up = array.new_bool()
var line[]  ln_grid_dn = array.new_line()
var label[] lb_grid_dn = array.new_label()
var bool[]  ht_grid_dn = array.new_bool()

f_draw_level(y_price, txt, col, ls) =>
    lsz = f_lsz()
    ln  = f_line(bar_index, bar_index + 5, y_price, col, ls, 1)
    lb  = f_lbl(bar_index + 1, y_price, txt, col, lsz)
    [ln, lb]

f_ext_label(prefix, p, hr) =>
    not na(hr) ? prefix + " P" + str.tostring(p) + " [" + str.format("{0,number,#.#}", hr) + "%]" : prefix + " P" + str.tostring(p)

f_grid_lbl(mult, hr) =>
    hr_str = not na(hr) ? " [" + str.format("{0,number,#.#}", hr) + "%]" : ""
    "+" + str.tostring(mult) + "x AR" + hr_str

f_grid_lbl_dn(mult, hr) =>
    hr_str = not na(hr) ? " [" + str.format("{0,number,#.#}", hr) + "%]" : ""
    "-" + str.tostring(mult) + "x AR" + hr_str

f_bucket_name(b) =>
    b == 0 ? "InLo" : b == 1 ? "InHi" : b == 2 ? "AbvH" : "BlwL"

f_clear_levels() =>
    line.delete(ln_ehi50)
    label.delete(lb_ehi50)
    line.delete(ln_ehi75)
    label.delete(lb_ehi75)
    line.delete(ln_ehi90)
    label.delete(lb_ehi90)
    line.delete(ln_elo50)
    label.delete(lb_elo50)
    line.delete(ln_elo75)
    label.delete(lb_elo75)
    line.delete(ln_elo90)
    label.delete(lb_elo90)
    line.delete(ln_rhi50)
    label.delete(lb_rhi50)
    line.delete(ln_rhi75)
    label.delete(lb_rhi75)
    line.delete(ln_rlo50)
    label.delete(lb_rlo50)
    line.delete(ln_rlo75)
    label.delete(lb_rlo75)
    if array.size(ln_grid_up) > 0
        for i = 0 to array.size(ln_grid_up) - 1
            line.delete(array.get(ln_grid_up, i))
            label.delete(array.get(lb_grid_up, i))
    if array.size(ln_grid_dn) > 0
        for i = 0 to array.size(ln_grid_dn) - 1
            line.delete(array.get(ln_grid_dn, i))
            label.delete(array.get(lb_grid_dn, i))
    array.clear(ln_grid_up)
    array.clear(lb_grid_up)
    array.clear(ht_grid_up)
    array.clear(ln_grid_dn)
    array.clear(lb_grid_dn)
    array.clear(ht_grid_dn)

if ny_start
    f_clear_levels()
    ht_ehi50 := false
    ht_ehi75 := false
    ht_ehi90 := false
    ht_elo50 := false
    ht_elo75 := false
    ht_elo90 := false
    ht_rhi50 := false
    ht_rhi75 := false
    ht_rlo50 := false
    ht_rlo75 := false

    bkt = f_bucket_name(cur_bucket)

    // Asia range grid
    if show_grid and not na(cur_asia_rng)
        grid_ls = grid_line_style == "Solid" ? "solid" : grid_line_style == "Dashed" ? "dashed" : "dotted"
        for i = 1 to grid_multiples
            up_price = ny_o + i * cur_asia_rng
            dn_price = ny_o - i * cur_asia_rng
            hr_i     = array.get(hr_grid, i - 1)
            up_ln = f_line(bar_index, bar_index + 5, up_price, col_grid, grid_ls, grid_line_width)
            up_lb = f_lbl(bar_index + 1, up_price, f_grid_lbl(i, hr_i), col_grid, f_lsz())
            dn_ln = f_line(bar_index, bar_index + 5, dn_price, col_grid, grid_ls, grid_line_width)
            dn_lb = f_lbl(bar_index + 1, dn_price, f_grid_lbl_dn(i, hr_i), col_grid, f_lsz())
            array.push(ln_grid_up, up_ln)
            array.push(lb_grid_up, up_lb)
            array.push(ht_grid_up, false)
            array.push(ln_grid_dn, dn_ln)
            array.push(lb_grid_dn, dn_lb)
            array.push(ht_grid_dn, false)

    // Extension levels
    if show_ext_levels
        if not na(lv_ext_hi_p50)
            [l, lb] = f_draw_level(lv_ext_hi_p50, f_ext_label("ExtHi " + bkt, 50, hr_ext_hi_p50), col_ext_up, "dotted")
            ln_ehi50 := l
            lb_ehi50 := lb
        if not na(lv_ext_hi_p75)
            [l, lb] = f_draw_level(lv_ext_hi_p75, f_ext_label("ExtHi " + bkt, 75, hr_ext_hi_p75), col_ext_up, "dotted")
            ln_ehi75 := l
            lb_ehi75 := lb
        if not na(lv_ext_hi_p90)
            [l, lb] = f_draw_level(lv_ext_hi_p90, f_ext_label("ExtHi " + bkt, 90, hr_ext_hi_p90), col_ext_up, "dashed")
            ln_ehi90 := l
            lb_ehi90 := lb
        if not na(lv_ext_lo_p50)
            [l, lb] = f_draw_level(lv_ext_lo_p50, f_ext_label("ExtLo " + bkt, 50, hr_ext_lo_p50), col_ext_dn, "dotted")
            ln_elo50 := l
            lb_elo50 := lb
        if not na(lv_ext_lo_p75)
            [l, lb] = f_draw_level(lv_ext_lo_p75, f_ext_label("ExtLo " + bkt, 75, hr_ext_lo_p75), col_ext_dn, "dotted")
            ln_elo75 := l
            lb_elo75 := lb
        if not na(lv_ext_lo_p90)
            [l, lb] = f_draw_level(lv_ext_lo_p90, f_ext_label("ExtLo " + bkt, 90, hr_ext_lo_p90), col_ext_dn, "dashed")
            ln_elo90 := l
            lb_elo90 := lb

    // Reversal levels
    if show_rev_levels
        if not na(lv_rev_hi_p50)
            [l, lb] = f_draw_level(lv_rev_hi_p50, f_ext_label("RevHi " + bkt, 50, hr_rev_hi_p50), col_rev, "dotted")
            ln_rhi50 := l
            lb_rhi50 := lb
        if not na(lv_rev_hi_p75)
            [l, lb] = f_draw_level(lv_rev_hi_p75, f_ext_label("RevHi " + bkt, 75, hr_rev_hi_p75), col_rev, "dotted")
            ln_rhi75 := l
            lb_rhi75 := lb
        if not na(lv_rev_lo_p50)
            [l, lb] = f_draw_level(lv_rev_lo_p50, f_ext_label("RevLo " + bkt, 50, hr_rev_lo_p50), col_rev, "dotted")
            ln_rlo50 := l
            lb_rlo50 := lb
        if not na(lv_rev_lo_p75)
            [l, lb] = f_draw_level(lv_rev_lo_p75, f_ext_label("RevLo " + bkt, 75, hr_rev_lo_p75), col_rev, "dotted")
            ln_rlo75 := l
            lb_rlo75 := lb

// ============================================================
// LEVEL UPDATE HELPERS
// ============================================================
f_update_hi(ln, lb, bi, y, hit, orig_txt) =>
    bool new_hit = hit
    if not na(ln)
        line.set_x2(ln, bi + 5)
    if not na(lb)
        label.set_x(lb, bi + 5)
    if not hit and not na(y) and high >= y
        new_hit := true
        if not na(ln)
            line.set_color(ln, col_hit)
        if not na(lb)
            label.set_textcolor(lb, col_hit)
            label.set_text(lb, " ✓ " + orig_txt)
    new_hit

f_update_lo(ln, lb, bi, y, hit, orig_txt) =>
    bool new_hit = hit
    if not na(ln)
        line.set_x2(ln, bi + 5)
    if not na(lb)
        label.set_x(lb, bi + 5)
    if not hit and not na(y) and low <= y
        new_hit := true
        if not na(ln)
            line.set_color(ln, col_hit)
        if not na(lb)
            label.set_textcolor(lb, col_hit)
            label.set_text(lb, " ✓ " + orig_txt)
    new_hit

// ============================================================
// EXTEND LEVELS DURING NY
// ============================================================
if is_ny
    bi  = bar_index
    bkt = f_bucket_name(cur_bucket)

    if show_ext_levels
        ht_ehi50 := f_update_hi(ln_ehi50, lb_ehi50, bi, lv_ext_hi_p50, ht_ehi50, f_ext_label("ExtHi " + bkt, 50, hr_ext_hi_p50))
        ht_ehi75 := f_update_hi(ln_ehi75, lb_ehi75, bi, lv_ext_hi_p75, ht_ehi75, f_ext_label("ExtHi " + bkt, 75, hr_ext_hi_p75))
        ht_ehi90 := f_update_hi(ln_ehi90, lb_ehi90, bi, lv_ext_hi_p90, ht_ehi90, f_ext_label("ExtHi " + bkt, 90, hr_ext_hi_p90))
        ht_elo50 := f_update_lo(ln_elo50, lb_elo50, bi, lv_ext_lo_p50, ht_elo50, f_ext_label("ExtLo " + bkt, 50, hr_ext_lo_p50))
        ht_elo75 := f_update_lo(ln_elo75, lb_elo75, bi, lv_ext_lo_p75, ht_elo75, f_ext_label("ExtLo " + bkt, 75, hr_ext_lo_p75))
        ht_elo90 := f_update_lo(ln_elo90, lb_elo90, bi, lv_ext_lo_p90, ht_elo90, f_ext_label("ExtLo " + bkt, 90, hr_ext_lo_p90))

    if show_rev_levels
        ht_rhi50 := f_update_lo(ln_rhi50, lb_rhi50, bi, lv_rev_hi_p50, ht_rhi50, f_ext_label("RevHi " + bkt, 50, hr_rev_hi_p50))
        ht_rhi75 := f_update_lo(ln_rhi75, lb_rhi75, bi, lv_rev_hi_p75, ht_rhi75, f_ext_label("RevHi " + bkt, 75, hr_rev_hi_p75))
        ht_rlo50 := f_update_hi(ln_rlo50, lb_rlo50, bi, lv_rev_lo_p50, ht_rlo50, f_ext_label("RevLo " + bkt, 50, hr_rev_lo_p50))
        ht_rlo75 := f_update_hi(ln_rlo75, lb_rlo75, bi, lv_rev_lo_p75, ht_rlo75, f_ext_label("RevLo " + bkt, 75, hr_rev_lo_p75))

    if show_grid
        if array.size(ln_grid_up) > 0
            for i = 0 to array.size(ln_grid_up) - 1
                ln_i     = array.get(ln_grid_up, i)
                lb_i     = array.get(lb_grid_up, i)
                hit_i    = array.get(ht_grid_up, i)
                up_price = ny_o + (i + 1) * cur_asia_rng
                hr_i     = array.get(hr_grid, i)
                new_hit  = f_update_hi(ln_i, lb_i, bi, up_price, hit_i, f_grid_lbl(i + 1, hr_i))
                array.set(ht_grid_up, i, new_hit)
        if array.size(ln_grid_dn) > 0
            for i = 0 to array.size(ln_grid_dn) - 1
                ln_i     = array.get(ln_grid_dn, i)
                lb_i     = array.get(lb_grid_dn, i)
                hit_i    = array.get(ht_grid_dn, i)
                dn_price = ny_o - (i + 1) * cur_asia_rng
                hr_i     = array.get(hr_grid, i)
                new_hit  = f_update_lo(ln_i, lb_i, bi, dn_price, hit_i, f_grid_lbl_dn(i + 1, hr_i))
                array.set(ht_grid_dn, i, new_hit)

if ny_end
    f_clear_levels()

// ============================================================
// TABLE STYLE CONSTANTS + ROW HELPER (top-level — Pine v5 forbids nested functions)
// ============================================================
t_sz  = table_sz_input == "Tiny" ? size.tiny : table_sz_input == "Small" ? size.small : size.normal
t_fnt = font.family_monospace
t_tc  = color.rgb(40,40,40)
t_hbg = color.rgb(30,30,30)
t_htc = color.white
t_rbg = color.rgb(245,245,245)

f_row(t, r, k, v) =>
    table.cell(t, 0, r, k, text_size=t_sz, text_color=t_tc, bgcolor=t_rbg, text_font_family=t_fnt)
    table.cell(t, 1, r, v, text_size=t_sz, text_color=t_tc, bgcolor=color.white, text_font_family=t_fnt)
    table.merge_cells(t, 1, r, 11, r)

// ============================================================
// STATS TABLE
// ============================================================
if show_table and (barstate.islast or is_ny or barstate.isrealtime)
    tpos = table_pos_input == "Top Left"    ? position.top_left   :
           table_pos_input == "Top Right"   ? position.top_right  :
           table_pos_input == "Bottom Left" ? position.bottom_left : position.bottom_right

    var table tbl = table.new(tpos, 12, 14,
         bgcolor=color.white,
         border_color=color.rgb(180,180,180),
         frame_color=color.rgb(180,180,180),
         frame_width=1, border_width=1)

    m_arr   = cur_bucket == 0 ? mult_0 : cur_bucket == 1 ? mult_1 : cur_bucket == 2 ? mult_2 : mult_3

    asia_rng_z   = not na(asia_rng) ? f_zscore(asia_ranges, asia_rng) : na
    asia_rng_pct = not na(asia_rng) ? f_pctrank(asia_ranges, asia_rng) : na
    asia_n       = array.size(asia_ranges)

    ny_med_mult  = f_pct(m_arr, 50)
    ny_avg_mult  = f_mean(m_arr)
    ny_med_pts   = not na(ny_med_mult) and not na(asia_rng) ? ny_med_mult * asia_rng : na
    ny_avg_pts   = not na(ny_avg_mult) and not na(asia_rng) ? ny_avg_mult * asia_rng : na
    bucket_n     = array.size(m_arr)

    cur_ny_rng   = is_ny and not na(ny_start_bar_idx) and bar_index > ny_start_bar_idx ? ny_h - ny_l : na
    expected_rng = not na(ny_med_mult) and not na(asia_rng) ? ny_med_mult * asia_rng : na
    completion      = not na(cur_ny_rng) and not na(expected_rng) and expected_rng > 0 ? cur_ny_rng / expected_rng : na
    expected_rng_avg = not na(ny_avg_mult) and not na(asia_rng) ? ny_avg_mult * asia_rng : na
    completion_avg  = not na(cur_ny_rng) and not na(expected_rng_avg) and expected_rng_avg > 0 ? cur_ny_rng / expected_rng_avg : na

    bkt_name = cur_bucket == 0 ? "Inside Lower Half" :
               cur_bucket == 1 ? "Inside Upper Half" :
               cur_bucket == 2 ? "Above Asia High"   :
               cur_bucket == 3 ? "Below Asia Low"    : "Unknown"

    compl_str     = not na(completion)     ? str.format("{0,number,#.#}", completion     * 100.0) + "%" : "-"
    compl_avg_str = not na(completion_avg) ? str.format("{0,number,#.#}", completion_avg * 100.0) + "%" : "-"

    // Row 0: header
    table.cell(tbl, 0, 0, "NY vs Asia  [N=" + str.tostring(asia_n) + "]",
               text_size=t_sz, text_color=t_htc, bgcolor=t_hbg, text_font_family=t_fnt)
    table.merge_cells(tbl, 0, 0, 11, 0)

    f_row(tbl, 1, "Asia Range (pts)",
          not na(asia_rng) ? str.format("{0,number,#.##}", asia_rng) : "-")
    f_row(tbl, 2, "Asia Range Z-Score",
          not na(asia_rng_z) ? str.format("{0,number,#.##}", asia_rng_z) : "-")
    f_row(tbl, 3, "Asia Range Pctile",
          not na(asia_rng_pct) ? str.format("{0,number,#.#}", asia_rng_pct) + "%" : "-")
    f_row(tbl, 4, "NY Open vs Asia",  bkt_name)
    f_row(tbl, 5, "Bucket Sessions",  str.tostring(bucket_n))
    f_row(tbl, 6, "Sessions Captured", str.tostring(asia_n))
    f_row(tbl, 7, "Median NY Rng (x)",   not na(ny_med_mult) ? str.format("{0,number,#.##}", ny_med_mult) + "x" : "-")
    f_row(tbl, 8, "Avg NY Rng (x)",      not na(ny_avg_mult) ? str.format("{0,number,#.##}", ny_avg_mult) + "x" : "-")
    f_row(tbl, 9, "Median NY Rng (pts)", not na(ny_med_pts)  ? str.format("{0,number,#.##}", ny_med_pts)  : "-")
    f_row(tbl, 10, "Avg NY Rng (pts)",   not na(ny_avg_pts)  ? str.format("{0,number,#.##}", ny_avg_pts)  : "-")
    f_row(tbl, 11, "NY Range (pts)",     not na(cur_ny_rng)  ? str.format("{0,number,#.##}", cur_ny_rng)  : "-")

    // Row 12: Range Complete (median-based)
    table.cell(tbl, 0, 12, "Complete (med)", text_size=t_sz, text_color=t_tc, bgcolor=t_rbg, text_font_family=t_fnt)
    table.cell(tbl, 1, 12, compl_str, text_size=t_sz, text_color=t_tc, bgcolor=color.white, text_font_family=t_fnt)
    table.merge_cells(tbl, 1, 12, 11, 12)

    // Row 13: Range Complete (mean-based)
    table.cell(tbl, 0, 13, "Complete (avg)", text_size=t_sz, text_color=t_tc, bgcolor=t_rbg, text_font_family=t_fnt)
    table.cell(tbl, 1, 13, compl_avg_str, text_size=t_sz, text_color=t_tc, bgcolor=color.white, text_font_family=t_fnt)
    table.merge_cells(tbl, 1, 13, 11, 13)

// ============================================================
// ALERTS
// ============================================================
alertcondition(ny_start, title="NY Session Start", message="NY session opened")
alertcondition(is_ny and not na(lv_ext_hi_p50) and high >= lv_ext_hi_p50 and not ht_ehi50, title="Hit ExtHi P50", message="Price reached ExtHi P50 level")
alertcondition(is_ny and not na(lv_ext_lo_p50) and low  <= lv_ext_lo_p50 and not ht_elo50, title="Hit ExtLo P50", message="Price reached ExtLo P50 level")
alertcondition(is_ny and not na(lv_rev_hi_p50) and low  <= lv_rev_hi_p50 and not ht_rhi50, title="Hit RevHi P50", message="Price reached RevHi P50 level")
alertcondition(is_ny and not na(lv_rev_lo_p50) and high >= lv_rev_lo_p50 and not ht_rlo50, title="Hit RevLo P50", message="Price reached RevLo P50 level")

Overview

NY vs Asia Statistical Levels draws optional session boxes, a grid of Asia-range multiples from the New York open, and statistical extension and reversal levels at the 50th, 75th, and 90th percentiles of a chosen lookback. A stats table summarizes the results. Lookback length, colors, line style, and which boxes, grid lines, and levels appear are user settings. It does not generate trade entries or exits.

Details

Published by

TamaraKGreen on GitHub

Code credit

lucymatos, as written in the script's header

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Sessions & time, Support & resistance, Screeners & dashboards

Markets

Forex

Timeframes

M15-H1

Risk Notes

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

Settings

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

SettingTypeDefaultVariable
Lookback Sessionsint100hist_sessions
Label SizestringSmalllabel_sz_input
Show Stats Tablebooltrueshow_table
Table PositionstringBottom Righttable_pos_input
Table Text SizestringTinytable_sz_input
Show Asia Range Boxbooltrueshow_asia_box
Show London Range Boxbooltrueshow_london_box
Show NY Range Boxbooltrueshow_ny_box
Box Fill Transparencyfloat90bg_transp
Box Outlinebooltrueshow_outline
Asia Box Colorcolorcolor.rgb(255,152,0)col_asia_box
London Box Colorcolorcolor.rgb(33,87,243)col_london_box
Show all 24 settings
SettingTypeDefaultVariable
NY Box Colorcolorcolor.rgb(255,0,55)col_ny_box
Show Asia Range Gridbooltrueshow_grid
Multiples above & below openint3grid_multiples
Grid Level Colorcolorcolor.rgb(120,180,220)col_grid
Grid Line StylestringDashedgrid_line_style
Grid Line Widthint1grid_line_width
Show Extension Levels (P50/75/90 above & below Asia range)booltrueshow_ext_levels
Show Reversal Levels (P50/75 from extension)booltrueshow_rev_levels
Extension Above Colorcolorcolor.rgb(80,200,80)col_ext_up
Extension Below Colorcolorcolor.rgb(200,80,80)col_ext_dn
Reversal Level Colorcolorcolor.rgb(200,160,40)col_rev
Hit Level Color (✓)colorcolor.rgb(130,130,130)col_hit
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