换手率约束组合优化:把调仓成本写进目标函数
均值方差优化器每个月都想把组合翻个底朝天:估计噪声让最优权重月月剧烈跳动,5 资产月度调仓的年化换手高达 6.5 倍,30bp 单边成本直接把年化收益从 +1.75% 磨成 -0.22%。本文把 L1 换手惩罚项写进目标函数,用近端梯度求解:λ=0.0005 就把年化换手从 6.5 压到 2.4,净费后年化从 -0.22% 翻正到 +1.43%,最大回撤同步收窄 9 个百分点。5 组独立模拟扫描 λ 画出换手-净收益前沿,并诚实揭穿一个反直觉结论:10% 的无交易带几乎无效——因为估计噪声导致的权重摆动远大于 10%。附完整 Python 与四类真实陷阱(中高阶)。
均值方差优化有一个课本从不告诉你的隐藏账单:它每个月都想把你的组合翻个底朝天。你用过去半年的收益率估计期望和协方差,下个月数据滚动一格,估计值轻微抖动,优化器输出的”最优权重”却大幅跳动——因为 MV 优化对输入的敏感度是出了名的病态。权重月月大搬家,换手率就是你付给这种病态的月租。
结论先放这:在一个 5 资产、月度调仓的合成实验里,朴素 MV 的年化换手高达 6.5 倍(单次调仓平均搬动 54.5% 的仓位),30bp 单边成本把毛年化 +1.75% 直接磨成净 -0.22%。把 L1 换手惩罚 λ‖w−w_prev‖₁ 写进目标函数后,λ=0.0005 就让年化换手降到 2.4、净年化翻正到 +1.43%、最大回撤从 -35.7% 收窄到 -26.7%。 而一个看起来很合理的替代方案——10% 无交易带——几乎没用:换手只从 6.54 降到 6.18。原因很扎心:估计噪声导致的权重摆动远大于 10%,带子根本拦不住。
一、换手是估计噪声的放大镜#
先看问题有多严重。合成 5 个资产、5 年日频收益(预设年化收益 4%~10%、波动 8%~25%、相关矩阵非平凡),用 126 天滚动窗口估计 μ 和 Σ,每 21 天做一次标准 MV 优化(多头、归一化):
def mv_weights(mu, S, gamma=8.0):
"""无约束均值方差(多头归一化)"""
w = np.linalg.solve(gamma * S, mu)
w = np.clip(w, 0, None)
s = w.sum()
return w / s if s > 1e-12 else np.ones(len(mu)) / len(mu)python实测结果:单次调仓平均换手 0.545——也就是说每个月平均有 54.5% 的仓位在搬家,年化换手 6.54 倍。这不是市场变了,这是 126 天窗口里滚进滚出的几十个样本点把 μ 的估计搅得天翻地覆,优化器忠实地放大了这些噪声。
对着 30bp 的单边成本(对多资产组合含冲击成本这不算高),账单如下:
| 方案 | 年化换手 | 毛年化 | 净年化 | 净 Sharpe | 净 MDD |
|---|---|---|---|---|---|
| 朴素 MV | 6.54 | +1.75% | -0.22% | 0.05 | -35.7% |
| L1 惩罚 λ=0.0005 | 2.36 | +2.15% | +1.43% | 0.18 | -26.7% |
| 10% 无交易带 | 6.18 | +1.21% | -0.64% | 0.01 | -35.6% |
毛收益本来就不高(估计噪声下 MV 的通病),成本一扣直接转负。而 L1 惩罚不仅省了成本,毛收益反而更高——少折腾本身就是一种正则化,抑制了对噪声的过度反应。

二、把成本写进目标函数:L1 惩罚与近端梯度#
核心改动只有一项:优化目标从”最大化风险调整收益”变成”最大化风险调整收益减去调仓账单”:
第三项就是换手惩罚:每单位权重搬动收 λ 的罚金。λ 的经济含义非常直白——它就是你对单边交易成本的估计(若成本 30bp,λ 取 0.003 附近就是”按真实账单收费”)。
L1 项不可导,标准做法是近端梯度(proximal gradient):先按光滑部分走一步梯度,再对 (w − w_prev) 做软阈值收缩——小于阈值的变动直接归零,大于阈值的变动被削去一截:
def mv_weights_turnover(mu, S, w_prev, lam, gamma=8.0, iters=2000):
"""L1 换手惩罚 MV:近端投影梯度"""
w = w_prev.copy()
lr = 0.8
for _ in range(iters):
grad = mu - gamma * (S @ w)
w_new = w + lr * grad
# 近端算子:朝 w_prev 软阈值收缩
d = w_new - w_prev
d = np.sign(d) * np.maximum(np.abs(d) - lr * lam, 0.0)
w_new = np.clip(w_prev + d, 0, None)
w = w_new / w_new.sum()
return wpython软阈值的行为正是我们想要的交易逻辑:预期收益的改善不足以覆盖 λ 的搬仓费,就原地不动;足以覆盖,也只搬”值得搬”的那部分。这比事后砍单优雅得多——它让优化器在做决策时就知道交易不是免费的。

三、λ 扫描:换手-净收益前沿#
λ 是新引入的超参数,它的选择不能拍脑袋。用 5 组独立随机种子生成 5 套数据,对每个 λ 取平均(避免单一路径的运气污染结论):
| λ | 年化换手 | 净年化 | 净 Sharpe |
|---|---|---|---|
| 0 | 7.19 | 4.82% | 0.41 |
| 0.0002 | 3.84 | 5.30% | 0.49 |
| 0.0005 | 2.07 | 6.08% | 0.56 |
| 0.001 | 0.74 | 5.55% | 0.58 |
| 0.002 | 0.07 | 6.04% | 0.59 |
| 0.004 | ~0 | 6.57% | 0.60 |
两个规律值得盯住:
第一,换手对 λ 极度敏感。λ 从 0 到 0.0005(仅 5bp 的惩罚),换手从 7.2 倍腰斩再腰斩到 2.1 倍。这说明朴素 MV 的大部分交易是”边际改善微弱”的噪声交易——只要收一点点过路费,它们就自动消失了。
第二,净 Sharpe 在 λ 增大过程中单调爬升后趋平。在这个合成环境里甚至 λ 越大越好(因为预设的真实 μ 是常数,最优策略本来就该躺平持有)。真实市场中 alpha 会衰减、regime 会切换,λ 过大会让你抱着过期的仓位不放——前沿会呈倒 U 形,最优 λ 在成本率的 1~3 倍附近。

四、反直觉发现:无交易带为什么失灵#
无交易带(no-trade band)是实务中常见的土办法:目标权重和当前权重差距小于阈值(比如 10%)就不动。听起来和 L1 惩罚很像,实测却几乎无效——换手只从 6.54 降到 6.18,净收益依然为负。
原因值得写在墙上:无交易带假设权重变动大部分是小碎步,但 MV 估计噪声导致的权重摆动是大开大合的。这个实验里目标权重经常从 60% 直接跳到 5%(某资产的 μ 估计从正翻负),10% 的带子对这种量级的跳动毫无拦截能力。带子只过滤了本来就不多的小额调整,大额的噪声交易一笔没拦住。
L1 惩罚则不同:它不是”小于阈值不动”,而是对每一单位的搬动连续收费——大额搬动也要被削减到”边际收益 = 边际成本”的位置。这就是把成本写进目标函数与事后打补丁的本质差别。
无交易带并非一无是处:对指数跟踪、风险平价这类目标权重本身平稳的策略,它简单有效。它失灵的前提条件是”目标权重本身被噪声驱动大幅跳动”——而这恰恰是朴素 MV 的常态。
五、成本敏感性:成本越贵,纪律越值钱#
把单边成本从 5bp 扫到 50bp:

5bp 的低成本环境下(大型宽基 ETF、高流动性期货),三种方案差距不大,朴素 MV 甚至还能维持正收益——换手率约束在这里是锦上添花。但成本抬到 30bp 以上(小盘股、公司债、任何有冲击成本的规模化交易),朴素 MV 和无交易带的净收益深度转负,只有 L1 惩罚还站着。换手控制的价值与成本率成正比:交易越贵的市场,把成本写进目标函数越是生死攸关。
六、四类真实陷阱#
陷阱一:λ 与再平衡频率必须绑定校准。 同一个 λ,月度调仓和日度调仓的实际约束力差 21 倍——日度调仓每次搬动少但次数多,累计换手可能不降反升。换频率必须重新扫描 λ,不能沿用旧值。
陷阱二:L1 惩罚制造隐性锚定偏差。 w_prev 成为每次优化的引力中心,意味着你的持仓有路径依赖:同一天、同样的市场信息,去年建仓的组合和今天新建的组合会给出不同的”最优权重”。这在单账户里是特性(省成本),在多账户管理里是合规噩梦(同策略不同持仓)。
陷阱三:合成实验高估了惩罚的收益端好处。 本文预设真实 μ 恒定,“少动”天然正确,所以 λ 越大毛收益越高。真实市场 alpha 有半衰期:动量信号半衰期几周,λ 过大会让你系统性迟到。正确姿势是把 λ 对齐真实成本率,把 alpha 衰减速度作为 λ 的上限约束,而不是拿样本内净收益反推 λ。
陷阱四:换手率不等于成本的全部。 ∑|Δw| 计价的线性成本模型忽略了冲击成本的凹性(大单成本超线性增长)与买卖不对称(融券、印花税单边征收)。规模上去之后应把惩罚项升级为分段线性或 3/2 次幂冲击模型——数学上依然可以用近端算子处理,只是软阈值函数换个形状。
结语#
换手率约束的本质,是承认一个朴素的事实:优化器眼里的”更优权重”,扣掉搬仓费之后经常是”更差权重”。把 λ‖w−w_prev‖₁ 写进目标函数只需要十行代码,但它改变的是决策哲学——从”先算出理想仓位再心疼成本”变成”在下单之前就让成本参与投票”。这个实验里它把净年化从 -0.22% 拉回 +1.43%;在真实市场更高的成本和更大的规模下,这条惩罚项往往就是策略能不能活下来的分界线。