On this page
Opening Range 9:30 - 9:45 - Technical Guide
Draws the 9:30-9:45 NY opening range high, low and midpoint for each visible trading day as a rectangle and dashed midline.
| Language | Indie Script v5 |
| Platform | TakeProfit |
| Category | Support & resistance |
| Type | Indicator |
| Author | @USERNAME_NOT_SET on TakeProfit |
| License | MIT |
| Live script | Open on TakeProfit |
| Source file | Opening Range 930 - 945.indie5 |
Overview
Opening Range (9:30-9:45 NY) is an overlay indicator that marks the price extremes reached during the first 15 minutes of the New York session. For every visible trading day it collects the highest high and lowest low of bars whose New York time falls in [9:30, 9:45), then draws a white rectangle spanning from 9:30 to 16:00 at those levels.
On the chart it shows the OR high as the top edge, the OR low as the bottom edge, vertical borders at 9:30 and 16:00, and a dashed white midline at the average of the two. It is meant for intraday charts such as 1-min, 5-min and 15-min, and simply visualises the opening range without computing signals.
How it works
- Converts the bar’s UTC timestamp to New York time using
NY_OFFSETand computes the NY midnight base and minutes since midnight. - Skips bars outside 9:30-16:00 NY:
calcreturns immediately, so extended-session bars never affect the range. - Detects a new NY day by comparing an integer day key; on a new day it seeds OR high/low with the current bar and resets validity/drawn flags.
- While
ny_m < OR_END, marks the OR as valid and expands_or_hi/_or_lousingmaxandminagainst the current bar’s high and low. - After 9:45, checks that the OR is valid and not yet drawn; then converts the OR boundary times back to UTC seconds for
AbsolutePosition. - Draws four white
LineSegmentborders forming a rectangle from 9:30 to 16:00 at OR high and OR low levels. - Draws a dashed white midline at
(or_hi + or_lo) * 0.5and sets_or_drawnso later bars of the same day do not redraw.
Mathematical model
\[\text{OR high} = \max_{t \in [9:30,\, 9:45)} \text{high}(t)\] \[\text{OR low} = \min_{t \in [9:30,\, 9:45)} \text{low}(t)\] \[\text{midline} = \frac{\text{OR high} + \text{OR low}}{2}\]Logic flow
flowchart TD
A["calc called for bar"] --> B["Compute NY time and minute"]
B --> C{"Outside 9:30 to 16:00?"}
C -- Yes --> E["Return"]
C -- No --> D{"New day key?"}
D -- Yes --> F["Reset OR high low valid drawn"]
D -- No --> G{"Within 9:30 to 9:45?"}
F --> G
G -- Yes --> H["Update OR high and low"]
H --> E
G -- No --> I{"OR valid and not drawn?"}
I -- Yes --> J["Draw rectangle and dashed midline"]
J --> K["Mark drawn"]
K --> E
I -- No --> E
Code walkthrough
Timezone and session constants
Lines 23-26 of Opening Range 930 - 945.indie5:
NY_OFFSET = 4 * 3600 # seconds east of UTC for New York (EDT)
OR_START = 9 * 60 + 30 # 570 min → 9:30 NY (session open & OR open)
OR_END = 9 * 60 + 45 # 585 min → 9:45 NY (OR window closes)
SESS_CLOSE = 16 * 60 # 960 min → 16:00 NY (session close)
These four constants define every time boundary used by the script. NY_OFFSET is in seconds east of UTC and set for EDT; OR_START, OR_END and SESS_CLOSE are minutes since NY midnight. The header explicitly notes that wintertime requires changing the offset to 5 * 3600.
Persistent state variables
Lines 32-38 of Opening Range 930 - 945.indie5:
def __init__(self):
# Persistent state variables (survive across bar-by-bar calls)
self._day = self.new_var(-1) # midnight-NY timestamp of current session
self._or_hi = self.new_var(0.0) # OR high accumulator
self._or_lo = self.new_var(0.0) # OR low accumulator
self._or_valid = self.new_var(False) # True once we have seen at least 1 OR bar
self._or_drawn = self.new_var(False) # True once the rectangle is drawn for this day
new_var creates state that survives across bar-by-bar calls to calc. _day identifies the current NY day, _or_hi and _or_lo accumulate the opening range, _or_valid records whether any OR bar was seen, and _or_drawn prevents redrawing the rectangle on every later bar of the same day.
New session detection and OR accumulation
Lines 50-66 of Opening Range 930 - 945.indie5:
# Unique integer key for this calendar day (NY midnight as int)
day_key = int(base)
# ── Detect a new trading session ────────────────────────────────────
if day_key != self._day.get():
self._day.set(day_key)
self._or_hi.set(self.high[0])
self._or_lo.set(self.low[0])
self._or_valid.set(False)
self._or_drawn.set(False)
# ── Accumulate Opening Range (9:30 inclusive → 9:45 exclusive) ──────
if ny_m < OR_END:
self._or_valid.set(True)
self._or_hi.set(max(self._or_hi.get(), self.high[0]))
self._or_lo.set(min(self._or_lo.get(), self.low[0]))
return # keep collecting until OR window closes
The integer NY midnight timestamp acts as a day key. On a new day, high and low are seeded from the first session bar, then the OR is expanded only while ny_m < OR_END. The early return inside the OR window keeps the script from attempting to draw before 9:45.
Guards and UTC boundary timestamps
Lines 68-83 of Opening Range 930 - 945.indie5:
# Guard: only draw if we collected real OR data for this session
if not self._or_valid.get():
return
# Guard: already drawn for today - nothing more to do
if self._or_drawn.get():
return
# ── Compute UTC boundary timestamps for this NY day ─────────────────
# base is midnight NY in NY-local seconds; add NY_OFFSET to get UTC
t_open = base + OR_START * 60 + NY_OFFSET # 9:30 NY in UTC seconds
t_close = base + SESS_CLOSE * 60 + NY_OFFSET # 16:00 NY in UTC seconds
or_hi = self._or_hi.get()
or_lo = self._or_lo.get()
or_mid = (or_hi + or_lo) * 0.5
Two guards ensure the rectangle is drawn only when real OR data exists and only once per day. t_open and t_close convert the NY-local boundaries back to UTC seconds by adding NY_OFFSET; these are the coordinates used by AbsolutePosition. The midline is computed as the simple average of the OR high and low.
Top, bottom and left borders
Lines 85-107 of Opening Range 930 - 945.indie5:
# ── Top border (OR high, spanning 9:30 to 16:00) ────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_hi),
AbsolutePosition(t_close, or_hi),
color=color.WHITE,
line_width=1,
))
# ── Bottom border (OR low, spanning 9:30 to 16:00) ──────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_lo),
AbsolutePosition(t_close, or_lo),
color=color.WHITE,
line_width=1,
))
# ── Left border (vertical line at 9:30) ─────────────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_hi),
AbsolutePosition(t_open, or_lo),
color=color.WHITE,
line_width=1,
))
Three white solid LineSegment objects create the top edge at OR high, the bottom edge at OR low, and the left vertical edge at 9:30. Each segment uses AbsolutePosition with fixed timestamps and price levels, so the levels remain static once drawn.
Right border and dashed midline
Lines 109-127 of Opening Range 930 - 945.indie5:
# ── Right border (vertical line at 16:00) ───────────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_close, or_hi),
AbsolutePosition(t_close, or_lo),
color=color.WHITE,
line_width=1,
))
# ── Dashed midline at (OR high + OR low) / 2 ────────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_mid),
AbsolutePosition(t_close, or_mid),
color=color.WHITE,
line_width=1,
line_style=line_segment_style.DASHED,
))
# Mark as drawn so subsequent bars of the same day don't recreate it
self._or_drawn.set(True)
The right vertical edge closes the rectangle at 16:00. The midline uses line_segment_style.DASHED to distinguish it from the solid borders. Setting _or_drawn at the end ensures the same day’s drawing is not recreated on subsequent bars.
Reading the chart
- The white rectangle marks the session’s opening range: top edge = OR high, bottom edge = OR low.
- The left vertical line is at 9:30 NY, the right vertical line at 16:00 NY.
- The dashed white horizontal line is the midpoint
(OR high + OR low) / 2. - Bars outside 9:30-16:00 NY are ignored completely.
- A day with no bars between 9:30 and 9:45 produces no rectangle for that day.
Implementation notes
- The timezone is hard-coded as EDT (
NY_OFFSET = 4 * 3600); the header instructs changing it to5 * 3600for EST in winter. - The OR window is inclusive at 9:30 and exclusive at 9:45 because the condition is
ny_m < OR_END. - If no bar falls inside the 9:30-9:45 window on a given day,
_or_validstaysFalseand the guard on line 69 prevents drawing. - The drawing is created once per day on the first post-OR bar, using the final accumulated high and low; it is not updated later in the session.
FAQ
How do I switch the indicator to New York standard time?
Change NY_OFFSET on line 23 from 4 * 3600 to 5 * 3600. That is the only timezone adjustment required by this script.
Can I change the opening range window or the session close time?
Yes. Edit OR_START, OR_END, and SESS_CLOSE, which are minutes since NY midnight. For example, 9 * 60 + 30 is 9:30 and 9 * 60 + 45 is 9:45; the drawn rectangle spans from OR_START to SESS_CLOSE.
Why does nothing appear for some days?
If no visible bar has a NY time between 9:30 and 9:45, _or_valid remains False and the guard on line 69 returns before drawing. This can happen on partial sessions, holidays, or charts whose first bar starts after 9:45.
Full source code
Indie Script v5, as published on TakeProfit. Copy it into the platform’s script editor or open the live script.
# indie:lang_version = 5
##############################################################################
# Opening Range Indicator – 9:30 to 9:45 NY time (EDT / UTC-4)
#
# In winter when UTC-5 change NY_OFFSET = 4 * 3600 to 5 * 3600
#
# Draws for EVERY visible trading day:
# • A white rectangle : left=9:30, right=16:00, top=OR high, bottom=OR low
# • A dashed white midline at (OR high + OR low) / 2
#
# Works on 1-min, 5-min and 15-min charts.
#
# ── Timezone note ──────────────────────────────────────────────────────────
# NY_OFFSET = 4 * 3600 → EDT (UTC-4, summer / daylight-saving time)
# Change to 5 * 3600 → EST (UTC-5, winter standard time)
##############################################################################
from indie import indicator, MainContext, color
from indie.drawings import LineSegment, AbsolutePosition, line_segment_style
# ── Session / timezone constants ──────────────────────────────────────────────
NY_OFFSET = 4 * 3600 # seconds east of UTC for New York (EDT)
OR_START = 9 * 60 + 30 # 570 min → 9:30 NY (session open & OR open)
OR_END = 9 * 60 + 45 # 585 min → 9:45 NY (OR window closes)
SESS_CLOSE = 16 * 60 # 960 min → 16:00 NY (session close)
# ── Indicator ─────────────────────────────────────────────────────────────────
@indicator('Opening Range (9:30-9:45 NY)', overlay_main_pane=True)
class Main(MainContext):
def __init__(self):
# Persistent state variables (survive across bar-by-bar calls)
self._day = self.new_var(-1) # midnight-NY timestamp of current session
self._or_hi = self.new_var(0.0) # OR high accumulator
self._or_lo = self.new_var(0.0) # OR low accumulator
self._or_valid = self.new_var(False) # True once we have seen at least 1 OR bar
self._or_drawn = self.new_var(False) # True once the rectangle is drawn for this day
def calc(self):
ts = self.time[0] # UTC seconds (per Indie docs)
ny = ts - NY_OFFSET # shift to NY local time (seconds)
base = ny - (int(ny) % 86400) # midnight of this NY day (NY-local seconds)
ny_m = (int(ny) % 86400) // 60 # minutes elapsed since NY midnight
# ── Skip bars outside the regular session ───────────────────────────
if ny_m < OR_START or ny_m >= SESS_CLOSE:
return
# Unique integer key for this calendar day (NY midnight as int)
day_key = int(base)
# ── Detect a new trading session ────────────────────────────────────
if day_key != self._day.get():
self._day.set(day_key)
self._or_hi.set(self.high[0])
self._or_lo.set(self.low[0])
self._or_valid.set(False)
self._or_drawn.set(False)
# ── Accumulate Opening Range (9:30 inclusive → 9:45 exclusive) ──────
if ny_m < OR_END:
self._or_valid.set(True)
self._or_hi.set(max(self._or_hi.get(), self.high[0]))
self._or_lo.set(min(self._or_lo.get(), self.low[0]))
return # keep collecting until OR window closes
# Guard: only draw if we collected real OR data for this session
if not self._or_valid.get():
return
# Guard: already drawn for today - nothing more to do
if self._or_drawn.get():
return
# ── Compute UTC boundary timestamps for this NY day ─────────────────
# base is midnight NY in NY-local seconds; add NY_OFFSET to get UTC
t_open = base + OR_START * 60 + NY_OFFSET # 9:30 NY in UTC seconds
t_close = base + SESS_CLOSE * 60 + NY_OFFSET # 16:00 NY in UTC seconds
or_hi = self._or_hi.get()
or_lo = self._or_lo.get()
or_mid = (or_hi + or_lo) * 0.5
# ── Top border (OR high, spanning 9:30 to 16:00) ────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_hi),
AbsolutePosition(t_close, or_hi),
color=color.WHITE,
line_width=1,
))
# ── Bottom border (OR low, spanning 9:30 to 16:00) ──────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_lo),
AbsolutePosition(t_close, or_lo),
color=color.WHITE,
line_width=1,
))
# ── Left border (vertical line at 9:30) ─────────────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_hi),
AbsolutePosition(t_open, or_lo),
color=color.WHITE,
line_width=1,
))
# ── Right border (vertical line at 16:00) ───────────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_close, or_hi),
AbsolutePosition(t_close, or_lo),
color=color.WHITE,
line_width=1,
))
# ── Dashed midline at (OR high + OR low) / 2 ────────────────────────
self.chart.draw(LineSegment(
AbsolutePosition(t_open, or_mid),
AbsolutePosition(t_close, or_mid),
color=color.WHITE,
line_width=1,
line_style=line_segment_style.DASHED,
))
# Mark as drawn so subsequent bars of the same day don't recreate it
self._or_drawn.set(True)