03: Portfolio Optimization (QP)
| Problem Class | Solver | Key APIs | Links |
|---|---|---|---|
| Convex Quadratic Programming (QP) | HiGHS (default) | m.continuous(), m.constraint(), c, minimize, dot, QuadExpr |
The Markowitz mean-variance portfolio optimization problem is the foundation of modern quantitative finance: allocate capital across risky assets to minimize overall portfolio variance (risk) subject to achieving a minimum required expected return.
INTERACTIVE EXAMPLE · 5 ASSETS CONVEX QP (QuadExpr)
Portfolio Allocation Optimizer (Markowitz QP)
Adjust the target expected return and click '▶ Run Optimization' to solve the 5-asset Quadratic Program.
US Tech Equity
25.0% E[R]=12.0%
Global Index
30.0% E[R]=8.5%
Real Estate (REIT)
15.0% E[R]=7.0%
Gold (Hedge)
10.0% E[R]=5.5%
Gov Bonds
20.0% E[R]=3.5%
Mathematical Formulation
Let denote the allocation weight for asset :
where is the positive semi-definite asset return covariance matrix.
Implementation in WaSHI
typescript
import { Model, QuadExpr, sum, dot, c, minimize } from "@ohtaman/washi";
async function solvePortfolio() {
const m = new Model("PortfolioQP");
const assets = ["Tech", "Bonds", "GlobalEq", "RealEstate", "Gold"];
const expectedReturns = [0.12, 0.04, 0.09, 0.07, 0.05];
const targetReturn = 0.085; // 8.5% minimum expected return
// 5x5 Covariance Matrix
const cov = [
[0.040, 0.002, 0.025, 0.015, 0.005],
[0.002, 0.005, 0.003, 0.002, 0.001],
[0.025, 0.003, 0.030, 0.018, 0.008],
[0.015, 0.002, 0.018, 0.025, 0.004],
[0.005, 0.001, 0.008, 0.004, 0.020],
];
// Allocation weights w_i in [0, 1]
const w = m.continuous(assets, { lb: 0, ub: 1 });
// 1. Budget constraint: sum(w_i) == 1.0 (100%)
m.constraint(c`${sum(assets, (a) => w[a])} == 1.0`, "budget_limit");
// 2. Expected return constraint: sum(r_i * w_i) >= 8.5%
const retMap = Object.fromEntries(assets.map((a, i) => [a, expectedReturns[i]]));
m.constraint(c`${dot(retMap, w)} >= ${targetReturn}`, "target_return");
// 3. Quadratic Objective: minimize risk w^T * Sigma * w
let risk = new QuadExpr();
for (let i = 0; i < assets.length; i++) {
for (let j = 0; j < assets.length; j++) {
if (cov[i][j] !== 0) {
risk = risk.add(w[assets[i]].mul(w[assets[j]]).mul(cov[i][j]));
}
}
}
m.objective(minimize(risk));
// Solve natively with HiGHS Convex QP engine
const status = await m.solve();
console.log(`Status: ${status}`);
console.log(`Variance: ${m.objectiveValue?.toFixed(6)}`);
console.log(`Volatility (Std Dev): ${((Math.sqrt(m.objectiveValue || 0)) * 100).toFixed(2)}%`);
assets.forEach((name) => {
console.log(` ${name.padEnd(10)}: ${((w[name].value ?? 0) * 100).toFixed(2)}%`);
});
}
solvePortfolio();