Bottom line: the first 5-minute ES RTH bar opens on its high or low (no tail on the opening side) on only 3.6% of days in 16 years. On those days, price trades back to the open the same day 76.2% of the time.
And it comes back fast: on half of those days, price is back at the open before 10:00. The whole daily bar opened on its high or low on only 35 of 3,924 days (0.89%).
How I use it: when the first bar opens on its low and runs straight up, I don’t assume the open is today’s low. On three days out of four, price comes back to test the open, so it’s a pullback level worth waiting for.
Daily bars almost always have tails: days when the open is exactly the day’s high or low are very rare. So when the first bar of the day opens on its own high or low (a “shaved open”: the opening side has been shaved off, with no tail), does price come back to test the open later? How long does it take?
2010–2026 · the first 5-minute bar opens on its high or low
3.64%
143 of 3,924 days · open on the low 78, open on the high 66
How long until price comes back to the openOnly days whose first bar opens on its high or low. X axis: time of day in RTH. Y axis: share of those days that have traded back to the open; dashed line: close of the first bar.
Changing the first bar's sizeThe longer the first bar, the rarer a shaved open, and the less often price comes back.
| First bar | Opens on high / low | Back to the open that day | Open holds to the close |
|---|---|---|---|
| 1 min | 3077.82% | 89.6% | 11.4% |
| 5 min | 1433.64% | 76.2% | 24.5% |
| 15 min | 942.40% | 63.8% | 37.2% |
| 30 min | 802.04% | 57.5% | 43.8% |
| 60 min | 581.48% | 41.4% | 60.3% |
Open on the low = the open equals the first bar's low exactly (no lower tail); open on the high likewise. Back to the open = after the first bar closes, price trades at the open again. Open holds to the close = the open is the RTH low (high) of the day. Hover (or tap) the chart to see the numbers.
Rare: only 3.6% for a 5-minute first bar
Out of 3,924 trading days, the first 5-minute bar opened on its low on 78 days and on its high on 66, 143 days in total (3.64%; one day was both, 2020-03-16, when a circuit breaker halted trading right at the open).
The longer the first bar, the harder it is for the open to be that bar’s high or low: 7.8% for a 1-minute bar, 2.4% for 15 minutes, and only 1.5% for 60 minutes.
Most come back, and quickly
On the 143 days when the first 5-minute bar opened on its high or low:
| By | Back to the open |
|---|---|
| 9:40 (close of bar 2) | 24.5% |
| 10:00 | 49.7% |
| 10:30 | 61.5% |
| 11:30 | 66.4% |
| Close | 76.2% |
Of the days that came back, half did so by the close of bar 3 (9:45). So the “shaved” side of the opening bar usually gets its tail filled in quickly.
The remaining 24.5% are days when the open held all the way to the close as the day’s low (high), meaning the daily bar opened on its low (high).
The longer the first bar, the less often price comes back
| First bar | Days | Back to the open | Holds to the close |
|---|---|---|---|
| 1 minute | 307 (7.82%) | 89.6% | 11.4% |
| 5 minutes | 143 (3.64%) | 76.2% | 24.5% |
| 15 minutes | 94 (2.40%) | 63.8% | 37.2% |
| 30 minutes | 80 (2.04%) | 57.5% | 43.8% |
| 60 minutes | 58 (1.48%) | 41.4% | 60.3% |
The reason is simple: the 35 days when the daily bar opened on its high or low are always included, whatever the first bar’s size. The longer the first bar, the fewer “false” shaved opens are left besides those 35, so the share that comes back naturally drops. With a 60-minute first bar, six in ten of the shaved opens are days when the open held to the close.
So for this question, a first bar of 1 to 30 minutes works best. A 60-minute first bar already takes up a seventh of the session, leaving much less time for price to “come back”.
Definition and how to reproduce it
Opening on the high or low
- Data: cleaned 1-minute ES bars, 2010-06 to 2026-09, 3,924 trading days. See how I cleaned it.
- RTH: 09:30–16:15 US Eastern. Open: the open of the 09:30 1-minute bar.
- First bar: 1, 5, 15, 30 and 60 minutes, built from the 1-minute bars.
- Opens on the low: the open equals the first bar’s low exactly, so the first bar has no lower tail. Opens on the high works the same way.
- Back to the open: after the first bar closes, price trades at the open again (for a bar that opened on its low, the low falls back to the open).
- Open holds to the close: the open is the RTH low (high) of the whole day, the same as the daily bar opening on its low (high).
I have a CSV of cleaned 1-minute ES futures bars (Databento's ES.c.0 continuous contract, with columns ts_event, open, high, low; timestamps in UTC). Please write Python code that checks whether price comes back to the open after the first RTH bar opens on its high or low (no tail on the opening side). Rules: 1. Convert timestamps to US Eastern time (US/Eastern), handling daylight saving time automatically. Use RTH only, 09:30–16:15, meaning 1-minute bars from 09:30 to 16:14. Open = the open of the 09:30 1-minute bar. 2. Use 1, 5, 15, 30 and 60 minutes for the first bar (built from the 1-minute bars; for 5 minutes, that's the five bars from 09:30 to 09:34). 3. Opens on the low: the open equals the first bar's low exactly (no tolerance); opens on the high: the open equals the first bar's high exactly. Together these are "opens on its high or low". 4. Back to the open: after the first bar closes, for a bar that opened on its low, some minute's low ≤ the open; for a bar that opened on its high, some minute's high ≥ the open. Record the first minute it happens. 5. Open holds to the close: the open is the RTH low (or high) of the whole day. That's the same as "the daily bar opens on its high or low", and it doesn't depend on the first bar's size. 6. Print: the sample size, and the number of days the daily bar opens on its high and on its low; for each first-bar size, the number and share of days that open on the high or low, the share that come back to the open that day, and the share where the open holds to the close; for the 5-minute first bar, also print the share back to the open by 9:40, 10:00, 10:30 and 11:30, and the time by which half of the days that came back had done so. Please add comments to every step explaining why it's done that way.
# Open on the high / low: when the first RTH bar opens on its own high or low, does price come back to the open later?
# Input: cleaned 1-minute ES bars (Databento ES.c.0, timestamps in UTC)
import numpy as np
import pandas as pd
df = pd.read_csv("ES-1min_clean.csv", usecols=["ts_event", "open", "high", "low"])
t = pd.to_datetime(df.ts_event, utc=True).dt.tz_convert("US/Eastern").dt.tz_localize(None)
m = t.dt.hour * 60 + t.dt.minute - 570 # minutes since 09:30
rth = df[(m >= 0) & (m < 405)].assign(date=t.dt.normalize(), m=m) # RTH 09:30–16:15
# A table with one row per day and 405 columns (one per minute); a minute with no trades is empty
H = rth.pivot(index="date", columns="m", values="high").reindex(columns=range(405)).values
L = rth.pivot(index="date", columns="m", values="low").reindex(columns=range(405)).values
op = rth.pivot(index="date", columns="m", values="open")[0].values # open = the open of the 09:30 bar
day_hi, day_lo = np.nanmax(H, 1), np.nanmin(L, 1)
keep = (op == day_hi) | (op == day_lo) # the open is the day's high or low
print(f"Sample {len(op)} days; daily bar opens on its high {(op == day_hi).sum()} days, on its low {(op == day_lo).sum()} days, {100 * keep.mean():.2f}% in total")
for T in [1, 5, 15, 30, 60]: # different sizes for the first bar
on_lo = op == np.nanmin(L[:, :T], 1) # opens on the low: open = the first bar's low (exactly equal)
on_hi = op == np.nanmax(H[:, :T], 1) # opens on the high
shaved = on_lo | on_hi
# After the first bar closes, the first minute price trades back to the open (opened on the low: low ≤ open; opened on the high: high ≥ open)
back = np.where(on_lo[:, None], L <= op[:, None], H >= op[:, None])
back[:, :T] = False
when = np.where(back.any(1), back.argmax(1), -1)[shaved]
n = shaved.sum()
print(f"First bar {T:2d} min: opens on its high or low {n} days ({100 * n / len(op):.2f}%, low {on_lo.sum()}, high {on_hi.sum()}); "
f"back to the open that day {100 * (when >= 0).mean():.1f}%; open holds to the close {100 * keep[shaved].mean():.1f}%")
if T == 5:
for hhmm, mins in [("9:40", 10), ("10:00", 30), ("10:30", 60), ("11:30", 120)]:
print(f" Back to the open by {hhmm}: {100 * ((when >= 0) & (when < mins)).mean():.1f}%")
med = int(np.median(when[when >= 0])) + 1 # median return time among days that came back (minutes after 09:30, at that minute's close)
print(f" Of the days that came back, half did so by {9 + (30 + med) // 60}:{(30 + med) % 60:02d}")Limitations
- The sample is small: only 143 days with a 5-minute first bar, a few to twenty a year, so the rate jumps around a lot when you pick a single year on the chart.
- It only checks whether price got back to the open: whether it bounced from there or broke through isn’t measured.
- It’s sensitive to the data: “opens on its high or low” requires the open to equal the high (low) exactly, and one tick off doesn’t count. With a different data source, a slightly different first print at the open could change the count.