Sector Rotation and Market Breadth
Sector Rotation and Market Breadth
Sector rotation is the strategy of shifting investment between economic sectors as market conditions change. Different sectors perform best at different stages of the economic cycle: defensive sectors (utilities, healthcare) outperform during recessions; cyclical sectors (technology, consumer discretionary) lead during expansions.
Market breadth measures the overall health of the market — not just whether major indexes are up, but whether most stocks are participating in the move. Narrow breadth (only a handful of large stocks rising) often precedes market corrections.
Sector Rotation Theory
The economic cycle typically moves through phases:
| Cycle Phase | Leading Sectors |
|---|---|
| Early expansion | Technology, Consumer Discretionary |
| Mid expansion | Industrials, Materials |
| Late expansion | Energy, Healthcare |
| Recession | Utilities, Consumer Staples |
By measuring which sectors are outperforming, you can identify which phase of the cycle we're in — and position accordingly.
Step 1 — Sector Performance Comparison
Computing returns by sector requires weighting individual stock returns appropriately. An equally-weighted sector average treats a $1B market cap company the same as a $1T one — simple but useful as a starting point.
WITH stock_returns AS (
SELECT
sp.company_id,
sp.price_date,
(sp.close_price - LAG(sp.close_price) OVER (PARTITION BY sp.company_id ORDER BY sp.price_date))
/ NULLIF(LAG(sp.close_price) OVER (PARTITION BY sp.company_id ORDER BY sp.price_date), 0) AS daily_return
FROM stock_prices sp
WHERE sp.price_date >= CURRENT_DATE - INTERVAL '90 days'
),
sector_returns AS (
SELECT
c.sector,
DATE_TRUNC('month', sr.price_date) AS month,
COUNT(DISTINCT sr.company_id) AS stocks,
ROUND((AVG(sr.daily_return) * 21 * 100)::NUMERIC, 2) AS approx_monthly_return_pct,
ROUND((STDDEV(sr.daily_return) * SQRT(252) * 100)::NUMERIC, 2) AS annualized_vol_pct
FROM stock_returns sr
JOIN companies c ON c.company_id = sr.company_id
WHERE sr.daily_return IS NOT NULL
GROUP BY c.sector, DATE_TRUNC('month', sr.price_date)
)
SELECT *,
RANK() OVER (PARTITION BY month ORDER BY approx_monthly_return_pct DESC) AS sector_rank_this_month
FROM sector_returns
ORDER BY month, sector_rank_this_month;
What This Returns
Monthly sector performance rankings. sector_rank_this_month = 1 is the best-performing sector in that month. Tracking how the rank changes month-to-month reveals rotation — Technology leading for months, then Energy taking over, for example. annualized_vol_pct shows which sectors are riskiest.