Smart Beta: Factor-Tilted Portfolio Construction
Empirical study of how portfolio construction rules translate factor views into realized returns, risk, and factor exposures — with a 2017 scenario where value and momentum diverged.
Overview
Factor-based investing separates two design choices: which risk premia you want to harvest, and how you map those views into tradable weights. This project isolates the second question by holding the investable universe fixed and varying only the optimizer objective.
We study four long-only, fully invested sleeves that rebalance daily: a sector-balanced equal-weight benchmark, a minimum-variance portfolio, a value-tilt sleeve (HML), and a value–momentum blend. The primary view uses the latest 252 trading days of prices (refreshed each pipeline run via the shared yfinance cache). A fixed 2017 reference window is included when history allows, illustrating the classic value-versus-momentum divergence.
Data & Infrastructure
Daily factor returns follow the Fama–French five-factor model plus a Carhart-style momentum series from the Ken French Data Library. The investable universe is a sector-balanced subset of S&P 500 constituents (roughly six names per GICS sector, selected with a fixed random seed for reproducibility), matching the spirit of a stratified baseline portfolio rather than cap-weighting the index.
The pipeline is fully open: Python pulls public factor files, downloads stock prices through yfinance, estimates rolling factor loadings with multivariate regression, and solves constrained weight problems with SciPy (long-only, fully invested). Outputs are serialized to JSON for the interactive project page.
Methodology
Rolling estimation: For each rebalance date, we estimate asset-level factor betas using a 250-trading-day trailing window of daily returns regressed on the six-factor panel (market, size, value, profitability, investment, momentum). Covariance for minimum-variance weights uses the same window with light shrinkage toward a diagonal target for numerical stability.
Portfolio rules: (1) Equal weight — fixed 1/N allocation. (2) Minimum variance — minimize portfolio variance subject to long-only weights summing to one. (3) Value tilt — maximize portfolio HML loading. (4) Value–momentum blend — maximize a weighted combination of HML and momentum loadings.
Implementation detail: Optimized weights are applied with a one-day lag (weights set at close t−1 affect return on day t) to avoid look-ahead bias in the backtest.
2017 Scenario: Value vs Momentum
Academic and practitioner literature documents a tendency for value and momentum to move in opposite directions over medium horizons. In 2017, value-oriented portfolios faced headwinds while momentum factors posted positive cumulative returns — the opposite pattern from 2016 for many implementations.
A pure value tilt therefore risks concentrated drawdowns in such regimes. The blended sleeve is designed to keep intentional HML exposure while adding momentum beta so that style cyclicality is partially offset. Minimum-variance and equal-weight benchmarks provide context for how much of performance comes from factor views versus risk budgeting alone.
Performance Metrics
We report cumulative return over the study window, annualized volatility (daily standard deviation scaled by √252), and a simple return-to-volatility ratio. Factor exposure time series show realized portfolio loadings on HML and MOM through time.
A correlation heatmap of factor returns highlights structural relationships in the data — notably the often-negative correlation between value and momentum — which motivates multi-style construction rather than single-factor bets.
Results
Interactive charts below refresh when you run `npm run pipeline` or `python engine/run_smart_beta_strategy.py` (also `npm run data:smart-beta-strategy`). Data is written to `data/smart-beta-strategy/` and mirrored to `public/` during the build.
Extensions
Extensions include full S&P 500 coverage (not a sector sample), transaction-cost and turnover constraints, alternative value/momentum blend weights, and stock-level filters within each sleeve. The architecture separates data generation (Python) from presentation (React) so each layer can evolve independently.