一、约束处理入门:组合优化中的常见约束类型

做组合优化,说白了就是在一堆限制条件里找最优解。

我刚开始接触这块时,总觉得约束是麻烦事。后来踩过几次坑才明白——没有约束的优化,就像没有护栏的悬崖。你算出来的权重再漂亮,实际交易时根本没法用。

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 三种约束的关系与权衡

这三种约束不是独立的。它们之间会互相影响。

我画了一张图,帮你理清思路:

组合优化 约束系统 权重约束 单只股票上下限 整数手约束 行业约束 行业比例限制 相对基准偏离 换手率约束 单期/累计换手 交易成本控制 相互影响 相互影响 联动 三种约束必须协同调整,不能孤立优化

从图上你能看出来,这三个约束是互相牵制的。权重约束影响行业约束的实现空间,行业约束又会影响换手率的高低。我建议的做法是:

  1. 先定权重约束:这是最基础的,决定了每只股票的可调范围
  2. 再定行业约束:在权重约束的基础上,控制行业暴露
  3. 最后调换手率:根据前两个约束的结果,设定合理的换手率上限

一个小技巧:如果你发现约束太多导致优化无解,可以试试「松弛变量」。就是给每个约束加一个允许偏离的小范围。比如行业约束从严格的30%改成25%-35%。这样优化器就有更多空间去找解。

1.5 实战中的常见问题

说了这么多,我总结几个实际中经常遇到的问题:

问题 原因 解决方法
优化无解 约束太紧,没有可行域 放松约束,或使用松弛变量
换手率过高 权重约束和行业约束变化太大 增加换手率惩罚项,或分步调仓
行业集中度过高 权重约束没考虑行业分布 加入行业约束,限制单一行业暴露
实盘与回测偏差大 约束条件在回测中被忽略 回测时就用实盘相同的约束条件

嗯,最后说一句。约束处理这件事,没有标准答案。不同的策略、不同的市场环境,需要不同的约束组合。关键是要理解每个约束背后的逻辑,然后根据实际情况去调整。

我做了这么多年,最大的体会就是:好的约束不是限制你,而是保护你。它让你的策略在极端行情下也能稳住,不会因为一次失误就全军覆没。


公众号:蓝海资料掘金营,微信deep3321