Lab notebook · chapter 3 of 7 · page 2 of 3

Choosing strategies continued

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.