# Sector Rotation Watchlist Dashboard — Concept Specification

**Idea source:** `c9223b91` / Captured 2026-06-17 by Andrew  
**Target project:** TradeDingo (trading-terminal-vs)  
**Status:** Pre-implementation spec — awaiting Andrew sign-off on Open Questions

---

## Goal

Build a Sector Rotation Dashboard inside TradeDingo that displays all 15 curated tech-sector watchlists on a single screen, ranks sectors by relative strength vs. SPY in real time, and fires an early-rotation alert when a sector's momentum ranking reverses — so Andrew can position in leaders before the crowd.

## Why It Matters

The "million in 2 months" thesis is simple: sector rotation happens fast, but the signal is visible *before* price explodes if you watch multiple sectors simultaneously. Most traders only watch one or two names; the edge is seeing which sector bucket is getting money flows first. Right now Andrew has no tooling for this — the TradeDingo pre-market briefing runs on a single flat watchlist that is currently *empty* (known blocker, nightly-sweep 2026-05-29). This dashboard solves that blocker structurally by organising tickers into sector buckets that:

1. Populate the pre-market briefing engine automatically (fixes the empty-watchlist blocker)
2. Add a sector-level layer above individual tickers — letting Andrew see the forest, not just trees
3. Surface rotation signals 1–3 days before they are obvious to general traders

---

## Target Users

**Primary:** Andrew (sole user, personal trading terminal — no multi-user requirement at v1)  
**Secondary:** Possible future TradeDingo subscribers, but v1 is 100% Andrew-only

**Primary use cases:**
1. Morning pre-market session (08:30–09:30 BST) — scan all sectors for overnight momentum, pick 2–3 hot sectors to focus on for the day's ORB trades
2. Intraday sector monitoring — check rotation mid-session to find second-wave moves
3. Watchlist management — add/remove/toggle individual tickers within sectors without touching the raw YAML

---

## Success Criteria

- [ ] All 15 sectors and their pre-loaded tickers render on the dashboard without manual data entry
- [ ] Each sector shows its 1D, 5D, and 1M aggregate performance (average of tickers), colour-coded green/red
- [ ] Sectors are sortable by: 1D performance, 5D performance, Rotation Score (RS delta vs. SPY)
- [ ] A "Hot Rotation" badge appears on any sector whose RS rank improved ≥3 positions in the last session
- [ ] Clicking a sector expands a ticker table showing: last price, 1D change %, premarket gap %, RVOL, and gap classification (from §1.1)
- [ ] Andrew can toggle individual tickers active/inactive; inactive tickers are excluded from briefing
- [ ] The pre-market briefing engine reads its watchlist from this dashboard's active-ticker set — the YAML file `brain/backlog/tradedingo/watchlist.md` is replaced by the dashboard as the source of truth
- [ ] All 15 sector watchlists are pre-seeded on first launch; Andrew does not have to type a single ticker
- [ ] No trade is executed automatically — dashboard is read-only intelligence only
- [ ] Dashboard loads within 3 seconds on Andrew's machine (acceptable given Flask + SQLite stack)
- [ ] Mobile-responsive enough to read on phone (sector cards stacked, no horizontal scroll required)

---

## Key User Flows

### Flow 1 — Morning Sector Scan (daily, ~5 min)

1. Andrew opens TradeDingo at ~08:30 BST
2. Dashboard auto-loads, fetches overnight data for all 15 sectors' tickers from the configured data provider
3. Sector cards render sorted by 1D performance (best → worst) by default
4. Andrew spots **Space** and **Semis** in the top 3 with green badges — hot overnight
5. He clicks **Space** → sector expands to show $ASTS $RKLB $RDW $LUNR with premarket gaps and RVOL
6. He sees $ASTS +5.2% gap, RVOL 3.4× → eligible for Gap-and-Go setup (§1.1 classification auto-applied)
7. He notes $RKLB as a secondary watch
8. Dashboard pre-market briefing fires at 08:30 BST automatically via cron — covers all active tickers across all sectors
9. Andrew checks Telegram, reads the briefing, marks $ASTS and $RKLB as "Watch today" via a one-click star

### Flow 2 — Rotation Alert Detection (intraday)

1. At 10:15 AM ET (post-ORB window), the rotation engine re-scores all sectors against SPY's move
2. **Defense** sector has moved from rank 12 → rank 3 in the past 30 min (RS gaining)
3. Dashboard highlights the sector card with a pulsing amber "Rotation Detected" badge
4. Andrew receives a Telegram alert: `ROTATION ALERT: Defense (+$PLTR +$KTOS) climbing vs SPY — RS rank 12→3 in 30min`
5. Andrew opens dashboard, clicks Defense sector, reviews tickers
6. Takes manual position decision (no auto-entry)

### Flow 3 — Watchlist Management (occasional)

1. Andrew wants to add $UBER to Autonomous sector
2. He clicks the **+** icon on the Autonomous sector card
3. A modal appears with a search field — he types "UBER", selects from live results
4. Ticker is added to the sector, active by default
5. Change is persisted to the database; next pre-market briefing includes $UBER
6. Andrew can also toggle any ticker off (greyed out) without deleting it

### Flow 4 — Historical Rotation Review (weekly)

1. Andrew clicks "History" on a sector card to see a 30-day RS rank chart for that sector vs. SPY
2. Chart shows inflection points where sector gained/lost rank
3. Andrew can visually back-test: "this is where I should have rotated in"

---

## Technical Approach

### Stack (existing TradeDingo)
- **Backend:** Flask (Python), SQLite via SQLAlchemy
- **Frontend:** Jinja2 templates + vanilla JavaScript (or lightweight Alpine.js)
- **Charts:** Chart.js (already available in TradeDingo)
- **Market data:** Polygon.io free tier (pending Andrew confirmation — see Open Questions)
- **Alerting:** Telegram Bot API (already wired via brain/backlog notification stack)

### New Components

#### 1. Sector Watchlist Data Model

```python
# New SQLite tables

class Sector(Model):
    id: int (PK)
    slug: str          # e.g. "space", "semis"
    display_name: str  # e.g. "Space"
    sort_order: int    # display order
    icon_emoji: str    # optional: 🚀 for space etc.
    created_at: datetime

class SectorTicker(Model):
    id: int (PK)
    sector_id: FK → Sector
    ticker: str        # e.g. "ASTS"
    active: bool       # true = included in briefing
    added_by: str      # "seed" | "andrew"
    created_at: datetime

class SectorSnapshot(Model):
    id: int (PK)
    sector_id: FK → Sector
    snapshot_date: date
    avg_1d_pct: float
    avg_5d_pct: float
    avg_1m_pct: float
    rs_rank: int       # rank vs SPY at snapshot time
    created_at: datetime
```

#### 2. Seed Data (15 sectors, pre-loaded)

```python
SECTOR_SEED = {
    "memory":         ["MU", "SNDK", "WDC", "STX"],
    "semis":          ["NVDA", "AMD", "ARM", "INTC"],
    "networking":     ["AVGO", "MRVL", "CRDO"],
    "photonics":      ["AAOI", "LITE", "COHR", "NVTS", "GLW"],
    "infrastructure": ["DELL", "SMCI"],
    "data-centers":   ["IREN", "CIFR", "APLD", "NBIS"],
    "software":       ["MSFT", "NOW", "SNOW"],
    "defense":        ["PLTR", "KTOS", "AVAV"],
    "drones":         ["ONDS", "DPRO", "UMAC"],
    "robotics":       ["OUST", "SYM", "TSLA"],
    "space":          ["ASTS", "RKLB", "RDW", "LUNR"],
    "quantum":        ["IONQ", "QBTS", "RGTI"],
    "nuclear":        ["OKLO"],
    "fintech":        ["HOOD", "SOFI", "AFRM"],
    "copper":         ["FCX", "SCCO", "TECK"],
    "autonomous":     ["JOBY", "ACHR"],
}
```

#### 3. Rotation Score Algorithm

```
RS_score(sector, date) = avg(ticker_1d_change) - SPY_1d_change
RS_rank(date) = rank sectors by RS_score, 1 = best

Rotation_Alert triggers when:
  RS_rank(today) ≤ 5  AND  RS_rank(yesterday) ≥ 9
  (sector jumped into top-5 from bottom-half)
```

#### 4. Data Fetch Strategy
- Pre-market snapshot: single batch call at 08:15 BST for all active tickers
- Intraday re-score: every 30 min during US market hours (14:30–21:00 UTC)
- Store snapshots in `SectorSnapshot` table (30-day rolling retention)
- Source: Polygon.io `/v2/aggs/ticker/{ticker}/prev` + `/v2/last/trade/{ticker}` (free tier)

#### 5. Pre-Market Briefing Integration
- Existing cron reads tickers from `SectorTicker WHERE active = true`
- This replaces the current empty `watchlist.md` YAML (that YAML becomes a read-only export/backup)
- No changes to the briefing format — only the ticker source changes

### Routes (new Flask endpoints)

```
GET  /sectors                         → sector dashboard page
GET  /api/sectors                     → JSON list of sectors + aggregate stats
GET  /api/sectors/<slug>/tickers      → tickers for one sector with latest price data
POST /api/sectors/<slug>/tickers      → add ticker to sector
PUT  /api/sectors/<slug>/tickers/<t>  → toggle active/inactive
DELETE /api/sectors/<slug>/tickers/<t>→ remove ticker
GET  /api/sectors/rotation-history    → 30-day RS rank history for charts
```

---

## File Targets

### Create (new files)
| File | Purpose |
|---|---|
| `app/models/sector.py` | Sector + SectorTicker + SectorSnapshot SQLAlchemy models |
| `app/routes/sectors.py` | Flask blueprint for all /sectors/* routes |
| `app/services/sector_data.py` | Polygon.io fetch + RS scoring + rotation detection |
| `app/templates/sectors/dashboard.html` | Main sector dashboard Jinja template |
| `app/templates/sectors/_sector_card.html` | Partial: single sector card with ticker table |
| `app/static/js/sectors.js` | Alpine.js / vanilla JS for expand/collapse, sort, search |
| `app/static/css/sectors.css` | Sector dashboard styles |
| `app/seeds/sector_seed.py` | One-shot seed script for 15 sectors + tickers |
| `tests/test_sector_rotation.py` | Unit tests for RS scoring algorithm |

### Modify (existing files)
| File | Change |
|---|---|
| `app/models/__init__.py` | Import Sector, SectorTicker, SectorSnapshot |
| `app/routes/__init__.py` | Register sectors blueprint |
| `app/services/premarket_briefing.py` | Change ticker source from YAML to SectorTicker DB query |
| `brain/backlog/tradedingo/watchlist.md` | Mark as deprecated; point to DB source |
| `app/templates/base.html` | Add "Sectors" nav link |

---

## Open Questions for Andrew

1. **Data provider:** Polygon.io free tier allows 5 API calls/minute. With 60+ active tickers across 15 sectors, a full refresh takes ~12 minutes at free tier — too slow for intraday. Do you want to pay for Polygon Starter (~$29/mo) or use a different provider (Alpaca, Yahoo Finance, CBOE)? **This is the single biggest unresolved dependency.**

2. **Intraday refresh frequency:** How often should the sector dashboard refresh during market hours — every 5 min, 15 min, 30 min? More frequent = more API cost and more useful. Less frequent = cheaper and still useful for sector-level work.

3. **Rotation alert threshold:** The spec uses "rank improved ≥5 positions" to trigger a Telegram alert. Is that too sensitive / not sensitive enough? Would you rather: (a) top-5 entry from outside top-5, (b) any 3-rank move, (c) only absolute RS crossing SPY by >1%?

4. **Mobile priority:** Is the sector dashboard likely to be used on your phone (e.g. checking mid-session from your phone)? If so, mobile layout needs to be treated as primary — different design choices.

5. **History depth:** 30-day rolling RS history is specified. Do you need longer (90 days, 1 year) for pattern recognition, or is 30 days sufficient?

6. **Sector addition:** The 15 sectors are fixed seed. Should Andrew be able to create entirely new sectors from the UI, or is the list locked and only tickers within sectors are manageable?

7. **SPY as benchmark:** The rotation score uses SPY as the benchmark. Should QQQ be an alternative benchmark (given the tech-heavy sector list), or use both?

8. **Watchlist.md deprecation:** The pre-market briefing currently sources tickers from `brain/backlog/tradedingo/watchlist.md` (currently empty). When the DB is live, this YAML should be deprecated. Do you want a one-way sync (DB → YAML export) so the YAML always reflects the DB, or fully replace the YAML as source of truth?
