A self-directed attempt to understand how allocators think about risk - built from scratch in Excel, VBA, and Python to explore four portfolio construction methodologies and the mechanics behind them.
Overview
The model dynamically adapts to any portfolio of up to 30 public equities and computes optimal weights under four distinct allocation philosophies - from naïve equal-weighting to fully optimised risk parity.
Equal capital to every security. The simplest benchmark - no assumptions about risk or correlation.
Weights inversely proportional to each asset's GARCH-estimated conditional volatility. Lower-risk names receive higher allocation.
True risk parity. Every asset contributes equally to total portfolio variance - requires full covariance matrix and Python optimisation.
Minimises total portfolio variance subject to weight constraints. Naturally concentrates in the lowest-vol, least-correlated names.
Workflow
Step 01 - Security Entry
Enter Bloomberg tickers for up to 30 equities. The sheet uses BDP and BDH functions to auto-populate security names, live market capitalisations, and YTD performance in real time.
Step 02 - Price Data
Bloomberg fetches daily closing prices for each security going back 750 trading days. Daily returns are calculated alongside - these are the raw inputs that feed every downstream calculation.
Step 03 - GARCH Volatility
A GJR-GARCH(1,1) model estimates conditional volatility for each security using 3 years of daily returns. Parameters α, β, and γ are optimised per asset by maximising the log-likelihood function via the Excel Solver.
Step 04 - Matrix Construction
Three matrices are computed: the correlation matrix, the variance-covariance matrix (Ω), and the Ω × Weights matrix. All are dynamically linked and update whenever the allocation method or underlying weights change.
Step 05 - Portfolio Output
Select the desired method from the DistMeth dropdown. EW and IV weights recalculate instantly. ERC and MinVol trigger standalone Python solvers - bundled as compiled executables, so no Python install is needed on the recipient machine.
The Math
Two ideas sit at the core of the model: a time-varying estimate of each asset's risk, and an allocation rule that does something sensible with it.
Historical vol treats every past return equally. GARCH assumes volatility is persistent - a big shock today makes tomorrow riskier - and lets recent observations matter more. The GJR variant adds an asymmetric term to capture the leverage effect: bad news (negative returns) tends to spike vol more than equivalent good news.
where $\mathbf{1}_{t-1} = 1$ if $\varepsilon_{t-1} < 0$ (leverage indicator)
$\alpha + \beta + \gamma/2 < 1$ is the stationarity condition. Parameters are calibrated per asset by maximising the log-likelihood over 3 years of daily returns using the Excel Solver.
Portfolio volatility is not just a weighted average of individual vols - it depends on how assets co-move. Given a weight vector $\mathbf{w}$ and variance-covariance matrix $\boldsymbol{\Omega}$, portfolio volatility is:
The risk contribution $\text{RC}_i$ of asset $i$ is its marginal contribution to $\omega_p$, scaled by its weight:
By Euler's theorem, $\sum_i \text{RC}_i = \omega_p$. The three allocation methods each define a different target for these contributions:
Download
Includes the Excel workbook, both solver executables, and a README. Requires Microsoft Excel with macros enabled and Bloomberg Terminal access for live data.
~160 MB · assembled in your browser on click