Overnight vs intraday
python fetch_yahoo.py --years 10 --adjust split --out data/bars_10y_split.csv
python overnight.py --bars data/bars_10y_split.csv
Splits each name's price return into two legs that multiply back to it:
overnight = open(t)/close(t-1), intraday = close(t)/open(t).
Use a split-only file. The usual adjclose/close adjustment cancels out of
the intraday leg (same factor on open and close) but not the overnight one,
where consecutive bars carry different factors — that credits every dividend to
an overnight gap. --adjust split exists for this.
Note that Yahoo's raw quote values are already split-adjusted; verified
against NVDA's 2024 10-for-1, which runs 120.89 → 121.79 straight through. So
--adjust split applies no factor at all. Applying one double-adjusts and was
wrong by 40× for NVDA before this was caught. test_overnight.py fails if the
committed file ever shows a split-shaped gap.
SPY, 2016–2026, the one unbiased read:
| leg | cumulative | annualised | Sharpe | bps/session |
|---|---|---|---|---|
| overnight | +138.3% | +9.10% | 0.81 | 3.72 |
| intraday | +47.6% | +3.98% | 0.35 | 1.92 |
The effect is real and it is not stable. 2022 was −14.7% overnight and 2023 +4.3%, against +22.3% in 2024. Break-even round-trip cost is 3.72bps — one round trip per session means the mean is the hurdle — leaving roughly +3.7%/yr at 2bps and nothing at 5bps.
The all-names figure (+17.6%/yr overnight) is survivorship-biased: the universe is 20 mega-caps chosen today. Dropping the five biggest winners takes it to +11.3%/yr. Prefer the SPY row.
This is a measurement, not a strategy. Capturing the overnight leg needs MOC/MOO execution the daily-bar replay engine cannot represent — it fills against the next bar — so it is deliberately not wired into the harness.