Below are three assets with different volatilities. Drag the sliders to change each asset's annual volatility, then press Solve to find the risk-parity weights — the allocation where every asset contributes exactly one third of the total portfolio risk.
<!-- Risk parity demo — equal risk contribution across uncorrelated assets -->
<p class="hint">Each asset has a different annual volatility. Risk parity finds weights $w_i \propto 1/\sigma_i$ so every asset contributes the <b>same fraction</b> of total portfolio risk. Adjust the sliders and press Solve.</p>
<div class="assets" id="assets">
<div class="asset-row">
<span class="asset-label">Asset A (stocks)</span>
<input type="range" class="vol-slider" id="vol0" min="5" max="40" value="20" step="1">
<span class="vol-val" id="vval0">20%</span>
</div>
<div class="asset-row">
<span class="asset-label">Asset B (bonds)</span>
<input type="range" class="vol-slider" id="vol1" min="5" max="40" value="10" step="1">
<span class="vol-val" id="vval1">10%</span>
</div>
<div class="asset-row">
<span class="asset-label">Asset C (cash)</span>
<input type="range" class="vol-slider" id="vol2" min="5" max="40" value="5" step="1">
<span class="vol-val" id="vval2">5%</span>
</div>
</div>
<div class="btns">
<button id="solve" type="button">Solve</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="result" id="result"></div>
<div class="bars" id="bars"></div>
/* Styles for the risk parity demo layout and bar chart */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.assets { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .8rem; }
.asset-row { display: flex; align-items: center; gap: .5rem; }
.asset-label { width: 90px; font-size: .85rem; font-weight: 600; color: #1d3557; }
.vol-slider { flex: 1; accent-color: #1d3557; }
.vol-val { width: 38px; font-size: .85rem; text-align: right; color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result { font-size: .9rem; margin-bottom: .7rem; min-height: 1.2em; }
.result.ok { color: #0a7d33; font-weight: 600; }
.bars { display: flex; flex-direction: column; gap: .4rem; }
.bar-row { display: flex; align-items: center; gap: .5rem; }
.bar-label { width: 90px; font-size: .8rem; color: #555; }
.bar-track { flex: 1; height: 18px; background: #e8eef3; border-radius: 4px; overflow: hidden; position: relative; }
.bar-fill { height: 100%; border-radius: 4px; transition: width .35s; }
.bar-fill.a { background: #e63946; }
.bar-fill.b { background: #457b9d; }
.bar-fill.c { background: #2a9d8f; }
.bar-pct { width: 42px; font-size: .8rem; text-align: right; color: #333; }
.legend { display: flex; gap: .7rem; margin-bottom: .4rem; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: .3rem; font-size: .78rem; color: #555; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Algorithm: for uncorrelated assets, risk parity weights are */
/* w_i proportional to 1/sigma_i. We normalize so weights sum to 1. */
/* This is exact for zero correlation; with correlations a convex solver is needed. */
const ASSETS = ['a', 'b', 'c'];
const COLORS = ['a', 'b', 'c'];
/* Read volatility sliders (convert percent to decimal) */
function getVols() {
return ASSETS.map((_, i) => parseFloat(document.getElementById('vol' + i).value) / 100);
}
/* Inverse-volatility weights = exact risk parity for uncorrelated assets */
function naiveWeights(vols) {
const inv = vols.map(v => 1 / v);
const total = inv.reduce((a, b) => a + b, 0);
return inv.map(x => x / total);
}
/* Portfolio volatility (uncorrelated assets = sqrt of sum of (w*sigma)^2) */
function portfolioVol(w, vols) {
/* uncorrelated: variance = sum of (w_i * sigma_i)^2 */
const variance = w.reduce((s, wi, i) => s + wi * wi * vols[i] * vols[i], 0);
return Math.sqrt(variance);
}
/* Total risk contribution of each asset = w_i * MRC_i / portfolioVol */
function riskContributions(w, vols) {
const pVol = portfolioVol(w, vols);
if (pVol < 1e-12) return w.map(() => 0);
/* uncorrelated MRC_i = w_i * sigma_i^2 / portfolioVol */
return w.map((wi, i) => wi * wi * vols[i] * vols[i] / pVol);
}
/* Compute risk parity weights and render result */
function solve() {
const vols = getVols();
const w = naiveWeights(vols);
const pv = portfolioVol(w, vols);
const trc = riskContributions(w, vols);
const fracs = trc.map(t => t / pv);
render(w, fracs, pv, true);
}
function renderBars(w, fracs, isSolution) {
const bars = document.getElementById('bars');
bars.innerHTML = '';
/* Legend items for bar chart */
const legends = [
{ cls: 'a', label: 'Asset A (stocks)' },
{ cls: 'b', label: 'Asset B (bonds)' },
{ cls: 'c', label: 'Asset C (cash)' }
];
/* Two bar sections — capital weights and risk fractions */
const sections = [
{ title: 'Capital allocation (weights)', values: w },
{ title: 'Risk contribution (fraction of total risk)', values: fracs }
];
for (const sec of sections) {
const header = document.createElement('div');
header.style.cssText = 'font-size:.78rem;color:#888;margin:.3rem 0 .15rem;font-weight:600;';
header.textContent = sec.title;
bars.appendChild(header);
sec.values.forEach((val, i) => {
const row = document.createElement('div');
row.className = 'bar-row';
const pct = Math.round(val * 100);
row.innerHTML = `<span class="bar-label">${legends[i].label}</span>` +
`<div class="bar-track"><div class="bar-fill ${COLORS[i]}" style="width:${pct}%"></div></div>` +
`<span class="bar-pct">${pct}%</span>`;
bars.appendChild(row);
});
}
}
function render(w, fracs, pVol, isSolution) {
const res = document.getElementById('result');
if (isSolution) {
const pvPct = (pVol * 100).toFixed(1);
res.textContent = i18nFmt('Risk parity weights found. Portfolio volatility: %pv%% per year.', { pv: pvPct });
res.className = 'result ok';
} else {
res.textContent = '';
res.className = 'result';
}
renderBars(w, fracs, isSolution);
}
/* Reset sliders to defaults */
function resetDemo() {
document.getElementById('vol0').value = 20;
document.getElementById('vol1').value = 10;
document.getElementById('vol2').value = 5;
ASSETS.forEach((_, i) => updateVolLabel(i));
document.getElementById('result').textContent = '';
document.getElementById('result').className = 'result';
document.getElementById('bars').innerHTML = '';
}
function updateVolLabel(i) {
document.getElementById('vval' + i).textContent =
document.getElementById('vol' + i).value + '%';
}
ASSETS.forEach((_, i) => {
document.getElementById('vol' + i).addEventListener('input', () => {
updateVolLabel(i);
document.getElementById('result').textContent = '';
document.getElementById('bars').innerHTML = '';
});
});
document.getElementById('solve').addEventListener('click', solve);
document.getElementById('reset').addEventListener('click', resetDemo);
/* Auto-run on load so the chart is visible immediately */
solve();
<!-- Risk parity demo — equal risk contribution across uncorrelated assets -->
<p class="hint">Each asset has a different annual volatility. Risk parity finds weights $w_i \propto 1/\sigma_i$ so every asset contributes the <b>same fraction</b> of total portfolio risk. Adjust the sliders and press Solve.</p>
<div class="assets" id="assets">
<div class="asset-row">
<span class="asset-label">Asset A (stocks)</span>
<input type="range" class="vol-slider" id="vol0" min="5" max="40" value="20" step="1">
<span class="vol-val" id="vval0">20%</span>
</div>
<div class="asset-row">
<span class="asset-label">Asset B (bonds)</span>
<input type="range" class="vol-slider" id="vol1" min="5" max="40" value="10" step="1">
<span class="vol-val" id="vval1">10%</span>
</div>
<div class="asset-row">
<span class="asset-label">Asset C (cash)</span>
<input type="range" class="vol-slider" id="vol2" min="5" max="40" value="5" step="1">
<span class="vol-val" id="vval2">5%</span>
</div>
</div>
<div class="btns">
<button id="solve" type="button">Solve</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="result" id="result"></div>
<div class="bars" id="bars"></div>
/* Styles for the risk parity demo layout and bar chart */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.assets { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .8rem; }
.asset-row { display: flex; align-items: center; gap: .5rem; }
.asset-label { width: 90px; font-size: .85rem; font-weight: 600; color: #1d3557; }
.vol-slider { flex: 1; accent-color: #1d3557; }
.vol-val { width: 38px; font-size: .85rem; text-align: right; color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result { font-size: .9rem; margin-bottom: .7rem; min-height: 1.2em; }
.result.ok { color: #0a7d33; font-weight: 600; }
.bars { display: flex; flex-direction: column; gap: .4rem; }
.bar-row { display: flex; align-items: center; gap: .5rem; }
.bar-label { width: 90px; font-size: .8rem; color: #555; }
.bar-track { flex: 1; height: 18px; background: #e8eef3; border-radius: 4px; overflow: hidden; position: relative; }
.bar-fill { height: 100%; border-radius: 4px; transition: width .35s; }
.bar-fill.a { background: #e63946; }
.bar-fill.b { background: #457b9d; }
.bar-fill.c { background: #2a9d8f; }
.bar-pct { width: 42px; font-size: .8rem; text-align: right; color: #333; }
.legend { display: flex; gap: .7rem; margin-bottom: .4rem; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: .3rem; font-size: .78rem; color: #555; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Algorithm: for uncorrelated assets, risk parity weights are */
/* w_i proportional to 1/sigma_i. We normalize so weights sum to 1. */
/* This is exact for zero correlation; with correlations a convex solver is needed. */
const ASSETS = ['a', 'b', 'c'];
const COLORS = ['a', 'b', 'c'];
/* Read volatility sliders (convert percent to decimal) */
function getVols() {
return ASSETS.map((_, i) => parseFloat(document.getElementById('vol' + i).value) / 100);
}
/* Inverse-volatility weights = exact risk parity for uncorrelated assets */
function naiveWeights(vols) {
const inv = vols.map(v => 1 / v);
const total = inv.reduce((a, b) => a + b, 0);
return inv.map(x => x / total);
}
/* Portfolio volatility (uncorrelated assets = sqrt of sum of (w*sigma)^2) */
function portfolioVol(w, vols) {
/* uncorrelated: variance = sum of (w_i * sigma_i)^2 */
const variance = w.reduce((s, wi, i) => s + wi * wi * vols[i] * vols[i], 0);
return Math.sqrt(variance);
}
/* Total risk contribution of each asset = w_i * MRC_i / portfolioVol */
function riskContributions(w, vols) {
const pVol = portfolioVol(w, vols);
if (pVol < 1e-12) return w.map(() => 0);
/* uncorrelated MRC_i = w_i * sigma_i^2 / portfolioVol */
return w.map((wi, i) => wi * wi * vols[i] * vols[i] / pVol);
}
/* Compute risk parity weights and render result */
function solve() {
const vols = getVols();
const w = naiveWeights(vols);
const pv = portfolioVol(w, vols);
const trc = riskContributions(w, vols);
const fracs = trc.map(t => t / pv);
render(w, fracs, pv, true);
}
function renderBars(w, fracs, isSolution) {
const bars = document.getElementById('bars');
bars.innerHTML = '';
/* Legend items for bar chart */
const legends = [
{ cls: 'a', label: 'Asset A (stocks)' },
{ cls: 'b', label: 'Asset B (bonds)' },
{ cls: 'c', label: 'Asset C (cash)' }
];
/* Two bar sections — capital weights and risk fractions */
const sections = [
{ title: 'Capital allocation (weights)', values: w },
{ title: 'Risk contribution (fraction of total risk)', values: fracs }
];
for (const sec of sections) {
const header = document.createElement('div');
header.style.cssText = 'font-size:.78rem;color:#888;margin:.3rem 0 .15rem;font-weight:600;';
header.textContent = sec.title;
bars.appendChild(header);
sec.values.forEach((val, i) => {
const row = document.createElement('div');
row.className = 'bar-row';
const pct = Math.round(val * 100);
row.innerHTML = `<span class="bar-label">${legends[i].label}</span>` +
`<div class="bar-track"><div class="bar-fill ${COLORS[i]}" style="width:${pct}%"></div></div>` +
`<span class="bar-pct">${pct}%</span>`;
bars.appendChild(row);
});
}
}
function render(w, fracs, pVol, isSolution) {
const res = document.getElementById('result');
if (isSolution) {
const pvPct = (pVol * 100).toFixed(1);
res.textContent = i18nFmt('Risk parity weights found. Portfolio volatility: %pv%% per year.', { pv: pvPct });
res.className = 'result ok';
} else {
res.textContent = '';
res.className = 'result';
}
renderBars(w, fracs, isSolution);
}
/* Reset sliders to defaults */
function resetDemo() {
document.getElementById('vol0').value = 20;
document.getElementById('vol1').value = 10;
document.getElementById('vol2').value = 5;
ASSETS.forEach((_, i) => updateVolLabel(i));
document.getElementById('result').textContent = '';
document.getElementById('result').className = 'result';
document.getElementById('bars').innerHTML = '';
}
function updateVolLabel(i) {
document.getElementById('vval' + i).textContent =
document.getElementById('vol' + i).value + '%';
}
ASSETS.forEach((_, i) => {
document.getElementById('vol' + i).addEventListener('input', () => {
updateVolLabel(i);
document.getElementById('result').textContent = '';
document.getElementById('bars').innerHTML = '';
});
});
document.getElementById('solve').addEventListener('click', solve);
document.getElementById('reset').addEventListener('click', resetDemo);
/* Auto-run on load so the chart is visible immediately */
solve();
Notice: when one asset is far more volatile than the others, risk parity gives it a much smaller weight. The naive 60/40 intuition breaks down completely once volatilities differ — and they always do.
Comments
Loading comments...