2. 案例1

在行业轮动策略中,动量因子是最常用的打分维度之一:把各行业过去一段时间的涨幅排序,买入排名靠前的行业,定期换仓。看起来逻辑简单,但真正落地时,几乎所有人都会先遇到一个参数:回看窗口长度。用20个交易日、60个交易日还是120个交易日计算动量,会直接改变行业排序,进而改变持仓、换手和最终收益。本案例要处理的,正是"参数只动了一点点,回测曲线却像换了一个策略"这种典型现象。

先明确一个参照约定:本节所有动量都定义为区间收益率,即回看窗口末收盘价除以窗口初收盘价减一,数值越大表示动量越强,排序时降序取前N个行业。窗口长度以交易日为单位,全文与图示保持一致。

现象:一条平滑曲线旁边的"锯齿"

假设我们构建了一个最简行业轮动策略:以某行业指数池(示例取28个申万一级行业,数据为教学假设)为标的,每月最后一个交易日调仓,按动量因子降序取前5个行业等权持有,交易成本单边万分之五。固定其他条件,只把回看窗口从20日逐步增加到120日,步长5日,得到21组年化收益。

如果策略稳健,这21个点应该连成一条平缓的曲线——窗口长短只带来温和的收益衰减或提升。但实际常见的情况是:窗口取55日时年化收益约14%,取60日时骤降到3%,取65日又回到12%。相邻两个参数只差5个交易日,年化收益却相差11个百分点。这种"尖刺"就是参数敏感性的直观信号,它提示我们:策略的表现可能高度依赖某个特定窗口恰好捕捉到的几段行情,而不是依赖动量逻辑本身。

回看窗口 × 调仓频率:年化收益热力图(示例数据) 颜色越暖表示年化收益越高;同一行内颜色剧烈跳变即为参数敏感区 20日 40日 60日 80日 100日 120日 140日 周频 双周 月频 双月 季频 6.1 8.4 10.2 11.7 13.0 8.9 10.5 12.1 13.6 14.8 10.8 12.4 3.2 13.9 15.1 11.9 13.3 10.6 14.7 4.5 13.1 14.4 11.8 5.1 9.7 14.2 4.8 12.9 11.3 8.2 5.6 9.9 8.7 10.9 7.4 60日/月频骤降 120日/双周骤降 读图要点 · 暖色块集中区:参数稳健 · 相邻格跳变≥5个百分点: 敏感区,需重点排查 · 同一窗口跨调仓频率 表现不一致,说明收益 依赖特定换手节奏 · 孤立高收益格(如60日 季频15.1)多为偶然

上图是本节的核心诊断工具:以回看窗口为行、调仓频率为列,把年化收益填进格子里形成热力图。它比单条曲线更有信息量,因为可以同时观察两个参数维度的交互。图中用红色虚线框标出了两处典型敏感区:60日窗口在月频下收益骤降到3.2%,而相邻的40日和80日都在12%以上;120日窗口在双周频下只有4.8%,相邻的100日和140日却分别是14.4%和9.9%。这种"孤立低谷"比"整体偏低"更值得警惕,因为它说明策略在某组参数上恰好踩空了关键行情。

为什么会剧变:排序翻转与持仓断层

参数微调导致收益剧变的机制,通常不是因子计算本身出错,而是行业排序在临界点附近发生翻转。动量排序是一个离散选择过程:只要两个行业的动量值接近,窗口稍作变化就可能让它们的名次互换。如果互换发生在第5名和第6名之间,持仓就会换掉一个行业;如果这个行业恰好是后续涨幅最大的,收益就会明显偏离。

可以用一个简化的教学示例说明。假设某月末,行业A和行业B的动量值如下:20日窗口下A为8.2%、B为7.9%,A排第5、B排第6,策略买入A;把窗口改成25日后,A变为7.1%、B变为7.6%,名次互换,策略改买B。若下个月B上涨12%、A上涨2%,仅这一个持仓差异就带来约2个百分点的组合收益差(等权5只,单只权重20%,收益差10个百分点,贡献2个百分点)。一个月如此,多次累积就形成曲线上的尖刺。

更隐蔽的情况是持仓断层:某组参数下前5名行业高度集中于同一风格(例如都是周期股),而相邻参数下前5名分散到多个风格。这时组合的风险暴露发生质变,收益差异不再来自选股,而来自风格押注。热力图上表现为整行或整列的颜色突变,而不是单个格子。

参数稳定性检验:三步定位问题

识别敏感性不能只看一条曲线,建议按以下顺序做稳定性检验。

第一步,邻域扰动测试。对每个候选参数,取其上下各2个步长的邻域,计算邻域内收益的极差和标准差。以60日窗口为例,邻域为50、55、60、65、70日,若这5个值的年化收益极差超过8个百分点,就标记为敏感参数。这个阈值不是绝对的,可按策略换手和目标波动调整,但必须有明确标准,否则"感觉还行"无法复现。

第二步,排序稳定性度量。在每个月末,记录不同窗口下前5名行业的名单,计算名单重合度(交集数量除以5)。若某窗口与其邻域的平均重合度低于60%,说明排序本身不稳定,收益剧变只是表象。重合度是比收益更前置的指标,因为它不依赖后续行情,能更早暴露问题。

第三步,分段一致性检验。把回测区间切成若干段(例如按年度),分别计算每组参数在各段的收益排名。稳健参数应在多数分段中都处于中上位置,而不是靠某一年的极端行情拉高整体。若某参数的总收益很高但只在一年里领先,其余年份垫底,基本可以判定为过拟合。

建议把上述三项做成一张参数稳定性检查表,每个候选参数一行,记录邻域极差、平均名单重合度、分段胜率三个字段。三项中任意两项不达标,就不应把该参数作为主参数,更不能据此宣称策略有效。

热力图诊断的实操细节

热力图不是画出来好看就行,几个细节决定它能否真正用于判断。第一,颜色映射要固定:所有格子用同一套收益到颜色的映射,且最好以0为中性色、正收益偏暖、负收益偏冷,避免每组参数各自归一化导致视觉误导。第二,格子要标数值:颜色只提供直觉,判断跳变幅度必须看数字,相邻格差值超过阈值时用边框标出。第三,行列顺序要按参数单调排列,不能按收益排序,否则敏感区会被打散,看不出邻域关系。

在实现上,用Python的pandas做参数网格扫描即可:外层循环窗口长度,内层循环调仓频率,每个组合调用一次回测函数,把年化收益、最大回撤、换手率一起存入结果表,最后用透视表转成矩阵。示例伪代码如下,参数与数据均为教学假设:

windows = range(20, 145, 5)      # 回看窗口,交易日
freqs = ['W', '2W', 'M', '2M', 'Q']  # 调仓频率
results = []
for w in windows:
    for f in freqs:
        ret = backtest(momentum_window=w, rebalance=f,
                       top_n=5, cost=0.0005)
        results.append({'window': w, 'freq': f,
                        'ann_ret': ret.annual_return,
                        'mdd': ret.max_drawdown,
                        'turnover': ret.turnover})
grid = pd.DataFrame(results).pivot(
    index='window', columns='freq', values='ann_ret')

得到矩阵后,除了画热力图,还应计算一个简单的敏感度指标:对每一行求相邻列的收益差绝对值之和,再对每一列求相邻行的差绝对值之和,两者都大的区域就是二维敏感区。这个指标可以直接排序,帮助快速锁定需要人工复核的参数组合。

结果判读与处置

发现敏感区后,不要急着"换一组好看的参数"。正确的处置顺序是:先确认敏感区是否由少数几个调仓日的排序翻转造成,回看那几个月的行业动量值和持仓名单;再检查是否存在数据问题,例如某行业在窗口边界附近有停牌、分红除权未复权,导致收益率计算异常;最后才考虑参数本身是否合理。若确认是排序临界翻转,可以采取两种改进:一是对动量值做平滑(例如取多个窗口动量的均值或排名平均),降低单一窗口的偶然性;二是设置缓冲区,排名前5买入、跌出前8才卖出,减少临界点附近的频繁换手。

需要强调的是,参数稳定性检验的目的不是找到一个"最优参数",而是判断策略的收益是否来自可重复的逻辑。如果一组参数在邻域内表现平稳、名单重合度高、分段胜率稳定,即使它的绝对收益不是网格里最高的,也更值得作为主参数。反之,网格里那个孤立的最高点,往往正是过拟合最典型的伪装。