Background Paths
Background Paths
Back to Projects

Macro Factor Model for Sector Rotation & SPY Timing

End-to-end macro factor model using rolling OLS, sector rotation, and SPY timing to test whether macro data can systematically improve equity allocation.

OOS: May 2020 – Sep 2025Sector L/S Sharpe 1.15 vs EW 0.62
Timeline: 2025 · Tools: Python, pandas, NumPy, Matplotlib · Data: FRED, Yahoo Finance
PythonpandasNumPyyfinanceFREDMatplotlibBacktesting

TL;DR

  • Built a rolling OLS macro factor model using 12 US macro indicators (growth, inflation, yield curve, credit, liquidity, USD).
  • Tested both SPY timing (risk-on vs cash) and sector long/short rotation with trend and volatility overlays.
  • Found that the model behaves like a defensive, value-tilted allocator: weak at shorting AI bubbles, but good at capital protection and sector rotation.
  • Produced a full research pipeline: data ingestion → factor engineering → rolling regressions → portfolio construction → performance evaluation.

What this project demonstrates

  • Design and implement an end-to-end systematic macro factor model in Python (pandas, NumPy, Matplotlib).
  • Use relative sector returns instead of absolute returns to reduce market-direction noise.
  • Combine macro factors with price-based trend filters and inverse-volatility weighting to stabilise performance.
  • Critically evaluate regime-dependent models (macro vs AI momentum) and iterate on model design.

Pipeline

FRED & Yahoo Finance
Monthly macro factors
Lag + standardise
Rolling 84-month OLS
SPY timing & Sector L/S
Backtest & evaluation

Key Visuals

Macro regime dashboard showing z-scores of US growth (IP YoY), inflation (CPI YoY), yield curve (10Y-2Y spread), and real policy rate from 2008 to 2026

Figure 1: Macro regime dashboard. Standardised growth and inflation (top panel) and financial conditions (bottom panel) over the sample period.

Rolling sector sensitivity heatmap to REAL_FF showing sectors XLY, XLV, XLU, XLRE, XLP, XLK, XLI, XLF, XLE, XLB from 2022 to 2025

Figure 2: Rolling sector betas to the real policy rate. Heatmap showing the time-varying sensitivity of US sectors to the chosen macro factor (here, the real Fed Funds rate). Warmer colours indicate higher positive sensitivity, colder colours indicate more negative sensitivity, illustrating that sector–macro relationships are not static over time.

Cumulative returns and drawdowns chart comparing Macro L/S Sectors V2, EW Sectors Benchmark, and SPY Buy & Hold from 2023 to 2025

Figure 3: Macro sector long/short strategy versus benchmarks. Top panel shows cumulative returns for the macro sector long/short strategy (V2), an equal-weight sector benchmark, and buy-and-hold SPY. Bottom panel shows corresponding drawdowns. The long/short strategy delivers stronger compounding with shallower drawdowns over the out-of-sample period.

Appendix: Math Foundations

1. Returns and Excess Returns

Let Pi,t be the month-end price of asset i at month t. The simple monthly return is:

ri,t = Pi,t / Pi,t-1 - 1

Let FFt denote the Effective Fed Funds rate at month t, expressed in annual percent. The monthly risk-free rate is approximated as:

rtrf = (1 + FFt/100)1/12 - 1

Excess returns (used throughout the model) are:

ri,tex = ri,t - rtrf

2. Macro Factor Construction

Let xt denote a generic monthly macro series (e.g. industrial production, CPI).

Year-on-year (YoY) growth:

xtYoY = xt / xt-12 - 1

Acceleration of growth/inflation (6-month change in YoY):

Δ6xtYoY = xtYoY - xt-6YoY

Unemployment gap (labour market slack):

UNRATE_GAPt = UNRATEt - UNRATĒt

where UNRATĒt is the 5-year rolling mean.

Real policy rate:

REAL_FFt = FEDFUNDSt - πt

Positive values indicate a restrictive real policy stance, negative values an accommodative stance.

3. Lag Structure and Regression Targets

To avoid look-ahead bias, the model uses macro data from month t-1 to forecast returns in month t. The lagged factor vector:

t = Xt-1

Prediction targets include SPY excess return and sector relative excess returns:

yj,t = rj,tex - rSPY,tex

4. Rolling OLS Model

Within each rolling 84-month window, the macro factors are standardised to have zero mean and unit variance. For a given window ending at month t-1:

Zτ = St-1(X̃τ - X̄t), τ ∈ Wt

For each asset i, a linear model is estimated:

yi,τ = αi + βiZτ + εi,τ

The OLS estimate:

θ̂i(t) = (XX)-1Xyi

5. SPY Timing Rule

The SPY timing strategy uses only the forecast for SPY's excess return. Define a binary exposure signal:

stSPY = 1 if r̂SPY,tex > 0, else 0

The realised portfolio excess return is:

rttiming = stSPY · rSPY,tex

6. Sector Long/Short Portfolio

Ranking by Predicted Relative Performance:

  • Rank sectors by ŷj,t from highest to lowest
  • Define the long set Lt as the top N sectors (e.g. N=3)
  • Define the short-candidate set St as the bottom N sectors

Trend Filter:

A sector is eligible for shorting only if:

Pj,t < MAj,t(10)

Inverse-Volatility Weights:

wj,t(A) = (1/σj,t) / Σk∈A(1/σk,t)

Long/Short Return:

rtLS = Σj∈Lt wj,tL rj,tex - Σj∈S't wj,tS rj,tex

7. Performance Statistics

Given a monthly return series {rt} (already expressed in excess-return terms):

Cumulative wealth:

WT = Πt=1T(1 + rt)

CAGR:

CAGR = WT12/T - 1

Annualised volatility:

AnnVol = σm · √12

Sharpe ratio:

Sharpe = (μm / σm) · √12

Maximum drawdown:

MaxDD = min1≤t≤T DDt