做了一年多的智能投顾项目,我最常被问的一句话是:“你这个模型能预测明天哪只股票涨吗?”每次我都得解释半天——智能投顾的核心从来不是择时也不是选股,而是资产配置。标题里的“资产配置与组合优化模型”,本质上就是把你对市场的判断、对波动的容忍度、对资金期限的要求,统一翻译成一串资产权重。这篇文章里,我会把自己从 0 到 1 搭这么一套模型的完整过程整理出来:经典框架怎么选、约束怎么设、代码怎么落、回测怎么避坑。适合正准备做量化配置系统的算法工程师、正在设计智能投顾产品的产品经理,以及想自己动手验证组合优化思路的投资者。
1. 先想明白:资产配置到底在解决什么问题
1.1 智能投顾为什么盯住配置而不是选股
很多刚入行的人把智能投顾理解成“自动选股机器人”,这其实是个方向性误会。选股解决的是“买什么”,而资产配置解决的是“在股市、债券、商品、现金之间如何分配资金”。成熟的投顾流程基本都是自上而下:先定大类配置,再在具体标的上做细微调整。少数优秀选股策略可以在短期跑出超额收益,但资金体量越大、时间越长,配置决策对结果的影响就越显著。
我在项目里见过不少同事,一上来就花大量精力优化选股因子,结果组合净值在全球市场普跌时照样大幅回撤。问题不在选股因子的质量,而在组合暴露的风险源太过集中。配置模型的职责恰恰是把总风险额度拆分到不同风险源上,让任何单一市场冲击都不会把整个组合打穿。
1.2 一个被反复验证的结论
行业里有个被反复引用的归因结论:对大型机构账户来说,组合长期收益差异的绝大部分来自资产配置,而不是具体标的挑选。我第一次看到这个结论时也怀疑过,后来拿自己负责的模拟组合拆了一遍,发现周期拉长到三年以上,配置部分的贡献确实占据了主导。这不是说选股没用,而是说配置是地基,选股是装修——地基歪了,装修做得再漂亮也没法住。
所以智能投顾系统里,组合优化模型从来不是“锦上添花”的模块,而是整个产品的主骨架。客户问卷收集上来的风险偏好、投资期限、流动性需求,最终都要变成模型的输入参数;模型输出的权重,又直接决定客户每天看到的盈亏曲线长什么样。这个链路里任何一环粗糙,产品体验都会立刻暴露出来。
2. 组合优化模型:把“感觉”变成一次数学求解
2.1 均值-方差框架的目标函数与约束
我们以经典均值-方差框架为基础。给定 n 类资产的年化预期收益向量 μ 和协方差矩阵 Σ,用权重向量 w 表示资金分配。组合的预期收益 μ_p = w^T μ,组合方差 σ_p^2 = w^T Σ w。
优化目标通常有两种写法。一种是在收益达到最低要求的前提下最小化方差;另一种是带风险厌恶系数的形式,最大化 w^T μ - λ w^T Σ w。λ 越大,模型越不愿意承担波动。我实际项目中一般用第二种,因为风险厌恶系数直接暴露在外,方便业务侧理解,也方便做敏感性分析。
我用一个包含四类资产的模拟项目 X 来演示参数设计,资产分别是权益、债券、商品和现金。
| 资产 | 年化预期收益 | 年化波动率 | 权重上限 |
|---|---|---|---|
| 权益 | 7.0% | 16% | 30% |
| 债券 | 3.5% | 5% | 60% |
| 商品 | 4.2% | 14% | 20% |
| 现金 | 2.0% | 0.5% | 30% |
把这些数字代入目标函数,最优解通常会落在某个边界组合附近。但真正的坑不在求解,而在这些数字本身怎么来。
注意:预期收益不是预测点位,而是设定一个长期合理中枢。如果直接拿过去三年收益外推,模型大概率会把所有仓位压到过去最赚钱的那类资产上,这在实盘里非常危险。
2.2 协方差矩阵:整个模型最脆弱的零件
预期收益容易拍脑袋,协方差矩阵则容易拍出垃圾。直接用历史日收益算样本协方差,最大的问题是噪声:当资产数量 n 接近样本期长度 T 时,矩阵的特征值会严重失真,甚至出现奇异矩阵。更麻烦的是,样本协方差对异常点极其敏感,一次极端行情就会把相关性结构扭曲。
标准的处理方式是收缩估计:把样本协方差朝一个更稳定的目标矩阵收缩,收缩强度由样本量决定。另一个实用技巧是使用指数加权移动平均来估计波动率,让近期的信息权重更高,而不是所有历史日一视同仁。
这里有个容易被忽视的细节:协方差矩阵的估计质量直接决定权重边界解的形状。矩阵里的相关系数差 0.1,优化器就有可能在“偏向权益”和“偏向商品”两个截然不同的组合之间横跳。所以我在项目里会同时保留原始协方差、收缩后协方差、指数加权协方差三套版本,跑完对比再决定用哪套作为上线版本。
2.3 目标收益和风险预算怎么定
组合优化最尴尬的地方在于预期收益。用历史收益作外推,长期看大概率出错;用宏观模型预测,又很容易引入主观偏差。我的做法是设置两层:先设长期中性预期(比如根据资产本身的长期收益中枢),再在模型外单独叠加一个“观点调整模块”,避免把临时判断直接灌进优化器。
风险预算则相反,它通常由客户特征决定。用户的年龄、资金期限、对最大回撤的容忍度,都会被映射到一个组合目标波动率上。目标波动率定了,预期收益只要落在合理区间内,优化结果不会太离谱。我在回测里最不喜欢看到的情况,就是把目标收益设得过高,然后靠约束硬凑出一个高波动组合——这种组合在历史上任何一次大波动里都撑不住。
3. 从均值-方差到风险平价:换个角度看风险
3.1 风险平价的直觉
均值-方差模型解决的是“在认领的风险和期望收益之间找平衡”,但它对输入参数极其敏感。于是出现了另一条思路:不让模型去猜谁将来涨得多,只求风险别集中在某一个角落。
风险平价的目标是让每类资产对组合整体波动率的贡献大致相等。一个直观类比:一辆四轮车,如果四个轮子承重一样,任何一个轮子出问题都不至于整车失控;如果一个轮子承重 80%,就算其他部件再好,风险点还是那一处。这个直觉放到资产配置里,就是不要把整个组合的命运押在“权益会上涨”这一个假设上。
3.2 风险贡献的计算与迭代
定义边际风险贡献 MRC_i = (Σw)_i / σ_p,它表示权重 w_i 增加一点点时组合波动率的变化量。再把权重乘回去,得到资产 i 对组合总风险的贡献 RC_i = w_i × MRC_i。容易验证所有 RC_i 加起来等于组合波动率 σ_p。
风险平价要做的就是找一组权重,让所有 RC_i 都等于总波动率的 1/n。资产相关性不是零的时候,不能简单用“波动率倒数归一化”的近似式,必须用迭代算法求解。常见做法是循环坐标下降:固定其他权重,轮换调整单个资产的权重,让该资产的 RC_i 逐步逼近目标值;循环若干轮,直到最大偏差小于阈值。
这个迭代过程在实际代码里并不难写,难的是判断它是否收敛。我的经验是,设定一个严格的容差,同时给迭代次数设上限,防止个别资产权重在几个值之间反复振荡。如果振荡严重,先检查协方差矩阵是否病态,再考虑收缩处理。
3.3 因子配置:穿透大类资产看风险
风险平价解决的是“风险分散”,但大类资产之间的风险并不是完全隔离的。股票大跌时,可转债、高收益债往往跟着跌,因为它们背后共同暴露于企业盈利与信用这类风险因子。再比如黄金在滞胀时期和商品一起涨,但在流动性危机时却可能被抛售。
因子配置的思路是先把资产收益对若干风险因子做回归,得到每类资产的因子暴露,再在因子层上做风险均衡或优化。这样做的好处是避免把“看似不同的资产”当成独立风险源,配置结果更容易解释。坏处是因子收益的估计比资产收益更难,需要更长的时间序列和更干净的风格数据。实际项目中,我会先保留一层简单的大类资产配置结果,同时引入因子风险的视角作为复核和压力测试工具。
4. 从模型到代码:一个可落地的配置引擎
4.1 数据准备:干净对齐比模型更重要
优化模型再漂亮,喂进去的是脏数据,吐出来的一定是垃圾权重。数据准备的第一步是时间对齐:不同资产类别的交易日不完全一致,必须用公共交易日截面,否则相关性估计里全是错位信息。第二步是处理缺失值和异常跳点:停牌期间用前值填充会平滑掉真实风险,我的习惯是缺失超过一定比例的资产直接剔除出样本,而不是补出一个虚假的价格。
第三步是确认价格序列是否经过复权和调整。这一步出问题,后面所有回测都会莫名其妙地好得离谱。协方差矩阵的估计窗口也值得留意:窗口太短,估计噪声大;窗口太长,平均掉市场结构变化。我的经验是先用三年日频收益做样本估计,再叠加指数加权,让近期波动贡献更高。这样既保留长期分布的大体形状,又不会让三年前的行情一直绑架今天的组合。
4.2 求解器与权重计算
组合优化本质上是一个带线性约束和二次目标的凸优化问题,可以直接用现成的凸优化库求解。以 Python 生态为例,我习惯用 cvxpy 封装目标函数和约束,求解器选开源求解器就够了。对于几十类资产的日频调仓,运算时间根本不是瓶颈;真正的瓶颈是参数怎么进、权重怎么出、出错了怎么查。
import numpy as np import cvxpy as cp w = cp.Variable(n) risk = cp.quad_form(w, cov_matrix) expected_return = mu_annual @ w constraints = [ cp.sum(w) == 1.0, w >= 0.0, w <= upper_bound, # 每类资产权重上限 expected_return >= min_return, # 最低收益要求 cp.sum(cp.abs(w - current_w)) <= max_turnover # 换手约束 ] prob = cp.Problem(cp.Minimize(risk), constraints) prob.solve(solver=cp.OSQP) weights_solution = w.value约束不是写上去就完事。换手约束尤其要小心:如果 max_turnover 设得太紧,优化器可能为了满足换手限制而放弃真正的风险目标,结果组合偏离既定风险预算。所以我把换手约束做成参数,每周调一次,观察它和风险目标之间的博弈曲线。
4.3 调仓节奏:日历调仓与阈值调仓
模型给的是目标权重,但从目标到持仓中间隔着一个调仓成本。常见的调仓策略有两种。日历调仓:固定在每月或每季度末执行一次,简单、可预期、容易做合规回测。阈值调仓:只有当实际权重与目标权重偏离超过一定比例时才触发调整。
阈值调仓能显著降低交易次数,但会引入路径依赖,回测时不能只测一条固定路径。我实际中偏好组合式:每季度固定审视一次,平时只做阈值触发,防止权重漂移太远。单次调仓时,把交易成本直接算进约束或目标函数,比事后扣收益更直观。
5. 回测结果评估:别被一条净值曲线冲昏头脑
5.1 三类让回测结果变假的问题
回测里最典型的坑有三个。第一是幸存者偏差:如果只把今天还活着的资产放进历史回测,等于自动剔除了那些中途失败退场的标的,结果自然漂亮。第二是前视偏差:用最新的价格调整因子计算历史收益,或者用当前成分股回放历史指数,都会把未来信息偷渡到过去。第三是过拟合:反复调节风险厌恶系数、协方差窗口、权重上下限,直到历史业绩曲线顺眼,这是一种非常隐蔽的过度拟合。
识别过拟合的办法是样本外测试和参数扰动。把参数上下浮动 10%,如果业绩剧烈变化,说明模型正在“背答案”而不是“理解题目”。我在项目里有个不成文的规矩:任何一个新加的参数,必须在数据前半段定好,再用后半段验证;只有前后两段表现都合理,才允许进入模拟盘。
5.2 评估指标:夏普之外还要看什么
单一夏普比率很容易骗人。一个组合年化收益很高、夏普很亮眼,但最大回撤达到 40%,客户早就拿不住钱了。所以我习惯看一组指标,而不是一个数字。
| 指标 | 口径 | 关注点 |
|---|---|---|
| 年化收益 | 几何收益换算 | 复利下的真实增长 |
| 波动率 | 收益标准差 | 收益的稳定程度 |
| 夏普比率 | (组合收益-无风险)/波动率 | 单位风险有多少回报 |
| 最大回撤 | 任意高点后最低点的最大跌幅 | 客户能否拿得住 |
| 卡玛比率 | 年化收益/最大回撤 | 回撤后的修复能力 |
另外要坚持看滚动窗口下的指标。固定一段时间的总指标大概率会掩盖中途的连续低迷。我会同时画滚动 12 个月的夏普曲线,如果曲线长期处于低位,说明策略在大部分时间里都没赚钱,只是靠某一段行情把总表现拉了上来。这种策略在实盘里很难坚持。
5.3 压力测试:给组合泼冷水
历史回测只是平均水平,金融市场上真正伤害资金的是极端时期。压力测试的思路是:把历史上几次极端行情的时间段抽出来,看组合在这些窗口里怎么表现。比如权益大跌叠加利率上行、商品价格暴跌引发流动性踩踏、无风险利率长期贴着零。
在这些场景里,平时低相关甚至负相关的资产会突然同向波动,相关性矩阵不再是回测里的样子。因此我在组合上线前,会把相关性矩阵做“压力态修正”:把各类资产之间的最低相关性设在一个不高估分散收益的水平上,重新优化一遍。如果修正后的组合还能守住目标波动率,才敢拿去跑模拟盘。
6. 踩坑实录与问题排查技巧
6.1 协方差矩阵不稳定的自查清单
最常遇到的问题就是协方差矩阵崩坏,表现为求解器报错、权重反复横跳、或者最终组合波动率和输入严重不符。排查顺序我很有经验:先看资产数量与样本天数的比例,如果资产数量大到接近样本天数,先砍资产;再看数据里有没有完全共线的资产,比如同时持有了 ETF 和它跟踪的指数,这类重复风险源会直接撕碎矩阵。
最后才考虑用收缩估计,因为收缩会人为压低相关性,不能一开始就靠它解决所有问题。我在实际项目里见过一版权重,优化器连续两次给出完全相反的结果,查到最后发现是某两类资产在样本期里具有接近 1 的相关性,却还分别占用两个维度。把它们合并成一个大类之后,求解结果一下子稳定了。
6.2 权重集中度过高的处理
另一个高频问题是优化结果把大部分权重压到某一类资产上,和风险预算初衷完全不符。原因通常不是求解器出错,而是预期收益输入里出现了“明星资产”:某类资产的历史收益异常高,均值-方差模型认定它是免费午餐,于是全部仓位都压上去。
处理方式不是删掉这只资产,而是给预期收益加一层平滑,或者在目标函数里加入惩罚项,让权重往分散方向倾斜。权重上限约束务必在开发阶段就加上,后面少很多麻烦。我见过不少项目上线前才想起加约束,结果加过之后整个权重分布重排,业务侧又得重新评审一遍。
6.3 调仓成本与特殊时期的取舍
实盘里指数基金之间的价差、冲击成本、税费都会吃掉模型收益。把调仓成本放进换手约束之后,权重变化量明显被压缩,组合更稳定。但换手约束太紧也会阻碍风险修正:市场刚刚剧烈波动之后,模型想加大防守资产的权重,却被换手上限卡住,结果组合反而暴露在下跌风险里。
所以我在特殊时期会临时放宽换手约束,而不是机械执行平时的参数。这件事需要在系统里做成可配置项,并且每次手动干预都要留痕迹、做记录。模型自动运行固然省心,但遇到极端行情时,人的判断依然存在价值。
最后说点私人感受。做这个项目之前,我以为难点在数学和算法,做到后面发现真正难的是把每一个“看起来合理”的假设都拿出来验证一遍。最稳的组合往往不是收益最高的,而是参数扰动后依然稳的那个。资产配置与组合优化模型本质上不是帮你赚更多钱,而是帮你把风险控制在自己能承受的范围内。这个认知,可能比任何一组权重都值钱。