3、权重边界约束:个股/资产权重上下限、行业权重限制、多空头限制
权重边界约束,说白了就是给投资组合里的每个资产「画个圈」。
你不能让某只股票占比太大,也不能让某个行业过于集中。更不能在明明只能做多的账户里偷偷做空。这些约束,是量化投资中最基础也最容易被忽视的「安全护栏」。
我个人习惯把权重约束分成三类来理解:
- 个股/资产权重上下限:单只股票最多买多少,最少留多少
- 行业权重限制:某个行业不能超过总仓位的X%
- 多空头限制:做多和做空的资金比例怎么分配
咱们一个一个来看。
3.1 个股权重上下限:别把鸡蛋放一个篮子里
这个约束最直观。你想想看,如果某只股票权重到了30%,它一天跌停,整个组合就亏3%。这谁受得了?
所以我们会设置:
- 上限:比如单只股票不超过总资产的5%或10%
- 下限:比如单只股票不低于0%(即不允许做空),或者不低于-2%(允许少量做空)
核心公式:
对于每只资产 i,其权重 w_i 必须满足:
w_min ≤ w_i ≤ w_max
其中 w_min 和 w_max 是预设的上下限。
我在项目中遇到过这样一个坑:有个策略跑得挺好,年化收益20%+,但回撤特别大。一查原因,发现某只小盘股权重到了15%。那段时间它连续跌停,组合直接崩了。从那以后,我所有策略都强制加上了单只股票5%的上限。
3.2 行业权重限制:别让行业风险绑架你
个股约束只能管住单只股票,但管不住行业集中度。
举个例子:你买了10只银行股,每只只占2%,看起来分散了。但整个银行板块占了20%的仓位。一旦银行出利空,你照样亏得厉害。
行业权重限制就是解决这个问题的:
- 设定每个行业的最大权重,比如不超过20%
- 也可以设定最小权重,比如不低于5%(如果你看好某个行业)
我的经验:行业分类最好用申万一级行业,或者你自己定义的行业标签。别用太细的分类,否则约束太多,优化器容易跑不出解。
我曾经做过一个回测,不加行业约束时夏普比率1.5,加了行业约束后降到1.2。但实盘表现反而更好。为什么?因为行业约束避免了「过拟合」——你回测里表现好的行业,未来不一定好。
3.3 多空头限制:做多和做空不是一回事
很多初学者以为「权重可以是负的,那就是做空」。但实际交易中,做多和做空的资金管理完全不同。
多空头限制通常包括:
- 总多头仓位:所有做多资产权重之和,比如不超过100%
- 总空头仓位:所有做空资产权重之和(取绝对值),比如不超过50%
- 净头寸:多头仓位 - 空头仓位,比如要求净头寸为0(市场中性)
注意:做空不是免费的。融券有成本,而且可能被强制平仓。所以空头仓位一定要留足安全边际。
我记得有一次帮朋友调试策略,他设了「净头寸为0」的约束,但没设空头上限。结果优化器给出了「做多200%,做空200%」的解——虽然净头寸是0,但杠杆高得吓人。这种策略一遇到波动就爆仓。
3.4 代码实现:用Python搞定权重约束
下面我用一个简单的例子,展示如何在优化中加入这些约束。
import numpy as np
from scipy.optimize import minimize
# 假设我们有5只股票
n_assets = 5
expected_returns = np.array([0.12, 0.08, 0.15, 0.10, 0.09])
cov_matrix = np.random.randn(n_assets, n_assets)
cov_matrix = cov_matrix @ cov_matrix.T # 确保正定
# 定义目标函数:最大化夏普比率
def neg_sharpe(weights):
port_return = np.dot(weights, expected_returns)
port_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
return -port_return / port_vol # 负号是因为我们要最小化
# 约束条件
constraints = [
# 1. 个股权重上下限:每只股票在0%到10%之间
{'type': 'ineq', 'fun': lambda w: w - 0.00}, # w >= 0%
{'type': 'ineq', 'fun': lambda w: 0.10 - w}, # w <= 10%
# 2. 行业权重限制:假设前两只股票属于"金融"行业
# 金融行业总权重不超过20%
{'type': 'ineq', 'fun': lambda w: 0.20 - (w[0] + w[1])},
# 3. 多空头限制:净头寸为0(市场中性)
{'type': 'eq', 'fun': lambda w: np.sum(w)}, # 权重之和为0
# 4. 总多头仓位不超过100%,总空头仓位不超过50%
{'type': 'ineq', 'fun': lambda w: 1.0 - np.sum(w[w > 0])}, # 多头 <= 100%
{'type': 'ineq', 'fun': lambda w: 0.5 - np.sum(np.abs(w[w < 0]))}, # 空头 <= 50%
]
# 初始权重(随机)
init_weights = np.random.randn(n_assets)
init_weights = init_weights / np.sum(np.abs(init_weights)) # 归一化
# 优化求解
result = minimize(neg_sharpe, init_weights, constraints=constraints,
method='SLSQP', options={'maxiter': 1000})
print("最优权重:", result.x)
print("负夏普比率:", result.fun)
代码说明:
- 我用的是SLSQP算法,它支持等式和不等式约束
- 个股上下限用了两个不等式约束:w >= 0% 和 w <= 10%
- 行业约束通过组合多个资产的权重来实现
- 多空头约束用等式约束保证净头寸为0
3.5 避坑指南:我踩过的几个坑
做权重约束优化,有几个地方特别容易出问题:
- 约束太紧导致无解:比如你既要求每只股票不超过5%,又要求行业集中度不低于30%,还要求净头寸为0。这种组合很可能无解。我的建议是:先放松约束,看看最优解在哪里,再逐步收紧。
- 忽略交易成本:权重约束会让调仓更频繁。每次调仓都有手续费和冲击成本。我一般会在优化目标里加上一个「换手率惩罚项」。
- 行业分类过时:有些股票业务变了,但行业标签没更新。比如某公司原来是房地产,现在转型做新能源。如果你还用旧标签,行业约束就形同虚设。
一个小技巧:在实盘前,先用历史数据做一次「约束压力测试」。看看在极端行情下(比如2020年3月),你的约束条件是否还能被满足。如果不行,说明约束太理想化了。
3.6 知识体系总览
下面这张图,把权重边界约束的核心逻辑串起来了:
这张图把三类约束并列展示,它们之间是互补关系。个股约束管微观,行业约束管中观,多空头约束管宏观。三者配合,才能构建一个真正稳健的组合。
最后提醒一句:约束条件不是越多越好。每加一个约束,优化器的可行域就缩小一圈。有时候,少即是多。
公众号:蓝海资料掘金营,微信deep3321