Skip to content

Instantly share code, notes, and snippets.

@dp21g
Created October 13, 2025 18:31
Show Gist options
  • Save dp21g/12e3073e551ef97cca2f2fd173b679b8 to your computer and use it in GitHub Desktop.
Save dp21g/12e3073e551ef97cca2f2fd173b679b8 to your computer and use it in GitHub Desktop.
untouched opens
//@version=5
indicator("Hourly Open Untouched Zones", overlay=true, max_boxes_count=500, max_lines_count=500, max_labels_count=500)
// —————————————————————————————————————————————————————————————————————————————
// Part 3: User Customization (Inputs)
// —————————————————————————————————————————————————————————————————————————————
var string G_COLORS = "Colors & Transparency"
c_box_a_base = input.color(color.blue, "Box A Color", group=G_COLORS, inline="box_a")
c_box_a_transp = input.int(80, "Transparency", minval=0, maxval=100, group=G_COLORS, inline="box_a")
c_box_b_base = input.color(color.orange, "Box B Color", group=G_COLORS, inline="box_b")
c_box_b_transp = input.int(85, "Transparency", minval=0, maxval=100, group=G_COLORS, inline="box_b")
c_untouched_box_base = input.color(color.fuchsia, "Untouched 'Pink' Box Color", group=G_COLORS, inline="untouched_box")
c_untouched_box_transp = input.int(75, "Transparency", minval=0, maxval=100, group=G_COLORS, inline="untouched_box")
c_hourly_line_base = input.color(color.red, "Hourly Open Line Color", group=G_COLORS, inline="hourly_line")
c_half_hour_line_base = input.color(color.blue, "Half-Hour Line Color", group=G_COLORS, inline="half_hour_line")
c_breakout_label_base = input.color(color.purple, "Breakout Label Color", group=G_COLORS, inline="breakout_label")
var string G_LINES = "Line Styles"
s_hourly_line = input.string("Solid", "Hourly Open Line Style", options=["Solid", "Dashed", "Dotted"], group=G_LINES)
w_hourly_line = input.int(2, "Hourly Open Line Width", minval=1, group=G_LINES)
s_half_hour_line = input.string("Dashed", "Half-Hour Open Line Style", options=["Solid", "Dashed", "Dotted"], group=G_LINES)
w_half_hour_line = input.int(1, "Half-Hour Open Line Width", minval=1, group=G_LINES)
var string G_LABEL = "Breakout Label/Symbol"
s_breakout_symbol = input.string("▲", "Breakout Symbol", options=["▲", "▶", "◆", "⭐"], group=G_LABEL)
s_breakout_size = input.string(size.small, "Breakout Symbol Size", options=[size.tiny, size.small, size.normal, size.large, size.huge], group=G_LABEL)
c_box_a = color.new(c_box_a_base, c_box_a_transp)
c_box_b = color.new(c_box_b_base, c_box_b_transp)
c_untouched_box = color.new(c_untouched_box_base, c_untouched_box_transp)
// —————————————————————————————————————————————————————————————————————————————
// Helper Functions & Global Variables
// —————————————————————————————————————————————————————————————————————————————
f_get_line_style(style_string) =>
style_string == "Solid" ? line.style_solid : style_string == "Dashed" ? line.style_dashed : line.style_dotted
var string TZ = "America/New_York"
type HourlySession
int start_time
float hourly_open
float high_h1
float low_h1
float high_h2
float low_h2
bool second_half_touched_open
bool is_untouched_setup
bool line_mitigated
box box_h1
box box_h2
line line_open
type BreakoutSetup
float setup_high
int setup_time
bool triggered
var sessions = array.new<HourlySession>()
var setups = array.new<BreakoutSetup>()
var breakout_alert_fired_this_bar = false
// —————————————————————————————————————————————————————————————————————————————
// Data Fetching (Global Scope)
// —————————————————————————————————————————————————————————————————————————————
[time_30m, open_30m] = request.security(syminfo.tickerid, "30", [time, open], lookahead=barmerge.lookahead_off)
[time_30m_p1, open_30m_p1, high_30m_p1, close_30m_p1] = request.security(syminfo.tickerid, "30", [time[1], open[1], high[1], close[1]], lookahead=barmerge.lookahead_off)
// —————————————————————————————————————————————————————————————————————————————
// Core Logic
// —————————————————————————————————————————————————————————————————————————————
breakout_alert_fired_this_bar := false
ny_hour = hour(time, TZ)
ny_minute = minute(time, TZ)
is_first_half = ny_minute < 30
is_second_half = not is_first_half
is_new_hour_bar = ta.change(ny_hour)
// When a new hour starts, finalize the previous session AND create the new one.
if is_new_hour_bar
// --- Part 1: Finalize the session that just ended ---
if array.size(sessions) > 0
completed_session = array.last(sessions)
if not completed_session.second_half_touched_open
completed_session.is_untouched_setup := true
// Delete the old box and create a new pink box with priority
if not na(completed_session.box_h1) and not na(completed_session.high_h1) and not na(completed_session.low_h1)
start_time_h1 = completed_session.start_time
end_time_h1 = start_time_h1 + 30 * 60 * 1000
box.delete(completed_session.box_h1)
// Create pink box with the stored high/low from first half
completed_session.box_h1 := box.new(start_time_h1, completed_session.high_h1, end_time_h1, completed_session.low_h1, bgcolor=c_untouched_box, border_color=na, xloc=xloc.bar_time)
log.info("✅ PINK BOX CREATED for untouched zone at {0}. High: {1}, Low: {2}, Hourly Open: {3}", str.format_time(start_time_h1, "HH:mm", TZ), completed_session.high_h1, completed_session.low_h1, completed_session.hourly_open)
if close_30m_p1 > open_30m_p1
array.push(setups, BreakoutSetup.new(high_30m_p1, time_30m_p1, false))
else
if not na(completed_session.line_open)
time_h2_end = completed_session.start_time + 60 * 60 * 1000
line.set_x2(completed_session.line_open, time_h2_end)
// --- Part 2: Create the NEW session for the hour that is just starting ---
new_hourly_open = open
hour_start_ts = timestamp(TZ, year(time, TZ), month(time, TZ), dayofmonth(time, TZ), ny_hour, 0, 0)
hourly_line = line.new(hour_start_ts, new_hourly_open, hour_start_ts + 60 * 60 * 1000, new_hourly_open, xloc=xloc.bar_time, color=c_hourly_line_base, style=f_get_line_style(s_hourly_line), width=w_hourly_line)
array.push(sessions, HourlySession.new(hour_start_ts, new_hourly_open, na, na, na, na, false, false, false, na, na, hourly_line))
// Bar-by-bar logic for drawing boxes dynamically
if array.size(sessions) > 0
current_session = array.last(sessions)
if is_first_half
if na(current_session.box_h1)
// Create box on the first bar of the session
start_time_h1 = current_session.start_time
current_session.high_h1 := high
current_session.low_h1 := low
current_session.box_h1 := box.new(start_time_h1, high, time, low, bgcolor=c_box_a, border_color=na, xloc=xloc.bar_time)
else
// Update box on subsequent bars of the first half
current_session.high_h1 := math.max(nz(current_session.high_h1), high)
current_session.low_h1 := math.min(nz(current_session.low_h1, low), low)
box.set_top(current_session.box_h1, current_session.high_h1)
box.set_bottom(current_session.box_h1, current_session.low_h1)
box.set_right(current_session.box_h1, time)
else // is_second_half
if na(current_session.box_h2)
// Create box on the first bar of the second half
start_time_h2 = current_session.start_time + 30 * 60 * 1000
current_session.high_h2 := high
current_session.low_h2 := low
current_session.box_h2 := box.new(start_time_h2, high, time, low, bgcolor=c_box_b, border_color=na, xloc=xloc.bar_time)
log.info("Second half started at {0}:{1}. Hourly open: {2}, Current High: {3}, Current Low: {4}", str.tostring(ny_hour, "00"), str.tostring(ny_minute, "00"), current_session.hourly_open, high, low)
else
// Update box on subsequent bars of the second half
current_session.high_h2 := math.max(nz(current_session.high_h2), high)
current_session.low_h2 := math.min(nz(current_session.low_h2, low), low)
box.set_top(current_session.box_h2, current_session.high_h2)
box.set_bottom(current_session.box_h2, current_session.low_h2)
box.set_right(current_session.box_h2, time)
// Check for touch condition
if not current_session.second_half_touched_open
touched = (high >= current_session.hourly_open and low <= current_session.hourly_open)
if touched
current_session.second_half_touched_open := true
log.info("❌ Touch detected at {0}:{1}. High: {2}, Low: {3}, Hourly Open: {4}", str.tostring(ny_hour, "00"), str.tostring(ny_minute, "00"), high, low, current_session.hourly_open)
// Extend/Mitigate hourly open lines
for session_obj in sessions
if session_obj.is_untouched_setup and not session_obj.line_mitigated
// Check if current bar crosses the hourly open line
if high >= session_obj.hourly_open and low <= session_obj.hourly_open
session_obj.line_mitigated := true
line.set_x2(session_obj.line_open, time)
line.set_color(session_obj.line_open, color.new(c_hourly_line_base, 80))
log.info("✂️ Line mitigated at {0}:{1} for hourly open at {2}", str.tostring(ny_hour, "00"), str.tostring(ny_minute, "00"), str.format_time(session_obj.start_time, "HH:mm", TZ))
else
line.set_x2(session_obj.line_open, time + 60 * 1000)
// Check for breakout triggers
if is_second_half and array.size(setups) > 0
for setup in setups
if not setup.triggered and high > setup.setup_high
setup.triggered := true
breakout_alert_fired_this_bar := true
label.new(bar_index, setup.setup_high, s_breakout_symbol, style=label.style_label_up, color=c_breakout_label_base, textcolor=color.white, size=s_breakout_size, yloc=yloc.price, tooltip="Breakout of " + str.tostring(setup.setup_high, format.mintick) + " from " + str.format_time(setup.setup_time, "HH:mm", TZ))
break
// Draw half-hour line
is_new_30m_bar = ta.change(time_30m)
if is_new_30m_bar and is_second_half and not na(time_30m)
line.new(time_30m, open_30m, time_30m + 30 * 60 * 1000, open_30m, xloc=xloc.bar_time, color=c_half_hour_line_base, style=f_get_line_style(s_half_hour_line), width=w_half_hour_line)
// —————————————————————————————————————————————————————————————————————————————
// Housekeeping & Alerts
// —————————————————————————————————————————————————————————————————————————————
if array.size(sessions) > 20
old_session = array.shift(sessions)
if not na(old_session.box_h1)
box.delete(old_session.box_h1)
if not na(old_session.box_h2)
box.delete(old_session.box_h2)
if not na(old_session.line_open)
line.delete(old_session.line_open)
if array.size(setups) > 20
array.shift(setups)
alertcondition(breakout_alert_fired_this_bar, "Hourly Open Untouched Zone Breakout", "Valid breakout of a setup high has occurred in the second half of the hour.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment