用 cvxpy 做带约束的权重优化
行业暴露、单票上限、换手预算写成约束条件,比事后截断干净得多。
事后截断的问题
很多人的做法是:先按打分算权重,然后把超过 3% 的削到 3%,再归一化。这一步看起来无害,实际上它破坏了权重和信号之间的单调关系,也没有真正控制住行业暴露——削掉的部分被平摊回了同一个行业里的其他票。
把这些要求直接写成约束,让求解器在满足约束的前提下最大化预期收益,得到的组合结构完全不同。
import cvxpy as cp import numpy as np n = len(mu) # mu: 预期收益,长度 n w = cp.Variable(n) w0 = prev_weight # 上期权重,用来算换手 objective = cp.Maximize(mu @ w - 0.5 * gamma * cp.quad_form(w, Sigma)) constraints = [ cp.sum(w) == 1, # 满仓 w >= 0, # 只做多 w <= 0.03, # 单票上限 3% cp.abs(ind_matrix @ w - ind_bench) <= 0.02, # 行业偏离 ±2% cp.norm(w - w0, 1) <= 0.30, # 单期双边换手不超过 30% ] prob = cp.Problem(objective, constraints) prob.solve(solver=cp.OSQP) print(prob.status, prob.value)
gamma 怎么定
风险厌恶系数不要拍脑袋。先固定一个目标跟踪误差,比如年化 4%,然后二分搜索 gamma 使得优化解的事前跟踪误差落在这个数附近,这样参数才有可解释的含义。
协方差矩阵才是难点
目标函数里的 Sigma 用样本协方差几乎一定会失败:3000 只股票、250 个交易日,样本协方差矩阵是奇异的,求解器会在最小特征值方向上把杠杆推到极端。
- Ledoit-Wolf 收缩:把样本协方差往对角阵收缩,实现最简单,效果通常够用。
- 因子模型:用风格因子加行业因子构造结构化协方差,需要额外的因子暴露数据,但残差风险可控。
- 指数加权:给近期数据更高权重,对波动率突变响应更快,代价是估计噪声更大。
from sklearn.covariance import LedoitWolf Sigma = LedoitWolf().fit(ret_window.values).covariance_ Sigma = cp.psd_wrap(Sigma) # 数值上保证半正定,避免求解器报 non-convex
无解的时候怎么办
约束加多了迟早会遇到 infeasible。不要靠删约束来救,改成软约束:把违约量作为惩罚项放进目标函数,给一个足够大的系数。这样求解器总能返回一个解,而且你能从违约量上直接看出是哪条约束太紧。
slack = cp.Variable(nonneg=True) constraints = [ cp.norm(w - w0, 1) <= 0.30 + slack, ] objective = cp.Maximize(mu @ w - 0.5 * gamma * cp.quad_form(w, Sigma) - 50 * slack)
下一篇讲怎么把换手约束从单期扩展到多期,也就是带前瞻的动态规划版本。
cvxpy组合优化约束