halo 的技术博客

返回

均值方差优化有一个课本从不告诉你的隐藏账单:它每个月都想把你的组合翻个底朝天。你用过去半年的收益率估计期望和协方差,下个月数据滚动一格,估计值轻微抖动,优化器输出的”最优权重”却大幅跳动——因为 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
朴素 MV6.54+1.75%-0.22%0.05-35.7%
L1 惩罚 λ=0.00052.36+2.15%+1.43%0.18-26.7%
10% 无交易带6.18+1.21%-0.64%0.01-35.6%

毛收益本来就不高(估计噪声下 MV 的通病),成本一扣直接转负。而 L1 惩罚不仅省了成本,毛收益反而更高——少折腾本身就是一种正则化,抑制了对噪声的过度反应。

净费后净值对比:L1 惩罚跑赢朴素 MV 与无交易带,相对优势持续扩大

二、把成本写进目标函数:L1 惩罚与近端梯度#

核心改动只有一项:优化目标从”最大化风险调整收益”变成”最大化风险调整收益减去调仓账单”:

maxw  μwγ2wΣwλwwprev1\max_w \; \mu^\top w - \frac{\gamma}{2} w^\top \Sigma w - \lambda \|w - w_{prev}\|_1

第三项就是换手惩罚:每单位权重搬动收 λ 的罚金。λ 的经济含义非常直白——它就是你对单边交易成本的估计(若成本 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 w
python

软阈值的行为正是我们想要的交易逻辑:预期收益的改善不足以覆盖 λ 的搬仓费,就原地不动;足以覆盖,也只搬”值得搬”的那部分。这比事后砍单优雅得多——它让优化器在做决策时就知道交易不是免费的。

累计换手对比:朴素 MV 与无交易带的换手曲线几乎重合,L1 惩罚砍掉近三分之二

三、λ 扫描:换手-净收益前沿#

λ 是新引入的超参数,它的选择不能拍脑袋。用 5 组独立随机种子生成 5 套数据,对每个 λ 取平均(避免单一路径的运气污染结论):

λ年化换手净年化净 Sharpe
07.194.82%0.41
0.00023.845.30%0.49
0.00052.076.08%0.56
0.0010.745.55%0.58
0.0020.076.04%0.59
0.004~06.57%0.60

两个规律值得盯住:

第一,换手对 λ 极度敏感。λ 从 0 到 0.0005(仅 5bp 的惩罚),换手从 7.2 倍腰斩再腰斩到 2.1 倍。这说明朴素 MV 的大部分交易是”边际改善微弱”的噪声交易——只要收一点点过路费,它们就自动消失了。

第二,净 Sharpe 在 λ 增大过程中单调爬升后趋平。在这个合成环境里甚至 λ 越大越好(因为预设的真实 μ 是常数,最优策略本来就该躺平持有)。真实市场中 alpha 会衰减、regime 会切换,λ 过大会让你抱着过期的仓位不放——前沿会呈倒 U 形,最优 λ 在成本率的 1~3 倍附近。

换手-净收益前沿(5 组独立模拟平均):λ 加大换手骤降,净收益不降反升

四、反直觉发现:无交易带为什么失灵#

无交易带(no-trade band)是实务中常见的土办法:目标权重和当前权重差距小于阈值(比如 10%)就不动。听起来和 L1 惩罚很像,实测却几乎无效——换手只从 6.54 降到 6.18,净收益依然为负。

原因值得写在墙上:无交易带假设权重变动大部分是小碎步,但 MV 估计噪声导致的权重摆动是大开大合的。这个实验里目标权重经常从 60% 直接跳到 5%(某资产的 μ 估计从正翻负),10% 的带子对这种量级的跳动毫无拦截能力。带子只过滤了本来就不多的小额调整,大额的噪声交易一笔没拦住。

L1 惩罚则不同:它不是”小于阈值不动”,而是对每一单位的搬动连续收费——大额搬动也要被削减到”边际收益 = 边际成本”的位置。这就是把成本写进目标函数与事后打补丁的本质差别。

无交易带并非一无是处:对指数跟踪、风险平价这类目标权重本身平稳的策略,它简单有效。它失灵的前提条件是”目标权重本身被噪声驱动大幅跳动”——而这恰恰是朴素 MV 的常态。

五、成本敏感性:成本越贵,纪律越值钱#

把单边成本从 5bp 扫到 50bp:

成本敏感性:5bp 时三者接近,50bp 时只有 L1 惩罚还活着

5bp 的低成本环境下(大型宽基 ETF、高流动性期货),三种方案差距不大,朴素 MV 甚至还能维持正收益——换手率约束在这里是锦上添花。但成本抬到 30bp 以上(小盘股、公司债、任何有冲击成本的规模化交易),朴素 MV 和无交易带的净收益深度转负,只有 L1 惩罚还站着。换手控制的价值与成本率成正比:交易越贵的市场,把成本写进目标函数越是生死攸关。

六、四类真实陷阱#

陷阱一:λ 与再平衡频率必须绑定校准。 同一个 λ,月度调仓和日度调仓的实际约束力差 21 倍——日度调仓每次搬动少但次数多,累计换手可能不降反升。换频率必须重新扫描 λ,不能沿用旧值。

陷阱二:L1 惩罚制造隐性锚定偏差。 w_prev 成为每次优化的引力中心,意味着你的持仓有路径依赖:同一天、同样的市场信息,去年建仓的组合和今天新建的组合会给出不同的”最优权重”。这在单账户里是特性(省成本),在多账户管理里是合规噩梦(同策略不同持仓)。

陷阱三:合成实验高估了惩罚的收益端好处。 本文预设真实 μ 恒定,“少动”天然正确,所以 λ 越大毛收益越高。真实市场 alpha 有半衰期:动量信号半衰期几周,λ 过大会让你系统性迟到。正确姿势是把 λ 对齐真实成本率,把 alpha 衰减速度作为 λ 的上限约束,而不是拿样本内净收益反推 λ。

陷阱四:换手率不等于成本的全部。 ∑|Δw| 计价的线性成本模型忽略了冲击成本的凹性(大单成本超线性增长)与买卖不对称(融券、印花税单边征收)。规模上去之后应把惩罚项升级为分段线性或 3/2 次幂冲击模型——数学上依然可以用近端算子处理,只是软阈值函数换个形状。

结语#

换手率约束的本质,是承认一个朴素的事实:优化器眼里的”更优权重”,扣掉搬仓费之后经常是”更差权重”。把 λ‖w−w_prev‖₁ 写进目标函数只需要十行代码,但它改变的是决策哲学——从”先算出理想仓位再心疼成本”变成”在下单之前就让成本参与投票”。这个实验里它把净年化从 -0.22% 拉回 +1.43%;在真实市场更高的成本和更大的规模下,这条惩罚项往往就是策略能不能活下来的分界线。

换手率约束组合优化:把调仓成本写进目标函数
https://blog.halo26812.eu.org/blog/turnover-constrained-optimization
Author halo
Published at 2026年7月25日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨