3. 凸优化核心概念
在资金配置优化中,我们真正求解的不是“收益最大化”这句话本身,而是一个被严格定义的数学规划问题。CVXPY 的价值在于:它把建模语言与求解器解耦,但前提是模型必须满足凸性。理解凸集、凸函数、线性规划、二次规划、锥规划以及 DCP 规则,是写出可解、稳定、可复现策略模型的基础。
一、核心原理:凸集与凸函数
凸集的定义很直观:集合内任意两点连线上的所有点仍属于该集合。凸函数则要求函数图像上任意两点连线不低于函数曲线,即对任意 x、y 和 λ∈[0,1],有 f(λx+(1−λ)y) ≤ λf(x)+(1−λ)f(y)。这两个概念之所以关键,是因为凸优化问题具有一个极强性质:局部最优即全局最优。在资金配置中,这意味着只要模型是凸的,求解器找到的解就是全局最优解,不会陷入“看起来不错但实际更差”的局部陷阱。
常见的凸集包括:超平面、半空间、多面体、二阶锥、半正定锥。常见的凸函数包括:线性函数、二次函数(二次项系数半正定)、范数、最大值函数、指数函数。与之相对,收益率的“非凸”表达、整数手数约束、条件 VaR 的某些原始形式,都会破坏凸性。
二、方法流程:从LP到锥规划的建模阶梯
资金配置问题通常按复杂度分为三个层级。第一层是线性规划 LP:目标与约束均为线性,例如在给定预期收益下最小化交易成本。第二层是二次规划 QP:目标含二次项,典型代表是 Markowitz 均值—方差模型,最小化 wᵀΣw,约束为预算约束和权重上下限。第三层是锥规划:当风险度量涉及范数、VaR 或半正定矩阵时,问题进入二阶锥规划 SOCP 或半正定规划 SDP。
CVXPY 的建模流程可以概括为:声明变量 → 构造目标 → 添加约束 → 调用 solve() → 检查 status。关键在于,CVXPY 不会自动“猜测”你的问题是否凸,而是通过 DCP 规则进行静态校验。若校验失败,会直接抛出 DCPError,而不是交给求解器。
import cvxpy as cp
import numpy as np
n = 5
mu = np.array([0.08, 0.10, 0.12, 0.07, 0.09])
Sigma = np.diag([0.04, 0.05, 0.06, 0.03, 0.045])
w = cp.Variable(n)
ret = mu @ w
risk = cp.quad_form(w, Sigma)
objective = cp.Minimize(risk)
constraints = [cp.sum(w) == 1, w >= 0, ret >= 0.09]
prob = cp.Problem(objective, constraints)
prob.solve()
print("status:", prob.status)
print("optimal weights:", w.value)
print("risk:", prob.value)
这段代码是标准 QP。若把 cp.quad_form 换成 cp.norm(Sigma @ w, 2),则进入 SOCP;若再加入 cp.Variable((n,n), PSD=True) 的协方差矩阵变量,则进入 SDP。建模阶梯越往上,表达能力越强,但求解成本也越高。
三、案例场景:DCP违规与修复
最常见的 DCP 违规有三类。第一类是凸函数除以变量,例如 cp.sum_squares(w) / cp.sum(w),除法中分母为变量时通常非凸。第二类是凸函数等于凸函数,例如 cp.norm(w,2) == 1,等式约束要求两侧均为仿射。第三类是最大化凸函数,例如 cp.Maximize(cp.norm(w,2)),目标方向与凸性冲突。
典型报错:DCPError: Problem does not follow DCP rules. 这通常不是求解器能力不足,而是模型本身非凸。不要试图通过换求解器绕过,而应重构模型。
修复思路:把“凸等于凸”改为“凸 ≤ 凸”或引入辅助变量;把“最大化凸函数”改为“最小化其负函数”或使用凹函数;把“变量除法”改为引入辅助变量并添加锥约束。例如,cp.norm(w,2) == 1 可改为 cp.norm(w,2) <= 1,再通过目标函数驱动其取到边界。
| 问题类型 | 典型表达 | 是否DCP | 修复方式 |
|---|---|---|---|
| 线性规划 LP | Minimize cᵀw,Aw ≤ b | 是 | 直接求解 |
| 二次规划 QP | Minimize wᵀΣw,Σ半正定 | 是 | 直接求解 |
| 凸等于凸 | norm(w,2) == 1 | 否 | 改为 ≤ 或引入辅助变量 |
| 最大化凸函数 | Maximize norm(w,2) | 否 | 改为最小化负函数或换凹函数 |
| 变量除法 | sum_squares(w)/sum(w) | 否 | 引入辅助变量与锥约束 |
四、误区与复盘
第一个误区是“只要 CVXPY 能跑就是凸的”。实际上,CVXPY 的 DCP 校验是保守的:有些非 DCP 表达在数学上等价于凸问题,但无法通过规则校验。此时需要手动重写,而不是放弃凸优化。
第二个误区是“锥规划一定比 QP 慢”。SOCP 和 SDP 的单次求解确实更贵,但它们能表达更真实的风险约束,例如 CVaR、最大回撤近似、协方差不确定性。在资金配置中,模型真实性往往比求解速度更重要。
第三个误区是忽略数值尺度。收益率、权重、协方差矩阵的量级差异过大会导致求解器收敛困难。建议在建模前对收益和风险做统一量纲处理,并在求解后检查 prob.status 是否为 optimal。
复盘清单:① 目标方向是否正确(最小化凸/最大化凹);② 等式约束两侧是否均为仿射;③ 是否存在变量除法或变量乘积;④ 协方差矩阵是否半正定;⑤ 求解状态是否为 optimal;⑥ 权重是否满足预算与边界约束。