SCINTILLA // SECTOR ROTATION

loading…
Group Range Sectors

Leaders

Fund Size (AUM) 4 views — pick whichever reads best, it's just a display choice

Sector Heatmap tile size ≈ fund AUM · color = move over selected timeframe · click = toggle

Relative Performance % drag time axis = zoom · rebases % to left edge like TV

glow width = fund AUM, thickest at "now" (right) — drawn on the graph itself, behind the price lines, same idea as the RRG tail cones

Quadrant Positions

Rotation Map (RRG) vs SPY · weekly · 14w RS window

Tail Length5wrecommended: 5w — StockCharts' own default; no universal standard exists Sectors

Regime Read rule-based off the RRG data above — deterministic, not AI-generated

ETF Detail sub-industry & holdings are static reference (illustrative, not live) · technicals are live, computed off the daily closes above

SPECS & DATA SOURCES
Universe: 11 SPDR sector ETFs (XLK XLC XLY XLF XLI XLB XLE XLV XLP XLU XLRE) vs benchmark SPY.
Source-of-Truth Order (Move 1, Alan, 2026-07-24): Supabase wadinxqplrggagkvrdag is the source of truth. Yahoo is now a flagged fallback only, used exactly where a Supabase table is still empty/unwired. FMP is never used. Never render stale data as live — every DB-sourced number below shows its own updated_ts freshness inline, and missing data shows "missing", never a placeholder value.
① Supabase (project scintilla-live, same database that feeds scintillahub.ai itself, read via anon/RLS-gated key, eight independent reads, each polled every 5 min, each failure isolated to its own tab/section):
  • composite_staged (tf='D') — real Geiger composite/trend/momentum/conviction. Geiger tab.
  • derived_series (tf='1D') — RSI, Williams %R, MACD, CCI, ROC, Stochastic, EMA5/8/13/21/34, SMA50/100/150/200. Feeds Technicals scorecard, Sector Sentiment, Extension Watch, Actionable Read — REPLACES all in-browser EMA/RSI math when a ticker has a row here.
  • live_quotes — price, chg_pct, volume, prev_close. Feeds the ETF Detail Actionable Read quote line.
  • regime_state — per-ticker/per-term (SHORT/MID/LONG) bull/bear + score. Feeds the "DB Regime State" block in Regime Read, kept SEPARATE from this page's own RISK-ON/OFF badge (different signal shape — see Chain 2b).
  • ohlcv_history (tf='D' daily / 'W' weekly / '5' 5-min / '1' 1-min; timestamp = epoch seconds) — OHLCV price bars. PRIMARY price spine (wired 2026-07-30): feeds Chain 1 Relative-Performance % lines, Chain 2a RRG (weekly), heatmap %, Leaders. Replaces the Yahoo relay, which is now flagged-fallback only.
  • company_profile (avg_volume) — 30-day average volume, populated for all 12. Feeds RVOL = live_quotes.volume ÷ company_profile.avg_volume in the Actionable Read (wired 2026-07-30).
  • sector_rankings — this tool's PUBLISHED OUTPUT (C7, 2026-08-05): rank/score per sector, written hourly server-side by refresh_sector_rankings() (pg_cron) from composite_staged tf='D', universe-checked against ticker_cohorts. This page reads it back and shows it in the strip under the header — the allocation module consumes the same rows. Deploys for this page now go through the Vercel API directly (serialized), not the shared _queue file.
  • ticker_cohorts (cohort=MACRO) — THE universe table: boot-time check that all 11 sectors + SPY are present; any missing name is flagged in the badge strip, never silently assumed.
② Yahoo Finance (via same-origin relay /api/yahoo; fallbacks: hub relay, direct Yahoo, public proxies) — close-only daily (6mo) and intraday (1m/5m) series. FLAGGED FALLBACK ONLY as of 2026-07-30: the price spine (daily/weekly/1m/5m bars for RRG, Relative Performance % lines, heatmap %, Leaders) now reads Supabase ohlcv_history (①) as PRIMARY and only falls back to Yahoo if that DB read throws. Also still the flagged client-side fallback in Technicals/Sentiment when derived_series doesn't cover a ticker yet.
③ Static hand-compiled reference (hardcoded in this file) — fund AUM $B approximations (AUM map), GICS sub-industry lists and illustrative top-holdings/weights (ETF_DETAIL, TOP_HOLD_PCT). BLOCKED from moving to Supabase etf_holdings/etf_info right now — both tables confirmed EMPTY (0 rows) for our tickers as of 2026-07-24. Flagged everywhere it's used.
RVOL (WIRED 2026-07-30): ordered formula was live_quotes.volume ÷ etf_info.avg_volume, but etf_info is empty; company_profile.avg_volume IS populated for all 12, so RVOL now = live_quotes.volume ÷ company_profile.avg_volume, shown live in the Actionable Read. Falls back to explicit "missing" (never a fake number) if either input is absent.
sector_performance table exists (18k+ rows) but is explicitly NOT used per Alan's order — treated as off-limits regardless of row count.
Timeframe buttons: 1D/5D use intraday series (source ②); 1M/3M/6M use daily (source ②). Leaders list, heatmap colors, and the % lines all follow the selected timeframe. RRG always computes on daily→weekly-resampled data from source ②, independent of the timeframe buttons.
FORMULAS & COMPUTATIONS — what feeds what
This section is ordered as dependency chains: raw input → each derived step → what it's displayed as. Source tags: ① Supabase, ② Yahoo, ③ static.

Chain 1 — Relative Performance %: ohlcv_history daily/intraday closes (①, Yahoo flagged-fallback) → (close_i / close_base − 1) × 100, base = first visible bar, rebases live as you pan/zoom → drawn as the % lines. Normal/Glow toggle: in Glow mode only, the same closes are re-walked to build a taper-width polygon per sector (width ∝ √AUM from source ③, thin at the left edge → full width at "now") drawn on a canvas behind the crisp lines — same underlying % values either way. Migrated 2026-07-30: closes now come from Supabase ohlcv_history (①, tf='D'/'5'/'1') as PRIMARY; Yahoo flagged-fallback only. Same math.
Chain 2a — RRG (rotation math): ohlcv_history weekly closes (①, tf='W'; ~20y available, 2y window used, Yahoo flagged-fallback) → RS = 100 × sector/SPY → smoothed EMA(4) → RS-Ratio = 100 + z-score of smoothed RS over a rolling 14-week window, RS-Momentum = 100 + z-score of the 4-week change in RS-Ratio over the same window, both finished with EMA(2) → Quadrant (Leading/Weakening/Lagging/Improving off the ≥100/<100 split on both axes) → drawn as the RRG scatter + tails (head size ∝ √AUM from source ③). Migrated 2026-07-30: weekly bars confirmed (tf='W', 1044 bars/ticker ≈ 20y) and now sourced from ohlcv_history (①); Yahoo flagged-fallback only.
  Quadrant series, walked backward → Weeks In + "just crossed" flag (weeks==1) → Regime Read "just crossed" list, labeled lagging/confirmatory (can only fire after a rotation change already happened).
  RS-Ratio, grouped Cyclicals vs Defensives → averaged per group → spread → RISK-ON/RISK-OFF/MIXED badge (±0.6 threshold) + breadth counts → "What this means" rules block. This badge is THIS PAGE'S OWN basket-wide calc — see Chain 2b for the separate DB regime signal.
Chain 2b — DB Regime State (①, NOT computed on this page): Supabase regime_state row per ticker × term (SHORT/MID/LONG) → state (bull/bear) + score (−1..+1) shown as-is in a dedicated "DB Regime State" row under Regime Read. Deliberately kept separate from the RISK-ON/OFF badge above: that badge is one basket-wide call from this page's own RRG breadth math (Chain 2a); this is a different engine's per-ticker/per-term call — showing both, not merging them, so nothing is silently overwritten or reconciled without a visible rule.
Chain 2c — Extension Watch (forward-looking overbought/oversold): RSI(14) + Williams %R (both ①, from derived_series when available) + stretchPct = (price/trend-EMA − 1)×100 (Chain 3) → overbought if RSI≥68 OR stretch≥+8% OR Williams %R > −20 (its own overbought zone) → oversold if RSI≤32 OR stretch≤−8% OR Williams %R < −80 (its own oversold zone) → Extension Watch chips + rules text. This is the FORWARD-LOOKING counterpart to the lagging "just crossed" list (Chain 2a) — can fire before any quadrant cross confirms anything, and now has a real Williams %R vote instead of only RSI+stretch.
Chain 3 — Technicals scorecard / Sector Sentiment / ETF Detail Actionable Read: derived_series (①, tf='1D') → RSI, Williams %R, MACD (raw + signed), CCI, ROC, Stochastic, EMA21, SMA50, latest price backstopped by live_quotes (①) → up to 9 pass/fail conditions (price vs EMA21, price vs SMA50, EMA21 vs SMA50, RSI bands ×4, Williams %R overbought/oversold, MACD signed) → score = passes minus fails → STRONG BUY…STRONG SELL badge. Flagged Yahoo fallback: if a ticker has no derived_series row yet, falls back to client-computed EMA20/EMA50/RSI(14) from Yahoo closes (②), no Williams %R/MACD in that mode — the Technicals tab visibly states which mode is active. Same RSI + trend-EMA → Sector Sentiment = 50 + 0.8×(RSI−50) + 3×((price/trend-EMA−1)×100), clamped 0–100 (our own composite, not CNN's Fear & Greed). Actionable Read (top of ETF Detail) = Technicals score (this chain) + Geiger composite (Chain 4, ①) + Extension state (Chain 2c) + live quote/chg_pct (①, live_quotes) + RVOL (live: live_quotes.volume ÷ company_profile.avg_volume), reconciled: if Extension flags stretched, that overrides an agreeing Technicals/Geiger read → ALIGNED BULLISH/BEARISH, STRETCHED, or MIXED SIGNALS.
Chain 4 — Geiger (①, NOT computed on this page): Supabase composite_staged row (tf='D') → composite = 0.5×trend + 0.5×momentum (momentum = RSI 60% / Williams %R 40%, the "equalizer" mix set on scintillahub.ai itself; structure & volatility archived at 0 in the live system) → STRONG BULLISH…STRONG BEARISH verdict off the composite value. If the fetch fails, falls back to a local approximation using an older, superseded 0.30/0.25/0.20 weighting — clearly flagged, not the real number.
Chain 5 — AUM sizing (③, static): AUM $B map → RRG head/tail radius (√AUM), heatmap tile size (squarified treemap), Quadrant table's AUM column, Relative Performance Glow-mode taper width, and the standalone Fund Size panel's 4 display views — no different data underneath, purely a display choice.
Chain 6 — Sub-Industry / Top Holdings (③, static, BLOCKED from moving to ①): ETF_DETAIL hand-compiled list + TOP_HOLD_PCT illustrative decay curve → shown as-is, not derived from anything else, not live. Supabase etf_holdings/etf_info are the ordered replacement but both are confirmed EMPTY (0 rows) for our 12 tickers — logged as an outstanding blocker, not silently worked around.
Outstanding / logged (see Research_Logs/SECTOR_ROTATION_LOG.json, "move1_sprint_board"): (1) Chain 1 + Chain 2a price-bar migration to ohlcv_history — DONE 2026-07-30 (daily tf='D', weekly tf='W', 5m tf='5', 1m tf='1'; Yahoo flagged-fallback only). (2) Chain 6 holdings/fundamentals migration — STILL BLOCKED on empty etf_holdings/etf_info. (3) RVOL — DONE 2026-07-30 via company_profile.avg_volume. (4) Fundamentals tab (grade/P-E/yield/growth) still needs FMP, not built, explicit "pending FMP" placeholders (never wired per Alan's FMP-never rule for THIS table's data — Fundamentals tab is the one exception awaiting a future explicit source decision from Alan, not a live claim).
CODE & DEPLOY
Canonical source: /Users/alanharvey/SCINTILLA 0.5/scintilla-sector/index.html — its own standalone Vercel project (scintilla-sector, project id prj_5GJnTOR1uzYH4S7ZMZ5Iuy71y3n7). NOT part of _VERCEL_DEPLOY (the hub) anymore.
Live URL: https://sectorrotation.scintillahub.ai — real subdomain, bound directly to this project (no proxy/rewrite in the path). scintillahub.ai/sectorrotation still works as a redirect for old links.
Relay: scintilla-sector/api/yahoo.js (its own copy — do not point this page at the hub's relay).
Deploy: write /Users/alanharvey/SCINTILLA 0.5/scintilla-sector + a new stamp line into _queue/deploy_request.txt → launchd watcher ai.scintilla.autodeploy runs vercel deploy --prod from that dir only. Log: _queue/deploy_log.txt.
INSTRUCTIONS FOR OTHER CLAUDE SESSIONS
1. This page is fully isolated from the hub. It lives in its own folder (scintilla-sector/) and its own Vercel project, deployed independently. Deploying the hub (_VERCEL_DEPLOY/) CANNOT affect this page, and deploying this page CANNOT affect the hub. Do not merge them back together.
2. To change THIS page: edit /Users/alanharvey/SCINTILLA 0.5/scintilla-sector/index.html, then point _queue/deploy_request.txt line 1 at /Users/alanharvey/SCINTILLA 0.5/scintilla-sector (not _VERCEL_DEPLOY). Watcher deploys in ~15s.
3. To change the HUB (other pages), deploy _VERCEL_DEPLOY/ as before — it will never touch this page.
4. Need market data in a page? Use a same-origin relay like api/yahoo.js in your own project folder — don't reach across projects.
5. This page's owner-session log: Research_Logs/SECTOR_ROTATION_LOG.json.