Draft — First-pass exploratory study. Single 18-year sample, no VIX/regime stratification yet, no out-of-sample test. Read for context, not as a tradable signal.
Study — SPX vs SPY Morning Divergence

SPX’s open-bar Phase Oscillator is the lagging twin — SPY’s pre-market-cushioned reading is what carries the day.

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.

Step 01
Two indicator stacks
SPY 10m indicators (with ETH bars) and SPX 10m indicators (RTH-only) computed independently using identical Saty formulas.
Step 02
Snapshot at 09:30
First 10m RTH bar. Capture PO_SPY, PO_SPX, fast/slow cloud bullish flags for both. Compute d_open = PO_SPY − PO_SPX.
Step 03
Track resolution
Re-snapshot at 12:00 noon and 15:50 EOD. Decompose how the divergence closed: did SPY move toward SPX, or did SPX move toward SPY?
Sample
4,527 RTH days
2008-01-09 through 2026-04-09. First 5 days dropped for EMA200 warm-up. No VIX or regime filtering at this stage.
40
Median |PO_SPY − PO_SPX|
at 09:30 (oscillator units)
98
90th-pct |d_open|
p95 = 115 units
37.6%
Fast-cloud disagreement
SPY vs SPX at 09:30
92.6%
Of days, |d_eod| < |d_open|
convergence is universal

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,91238.511.54.545.0%35.7%
0.25 – 0.5%1,18537.917.44.635.4%39.5%
0.5 – 1%92442.525.75.331.3%40.4%
≥ 1%50648.033.46.225.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_PO85+0.07%+0.50%56.5%+0.56%
Gap-up · SPX_PO > SPY_PO151−0.05%+0.12%55.6%+0.06%
Gap-down · SPY_PO > SPX_PO186+0.14%+0.04%53.8%+0.18%
Gap-down · SPX_PO > SPY_PO84−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 SPY153−0.12%+0.25%52.3%+0.13%
Gap-up · SPY caught down to SPX83+0.19%+0.26%62.7%+0.45%
Gap-down · SPX caught down to SPY187+0.10%+0.05%56.2%+0.15%
Gap-down · SPY caught down to SPX83+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 bear231−0.04%+0.32%61.0%+0.28%
SPY bear · SPX bull189−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 candles_1d close) / prior candles_1d close · 100. 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.

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.