工具实战
围绕 cvxpy 建模基础,说明变量、目标函数和约束如何共同表达组合优化问题,并补充输入治理、求解诊断和工程落地的关键检查点。
cvxpy 第二讲讨论建模基础:变量、目标函数与约束表达。上一讲如果解决的是“为什么量化优化问题适合用 cvxpy 表达”,这一讲就进入更具体的层面:怎样把一个投资组合问题写成清晰、可检查、可复用的优化模型。
很多人第一次使用 cvxpy,会把它当成一个求解器调用工具:定义变量,写目标函数,列几个约束,然后求解。这个理解并不算错,但还不够。cvxpy 更重要的价值,是把数学模型以接近公式的方式表达出来,让研究、风控和工程之间可以对同一个问题说同一种语言。变量代表决策,目标函数代表偏好,约束代表边界。只要这三类对象清楚,模型就容易被审阅、调试和复盘。
量化优化里常见的问题包括权重分配、风险预算、换手控制、行业或风格暴露控制、跟踪误差控制、交易成本控制等。它们看起来都可以写成“最大化收益”或“最小化风险”,但真实项目里很少只有一个目标。更多时候,模型要在收益、风险、成本、约束和执行可行性之间折中。cvxpy 的意义就在于,让这些折中可以被明确写出来,而不是藏在一堆临时规则里。
本文聚焦 cvxpy 建模基础的专业表达方法,不讨论收益承诺,也不把某个优化公式写成通用答案。这里重点讲建模习惯:如何定义变量,如何把目标函数写得可解释,如何把约束表达成可维护的形式,如何检查模型是否符合凸优化要求,如何把优化结果纳入研究流程。目标是让读者能从“会写代码”走向“会设计模型”。
写 cvxpy 模型之前,先定义问题。一个投资组合优化问题至少要说明四件事:优化对象是什么,输入数据是什么,决策变量是什么,输出结果如何使用。比如优化对象可能是下一期组合权重,输入可能包括预期收益、协方差矩阵、当前持仓、行业暴露矩阵和交易成本参数,输出可能是目标权重或调仓指令。
问题定义不清,会直接反映到模型里。比如你想控制换手,却没有当前持仓;想控制行业暴露,却没有行业暴露矩阵;想控制风险,却没有稳定的风险估计;想最大化预期收益,却没有说明预期收益来自哪里。这些缺口不能靠 cvxpy 自动补齐。优化模型只会忠实执行你写下的数学表达,它不会替你判断输入是否合理。
在量化研究中,问题定义还要和交易频率、标的池、成本假设和风控边界保持一致。一个月度调仓模型和日度调仓模型,换手约束、成本约束和风险估计窗口都可能不同。一个宽基股票池和一个行业内股票池,行业约束的意义也不同。问题定义越具体,模型越不容易写成看似通用、实际无法执行的形式。
建议在建模前写一份简短的模型说明。说明不需要很长,但要能回答:这个模型做什么,不做什么;哪些输入是外部给定,哪些变量由模型决定;哪些约束是硬约束,哪些偏好通过目标函数体现;求解失败时如何处理。把这些问题先写清楚,后面的代码会简洁很多。
变量是优化模型的决策对象。在投资组合问题中,最常见的变量是资产权重向量。若组合有 n 个资产,权重变量通常可以写成长度为 n 的向量。这个变量不是历史持仓,也不是信号分数,而是模型最终要决定的结果。把变量含义说清楚,是建模的第一步。
变量维度必须和输入数据严格对齐。权重向量的顺序,应与预期收益向量、协方差矩阵、行业暴露矩阵、成本参数等输入保持一致。很多优化错误不是公式错,而是资产顺序错。cvxpy 不知道某个位置代表哪个资产,它只处理矩阵和向量。因此,进入模型之前必须建立清晰的资产索引,并在输出时按同一索引还原。
除了目标权重,有些模型还会引入辅助变量。比如为了表达换手,可以引入交易量变量,表示目标权重和当前权重之间的差异;为了表达绝对值,可以引入非负辅助变量;为了表达风险预算或偏离程度,也可能引入额外变量。辅助变量的作用是让复杂约束变得可表达、可求解,而不是让模型显得复杂。
变量设计要避免把不可决策对象写成变量。比如预期收益、协方差矩阵、行业暴露、成本参数,这些通常是输入,不是优化变量。把输入和变量混在一起,会导致模型含义混乱。一个简单判断是:求解器到底可以改变什么?能被求解器改变的,才是变量;只能由外部估计或配置提供的,是参数或常量。
变量还需要边界。若不允许做空,就要有权重非负约束;若允许有限做空,就要有下限;若要控制单票集中度,就要有上限。不要假设求解器会自动给出符合业务直觉的答案。没有约束的变量可能得到数学上可行、业务上不可接受的结果。
变量设计还要考虑输出如何落地。目标权重如果最终要转换成交易指令,就必须和实际账户、最小交易单位、停牌状态和可交易价格衔接。基础 cvxpy 模型通常先处理连续权重,这是为了保持问题简洁和凸性,但研究报告里要说明这一步和真实执行之间还需要哪些转换。模型变量越清楚,后续执行层越容易接住。
目标函数表达模型偏好。常见目标包括最大化预期收益、最小化组合方差、最小化跟踪误差、最小化交易成本,或者把多个目标组合成一个带权重的表达。目标函数不是口号,它必须能清楚说明模型在不同目标之间如何取舍。
一个常见写法是风险调整目标,例如在收益项和风险项之间做权衡。收益项通常来自预期收益向量与权重变量的内积,风险项通常来自权重与协方差矩阵构成的二次型。若还要考虑交易成本,可以加入与调仓幅度相关的惩罚项。这样模型不是单纯追求信号最高,而是在收益、风险和成本之间做平衡。
目标函数中的系数要有解释。风险惩罚系数、成本惩罚系数、跟踪误差惩罚系数,如果只是为了让结果好看而调整,就会让模型变成黑箱。比较稳的做法,是把系数作为配置,并在实验报告中记录。每次系数变化,都应说明它代表更强的风险厌恶、更严格的成本控制,还是其他业务偏好。
目标函数还要注意尺度问题。收益、风险和成本的数值尺度可能不同,直接相加会导致某一项支配整体。建模时要检查各项量级,并通过合理的单位、归一化或参数设定让它们在同一决策语境下比较。尺度问题不解决,模型求解出来的结果可能看似稳定,实际只是被某一项压住。
在 cvxpy 里,目标函数要符合凸优化规则。比如最小化凸函数通常可行,最大化凹函数通常可行,但最大化凸函数或最小化凹函数就会违反规则。cvxpy 的 DCP 检查会帮助发现这类问题,但建模者仍然要理解目标表达的含义。不能因为代码通过语法检查,就认为模型表达正确。
一个最小组合优化目标可以写成风险调整形式。设权重向量为 $w$,预期收益向量为 $\mu$,协方差矩阵为 $\Sigma$,风险厌恶系数为 $\lambda$,当前权重为 $w_0$,交易成本惩罚系数为 $\gamma$,则可以把目标写成:
$$ \max_w\ \mu^T w - \lambda w^T \Sigma w - \gamma |w - w_0|_1 $$
这个表达的含义很直接:第一项偏向更高的预期收益,第二项惩罚组合风险,第三项惩罚过大的调仓幅度。它不是唯一正确形式,但适合作为基础模板,因为每一项都能对应到业务解释。专业建模时,不应只问目标值是否更高,还要问每一项是否代表清楚、尺度是否合理、参数是否可追溯。
约束表达业务边界。常见约束包括权重和为 1、权重上下限、行业暴露范围、风格暴露范围、换手上限、风险上限、持仓数量或集中度控制等。硬约束意味着模型必须满足,不满足就不可行;软约束则可以通过目标函数惩罚表达。两者要分清。
权重约束通常是组合模型的基础。若是全仓多头组合,可能需要权重和等于 1 且每个权重大于等于 0。若允许现金或杠杆,约束会不同。不要把权重和约束写成习惯动作,而要先问组合资金使用方式是什么。不同资金使用方式对应不同数学表达。
行业和风格约束要依赖暴露矩阵。比如行业暴露可以写成暴露矩阵与权重变量相乘,再限制在某个范围内。这里关键是矩阵维度和资产顺序要一致,且暴露定义要和风控口径一致。如果研究里使用一套行业分类,风控里使用另一套分类,约束结果就会出现解释冲突。
换手约束常常需要当前持仓。目标权重和当前权重之间的差异,代表调仓幅度。若要限制总换手,可以对绝对差异求和并设上限。若要考虑成本,可以把调仓幅度乘以成本参数放进目标函数或约束。这里要注意,换手约束和交易成本约束不是完全一样的概念,前者控制交易规模,后者控制成本影响。
有些约束看起来合理,但会让问题变得非凸。比如直接限制持仓数量,通常涉及整数变量,不再是普通凸优化问题。cvxpy 支持某些混合整数建模,但求解复杂度和可用求解器都会变化。对于基础量化优化流程,建议先把问题保持在凸优化范围内,再考虑更复杂的离散约束。
对应到上面的目标函数,基础约束可以先写成三类:资金约束、权重边界和风险或暴露约束。例如 $\sum_i w_i = 1$ 表示资金 fully invested;$0 \leq w_i \leq u_i$ 表示多头和单票上限;$B^T w$ 落在给定区间内表示行业或风格暴露控制。把这些约束拆开写,后续诊断不可行问题时才能知道到底是哪一类约束过紧。
用 cvxpy 表达时,最小骨架可以是这样的:
import cvxpy as cp
n = len(mu)
w = cp.Variable(n)
ret = mu @ w
risk = cp.quad_form(w, Sigma)
turnover = cp.norm1(w - w0)
objective = cp.Maximize(ret - risk_aversion * risk - cost_penalty * turnover)
constraints = [
cp.sum(w) == 1,
w >= 0,
w <= upper_bound,
]
problem = cp.Problem(objective, constraints)
problem.solve(solver=solver_name)
这段代码的价值不在于直接复制上线,而在于展示变量、目标和约束如何一一对应。专业项目还要补资产索引、参数校验、求解状态判断、输出检查和异常处理,但基础结构应保持清楚。
在 cvxpy 中,参数可以用来表示求解时会变化但不是决策变量的输入。比如预期收益、风险矩阵、当前持仓、约束上下限,都可以作为参数或外部常量进入模型。参数化的好处是,模型结构稳定,输入可以随时间更新。这样适合滚动优化或批量回测。
输入治理是优化模型稳定运行的前提。协方差矩阵是否半正定,预期收益是否有异常值,当前持仓是否和资产池对齐,成本参数是否缺失,约束上下限是否互相冲突,这些都要在求解前检查。否则求解失败时,很难判断是模型表达错误,还是输入数据不满足要求。
风险矩阵尤其需要谨慎。理论上组合方差可以用协方差矩阵表达,但实际估计的矩阵可能存在噪声、不稳定或数值问题。建模时不要把风险矩阵当成天然正确的输入。至少要记录来源、估计窗口、处理方式和更新时间。如果风险矩阵质量不稳定,优化结果也会不稳定。
参数治理还包括版本记录。一次优化结果应能追溯使用了哪一期预期收益、哪一版风险模型、哪一组约束参数、哪一个当前持仓快照。没有这些记录,后续无法解释为什么某次组合变化很大,也无法复盘求解失败原因。
模型写好后,不代表求解一定成功。求解结果可能是最优、不可行、无界、数值不稳定或求解器失败。每种状态都要分别处理。最优结果可以进入后续检查;不可行通常意味着约束冲突;无界通常意味着变量缺少必要边界;数值问题可能来自输入尺度或矩阵质量。
诊断不可行问题时,不要一次性删除大量约束。更稳的方法是逐层检查:先看基础权重约束是否可行,再加单票上下限,再加行业约束,再加换手约束,再加风险约束。这样能定位是哪一类约束造成冲突。如果所有约束一起上,求解失败只会给出一个模糊结果。
求解后还要检查输出,而不是直接相信结果。检查内容包括权重和是否满足要求,单票权重是否越界,行业暴露是否在范围内,换手是否超过限制,风险项是否合理,是否出现异常集中。cvxpy 给出的解是数学意义上的结果,进入交易前还要通过业务检查。
还要记录求解器、求解状态和关键日志。不同求解器对问题规模、数值稳定性和支持的表达类型有差异。研究阶段可以使用一种求解器,生产阶段可能需要另一种。若不记录求解器和状态,后续出现差异时很难复现。
诊断时还要区分“模型不可行”和“业务设定过紧”。例如单票上限、行业约束、换手约束和风险上限单独看都合理,但放在一起可能没有任何组合能同时满足。这个时候不应简单认为 cvxpy 出错,而要回到约束设计本身,判断哪些约束是必须满足的硬边界,哪些可以通过惩罚项表达为软偏好。优化模型的价值之一,就是把这些冲突显性化。
工程落地时,建议把模型拆成输入准备、模型构建、求解执行、结果检查和报告输出五个部分。输入准备负责对齐资产和参数;模型构建只表达变量、目标和约束;求解执行负责调用求解器;结果检查负责验证输出;报告输出负责记录输入版本、约束状态、目标值和异常信息。这样每一层职责清楚,问题也容易定位。
模型构建函数不要混入过多数据清洗逻辑。数据清洗应该在输入准备阶段完成,模型构建阶段只接收已经对齐且校验过的矩阵、向量和参数。这样可以减少隐性副作用,也方便单独测试模型表达是否符合预期。
报告输出很重要。一次优化至少应记录资产数量、约束配置、目标函数配置、求解器、求解状态、目标值、主要风险指标、换手、最大权重、主要暴露和异常提示。报告不需要写得很花,但要能支持复盘。优化模型最怕的不是求解慢,而是求解结果出了问题却没人知道为什么。
如果模型要进入周期性运行,还需要失败预案。比如求解不可行时,是放宽某个软约束,沿用上一期持仓,还是降低目标风险暴露;求解器失败时,是否切换求解器;输入异常时,是否跳过本期优化。这些规则必须提前定义,不能在生产时临时决定。
cvxpy 建模的核心不是把公式翻译成代码,而是把决策、偏好和边界写清楚。变量告诉求解器可以改变什么,目标函数告诉求解器偏好什么,约束告诉求解器不能越过什么边界。三者一旦混乱,模型就算能求解,也很难解释。
这一讲的重点可以概括为四点。第一,变量必须代表真实决策对象,并和输入资产顺序严格对齐。第二,目标函数要有明确经济含义,不能只为了让结果好看而堆惩罚项。第三,约束要区分硬边界和软偏好,并注意凸优化规则。第四,求解后必须诊断和记录,因为优化结果只有可追溯,才有研究价值。
后续继续学习 cvxpy 时,可以在这个基础上扩展更具体的组合优化主题,比如均值方差模型、风险预算、跟踪误差控制、换手与成本约束、多期优化和鲁棒优化。但无论模型如何扩展,变量、目标函数和约束这三个基础对象都不会变。把基础写稳,复杂模型才不会变成不可维护的公式堆叠。
风险揭示与免责声明
本页面内容仅用于量化研究与技术交流,旨在展示研究方法与流程,不构成对任何金融产品、证券或衍生品的要约、招揽、推荐或保证。
本文所涉历史数据、回测结果与示例参数不代表未来表现,也不应作为投资决策依据。
市场存在波动、流动性与执行偏差等不确定性,任何策略均可能出现收益波动或阶段性失效。
读者应结合自身风险承受能力进行独立判断,并在必要时咨询持牌专业机构意见。