Big overnight gaps slam SPX’s 10-minute EMA stack all at once at 09:30, because SPX has no extended-hours session. SPY’s 04:00–09:30 pre-market bars partially absorb the same gap, so SPY’s indicators arrive at the cash open already adjusted. The result: median |PO_SPY − PO_SPX| at 09:30 is 40 oscillator units — about two full Saty zones — and the fast cloud disagrees on 38% of trading days. Across 4,527 RTH days from 2008 through 2026, SPY’s reading wins.
Two parallel 10-minute datasets, joined on RTH timestamps. SPY indicators come from ind_10m (04:00–19:50 ET, mirroring a TradingView chart with pre-market on). SPX 10-minute bars are aggregated from FirstRateData 1-minute RTH-only data, then the same Saty Pivot Ribbon (EMA 8/13/21/48/200) and Phase Oscillator are computed from scratch on the SPX series. Gap is measured on SPY: (SPY 09:30 first-bar open − SPY prior daily close) / prior daily close.
d_open = PO_SPY − PO_SPX.PO zones are 23.6 units wide. The typical SPX-vs-SPY gap at the open is roughly two zones; the worst 5% of days are five zones apart. By 16:00 the median residual is just 5 oscillator units regardless of gap size — the divergence universally resolves. How it resolves is where the signal lives.
| |Gap| at open | N | Median |d_open| | Median |d_noon| | Median |d_eod| | Fast mismatch | Slow mismatch |
|---|---|---|---|---|---|---|
| < 0.25% | 1,912 | 38.5 | 11.5 | 4.5 | 45.0% | 35.7% |
| 0.25 – 0.5% | 1,185 | 37.9 | 17.4 | 4.6 | 35.4% | 39.5% |
| 0.5 – 1% | 924 | 42.5 | 25.7 | 5.3 | 31.3% | 40.4% |
| ≥ 1% | 506 | 48.0 | 33.4 | 6.2 | 25.9% | 35.8% |
Bigger gaps produce bigger divergences and slower convergence (median |d_noon| rises from 11.5 in the smallest bin to 33.4 in the ≥1% bin), but the EOD residual lands in the 4–6 unit range across all bins.
Split |gap| ≥ 1% days into four cohorts by gap direction and the sign of d_open = PO_SPY − PO_SPX. The afternoon return splits cleanly. The strongest case — gap-down with SPY’s PO higher than SPX’s — runs +0.18% full-day on average; its mirror — gap-down with SPX’s PO higher than SPY’s — runs −0.36% full-day with a 43% afternoon up-rate. A 0.54-percent spread between symmetric cohorts is a real intraday signal.
| |Gap| ≥ 1.0% cohort | N | Morning | Afternoon | Aft up-rate | Full-day |
|---|---|---|---|---|---|
| Gap-up · SPY_PO > SPX_PO | 85 | +0.07% | +0.50% | 56.5% | +0.56% |
| Gap-up · SPX_PO > SPY_PO | 151 | −0.05% | +0.12% | 55.6% | +0.06% |
| Gap-down · SPY_PO > SPX_PO | 186 | +0.14% | +0.04% | 53.8% | +0.18% |
| Gap-down · SPX_PO > SPY_PO | 84 | −0.03% | −0.34% | 42.9% | −0.36% |
Reading the cohorts: when gap-direction and PO-direction agree (SPY-bullish on a gap up, or SPY-more-bearish on a gap down where SPX hasn’t caught down yet), SPY drifts further in the gap’s direction over the day. When they disagree, the gap fades. The divergence direction is what predicts the afternoon, not the gap by itself.
Decompose the open→EOD convergence into how much each side moved. closure_by_spy = −sign(d_open) · ΔPO_SPY measures how much SPY moved toward SPX’s reading; closure_by_spx the reverse. Then bucket each |gap| ≥ 1% day by which side did more of the closing.
| |Gap| ≥ 1% · convergence path | N | Morning | Afternoon | Aft up-rate | Full-day |
|---|---|---|---|---|---|
| Gap-up · SPX caught up to SPY | 153 | −0.12% | +0.25% | 52.3% | +0.13% |
| Gap-up · SPY caught down to SPX | 83 | +0.19% | +0.26% | 62.7% | +0.45% |
| Gap-down · SPX caught down to SPY | 187 | +0.10% | +0.05% | 56.2% | +0.15% |
| Gap-down · SPY caught down to SPX | 83 | +0.06% | −0.36% | 37.3% | −0.29% |
On 62% of |gap| ≥ 1% days SPX is the one that moves — it absorbs the gap shock that SPY’s pre-market session already metabolized. The default story is “SPY had it right; SPX caught up.” The exception is the bottom row: when SPY is the side that ends up moving (i.e. SPX’s reading was the right one and SPY rolled over to match it), gap-down days deliver the worst afternoons in the sample — 37.3% afternoon up-rate, −0.36% afternoon mean. Rare, but informative.
Practical read: at the cash open after a big gap, weight SPY’s Saty stack — it is the leading indicator. SPX’s 09:30 PO/Ribbon are dominated by RTH-only EMA mechanics, not by genuine new information.
The Phase Oscillator is a continuous scalar; the Pivot Ribbon’s fast-cloud color (EMA8 vs EMA21) is binary and is the signal you read off a chart in one glance. Same finding, simpler to use in real-time.
| Fast cloud at 09:30 (|gap| ≥ 0.5%) | N | Morning | Afternoon | Aft up-rate | Full-day |
|---|---|---|---|---|---|
| SPY bull · SPX bear | 231 | −0.04% | +0.32% | 61.0% | +0.28% |
| SPY bear · SPX bull | 189 | −0.03% | −0.12% | 46.6% | −0.14% |
| Both bull (no mismatch) | 532 | +0.08% | +0.01% | 55.3% | +0.10% |
| Both bear (no mismatch) | 478 | −0.00% | +0.03% | 52.1% | +0.02% |
Mismatch days deliver a 0.42% full-day spread between SPY-bull/SPX-bear and SPY-bear/SPX-bull. The asymmetry holds: SPY’s color leads. When the two ribbons agree (both bull or both bear), the day is a wash relative to the unconditional baseline.
SPX is a price index of 500 stocks computed only during cash hours; SPY is an ETF that trades 04:00–20:00 ET. Saty’s Phase Oscillator on a 10-minute chart is EMA(((close − EMA21) / (3 · ATR14)) · 100, 3). EMA21 on the SPY 10m chart accumulates pre-market bars; on the SPX 10m chart it does not. After a gap, SPY’s 09:30 close sits closer to its already-adjusted EMA21, so PO_SPY reads near-neutral. SPX’s 09:30 close has just jumped onto an EMA stack still anchored to yesterday’s RTH levels, so PO_SPX reads extreme. The same logic applies to the Pivot Ribbon’s 8/21 fast-cloud color.
The largest example in the sample: 2026-04-08, a +2.6% gap up. PO_SPY at 09:30 was −11.6 (slightly bearish, EMA stack already adjusted). PO_SPX at 09:30 was +138 (extended_up — the gap detonated SPX’s normalization). By EOD the divergence collapsed to −19. SPY closed roughly flat for the day (−0.08%). The SPX “extended_up” signal was structural noise; the SPY near-neutral read was the truth.
Data: SPY from spy.db ind_10m (550K 10m rows, 04:00–19:50 ET, 2000-01 onward). SPX from FirstRateData SPX_full_1min_*.zip aggregated to 10m RTH (179K rows, 2008-01-02 onward). Common RTH date intersection: 4,573 days; 4,532 retained after gap-pricing; 4,527 after dropping 5 EMA-warm-up days.
Indicators: Pivot Ribbon EMAs and Phase Oscillator computed identically across both datasets using indicators.py’s formulas (Wilder ATR, span EMAs, no Saty period_index shift since these are intrabar quantities, not level anchors).
Snapshots: 09:30 first-bar values for divergence, 12:00 for noon checkpoint, 15:50 final RTH 10m bar for EOD. SPY morning return = open → 12:00 close; afternoon = 12:00 close → 15:50 close.
Gap: (SPY 09:30 first-bar open − prior . Daily candles are RTH-only and 2%-clipped against bad ticks, so the gap definition is the conventional cash-session open vs. prior cash close.
candles_1d close) / prior candles_1d close · 100
Open caveats — do not trade off this page yet: (1) No VIX-regime stratification. Edge size likely concentrates in higher-vol environments. (2) No FOMC/CPI/OpEx exclusion. (3) Sample includes 2008–2009 GFC tail; needs a 2010+ re-cut to confirm robustness. (4) No transaction-cost or slippage model on the implied morning fade trade. (5) Gap is measured on SPY; an SPX-native gap would be marginally cleaner. (6) Not yet checked whether a similar setup exists with the slow cloud (13/48) carrying more signal than the fast cloud (8/21).
backtest_spx_spy_divergence.py — analysis script.
analyst/spx_spy_divergence_summary.csv — 4,527 daily records.
analyst/spx_spy_divergence_by_gap.csv — binned summary.
analyst/spx_spy_divergence_signal_05.csv, _signal_10.csv — sign(d_open) cohorts.
analyst/spx_spy_divergence_convergence_05.csv, _10.csv — who-closed-the-gap cohorts.
analyst/spx_spy_divergence_ribbon_05.csv — fast-cloud mismatch cohorts.
analyst/spx_spy_divergence_run.log — full run log.