一、约束处理入门:组合优化中的常见约束类型
做组合优化,说白了就是在一堆限制条件里找最优解。
我刚开始接触这块时,总觉得约束是麻烦事。后来踩过几次坑才明白——没有约束的优化,就像没有护栏的悬崖。你算出来的权重再漂亮,实际交易时根本没法用。
1.1 权重约束:最基础也最容易被忽视
权重约束,就是限制每只股票能买多少。
最常见的两种:
- 上下限约束:比如单只股票不低于1%,不高于10%
- 整数手约束:必须按100股的整数倍交易
我在项目中遇到过一件事。有个同事做回测,权重算出来是3.14159%。结果实盘时发现,按这个比例根本买不了整数手。最后被迫调整,收益直接掉了0.5%。
核心要点:权重约束一定要在优化阶段就考虑进去。等算完再调整,效果会打折扣。
代码实现其实不复杂。用cvxopt或者scipy都能做:
# 权重上下限约束示例
import numpy as np
from scipy.optimize import minimize
# 定义约束:权重之和=1,且每个权重在[0.01, 0.10]之间
constraints = [
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, # 满仓
{'type': 'ineq', 'fun': lambda x: x - 0.01}, # 下限1%
{'type': 'ineq', 'fun': lambda x: 0.10 - x} # 上限10%
]
# 优化目标:最小化组合方差
def portfolio_variance(weights, cov_matrix):
return weights.T @ cov_matrix @ weights
result = minimize(portfolio_variance,
x0=np.ones(10)/10,
args=(cov_matrix,),
constraints=constraints)
个人习惯:我一般会把权重约束放在优化函数的第一位。为什么?因为它是所有约束的基础。权重不对,后面算行业约束、换手率约束都没意义。
1.2 行业约束:别把鸡蛋放在一个篮子里
行业约束,就是限制某个行业的持仓比例。
举个例子:你特别看好新能源,但不能全仓押上。监管要求、风控要求都会限制单一行业的暴露。
常见的行业约束形式:
- 绝对比例约束:某行业不超过总仓位的30%
- 相对基准约束:某行业偏离基准不超过±5%
- 行业中性约束:各行业权重与基准完全一致
嗯,这里要注意。行业约束和权重约束可能会打架。比如你设了单只股票上限10%,又设了某个行业上限20%。如果这个行业只有两只股票,那每只最多10%,行业上限20%其实用不上。
我曾经踩过的坑:有一次做多因子模型,行业约束设得太紧。结果优化器为了满足行业约束,被迫买了一些质量很差的股票。最后组合收益还不如不优化。所以约束不是越紧越好,要留点余地。
行业约束的数学表达:
# 行业约束示例
# 假设有5个行业,每个行业包含若干股票
industry_map = {
'科技': [0, 1, 2], # 股票索引
'消费': [3, 4, 5],
'医药': [6, 7],
'金融': [8, 9],
'能源': [10, 11, 12]
}
# 约束:每个行业权重不超过30%
def industry_constraint(weights, industry_map, max_weight=0.30):
constraints = []
for industry, stocks in industry_map.items():
industry_weight = np.sum(weights[stocks])
constraints.append(industry_weight - max_weight) # 必须 ≤ 0
return np.array(constraints)
1.3 换手率约束:别让交易成本吃掉收益
换手率约束,说白了就是限制你买卖的幅度。
你想想看,如果每次调仓都大进大出,交易成本会高得吓人。我见过一个策略,年化收益15%,但换手率高达2000%。扣除交易成本后,实际收益只剩3%。
换手率约束通常有两种:
- 单期换手率约束:本次调仓的买卖总额不超过某个比例
- 累计换手率约束:一段时间内的总换手率不超过某个阈值
我个人习惯用单期约束,因为它更直观、更容易控制。
# 换手率约束示例
# 假设当前持仓权重为 current_weights
# 目标权重为 target_weights
# 换手率 = sum(|target_weights - current_weights|) / 2
def turnover_constraint(target_weights, current_weights, max_turnover=0.20):
turnover = np.sum(np.abs(target_weights - current_weights)) / 2
return turnover - max_turnover # 必须 ≤ 0
# 在优化中加入换手率约束
constraints = [
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
{'type': 'ineq', 'fun': lambda x: 0.20 - turnover_constraint(x, current_weights)}
]
关键点:换手率约束和权重约束是联动的。如果你把单只股票上限设得很低,那每次调仓可能都需要大量买卖,换手率自然就高了。所以这几个约束要一起调,不能孤立地看。
1.4 三种约束的关系与权衡
这三种约束不是独立的。它们之间会互相影响。
我画了一张图,帮你理清思路:
从图上你能看出来,这三个约束是互相牵制的。权重约束影响行业约束的实现空间,行业约束又会影响换手率的高低。我建议的做法是:
- 先定权重约束:这是最基础的,决定了每只股票的可调范围
- 再定行业约束:在权重约束的基础上,控制行业暴露
- 最后调换手率:根据前两个约束的结果,设定合理的换手率上限
一个小技巧:如果你发现约束太多导致优化无解,可以试试「松弛变量」。就是给每个约束加一个允许偏离的小范围。比如行业约束从严格的30%改成25%-35%。这样优化器就有更多空间去找解。
1.5 实战中的常见问题
说了这么多,我总结几个实际中经常遇到的问题:
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 优化无解 | 约束太紧,没有可行域 | 放松约束,或使用松弛变量 |
| 换手率过高 | 权重约束和行业约束变化太大 | 增加换手率惩罚项,或分步调仓 |
| 行业集中度过高 | 权重约束没考虑行业分布 | 加入行业约束,限制单一行业暴露 |
| 实盘与回测偏差大 | 约束条件在回测中被忽略 | 回测时就用实盘相同的约束条件 |
嗯,最后说一句。约束处理这件事,没有标准答案。不同的策略、不同的市场环境,需要不同的约束组合。关键是要理解每个约束背后的逻辑,然后根据实际情况去调整。
我做了这么多年,最大的体会就是:好的约束不是限制你,而是保护你。它让你的策略在极端行情下也能稳住,不会因为一次失误就全军覆没。
公众号:蓝海资料掘金营,微信deep3321