做什么(One-liner)
多资产 Markowitz / 风险平价组合优化。scipy.optimize SLSQP 求 3 种目标(max Sharpe / min variance / equal risk contribution)+ 20-point efficient frontier。Long-only、sum=1 约束。
怎么用
Canvas 拖 portfolio_optimization_v1,上游接 ≥2 个 price_factor_v1(或直接给 symbols 参数),选 method(sharpe / min_variance / risk_parity)。输出最优权重 dict + 年化 return/vol/Sharpe + efficient_frontier。
核心公式
μ = mean(returns) × 252 # 年化收益向量
Σ = cov(returns) × 252 # 年化协方差矩阵
目标函数:
sharpe : minimize -(μ·w - rf) / sqrt(w'Σw)
min_variance : minimize w'Σw
risk_parity : minimize Σ_i (RC_i - σ_p/n)²
约束:
sum(w) = 1, 0 ≤ w_i ≤ 1
Starting w₀ = [1/n, ..., 1/n]
Solver: scipy.optimize.minimize(method='SLSQP', ftol=1e-9, maxiter=500)
Efficient frontier: 20 points, linspace(μ.min, μ.max);
for each target_ret: minimize w'Σw s.t. sum=1, μ·w=target_ret假设与适用场景
假设:yfinance 数据可用 + Σ 正定 + long-only + sum=1 + 1y lookback + 252 年化 + SLSQP 求解。
适用:跨资产 strategic allocation、efficient frontier 可视化、Markowitz 经典场景。
不适用:leverage、short selling、Black-Litterman priors、robust / shrinkage、multi-period、transaction costs。
输入 / 输出契约
{symbols, period?, method?, risk_free_rate?} → {symbols, method, period, weights: dict, annual_return, annual_volatility, sharpe_ratio, efficient_frontier: list[20], optimization_success, windows}。
Unknown method → fallback to sharpe(静默)。< 2 symbols → error。
已知局限
- SLSQP corner solutions:某些 seed 下 sharpe method 收敛到单一资产 — 正常 convex optimization 行为,不是 bug
risk_parity目标非 convex → SLSQP 找 local minimum(通常足够)ftol=1e-9/maxiter=500/ frontier 20 点 / bounds [0, 1] 均 hardcodedrisk_free_rate仅 sharpe 使用optimization_success=false时静默 fallback equal-weight — caller 需检查- 不输出 per-asset risk contribution(可计算但未暴露)
参考文献
Markowitz (1952) 原始 + Maillard et al. (2010) ERC + Kraft (1988) SLSQP algorithm + DeMiguel-Garlappi-Uppal (2009) 1/N baseline。详见 frontmatter。
Golden Test
tests/golden/fixtures/tier2/portfolio_optimize_route/,3-method parametrized over 4-symbol synthetic returns。1e-10 tolerance post-round(4)。9 tests:
matches_reference[sharpe/min_variance/risk_parity]— 3 method byte-equaldeterminism— 5 reruns byte-identicalweights_sum_to_one— normalization invariant(所有方法)long_only_constraint— w ∈ [0, 1](所有方法)efficient_frontier_structure— 20 点、每个 {return, volatility}、升序排序rejects_single_symbol— <2 → errorinvalid_method_falls_back_to_sharpe— unknown method → sharpe 等价
Note on testing history: 早期 golden(closed PR #39)错误地 target 了 legacy dead function analyze_portfolio_optimization —— 用户 sharp 观察 "Canvas 上看着没问题" 触发 post-audit,发现 Canvas 实际 POST /portfolio/optimize(本 operator)。2026-04-20 fix 删除 legacy 函数 + 重建此 golden。
Changelog
- 1.0.0 (2026-04-20) — 首次 Active(targets real Canvas route after legacy cleanup)

