pivots_and_key_levels_master_.pine
Copy
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © midnight pickle
//
// ============================================================================
// Pivots + Key Levels MP — merged master script
// PART 1: Pivot Enhanced (Fibonacci / Camarilla, R1-R5 / S1-S5 + alerts)
// PART 2: Key Levels (Asia session, Monday range, D/W/M/Y highs, lows, mids, opens)
//
// NOTE ON OBJECT BUDGET: TradingView caps a single indicator at 500 lines and
// 500 labels TOTAL. Both systems now share that budget. With default settings
// the total is small (~50 each), but a very large Pivot "Lookback Period"
// combined with all Key Levels enabled can hit the cap and drop oldest objects.
// ============================================================================
//@version=6
indicator('Pivots + Key Levels MP', shorttitle='Pivots+Levels MP', overlay=true, max_lines_count=500, max_labels_count=500, max_bars_back=5000)
// ============================================================================
// ██ PART 1 — PIVOTS ██
// ============================================================================
// Timeframe Options
AUTO = 'Auto'
HOURLY = 'Hourly'
DAILY = 'Daily'
WEEKLY = 'Weekly'
MONTHLY = 'Monthly'
QUARTERLY = 'Quarterly'
YEARLY = 'Yearly'
BIYEARLY = 'Biyearly'
TRIYEARLY = 'Triyearly'
QUINQUENNIAL = 'Quinquennial'
// Pivot Calculation Methods
FIBONACCI = 'Fibonacci'
CAMARILLA = 'Camarilla'
// Pivot input groups
grp_piv = '⭐ Pivots · Settings'
grp_piv_vis = '⭐ Pivots · Visuals'
grp_piv_lvl = '⭐ Pivots · Show/Hide Levels'
grp_piv_col = '⭐ Pivots · Colors'
grp_piv_alert = '⭐ Pivots · Alerts'
// Master enable for the whole pivot system
enable_pivots = input.bool(true, '🔥 Master Toggle: Pivots', group=grp_piv)
// Inputs
kind = input.string(title = 'Calculation Method', defval = 'Camarilla', options = [FIBONACCI, CAMARILLA], group = grp_piv)
pivot_time_frame = input.string(title = 'Timeframe', defval = DAILY, options = [AUTO, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY, BIYEARLY, TRIYEARLY, QUINQUENNIAL], group = grp_piv)
look_back = input.int(title = 'Lookback Period', defval = 1, minval = 1, maxval = 5000, tooltip = 'How many past periods of pivots to keep drawn. Note: total drawn objects (segments x enabled levels) share the 500 line / 500 label cap with the Key Levels below.', group = grp_piv)
is_daily_based = input.bool(title = 'Use Daily-based Values', defval = true, tooltip = 'Use daily OHLC values even on intraday charts', group = grp_piv)
show_labels = input.bool(title = 'Show Labels', defval = true, group = grp_piv_vis)
position_labels = input.string('Left', 'Label Position', options = ['Left', 'Right'], group = grp_piv_vis)
// Show/Hide Levels
show_p = input.bool(title = 'Show Pivot (P)', defval = true, group = grp_piv_lvl)
show_r1 = input.bool(title = 'Show R1/S1', defval = true, group = grp_piv_lvl)
show_r2 = input.bool(title = 'Show R2/S2', defval = true, group = grp_piv_lvl)
show_r3 = input.bool(title = 'Show R3/S3', defval = true, group = grp_piv_lvl, tooltip = 'Carries the H4/L4 value from the calculation. In published Camarilla material H4/L4 is the breakout level, so this line is not the "far extreme, rarely reached" that R3 means in standard pivot notation.')
show_r4 = input.bool(title = 'Show R4/S4', defval = true, group = grp_piv_lvl, tooltip = 'Carries the H5/L5 value from the calculation.')
show_r5 = input.bool(title = 'Show R5/S5', defval = true, group = grp_piv_lvl, tooltip = 'Carries the H6/L6 value from the calculation. TradingView\'s built-in Camarilla plots this same value at its R5, but the levels in between will not line up between the two indicators.')
// Color Settings
p_color = input.color(color.orange, 'Pivot Color', group = grp_piv_col)
s1r1_color = input.color(color.blue, 'S1/R1 Color', group = grp_piv_col)
s2r2_color = input.color(color.red, 'S2/R2 Color', group = grp_piv_col)
s3r3_color = input.color(color.purple, 'S3/R3 Color', group = grp_piv_col)
s4r4_color = input.color(color.green, 'S4/R4 Color', group = grp_piv_col)
s5r5_color = input.color(color.navy, 'S5/R5 Color', group = grp_piv_col)
// Alert Settings
use_close = input.bool(true, 'Use Close Price for Alerts', tooltip = 'When enabled, alerts trigger when the close CROSSES the level. When disabled, alerts trigger on price touch (bar range straddles the level).', group = grp_piv_alert)
// Arrays for storing pivot data
var arr_time = array.new_int()
var p = array.new_float()
var r1 = array.new_float()
var s1 = array.new_float()
var r2 = array.new_float()
var s2 = array.new_float()
var r3 = array.new_float()
var s3 = array.new_float()
var r4 = array.new_float()
var s4 = array.new_float()
var r5 = array.new_float()
var s5 = array.new_float()
// Arrays for storing line and label IDs for cleanup
var line_ids_p = array.new_line()
var line_ids_r1 = array.new_line()
var line_ids_s1 = array.new_line()
var line_ids_r2 = array.new_line()
var line_ids_s2 = array.new_line()
var line_ids_r3 = array.new_line()
var line_ids_s3 = array.new_line()
var line_ids_r4 = array.new_line()
var line_ids_s4 = array.new_line()
var line_ids_r5 = array.new_line()
var line_ids_s5 = array.new_line()
var label_ids_p = array.new_label()
var label_ids_r1 = array.new_label()
var label_ids_s1 = array.new_label()
var label_ids_r2 = array.new_label()
var label_ids_s2 = array.new_label()
var label_ids_r3 = array.new_label()
var label_ids_s3 = array.new_label()
var label_ids_r4 = array.new_label()
var label_ids_s4 = array.new_label()
var label_ids_r5 = array.new_label()
var label_ids_s5 = array.new_label()
// Variables for OHLC data
pivotX_open = float(na)
pivotX_open := nz(pivotX_open[1], open)
pivotX_high = float(na)
pivotX_high := nz(pivotX_high[1], high)
pivotX_low = float(na)
pivotX_low := nz(pivotX_low[1], low)
pivotX_prev_open = float(na)
pivotX_prev_open := nz(pivotX_prev_open[1])
pivotX_prev_high = float(na)
pivotX_prev_high := nz(pivotX_prev_high[1])
pivotX_prev_low = float(na)
pivotX_prev_low := nz(pivotX_prev_low[1])
pivotX_prev_close = float(na)
pivotX_prev_close := nz(pivotX_prev_close[1])
// Get the resolution for pivot calculation
get_pivot_resolution() =>
resolution = 'M'
if pivot_time_frame == AUTO
if timeframe.isintraday
resolution := timeframe.multiplier <= 15 ? 'D' : 'W'
resolution
else if timeframe.isweekly or timeframe.ismonthly
resolution := '12M'
resolution
else if pivot_time_frame == HOURLY
resolution := '60' // "Hourly" maps to 60 minutes
resolution
else if pivot_time_frame == DAILY
resolution := 'D'
resolution
else if pivot_time_frame == WEEKLY
resolution := 'W'
resolution
else if pivot_time_frame == MONTHLY
resolution := 'M'
resolution
else if pivot_time_frame == QUARTERLY
resolution := '3M'
resolution
else if pivot_time_frame == YEARLY or pivot_time_frame == BIYEARLY or pivot_time_frame == TRIYEARLY or pivot_time_frame == QUINQUENNIAL
resolution := '12M'
resolution
resolution
// Drawing functions with ID storage
draw_line(i, pivot, col, line_array) =>
if array.size(arr_time) > 1
new_line = line.new(array.get(arr_time, i), array.get(pivot, i), array.get(arr_time, i + 1), array.get(pivot, i), color = col, xloc = xloc.bar_time)
array.push(line_array, new_line)
draw_label(i, y, txt, txt_color, label_array) =>
if show_labels
offset = ' '
labels_align_str_left = position_labels == 'Left' ? txt + offset : offset + txt
x = position_labels == 'Left' ? array.get(arr_time, i) : array.get(arr_time, i + 1)
new_label = label.new(x = x, y = y, text = labels_align_str_left, textcolor = txt_color, style = label.style_label_center, color = #00000000, xloc = xloc.bar_time)
array.push(label_array, new_label)
// Helpers to fully clear stored drawing objects before each full redraw,
// preventing duplicate/stale line & label accumulation across realtime rollovers.
delete_lines(arr) =>
if array.size(arr) > 0
for ln in arr
line.delete(ln)
array.clear(arr)
delete_labels(arr) =>
if array.size(arr) > 0
for lb in arr
label.delete(lb)
array.clear(arr)
clear_all_drawings() =>
delete_lines(line_ids_p), delete_labels(label_ids_p)
delete_lines(line_ids_r1), delete_labels(label_ids_r1)
delete_lines(line_ids_s1), delete_labels(label_ids_s1)
delete_lines(line_ids_r2), delete_labels(label_ids_r2)
delete_lines(line_ids_s2), delete_labels(label_ids_s2)
delete_lines(line_ids_r3), delete_labels(label_ids_r3)
delete_lines(line_ids_s3), delete_labels(label_ids_s3)
delete_lines(line_ids_r4), delete_labels(label_ids_r4)
delete_lines(line_ids_s4), delete_labels(label_ids_s4)
delete_lines(line_ids_r5), delete_labels(label_ids_r5)
delete_lines(line_ids_s5), delete_labels(label_ids_s5)
// Pivot calculation methods
fibonacci() =>
pivotX_Median = (pivotX_prev_high + pivotX_prev_low + pivotX_prev_close) / 3
pivot_range = pivotX_prev_high - pivotX_prev_low
array.push(p, pivotX_Median)
array.push(r1, pivotX_Median + 0.382 * pivot_range)
array.push(s1, pivotX_Median - 0.382 * pivot_range)
array.push(r2, pivotX_Median + 0.618 * pivot_range)
array.push(s2, pivotX_Median - 0.618 * pivot_range)
array.push(r3, pivotX_Median + 1 * pivot_range)
array.push(s3, pivotX_Median - 1 * pivot_range)
array.push(r4, pivotX_Median + 1.272 * pivot_range)
array.push(s4, pivotX_Median - 1.272 * pivot_range)
array.push(r5, pivotX_Median + 1.618 * pivot_range)
array.push(s5, pivotX_Median - 1.618 * pivot_range)
// NOTE: Camarilla math left exactly as provided (per request). This is a
// deliberate custom variant, NOT the textbook Camarilla equation. Do not
// "correct" it against published formulas.
//
// LEVEL NAMING — read this before cross-referencing any Camarilla material.
// The internal H/L values are stored into the R/S arrays with a deliberate
// one-step shift, and H1/L1 are not plotted at all:
//
// plotted as ... R1 R2 R3 R4 R5
// comes from ... H2 H3 H4 H5 H6
// plotted as ... S1 S2 S3 S4 S5
// comes from ... L2 L3 L4 L5 L6
//
// Consequences worth knowing:
// * This script's R3/S3 carries the H4/L4 value. In published Camarilla
// material H4/L4 is the breakout level, so R3 here is NOT the "far
// extreme, rarely reached" that R3 means in standard pivot notation.
// * TradingView's built-in Pivot Points Standard → Camarilla plots only
// five levels and puts (prevHigh / prevLow) × prevClose at its R5. That
// value lives at this script's R5 too, but the levels in between will not
// line up, so the two indicators are not directly comparable.
camarilla() =>
pivotX_Median = (pivotX_prev_high + pivotX_prev_low + pivotX_prev_close) / 3
pivot_range = pivotX_prev_high - pivotX_prev_low
H4 = pivotX_prev_close + pivot_range * 1.1 / 2
H3 = pivotX_prev_close + pivot_range * 1.1 / 4
H2 = pivotX_prev_close + pivot_range * 1.1 / 6
H1 = pivotX_prev_close + pivot_range * 1.1 / 12
L1 = pivotX_prev_close - pivot_range * 1.1 / 12
L2 = pivotX_prev_close - pivot_range * 1.1 / 6
L3 = pivotX_prev_close - pivot_range * 1.1 / 4
L4 = pivotX_prev_close - pivot_range * 1.1 / 2
L5 = L4 - 1.168 * (L3 - L4)
H5 = H4 + 1.168 * (H4 - H3)
H6 = pivotX_prev_high / pivotX_prev_low * pivotX_prev_close
L6 = pivotX_prev_close - (H6 - pivotX_prev_close)
array.push(p, pivotX_Median)
array.push(r1, H2)
array.push(s1, L2)
array.push(r2, H3)
array.push(s2, L3)
array.push(r3, H4)
array.push(s3, L4)
array.push(r4, H5)
array.push(s4, L5)
array.push(r5, H6)
array.push(s5, L6)
// Main logic
resolution = get_pivot_resolution()
[sec_open, sec_high, sec_low, sec_close] = request.security(syminfo.tickerid, resolution, [open, high, low, close], lookahead = barmerge.lookahead_on)
sec_open_gaps_on = request.security(syminfo.tickerid, resolution, open, gaps = barmerge.gaps_on, lookahead = barmerge.lookahead_on)
// Fixed custom years resolution handling
var int year_counter = 0
var bool is_change_years = false
var bool custom_years_resolution = pivot_time_frame == BIYEARLY or pivot_time_frame == TRIYEARLY or pivot_time_frame == QUINQUENNIAL
if custom_years_resolution and bool(ta.change(time(resolution)))
// Increment counter on each timeframe change
year_counter := year_counter + 1
// Check if we've reached the target multiple
if pivot_time_frame == BIYEARLY and year_counter % 2 == 0
is_change_years := true
is_change_years
else if pivot_time_frame == TRIYEARLY and year_counter % 3 == 0
is_change_years := true
is_change_years
else if pivot_time_frame == QUINQUENNIAL and year_counter % 5 == 0
is_change_years := true
is_change_years
else
is_change_years := false
is_change_years
else
is_change_years := false
is_change_years
var is_change = false
var uses_current_bar = timeframe.isintraday
var change_time = int(na)
is_time_change = bool(ta.change(time(resolution))) and not custom_years_resolution or is_change_years
if is_time_change
change_time := time
change_time
if not uses_current_bar and is_time_change or uses_current_bar and not na(sec_open_gaps_on)
if is_daily_based
pivotX_prev_open := sec_open[1]
pivotX_prev_high := sec_high[1]
pivotX_prev_low := sec_low[1]
pivotX_prev_close := sec_close[1]
pivotX_open := sec_open
pivotX_high := sec_high
pivotX_low := sec_low
pivotX_low
else
pivotX_prev_high := pivotX_high
pivotX_prev_low := pivotX_low
pivotX_prev_open := pivotX_open
pivotX_open := open
pivotX_high := high
pivotX_low := low
pivotX_prev_close := close[1]
pivotX_prev_close
if barstate.islast and not is_change and array.size(arr_time) > 0
array.set(arr_time, array.size(arr_time) - 1, change_time)
else
array.push(arr_time, change_time)
if kind == FIBONACCI
fibonacci()
else if kind == CAMARILLA
camarilla()
// Cap stored data arrays to the lookback window.
if array.size(arr_time) > look_back
if array.size(arr_time) > 0
array.shift(arr_time)
// Shift data arrays
if array.size(p) > 0
array.shift(p)
if array.size(r1) > 0
array.shift(r1)
if array.size(s1) > 0
array.shift(s1)
if array.size(r2) > 0
array.shift(r2)
if array.size(s2) > 0
array.shift(s2)
if array.size(r3) > 0
array.shift(r3)
if array.size(s3) > 0
array.shift(s3)
if array.size(r4) > 0
array.shift(r4)
if array.size(s4) > 0
array.shift(s4)
if array.size(r5) > 0
array.shift(r5)
if array.size(s5) > 0
array.shift(s5)
is_change := true
is_change
else
if is_daily_based
pivotX_high := math.max(pivotX_high, sec_high)
pivotX_low := math.min(pivotX_low, sec_low)
pivotX_low
else
pivotX_high := math.max(pivotX_high, high)
pivotX_low := math.min(pivotX_low, low)
pivotX_low
// Get current pivot values for alerts
get_current_pivot_value(arr) =>
array.size(arr) > 0 ? array.get(arr, array.size(arr) - 1) : na
p_val = get_current_pivot_value(p)
r1_val = get_current_pivot_value(r1)
s1_val = get_current_pivot_value(s1)
r2_val = get_current_pivot_value(r2)
s2_val = get_current_pivot_value(s2)
r3_val = get_current_pivot_value(r3)
s3_val = get_current_pivot_value(s3)
r4_val = get_current_pivot_value(r4)
s4_val = get_current_pivot_value(s4)
r5_val = get_current_pivot_value(r5)
s5_val = get_current_pivot_value(s5)
// Precompute crosses so "Use Close" alerts actually fire.
p_cross = ta.cross(close, p_val)
r1_cross = ta.cross(close, r1_val)
s1_cross = ta.cross(close, s1_val)
r2_cross = ta.cross(close, r2_val)
s2_cross = ta.cross(close, s2_val)
r3_cross = ta.cross(close, r3_val)
s3_cross = ta.cross(close, s3_val)
r4_cross = ta.cross(close, r4_val)
s4_cross = ta.cross(close, s4_val)
r5_cross = ta.cross(close, r5_val)
s5_cross = ta.cross(close, s5_val)
// Alert Conditions (gated by the pivot master toggle)
// use_close = true -> close crosses the level
// use_close = false -> bar range straddles the level (touch)
r1_touch = enable_pivots and not na(r1_val) and (use_close ? r1_cross : (low <= r1_val and high >= r1_val))
s1_touch = enable_pivots and not na(s1_val) and (use_close ? s1_cross : (low <= s1_val and high >= s1_val))
r2_touch = enable_pivots and not na(r2_val) and (use_close ? r2_cross : (low <= r2_val and high >= r2_val))
s2_touch = enable_pivots and not na(s2_val) and (use_close ? s2_cross : (low <= s2_val and high >= s2_val))
r3_touch = enable_pivots and not na(r3_val) and (use_close ? r3_cross : (low <= r3_val and high >= r3_val))
s3_touch = enable_pivots and not na(s3_val) and (use_close ? s3_cross : (low <= s3_val and high >= s3_val))
r4_touch = enable_pivots and not na(r4_val) and (use_close ? r4_cross : (low <= r4_val and high >= r4_val))
s4_touch = enable_pivots and not na(s4_val) and (use_close ? s4_cross : (low <= s4_val and high >= s4_val))
r5_touch = enable_pivots and not na(r5_val) and (use_close ? r5_cross : (low <= r5_val and high >= r5_val))
s5_touch = enable_pivots and not na(s5_val) and (use_close ? s5_cross : (low <= s5_val and high >= s5_val))
p_touch = enable_pivots and not na(p_val) and (use_close ? p_cross : (low <= p_val and high >= p_val))
// Create alertcondition entries
alertcondition(r1_touch, title = 'R1 Touched', message = 'R1 Pivot Touched')
alertcondition(s1_touch, title = 'S1 Touched', message = 'S1 Pivot Touched')
alertcondition(r2_touch, title = 'R2 Touched', message = 'R2 Pivot Touched')
alertcondition(s2_touch, title = 'S2 Touched', message = 'S2 Pivot Touched')
alertcondition(r3_touch, title = 'R3 (Key Resistance) Touched', message = 'R3 STRONG Resistance Touched')
alertcondition(s3_touch, title = 'S3 (Key Support) Touched', message = 'S3 STRONG Support Touched')
alertcondition(r4_touch, title = 'R4 (Extreme) Touched', message = 'R4 Extreme Resistance Touched')
alertcondition(s4_touch, title = 'S4 (Extreme) Touched', message = 'S4 Extreme Support Touched')
alertcondition(r5_touch, title = 'R5 (ULTRA) Touched', message = 'R5 ULTRA Resistance Touched')
alertcondition(s5_touch, title = 'S5 (ULTRA) Touched', message = 'S5 ULTRA Support Touched')
alertcondition(p_touch, title = 'Pivot Touched', message = 'Pivot Point Touched')
// Drawing the pivot levels
if enable_pivots and barstate.islast and array.size(arr_time) > 0 and is_change
is_change := false
// Clear every previously drawn object before redrawing the full set.
clear_all_drawings()
if array.size(arr_time) > 2 and custom_years_resolution
last_pivot_time = array.get(arr_time, array.size(arr_time) - 1)
prev_pivot_time = array.get(arr_time, array.size(arr_time) - 2)
estimate_pivot_time = last_pivot_time - prev_pivot_time
array.push(arr_time, last_pivot_time + estimate_pivot_time)
else
array.push(arr_time, time_close(resolution))
for i = 0 to array.size(arr_time) - 2 by 1
if array.size(p) > 0 and show_p
draw_line(i, p, p_color, line_ids_p)
draw_label(i, array.get(p, i), 'P', p_color, label_ids_p)
if array.size(r1) > 0 and show_r1
draw_line(i, r1, s1r1_color, line_ids_r1)
draw_label(i, array.get(r1, i), 'R1', s1r1_color, label_ids_r1)
if array.size(s1) > 0 and show_r1
draw_line(i, s1, s1r1_color, line_ids_s1)
draw_label(i, array.get(s1, i), 'S1', s1r1_color, label_ids_s1)
if array.size(r2) > 0 and show_r2
draw_line(i, r2, s2r2_color, line_ids_r2)
draw_label(i, array.get(r2, i), 'R2', s2r2_color, label_ids_r2)
if array.size(s2) > 0 and show_r2
draw_line(i, s2, s2r2_color, line_ids_s2)
draw_label(i, array.get(s2, i), 'S2', s2r2_color, label_ids_s2)
if array.size(r3) > 0 and show_r3
draw_line(i, r3, s3r3_color, line_ids_r3)
draw_label(i, array.get(r3, i), 'R3', s3r3_color, label_ids_r3)
if array.size(s3) > 0 and show_r3
draw_line(i, s3, s3r3_color, line_ids_s3)
draw_label(i, array.get(s3, i), 'S3', s3r3_color, label_ids_s3)
if array.size(r4) > 0 and show_r4
draw_line(i, r4, s4r4_color, line_ids_r4)
draw_label(i, array.get(r4, i), 'R4', s4r4_color, label_ids_r4)
if array.size(s4) > 0 and show_r4
draw_line(i, s4, s4r4_color, line_ids_s4)
draw_label(i, array.get(s4, i), 'S4', s4r4_color, label_ids_s4)
if array.size(r5) > 0 and show_r5
draw_line(i, r5, s5r5_color, line_ids_r5)
draw_label(i, array.get(r5, i), 'R5', s5r5_color, label_ids_r5)
if array.size(s5) > 0 and show_r5
draw_line(i, s5, s5r5_color, line_ids_s5)
draw_label(i, array.get(s5, i), 'S5', s5r5_color, label_ids_s5)
// ============================================================================
// ██ PART 2 — KEY LEVELS ██
// ============================================================================
// 🔹 STYLE CONSTANTS
opt_solid = 'Solid'
opt_dashed = 'Dashed'
opt_dotted = 'Dotted'
// Helper function to convert string to line style
get_line_style(style_str) =>
style_str == opt_dashed ? line.style_dashed : style_str == opt_dotted ? line.style_dotted : line.style_solid
// 🔹 MASTER CONTROLS
grp_master = '🎯 Key Levels · Master Controls'
master_key = input.bool(true, '🔥 Master Toggle: All Key Levels', group=grp_master)
master_key_col_on = input.bool(false, '🎨 Use Single Master Color', group=grp_master, inline='mkc')
master_key_col = input.color(color.white, '', group=grp_master, inline='mkc')
master_key_style_on = input.bool(false, '〰️ Use Single Master Style', group=grp_master, inline='mks')
master_key_style = input.string(opt_solid, '', options=[opt_solid, opt_dashed, opt_dotted], group=grp_master, inline='mks')
master_key_width_on = input.bool(false, '📏 Use Single Master Width', group=grp_master, inline='mkw')
master_key_width = input.int(1, '', minval=1, maxval=10, group=grp_master, inline='mkw')
// Line length + merge tolerance (defaults preserve prior behavior)
line_len = input.int(10, 'Line Length (bars)', minval=1, group=grp_master)
merge_ticks = input.float(2, 'Merge Tolerance (ticks)', minval=0, group=grp_master, tooltip='Two levels closer than this many ticks are merged onto one line.')
// 🔹 ASIA SESSION
grp_asia = '🌏 Key Levels · Asia Session'
show_asia = input.bool(true, 'Show Asia H/L', group=grp_asia)
show_asia_mid = input.bool(true, 'Show Asia 50%', group=grp_asia)
asia_live = input.bool(true, 'Track Session Live', group=grp_asia, tooltip='On: while the Asia window is open the levels update with it. Off: only the last completed session is drawn, so the levels sit still until the window closes.')
asia_sess = input.session('1600-1800', 'Session', group=grp_asia, tooltip='Default is the 16:00-18:00 New York post-US-close consolidation window (the "Asia Box"). For the full Tokyo cash session use 0900-1800 with Asia/Tokyo, or 0000-0800 with UTC.')
asia_tz = input.string('America/New_York', 'Session Timezone', options=['America/New_York', 'UTC', 'Asia/Tokyo', 'Asia/Singapore', 'Asia/Hong_Kong', 'Europe/London', 'Pacific/Auckland'], group=grp_asia, tooltip='Anchor the session to the exchange that sets the schedule, not to your own clock. New York, London and Auckland observe DST; Tokyo, Singapore, Hong Kong and UTC do not.')
col_asia = input.color(color.new(color.aqua, 20), 'Color', group=grp_asia, inline='a1')
style_asia = input.string(opt_dashed, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_asia, inline='a1')
width_asia = input.int(1, 'Width', minval=1, maxval=10, group=grp_asia, inline='a1')
// 🔹 MONDAY RANGE
grp_mon = '📅 Key Levels · Monday Range'
show_mon = input.bool(true, 'Show Monday Range (H/L/O)', group=grp_mon)
col_mon = input.color(color.new(color.teal, 30), 'Color', group=grp_mon, inline='m1')
style_mon = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_mon, inline='m1')
width_mon = input.int(1, 'Width', minval=1, maxval=10, group=grp_mon, inline='m1')
// 🔹 DAILY LEVELS
grp_daily = '📆 Key Levels · Daily'
show_do = input.bool(true, 'Show Daily Open', group=grp_daily)
show_pd = input.bool(true, 'Show Previous Day H/L', group=grp_daily)
show_pd_mid = input.bool(true, 'Show Previous Day 50%', group=grp_daily)
col_pd = input.color(color.new(color.gray, 30), 'Color', group=grp_daily, inline='d1')
style_pd = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_daily, inline='d1')
width_pd = input.int(1, 'Width', minval=1, maxval=10, group=grp_daily, inline='d1')
// 🔹 WEEKLY LEVELS
grp_weekly = '📊 Key Levels · Weekly'
show_cw = input.bool(true, 'Show Current Week H/L', group=grp_weekly)
show_cw_mid = input.bool(true, 'Show Current Week 50%', group=grp_weekly)
col_cw = input.color(color.new(color.orange, 10), 'CW Color', group=grp_weekly, inline='w1')
style_cw = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_weekly, inline='w1')
width_cw = input.int(2, 'Width', minval=1, maxval=10, group=grp_weekly, inline='w1')
show_pw = input.bool(true, 'Show Previous Week H/L', group=grp_weekly)
show_pw_mid = input.bool(true, 'Show Previous Week 50%', group=grp_weekly)
col_pw = input.color(color.new(color.orange, 30), 'PW Color', group=grp_weekly, inline='w2')
style_pw = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_weekly, inline='w2')
width_pw = input.int(2, 'Width', minval=1, maxval=10, group=grp_weekly, inline='w2')
// 🔹 MONTHLY LEVELS
grp_monthly = '📈 Key Levels · Monthly'
show_cm = input.bool(true, 'Show Current Month H/L', group=grp_monthly)
show_cm_mid = input.bool(true, 'Show Current Month 50%', group=grp_monthly)
col_cm = input.color(color.new(color.red, 10), 'CM Color', group=grp_monthly, inline='mo1')
style_cm = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_monthly, inline='mo1')
width_cm = input.int(2, 'Width', minval=1, maxval=10, group=grp_monthly, inline='mo1')
show_pm = input.bool(true, 'Show Previous Month H/L', group=grp_monthly)
show_pm_mid = input.bool(true, 'Show Previous Month 50%', group=grp_monthly)
col_pm = input.color(color.new(color.red, 30), 'PM Color', group=grp_monthly, inline='mo2')
style_pm = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_monthly, inline='mo2')
width_pm = input.int(2, 'Width', minval=1, maxval=10, group=grp_monthly, inline='mo2')
// 🔹 YEARLY LEVELS
grp_yearly = '📅 Key Levels · Yearly'
show_cy = input.bool(true, 'Show Current Year H/L', group=grp_yearly)
show_cy_mid = input.bool(true, 'Show Current Year 50%', group=grp_yearly)
show_cy_open = input.bool(true, 'Show Current Year Open', group=grp_yearly)
col_cy = input.color(color.new(color.maroon, 10), 'CY Color', group=grp_yearly, inline='y1')
style_cy = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_yearly, inline='y1')
width_cy = input.int(3, 'Width', minval=1, maxval=10, group=grp_yearly, inline='y1')
show_py = input.bool(true, 'Show Previous Year H/L', group=grp_yearly)
show_py_mid = input.bool(true, 'Show Previous Year 50%', group=grp_yearly)
col_py = input.color(color.new(color.maroon, 30), 'PY Color', group=grp_yearly, inline='y2')
style_py = input.string(opt_solid, 'Style', options=[opt_solid, opt_dashed, opt_dotted], group=grp_yearly, inline='y2')
width_py = input.int(3, 'Width', minval=1, maxval=10, group=grp_yearly, inline='y2')
// 🔹 KEY LEVELS LOGIC
// 1. Fetch Data
// "previous period" levels use [1] + lookahead_on (confirmed prior period, non-repainting).
// "current period" levels use no-offset + lookahead_off (developing period, updates live by design).
//
// WHY THE CURRENT-PERIOD PATTERN IS CORRECT HERE, AND WHERE IT IS NOT.
// request.security(..., lookahead_off) with no offset returns two different
// things depending on the bar: on a HISTORICAL bar it returns the last CLOSED
// higher-timeframe value (so mid-week it hands back last week's high), and on
// a REALTIME bar it returns the live developing value.
//
// That is fine in this script because every key level is drawn inside
// `if master_key and barstate.islast` — only the realtime bar is ever read, so
// only the developing value is ever displayed. Two caveats follow from that:
// * In bar-replay, barstate.islast is a historical bar, so the "current"
// week/month/year lines will show the PREVIOUS period's extreme.
// * Do not reuse these variables in an alertcondition, or port them into a
// strategy. There the historical path is evaluated on every bar and the
// backtest silently trades a different series than the live script.
//
// Computing these natively with a timeframe.change() reset (the way the Monday
// range below is built) would fix the replay case, but it would BREAK the
// monthly and yearly levels on intraday charts: native accumulation only sees
// the bars actually loaded, which on a 5m chart is a few weeks, not a year.
// request.security is the right tool for deep periods; the constraint is that
// its result is only trustworthy on the last bar.
// --- Daily ---
d_o = request.security(syminfo.tickerid, 'D', open, lookahead=barmerge.lookahead_off)
[d_h, d_l] = request.security(syminfo.tickerid, 'D', [high[1], low[1]], lookahead=barmerge.lookahead_on)
d_mid = (d_h + d_l) / 2
// --- Weekly ---
[cw_h, cw_l] = request.security(syminfo.tickerid, 'W', [high, low], lookahead=barmerge.lookahead_off)
cw_mid = (cw_h + cw_l) / 2
[w_h, w_l] = request.security(syminfo.tickerid, 'W', [high[1], low[1]], lookahead=barmerge.lookahead_on)
w_mid = (w_h + w_l) / 2
// --- Monthly ---
[cm_h, cm_l] = request.security(syminfo.tickerid, 'M', [high, low], lookahead=barmerge.lookahead_off)
cm_mid = (cm_h + cm_l) / 2
[m_h, m_l] = request.security(syminfo.tickerid, 'M', [high[1], low[1]], lookahead=barmerge.lookahead_on)
m_mid = (m_h + m_l) / 2
// --- Yearly ---
[cy_h, cy_l, cy_o] = request.security(syminfo.tickerid, '12M', [high, low, open], lookahead=barmerge.lookahead_off)
cy_mid = (cy_h + cy_l) / 2
[py_h, py_l] = request.security(syminfo.tickerid, '12M', [high[1], low[1]], lookahead=barmerge.lookahead_on)
py_mid = (py_h + py_l) / 2
// 2. Monday Logic
var float mon_h = na
var float mon_l = na
var float mon_o = na
var bool mon_active = false
is_new_week = ta.change(time('W')) != 0
if is_new_week
mon_h := na
mon_l := na
mon_o := na
mon_active := false
is_monday = dayofweek == dayofweek.monday
if is_monday
if not mon_active
mon_h := high
mon_l := low
mon_o := open
mon_active := true
else
mon_h := math.max(mon_h, high)
mon_l := math.min(mon_l, low)
// 3. Asia Session Logic
//
// Native accumulation, not request.security. The Asia window is an intraday
// session, so its bars are already on the chart and there is nothing to fetch.
// That also means these levels are correct in bar-replay, unlike the current
// week/month/year values above.
//
// Requires an intraday chart timeframe that divides the session cleanly — 1m
// through 1H is fine. On 4H+ and daily charts no bar falls inside the window,
// asia_in is never true, and the levels stay na so nothing is drawn.
asia_in = not na(time(timeframe.period, asia_sess, asia_tz))
// Previous bar's state is carried in a var rather than read as asia_in[1].
// nz() has no series-bool overload in v6, and an unguarded asia_in[1] is na on
// bar 0, which makes the session-start test na (falsy) and silently skips the
// first session. A var initialised to false has neither problem.
var bool asia_was_in = false
var float asia_dev_h = na // session currently forming
var float asia_dev_l = na
var float asia_h = na // last completed session
var float asia_l = na
if asia_in and not asia_was_in
// first bar of a new session
asia_dev_h := high
asia_dev_l := low
else if asia_in
asia_dev_h := math.max(asia_dev_h, high)
asia_dev_l := math.min(asia_dev_l, low)
if not asia_in and asia_was_in
// session just closed — freeze it
asia_h := asia_dev_h
asia_l := asia_dev_l
asia_was_in := asia_in
// Draw the developing session while it is open (if wanted), else the last
// completed one. Both are na until the first session in the loaded history
// finishes, and merge_or_add_level skips na prices, so nothing is drawn.
asia_disp_h = asia_in and asia_live ? asia_dev_h : asia_h
asia_disp_l = asia_in and asia_live ? asia_dev_l : asia_l
asia_disp_mid = (asia_disp_h + asia_disp_l) / 2
// 4. Draw Active Key Levels (Smart Aggregation Engine)
var active_key_lines = array.new_line()
var active_key_labels = array.new_label()
format_price(price) =>
if syminfo.mintick < 0.00001
str.tostring(price, "#.#####")
else if syminfo.mintick < 0.0001
str.tostring(price, "#.####")
else if syminfo.mintick < 0.001
str.tostring(price, "#.###")
else if syminfo.mintick < 0.01
str.tostring(price, "#.##")
else if syminfo.mintick < 0.1
str.tostring(price, "#.#")
else
str.tostring(math.round(price))
merge_or_add_level(prices, names, colors, widths, styles, show, price, name, col, w, s) =>
if show and not na(price)
idx = -1
if array.size(prices) > 0
for i = 0 to array.size(prices) - 1
if math.abs(array.get(prices, i) - price) <= (syminfo.mintick * merge_ticks)
idx := i
break
if idx != -1
existing_name = array.get(names, idx)
array.set(names, idx, existing_name + " | " + name)
else
array.push(prices, price)
array.push(names, name)
array.push(colors, col)
array.push(widths, w)
array.push(styles, s)
if master_key and barstate.islast
// Clean up previous tick's drawings
if array.size(active_key_lines) > 0
for i = 0 to array.size(active_key_lines) - 1
line.delete(array.get(active_key_lines, i))
label.delete(array.get(active_key_labels, i))
array.clear(active_key_lines)
array.clear(active_key_labels)
// Temporary arrays to hold aggregated levels for this tick
prices = array.new_float()
names = array.new_string()
colors = array.new_color()
widths = array.new_int()
styles = array.new_string()
// Determine values based on master toggles
c_asia = master_key_col_on ? master_key_col : col_asia
s_asia = master_key_style_on ? master_key_style : style_asia
w_asia = master_key_width_on ? master_key_width : width_asia
c_mon = master_key_col_on ? master_key_col : col_mon
s_mon = master_key_style_on ? master_key_style : style_mon
w_mon = master_key_width_on ? master_key_width : width_mon
c_pd = master_key_col_on ? master_key_col : col_pd
s_pd = master_key_style_on ? master_key_style : style_pd
w_pd = master_key_width_on ? master_key_width : width_pd
c_cw = master_key_col_on ? master_key_col : col_cw
s_cw = master_key_style_on ? master_key_style : style_cw
w_cw = master_key_width_on ? master_key_width : width_cw
c_pw = master_key_col_on ? master_key_col : col_pw
s_pw = master_key_style_on ? master_key_style : style_pw
w_pw = master_key_width_on ? master_key_width : width_pw
c_cm = master_key_col_on ? master_key_col : col_cm
s_cm = master_key_style_on ? master_key_style : style_cm
w_cm = master_key_width_on ? master_key_width : width_cm
c_pm = master_key_col_on ? master_key_col : col_pm
s_pm = master_key_style_on ? master_key_style : style_pm
w_pm = master_key_width_on ? master_key_width : width_pm
c_cy = master_key_col_on ? master_key_col : col_cy
s_cy = master_key_style_on ? master_key_style : style_cy
w_cy = master_key_width_on ? master_key_width : width_cy
c_py = master_key_col_on ? master_key_col : col_py
s_py = master_key_style_on ? master_key_style : style_py
w_py = master_key_width_on ? master_key_width : width_py
// Add levels in order: Shortest timeframe → Longest timeframe
// NOTE (design): first level added at a coinciding price keeps its style/color/width;
// later ones only append their name. So shorter timeframes win styling on overlap.
// To make the higher timeframe dominate, reverse the add order below.
// Asia Session
merge_or_add_level(prices, names, colors, widths, styles, show_asia, asia_disp_h, "Asia H", c_asia, w_asia, s_asia)
merge_or_add_level(prices, names, colors, widths, styles, show_asia, asia_disp_l, "Asia L", c_asia, w_asia, s_asia)
merge_or_add_level(prices, names, colors, widths, styles, show_asia_mid, asia_disp_mid, "Asia 50%", c_asia, w_asia, s_asia)
// Monday Range
merge_or_add_level(prices, names, colors, widths, styles, show_mon, mon_h, "Mon H", c_mon, w_mon, s_mon)
merge_or_add_level(prices, names, colors, widths, styles, show_mon, mon_l, "Mon L", c_mon, w_mon, s_mon)
merge_or_add_level(prices, names, colors, widths, styles, show_mon, mon_o, "Mon O", c_mon, w_mon, s_mon)
// Daily Levels
merge_or_add_level(prices, names, colors, widths, styles, show_do, d_o, "D Open", c_pd, w_pd, s_pd)
merge_or_add_level(prices, names, colors, widths, styles, show_pd, d_h, "PDH", c_pd, w_pd, s_pd)
merge_or_add_level(prices, names, colors, widths, styles, show_pd, d_l, "PDL", c_pd, w_pd, s_pd)
merge_or_add_level(prices, names, colors, widths, styles, show_pd_mid, d_mid, "PD 50%", c_pd, w_pd, s_pd)
// Current Weekly Levels
merge_or_add_level(prices, names, colors, widths, styles, show_cw, cw_h, "CW High", c_cw, w_cw, s_cw)
merge_or_add_level(prices, names, colors, widths, styles, show_cw, cw_l, "CW Low", c_cw, w_cw, s_cw)
merge_or_add_level(prices, names, colors, widths, styles, show_cw_mid, cw_mid, "CW 50%", c_cw, w_cw, s_cw)
// Previous Weekly Levels
merge_or_add_level(prices, names, colors, widths, styles, show_pw, w_h, "PWH", c_pw, w_pw, s_pw)
merge_or_add_level(prices, names, colors, widths, styles, show_pw, w_l, "PWL", c_pw, w_pw, s_pw)
merge_or_add_level(prices, names, colors, widths, styles, show_pw_mid, w_mid, "PW 50%", c_pw, w_pw, s_pw)
// Current Monthly Levels
merge_or_add_level(prices, names, colors, widths, styles, show_cm, cm_h, "CM High", c_cm, w_cm, s_cm)
merge_or_add_level(prices, names, colors, widths, styles, show_cm, cm_l, "CM Low", c_cm, w_cm, s_cm)
merge_or_add_level(prices, names, colors, widths, styles, show_cm_mid, cm_mid, "CM 50%", c_cm, w_cm, s_cm)
// Previous Monthly Levels
merge_or_add_level(prices, names, colors, widths, styles, show_pm, m_h, "PMH", c_pm, w_pm, s_pm)
merge_or_add_level(prices, names, colors, widths, styles, show_pm, m_l, "PML", c_pm, w_pm, s_pm)
merge_or_add_level(prices, names, colors, widths, styles, show_pm_mid, m_mid, "PM 50%", c_pm, w_pm, s_pm)
// Current Yearly Levels
merge_or_add_level(prices, names, colors, widths, styles, show_cy, cy_h, "CY High", c_cy, w_cy, s_cy)
merge_or_add_level(prices, names, colors, widths, styles, show_cy, cy_l, "CY Low", c_cy, w_cy, s_cy)
merge_or_add_level(prices, names, colors, widths, styles, show_cy_mid, cy_mid, "CY 50%", c_cy, w_cy, s_cy)
merge_or_add_level(prices, names, colors, widths, styles, show_cy_open, cy_o, "CY Open", c_cy, w_cy, s_cy)
// Previous Yearly Levels
merge_or_add_level(prices, names, colors, widths, styles, show_py, py_h, "PYH", c_py, w_py, s_py)
merge_or_add_level(prices, names, colors, widths, styles, show_py, py_l, "PYL", c_py, w_py, s_py)
merge_or_add_level(prices, names, colors, widths, styles, show_py_mid, py_mid, "PY 50%", c_py, w_py, s_py)
// Draw the newly merged levels onto the chart (lines extend `line_len` bars)
if array.size(prices) > 0
for i = 0 to array.size(prices) - 1
lvl_p = array.get(prices, i)
n = array.get(names, i)
c = array.get(colors, i)
w = array.get(widths, i)
s_str = array.get(styles, i)
line_style = get_line_style(s_str)
price_formatted = format_price(lvl_p)
label_text = n + " (" + price_formatted + ")"
ln = line.new(bar_index, lvl_p, bar_index + line_len, lvl_p, color=c, width=w, style=line_style)
lb = label.new(bar_index + line_len, lvl_p, label_text, color=na, textcolor=c, style=label.style_label_left, size=size.small)
array.push(active_key_lines, ln)
array.push(active_key_labels, lb)
Show all 877 lines