Bottom line: on 94.4% of days, today’s RTH breaks at least one side of today’s overnight range. Only 5.6% of days break neither.
One side at a time: the overnight high gets taken out 61.1% of the time, the overnight low 54.6%.
How I use it: once you’ve decided which way you’re trading today, the overnight high or low on that side is a fairly reliable target.
The overnight session is also called Globex (the old-school traders’ name) or ETH.
2010–2026 · at least one side broken (Overnight H/L)
94.4%
3,878 trading days
Up only
39.8%
1,545 days
Down only
33.3%
1,293 days
Both sides
21.2%
823 days
Neither
5.6%
217 days
By yearRight column: share of days that broke at least one side. Click a year to see only that year.
Prior day vs overnight2010–2026
The overnight range almost always breaks
Out of 3,878 trading days, 94.4% broke at least one side of the overnight range.
And it’s steady: every year falls between 88.2% and 97.5%. Even the lowest year, 2020, came close to nine in ten.
The rest: two-sided breaks are more common
- Both sides broken (21.2%): about one day in five, RTH breaks both the top and the bottom of the overnight range.
- Neither side broken (5.6%): RTH stays inside the overnight range all day. That’s rare.
A weak direction bias
Up only is 39.8%, down only 33.3%, a 6.5-point gap. In 16 years, 5 had more downside breaks than upside: 2010, 2012, 2015, 2016 and 2022.
Were those 5 years all down years? I checked against the data, and no:
| Year | ES full-year change | Overnight high broken | Overnight low broken |
|---|---|---|---|
| 2010 (from June) | +17.9% | 56.7% | 59.0% |
| 2012 | +11.9% | 55.4% | 58.4% |
| 2015 | −0.1% | 53.4% | 61.3% |
| 2016 | +11.7% | 53.9% | 60.1% |
| 2022 | −19.0% | 59.7% | 63.4% |
Full-year change: from the RTH open on the year’s first trading day to the RTH close on its last. The continuous contract isn’t back-adjusted, so roll gaps are included and this is only approximate.
Only 2022 was a real bear market. 2015 was basically flat, and the other three years were up for the year. On the flip side, 2011 (−0.8%) and 2018 (−6.7%) finished down, yet the overnight range still broke to the upside more often.
So the full-year move has little to do with which way RTH breaks the overnight range. The overnight range is redrawn every day, and the side that breaks reflects that day’s action, not the year’s trend. Even in 2022, a bear market down 19% for the year, the overnight low broke only 3.7 points more often than the high.
By year: wider overnight ranges, lower break rates
Broadly, the wider the overnight range relative to RTH, the lower the break rate. The lowest and highest years:
| Year | Overnight width / RTH width (median) | At least one side broken |
|---|---|---|
| 2020 | 0.98 | 88.2% |
| 2019 | 0.85 | 90.0% |
| 2023 | 0.66 | 97.5% |
Overnight width / RTH width: for each day, divide “overnight high − overnight low” by “that day’s RTH high − RTH low”, then take the median across the year’s trading days. It’s not a percentage of price but the relative width of the two ranges: 0.98 means that on a typical day that year, the overnight range was about as wide as RTH; 0.66 means it was only about two-thirds as wide.
2020 was extremely volatile. The overnight range was almost as wide as RTH, so RTH had a harder time getting outside it. In 2023 the overnight range was relatively narrow, and RTH broke out of it almost every day.
Compared with the prior day’s high and low
Switch to “Prior day H/L” and you’ll see the prior day’s range breaks a bit less often (89.6%), and both sides break less than half as often as with the overnight range (9.7% vs 21.2%).
The reason is the width of the two ranges. The overnight range is narrower, with a median of only 0.77 of the same day’s RTH range. Yesterday’s RTH range is about as wide as today’s, with a median of 1.01. The narrower the range, the higher the odds of a one-sided or two-sided break. See Breaking the Prior Day’s High or Low.
Definition and how to reproduce it
Breaking the overnight high or low
- Data: cleaned 1-minute ES bars, 2010-06 to 2026-09. See how I cleaned it.
- RTH: 09:30–16:15 US Eastern, i.e. 1-minute bars from 09:30 to 16:14, or 81 five-minute bars.
- Overnight session: previous trading day 16:15 to 09:29 today. The overnight high / low is the highest high / lowest low in that window.
- Skipped days: the 46 days with incomplete overnight data (fewer than 800 one-minute bars) don’t count, leaving 3,878.
- Upside breakout: today’s RTH high is strictly above the overnight high, i.e. at least 1 tick (0.25 points) higher. Exactly equal doesn’t count.
- Downside breakout: today’s RTH low is strictly below the overnight low.
- Four outcomes: up only, down only, both sides, neither.
I have a CSV of cleaned 1-minute ES futures bars (Databento's ES.c.0 continuous contract, with columns ts_event, high, low; timestamps in UTC). Please write Python code that measures whether today's RTH breaks today's overnight high or low. Rules: 1. Convert timestamps to US Eastern time (US/Eastern), handling daylight saving time automatically. RTH = 09:30–16:15, meaning 1-minute bars from 09:30 to 16:14 (405 bars). 2. Overnight session = previous trading day 16:15 to 09:29 today. In practice: bars after 16:15 belong to the next trading day that has an RTH session; bars before 09:30 belong to today (or, if today has no RTH session, to the next trading day). Overnight high / low = the highest high / lowest low in that window. 3. Skip days with an incomplete overnight session: fewer than 800 one-minute bars. 4. Upside breakout: today's RTH high is strictly greater than the overnight high (equal doesn't count, i.e. at least 1 tick of 0.25 above). Downside breakout: today's RTH low is strictly less than the overnight low. 5. Put each day into one of four outcomes: up only, down only, both sides, neither. Print the sample size, the share of each outcome, the share of days that broke at least one side, and a table by year. Please add comments to every step explaining why it's done that way.
# Overnight breakout: did today's RTH break today's overnight (ETH) high / low?
# 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", "high", "low"])
t = pd.to_datetime(df.ts_event, utc=True).dt.tz_convert("US/Eastern").dt.tz_localize(None)
hm = t.dt.hour * 100 + t.dt.minute
df["date"] = t.dt.normalize()
is_rth = (hm >= 930) & (hm < 1615) # RTH 09:30–16:15
rth = df[is_rth].groupby("date").agg(hi=("high", "max"), lo=("low", "min"))
days = rth.index
# Overnight = previous trading day 16:15 to 09:29 today:
# bars after 16:15 belong to the next trading day, bars before 09:30 belong to today
eth = df[~is_rth]
anchor = eth.date + pd.to_timedelta((hm[~is_rth] >= 1615).astype(int), unit="D")
pos = days.searchsorted(anchor)
keep = pos < len(days)
night = (eth[keep].assign(day=days[pos[keep]])
.groupby("day").agg(hi=("high", "max"), lo=("low", "min"), bars=("high", "size")))
night = night[night.bars >= 800] # skip days with an incomplete overnight session (< 800 bars)
today = rth.loc[night.index]
up = today.hi > night.hi # strictly above; equal doesn't count
down = today.lo < night.lo
cls = pd.Series(np.select([up & down, up, down], ["both sides", "up only", "down only"], "neither"), index=today.index)
print("sample days", len(cls))
print((cls.value_counts(normalize=True) * 100).round(1).to_string())
print("at least one side %.1f%%" % (100 * (cls != "neither").mean()))Limitations
- It only checks whether price broke, not how far: one tick beyond is a breakout. The overnight range is narrow to begin with, so a break doesn’t mean a trend.
- It doesn’t track which side broke first: on two-sided days, up-then-down and down-then-up are lumped together.
- The 46 days with incomplete overnight data are excluded: RTH was normal on those days; only part of the overnight data is missing.
(Updated in October 2026: recalculated with the cleaned data, replaced the earlier sample numbers, and added the interactive stats and the reproduction card.)