Created
October 13, 2025 18:31
-
-
Save dp21g/12e3073e551ef97cca2f2fd173b679b8 to your computer and use it in GitHub Desktop.
Revisions
-
dp21g created this gist
Oct 13, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,197 @@ //@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.")