Posts  / XLB  / #POST-217922
REDDIT

Sector rotation analysis: 27 years of S&P 500 sector ETF rankings. 25% of transitions are major reversals.

E
Jan 27, 2026 · 01:28

Built a data platform as a side project. Used it to analyse sector rotation patterns across 27 years of S&P 500 sector ETF data.

---

**Methodology**

- **Data:** S&P 500 Sector SPDR ETFs (XLK, XLE, XLF, XLV, XLY, XLP, XLI, XLB, XLU, XLRE, XLC)
- **Period:** 1999-2026 (XLRE from 2015, XLC from 2018)
- **Approach:** Rank all sectors by annual return, track year-over-year transitions, count reversals

**Why ETFs, not stock averages:** Market-cap weighted. A $3T company has more influence than a $100M micro-cap. I initially used FMP's sector_performance data (simple averages) but got garbage. +73% single-day sector returns from micro-cap noise. ETFs reflect actual investable returns.

---

**Results**

| Metric | Count |
|:-------|:------|
| Total year-over-year transitions | 246 |
| Bottom-3 → Top-3 next year | 27 (11%) |
| Top-3 → Bottom-3 next year | 34 (14%) |
| Major reversals (either direction) | 61 (25%) |

No sector stayed #1 for more than 2 consecutive years.

---

**Recent rankings (2024-2026)**

| Year | #1 | #2 | #3 | Worst |
|:-----|:---|:---|:---|:------|
| 2024 | Comm Services (+36%) | Financials | Consumer Disc | Materials (0%) |
| 2025 | Technology (+25%) | Comm Services | Industrials | Utilities (-43%) |
| 2026 YTD | Materials (+7%) | Energy | Consumer Staples | Financials (-2%) |

Materials: worst in 2024, leading 2026 YTD. The pattern continues.

---

**Notable reversals**

| Sector | Year 1 | Rank | Year 2 | Rank |
|:-------|:-------|:-----|:-------|:-----|
| Energy | 2020 | #11 (-33%) | 2021 | #1 (+53%) |
| Energy | 2021 | #1 (+53%) | 2022 | #1 (+59%) |
| Technology | 1999 | #1 (+63%) | 2000 | #9 (-44%) |
| Financials | 2008 | #9 (-54%) | 2012 | #1 (+25%) |

---

**SQL**

Annual sector ETF returns with rankings:

```sql
WITH etf_prices AS (
SELECT
symbol,
EXTRACT(YEAR FROM CAST(date AS DATE)) as year,
FIRST(adjClose ORDER BY date) as first_close,
LAST(adjClose ORDER BY date) as last_close
FROM fmp.stock_eod
WHERE symbol IN ('XLK', 'XLE', 'XLF', 'XLV', 'XLY', 'XLP', 'XLI', 'XLB', 'XLU', 'XLRE', 'XLC')
GROUP BY symbol, year
)
SELECT
year,
symbol,
ROUND((last_close - first_close) / first_close * 100, 1) as return_pct,
ROW_NUMBER() OVER (PARTITION BY year ORDER BY (last_close - first_close) / first_close DESC) as rank
FROM etf_prices
ORDER BY year, rank;
```

Count reversal events:

```sql
WITH rankings AS (
SELECT
symbol,
year,
ROW_NUMBER() OVER (PARTITION BY year ORDER BY (last_close - first_close) / first_close DESC) as rank,
COUNT(*) OVER (PARTITION BY year) as total_sectors
FROM (
SELECT
symbol,
EXTRACT(YEAR FROM CAST(date AS DATE)) as year,
FIRST(adjClose ORDER BY date) as first_close,
LAST(adjClose ORDER BY date) as last_close
FROM fmp.stock_eod
WHERE symbol IN ('XLK', 'XLE', 'XLF', 'XLV', 'XLY', 'XLP', 'XLI', 'XLB', 'XLU', 'XLRE', 'XLC')
GROUP BY symbol, year
)
),
lagged AS (
SELECT
a.year,
a.symbol,
a.rank as this_year_rank,
b.rank as last_year_rank,
a.total_sectors
FROM rankings a
LEFT JOIN rankings b ON a.symbol = b.symbol AND a.year = b.year + 1
WHERE b.rank IS NOT NULL
)
SELECT
COUNT(*) as total_transitions,
SUM(CASE WHEN last_year_rank >= total_sectors - 2 AND this_year_rank <= 3 THEN 1 ELSE 0 END) as bottom3_to_top3,
SUM(CASE WHEN last_year_rank <= 3 AND this_year_rank >= total_sectors - 2 THEN 1 ELSE 0 END) as top3_to_bottom3
FROM lagged;
```

---

**What I haven't tested**

- Monthly/quarterly rotation signals
- Transaction cost impact
- Risk-adjusted returns
- Optimal holding period after reversal

Anyone here trading sector rotation? What signals work for timing entry?

---

*Data: FMP. DuckDB on Parquet.*