数据与算法

净化交叉验证的底层数学机理:金融时序建模中数据泄露与截断阻断

金融时序模型最危险的错误,不是预测方向错了,而是回测时偷看了未来。…

净化交叉验证的底层数学机理:金融时序建模中数据泄露与截断阻断

你用收益率、波动率、盘口成交、宏观因子训练模型,交叉验证分数漂亮,样本外回测也一路上扬。模型像装上了瞄准镜,扫损变少,胜率抬头,净值曲线甚至顺滑得不像交易系统。可一旦接入实时行情,信号开始失真,滑点放大,连续止损,最后不是模型崩溃,就是仓位把账户绞杀。

问题往往不在算法本身。随机森林没有突然失灵,神经网络也没有凭空变蠢。真正的问题是:你把带有未来信息的金融标签拆开、打散,再塞进了传统交叉验证。训练集和测试集表面上互不相同,时间窗口却已经互相咬住。

这就是金融时序建模里最隐蔽、也最致命的数据泄露。净化交叉验证不是换一种评分方式,而是重新规定训练样本和测试样本之间的边界:哪些样本必须剔除,哪些时间段必须封锁,哪些回测路径才算真正的样本外检验。

金融模型的回测分数,先要证明自己没有偷看未来;否则那条漂亮的净值曲线,只是泄露信息画出来的幻觉。

传统K折交叉验证,为什么在金融市场里会失效

独立同分布假设,先被市场结构击穿

传统K折交叉验证建立在一个干净的统计前提上:样本近似独立同分布。数据可以被切成若干份,轮流拿一份做测试,其余部分做训练。训练集与测试集之间没有直接关系,模型在训练集上学习,在测试集上接受检验。

自然语言分类、图像识别、普通回归任务里,这个框架通常能工作。因为一张图片的标签不会延续到下一张图片,一封邮件的分类结果也不会受到几分钟后另一封邮件的影响。

金融市场不是这种东西。

分钟线、Tick数据、订单簿快照、因子值和未来收益之间存在明显的时间依赖。波动率会聚集,成交量会持续扩张或收缩,盘口买卖压力会延续,趋势和反转会在一段时间内连续出现。你今天看到的价格行为,和下一根、下十根K线之间并不是互相隔绝的白纸。

假设你在时间点 \(t\) 生成一个样本,特征来自当时已经可见的数据,标签定义为未来一段时间内的收益:

\[

y_t = \frac{P_{t+h}}{P_t} - 1

\]

这里的 \(h\) 是预测期限。若 \(h=20\),标签就不是一个瞬间,而是从 \(t\) 延续到 \(t+20\) 的未来收益窗口。

如果你用传统K折把样本随机打散,时间点 \(t\) 的样本可能进入训练集,而时间点 \(t+5\)、\(t+10\) 或 \(t+15\) 的样本进入测试集。两个样本虽然索引不同,标签窗口却发生了重叠。

模型不需要直接看见测试集标签,也能通过高度相关的训练样本摸到测试集答案的边缘。这个泄露很隐蔽,代码不会报错,数据维度不会错,训练过程也会正常收敛。只有实时交易时,账户会用连续扫损告诉你真相。

前瞻性标签不是一个时间点,而是一段时间区间

金融机器学习中,最容易被低估的对象就是标签。

很多人把标签理解成某一时刻的涨跌方向。例如,用 \(t\) 时刻的特征预测 \(t+1\) 时刻价格上涨还是下跌。这种标签窗口很短,重叠范围有限。

但真正的交易标签往往不是这么简单。你可能使用未来20根K线的收益,也可能等待止盈、止损或时间限制中的某一个先触发。此时,一个训练样本的标签对应的是一个未来区间,而不是一个孤立时间点。

可以把第 \(i\) 个样本的标签时间窗口写成:

\[

\lambda_i = [t_i, \tau_i]

\]

其中:

  • \(t_i\) 是样本起始时间;
  • \(\tau_i\) 是标签结束时间;
  • \(\lambda_i\) 是该样本从起点到标签确定之间占用的时间区间。

若测试样本的标签窗口是:

\[

\lambda_j = [t_j, \tau_j]

\]

当两个区间满足:

\[

\lambda_i \cap \lambda_j \neq \varnothing

\]

就意味着训练样本和测试样本的标签窗口发生重叠。

传统K折只检查样本索引有没有重复,不检查标签窗口有没有交叉。这就像把两笔期货订单分配给不同账户,却不看它们是不是同时在抢同一个盘口。表面分开,风险仍然叠在一起。

时间顺序切分,也不能自动解决泄露

有人发现随机K折不可靠,于是改成按时间顺序切分:前面的数据训练,后面的数据测试。方向没错,但这仍然不是完整答案。

假设训练集截止于时间 \(T\),测试集从 \(T\) 之后开始。训练集中最后几个样本的标签窗口可能延伸到测试区间内部。也就是说,训练样本在切分点之前产生,但它的未来标签覆盖了测试数据所在的时间段。

举例来说:

  • 训练样本起点:10:00;
  • 标签需要观察未来30分钟;
  • 训练集最后一个样本起点:10:50;
  • 测试集从11:00开始。

这个训练样本的标签窗口会延续到11:20。测试集从11:00开始,但训练阶段已经使用了11:00至11:20之间的信息来确定标签。时间顺序看起来没有倒置,标签信息却已经穿过了切分线。

这不是简单的“训练数据早于测试数据”就能解决的问题。你必须追踪每一个标签真正结束的时间。

净化机制:从训练集中剔除与测试标签重叠的样本

净化不是删除测试数据,而是清空污染区域

净化,也就是Purging,处理的是标签窗口重叠问题。

在每一次交叉验证中,测试集先被确定。接下来,不是直接把剩余样本全部交给模型,而是逐个检查训练样本的标签时间窗口。凡是与测试样本标签窗口发生重叠的训练样本,一律从训练集剔除。

假设测试集包含样本集合 \(E\),其标签窗口覆盖的总体时间范围为:

\[

[t_{\min}^{E}, \tau_{\max}^{E}]

\]

对于一个候选训练样本 \(i\),如果它的标签结束时间 \(\tau_i\) 晚于测试窗口开始时间 \(t_{\min}^{E}\),同时它的标签开始时间 \(t_i\) 早于测试窗口结束时间 \(\tau_{\max}^{E}\),就需要将其移出训练集:

\[

\tau_i > t_{\min}^{E}

\quad \text{且} \quad

t_i < \tau_{\max}^{E}

\]

这就是区间相交判断。

在工程实现中,不能只拿样本的观测时间戳做过滤。你需要为每个样本保存至少两个时间字段:

字段含义用途
样本起始时间特征可用、信号生成的时间判断样本何时进入系统
标签结束时间止盈、止损或时间限制最终确定的时间判断未来信息何时闭合
测试区间当前折中用于检验的样本范围作为净化参照
净化窗口测试标签覆盖的时间范围剔除重叠训练样本
禁运窗口测试块之后额外封锁的缓冲时间降低序列相关性影响

如果你的数据表只有一个时间戳,却没有标签结束时间,那么净化交叉验证很可能只是摆设。你连泄露发生在哪个区间都不知道,谈不上精确阻断。

用三屏障标注法时,标签结束时间必须动态记录

三屏障标注法把标签建立在三道屏障之上:

1. 止盈屏障;

2. 止损屏障;

3. 时间限制屏障。

样本从 \(t_i\) 开始向前观察,哪一道屏障先被触发,哪一个时间就决定标签的结束。可以写成:

\[

\tau_i = \min(\tau_i^{\text{止盈}}, \tau_i^{\text{止损}}, \tau_i^{\text{时间}})

\]

因此,标签窗口是:

\[

\lambda_i = [t_i, \tau_i]

\]

这里最容易出错的地方,是把所有标签的结束时间粗暴设成固定的 \(t_i+h\)。如果实际交易中止损在第3根K线被击穿,标签已经结束;如果价格一直没有碰到止盈和止损,直到时间限制才结束。两种样本的标签窗口长度根本不同。

你却把它们全部当成固定20根K线,净化窗口就会失真:

  • 窗口设短了,残留泄露;
  • 窗口设长了,训练样本被无谓删除;
  • 对高频数据直接使用低频窗口,模型有效样本会被大面积绞杀;
  • 对低频数据使用过短窗口,回测分数依然会虚高。

所以,三屏障标注法与净化交叉验证必须共享同一套事件结束时间。标签生成模块说样本在某时刻结束,交叉验证模块就必须使用这个时刻,而不是重新猜一个固定期限。

净化的刀口,不是切在样本编号上,而是切在标签真正闭合的时间边界上。

一个具体的时间切分例子

假设有连续生成的交易信号,每个信号在未来最多观察30分钟。测试块覆盖11:00至11:30。

训练集中有以下三个样本:

  • 样本甲:10:10生成,10:25触发止盈;
  • 样本乙:10:40生成,11:05触发止损;
  • 样本丙:10:50生成,11:35达到时间限制。

样本甲的标签窗口是10:10至10:25,与测试窗口不重叠,可以保留。

样本乙的标签窗口是10:40至11:05,穿过测试区间起点11:00,必须剔除。

样本丙的标签窗口是10:50至11:35,不仅穿过测试区间,还延伸到测试块结束之后,同样必须剔除。

如果只按照样本生成时间判断,乙和丙都早于11:00,程序会错误地把它们留在训练集中。模型训练时使用了标签最终在测试期内才确定的样本,这就是实打实的前瞻性泄露。

禁运机制:净化之后,为什么还要继续封锁

净化解决标签重叠,禁运解决残留相关性

净化和禁运不是一回事。

净化针对的是标签窗口相互重叠。测试标签覆盖到哪里,训练样本就清理到哪里。它处理的是可直接描述的时间区间交叉。

禁运,也就是Embargoing,处理的是测试块结束以后仍然可能延续的序列相关性。即便训练标签没有与测试标签发生重叠,市场状态、波动结构和特征序列仍可能在相邻时间内持续传递。

测试块刚结束,后面的数据未必立刻与测试块独立。波动率冲击会延续,盘口失衡会拖尾,成交量状态会缓慢回落,收益序列也可能表现出自相关。对依赖时间序列结构的特征而言,紧贴测试块结束位置的训练样本,仍然可能携带测试阶段的状态痕迹。

禁运的做法,是在测试块之后额外划出一段缓冲区,不把这段区域交给训练。训练样本必须避开:

1. 测试块本身;

2. 与测试标签窗口重叠的区域;

3. 测试块结束之后的禁运缓冲区。

这段缓冲区的长度通常记作 \(embargo\_td\)。它不是固定的市场常数,也不能从某个通用比例里直接抄走。高频盘口数据、日线趋势因子、宏观经济变量,其自相关结构不同,禁运窗口必须根据具体数据和标签构造设定。

为什么测试块之后还会有泄露风险

考虑一个波动率特征。它使用过去一段时间的收益计算,如果测试块中出现剧烈行情,测试块结束后的若干样本仍可能受到这一波波动冲击影响。

再看一个移动平均差值。它虽然没有使用未来数据,但在测试块结束后的短时间内,特征值仍然携带测试阶段价格变化造成的状态转移。如果你把紧邻测试块的数据重新放回训练,模型会学习到一个过于连续、过于理想化的市场状态衔接。

再比如订单簿因子。盘口深度、撤单速度、主动买卖成交比例常常在冲击行情后出现连续变化。单根K线没有直接偷看未来,不代表相邻样本已经独立。市场不会按照你的折叠线切断记忆。

可以把序列相关性粗略理解为:

\[

X_t \not\perp X_{t+\Delta}

\]

当 \(\Delta\) 很小时,两个时点的特征状态仍然存在依赖。禁运不是证明相关性彻底消失,而是主动留出缓冲,避免训练样本紧贴测试区间,降低这种依赖对评估结果的干扰。

禁运窗口不能替代净化窗口

这两个步骤经常被混成一个“留出一段时间”的粗糙操作,结果两边都做不好。

如果只有禁运,没有净化,训练集中仍可能存在标签窗口穿过测试区间的样本。你把测试块后面空出一段时间,却放任训练标签穿透测试块本身,泄露照样发生。

如果只有净化,没有禁运,重叠标签被清掉了,但测试块后的近邻序列仍可能携带高度相关的市场状态。模型分数未必立刻失控,却会被评估得过于乐观。

两者的分工可以这样看:

机制直接处理的问题剔除对象主要风险
净化标签时间窗口重叠与测试标签区间相交的训练样本前瞻性标签泄露
禁运测试块附近的序列相关性测试块之后的一段缓冲样本状态延续导致的评估偏差
普通时间切分保持基本时间顺序测试块之前或之后的数据无法自动处理标签重叠
随机K折提高样本利用率随机分配样本破坏时序结构并放大泄露

设定禁运长度,别拿拍脑袋当参数优化

禁运窗口没有适用于所有资产、所有周期的统一最佳比例。这个结论不讨喜,但交易系统不靠讨喜运行。

你需要观察数据本身的相关性结构:

  • 特征的自相关函数衰减到什么程度;
  • 波动率冲击持续多久;
  • 标签期限有多长;
  • 交易信号是否高频密集重叠;
  • 资产是否存在夜盘、跨日或跨市场联动;
  • 特征是否经过滚动窗口、平滑处理或状态滤波;
  • 数据是否存在缺失、停牌、节假日和不连续交易时段。

如果一个因子使用过去60个交易日的滚动统计,禁运只设置几个小时,谈不上切断它的历史依赖。如果策略是秒级盘口交易,直接照搬日线模型的禁运长度,又会把本来稀缺的样本一刀扫掉。

参数的目标不是让训练集尽可能大,而是让样本外评估足够接近真实上线环境。训练样本少一点,模型还能重新训练;验证结果被污染,实盘资金会直接爆仓。

组合式净化交叉验证:不再迷信一条回测净值曲线

单次回测路径,无法描述策略稳定性

传统做法经常只看一条回测曲线:前半段训练,后半段测试。曲线向上,策略通过;曲线回撤,策略淘汰。

这套方法的问题在于,单条路径只给出一个结果。它可能恰好经历了适合策略的行情,也可能恰好避开了最难的阶段。你看到的是一个数字、一条曲线、一次市场顺序,而不是模型在多种样本外组合下的性能分布。

组合式净化交叉验证,简称CPCV,试图把单次回测扩展成多条完全样本外的回测路径。

先将时间序列划分为 \(N\) 个连续数据块,每次选取 \(k\) 个数据块作为测试集。理论上的测试组合数量为:

\[

C(N,k)=\frac{N!}{k!(N-k)!}

\]

每一种测试块组合都要经过净化和禁运,训练集不能使用被污染的样本。这样得到的不是一条回测路径,而是一组不同时间组合下的样本外结果。

研究框架中常用的回测路径数量表达为:

\[

\varphi = \frac{k}{N} C(N,k)

\]

这个表达式的意义,不是让你盲目追求更多折数,而是说明:同一段历史数据可以被组织成多条样本外路径,模型性能应当以分布来观察,而不是押在一条顺风路径上。

CPCV真正改变的,是你看待模型成绩的方式

假设同一个模型在多条样本外路径上得到不同的年化收益、最大回撤、夏普比率和换手率。你不会再只问“模型的夏普是多少”,而会继续追问:

  • 不同路径之间的收益差异是否剧烈;
  • 最大回撤是否集中在某些市场状态;
  • 盈利是否依赖少数几个测试块;
  • 换手和滑点放大后,收益是否仍然存在;
  • 模型在趋势、震荡、极端波动阶段是否出现结构性失效;
  • 哪些路径中出现连续扫损,原因是标签定义、特征失效,还是交易执行问题。

一个平均分漂亮、路径分布极度发散的模型,不能被当成稳定系统。它可能只是吃到了某种行情结构。一旦状态切换,模型就会在盘口里失去方向感。

相反,一个收益不夸张、各路径表现更接近的模型,往往更值得进入后续验证。金融交易不是算法竞赛。模型不需要每一段行情都把利润吃干净,但必须知道自己在哪些条件下会失灵。

组合数量增加,不等于有效信息无限增加

CPCV也不是魔法。组合数 \(C(N,k)\) 增加后,计算开销会迅速上升,训练过程变重,结果之间还可能存在共享数据块带来的相关性。

如果历史样本本身很短,硬切出大量数据块,会造成每个测试块过小、训练样本不稳定。反过来,如果数据块过长,组合数量和状态覆盖又不足。你需要在计算资源、样本数量、标签期限和市场状态覆盖之间做平衡。

更麻烦的是,净化和禁运会进一步减少可用训练样本。表面上你拥有多年行情,真正进入某次训练的样本可能已经被测试区间、标签重叠和缓冲时间连续削掉。此时如果还在模型层面堆更深的网络、更复杂的特征,通常不是解决问题,而是在更少的数据上制造更强的过拟合。

从标签生成到交叉验证:一套不能脱节的实操链条

净化交叉验证最怕模块之间互相甩锅。标签团队只负责生成标签,特征团队只负责加工数据,验证团队拿着最终表格做K折。每个模块单独看似正确,组合起来却把未来信息放进了训练集。

真正可靠的流程,至少要把以下环节串起来。

第一步:先定义信息可用时刻

每个特征必须有明确的可用时间,而不是只记录数据所属日期。

例如,收盘价在收盘后才确定,盘中成交量会随着交易进程更新,宏观数据有发布时间和修订时间,财报字段还可能存在公告日与报告期的差异。你需要记录的是模型在真实交易时刻能否看到该字段,而不是数据库里那一行数据写着哪个日期。

如果特征的发布时间已经晚于信号生成时间,哪怕它来自历史数据,也属于前瞻信息。净化交叉验证解决不了特征工程本身的未来函数。

第二步:为每个样本生成真实标签结束时间

对固定期限收益标签,标签结束时间可以由预测期限计算。

对三屏障标签,必须记录实际触发屏障的时间。止盈先触发,结束时间就是止盈时刻;止损先触发,结束时间就是止损时刻;两者都没有击穿,才使用时间限制。

每个样本至少要形成这样的记录:

样本特征可用时间标签触发方式标签结束时间
\(t_1\)止盈\(\tau_1\)
\(t_2\)止损\(\tau_2\)
\(t_3\)时间限制\(\tau_3\)

不要把标签结束时间藏在临时变量里。它是交叉验证的核心字段,必须能够追溯、检查和复算。

第三步:先分测试块,再做净化

不能先把数据随机打散,再对每折做一点时间过滤。测试块应当依据连续时间区间建立,然后针对当前测试块扫描训练候选样本。

流程可以压缩为以下动作:

1. 将时间序列划分为连续数据块;

2. 选定当前测试数据块;

3. 找出测试样本的整体标签覆盖区间;

4. 从候选训练集中剔除标签窗口与测试区间相交的样本;

5. 在测试块之后应用禁运窗口;

6. 对剩余训练样本执行特征处理和模型训练;

7. 在测试块上进行完全样本外预测;

8. 保存预测结果、交易成本、滑点和路径指标。

顺序不能颠倒。尤其不能先用全量数据拟合标准化器、缺失值填补器或特征选择器,再切分训练测试。否则即便标签已经净化,特征处理阶段仍然会把测试分布的信息带回训练过程。

第四步:记录每次切分到底删掉了什么

工程上不要只保存训练索引和测试索引,还要记录:

  • 当前测试块的起止时间;
  • 测试标签的最早开始时间和最晚结束时间;
  • 净化删除的训练样本数量;
  • 禁运删除的样本数量;
  • 最终训练样本数量;
  • 训练集与测试集之间的最小时间距离;
  • 是否存在标签区间交叉;
  • 特征滚动窗口的最大回看长度;
  • 训练过程中是否重新拟合了数据预处理组件。

这些日志不是给审计人员看的装饰品,而是排查泄露的弹药。某一折结果异常漂亮,先查它删了多少样本、测试窗口是否跨越夜盘、标签结束时间是否完整,而不是先给模型换参数。

净化交叉验证与滚动特征,最容易撞上的几个坑

滚动统计不一定安全,关键看边界

只使用过去数据的滚动均值、滚动波动率和移动平均,本身不等于安全。你要检查滚动窗口是否跨越了训练测试边界,以及窗口内的数据是否在真实交易时刻可用。

如果测试样本位于未来区间,使用其自身过去数据通常符合实时交易逻辑。但如果你先在全量数据上进行平滑、去噪、填充或归一化,再切分,操作就可能混入测试分布。

尤其是全样本标准化:

\[

z_t = \frac{x_t-\mu_{\text{all}}}{\sigma_{\text{all}}}

\]

这里的 \(\mu_{\text{all}}\) 和 \(\sigma_{\text{all}}\) 使用了全量样本,包括测试区间。正确做法是在每一折中只用训练集计算参数,再将训练参数应用到测试集:

\[

z_t^{\text{test}} =

\frac{x_t^{\text{test}}-\mu_{\text{train}}}

{\sigma_{\text{train}}}

\]

否则你只是把泄露从标签层转移到了预处理层。

事件样本密集时,训练数据会被大面积清理

当信号每分钟生成一次,而每个标签观察期限是30分钟,样本标签窗口必然高度重叠。一个测试块可能牵动大量训练样本被净化剔除。

这不是算法故障,而是数据结构决定的结果。你如果坚持保留所有重叠样本,就必须承认这些样本之间不是独立观测。把样本数量写得很大,不等于获得了同等数量的独立信息。

此时可以从几个方向处理:

  • 降低事件触发频率,避免每个时间点都生成标签;
  • 对相邻信号进行事件采样;
  • 重新设计标签期限,使其与策略持仓周期匹配;
  • 在评估中明确记录有效样本减少情况;
  • 不要因为训练样本下降,就偷偷取消净化。

期货跨交易时段,时间窗口不能只按自然时间计算

期货市场存在日盘、夜盘、休市和跨日交易时段。标签窗口按自然时间推进,和按实际交易分钟推进,得到的结果可能不同。

例如,夜盘结束后的休市时间不产生行情,但标签期限仍可能按交易日规则计算。你必须先明确标签定义:

  • 按固定自然时间结束;
  • 按固定交易K线数量结束;
  • 按下一个交易时段结束;
  • 按屏障触发结束。

这些规则会直接改变 \(\tau_i\),进而改变净化区域和禁运区域。时间索引如果没有处理交易日历,交叉验证的边界就会被休市时段扭曲。

高频盘口数据不能只看价格标签

盘口策略的泄露来源比价格收益更复杂。买一价、卖一价、队列深度、撤单比例、主动成交方向、撮合延迟,这些数据的时间戳必须统一到真实可用顺序。

如果你使用了某个时间桶结束时才汇总完成的盘口特征,却把它标记成时间桶开始时可用,模型已经提前获得了未来成交信息。净化只负责处理样本之间的标签和序列关系,不能替你修复错误时间戳。

更不能把同一笔成交拆成多个样本后,假装它们是互相独立的证据。市场微观结构里,重复采样会把一段行情的影响放大,让模型在回测里吃货,在实盘里被滑点反噬。

如何判断净化是否真的生效

净化交叉验证不是加上一个类名、传入两个参数,就能自动宣告模型清白。你需要对切分结果做可验证的检查。

先查标签区间有没有交叉

对每一次折叠,逐个检查训练样本和测试样本的标签区间。若存在:

\[

\max(t_i,t_j)

<

\min(\tau_i,\tau_j)

\]

就说明两个区间发生重叠,净化没有完成,或者区间边界定义存在错误。

在实际数据量较大时,不一定需要两两扫描,但必须通过排序、区间索引或专门的数据结构完成同等判断。不要只检查训练和测试的样本编号是否重复,那是最没有价值的检查。

再看禁运边界是否真正落地

测试块结束后,禁运区间必须从训练集中消失。需要检查测试结束时间与最近训练样本的时间距离,确认它是否满足设定的 \(embargo\_td\)。

这里还要注意标签结束时间。某些训练样本虽然起始时间很早,但标签结束时间很晚。它们可能跨过测试块结束位置,不能因为样本起点早就被放回训练集。

最后比较普通K折、时间切分和净化结果

三种评估方式放在一起,通常能暴露问题:

评估方式看到的成绩可能反映的内容
随机K折往往偏乐观样本相关性和标签泄露被混入
普通时间切分比随机K折保守保留时间顺序,但未必处理标签重叠
净化加禁运通常更接近真实同时处理标签交叉与近邻序列相关性

不能机械地认为净化后的分数必须更低。你的模型可能本来就没有泄露,或者标签期限极短,净化影响有限。但如果随机K折和普通时间切分成绩极高,加入净化后收益、胜率和稳定性同时大幅回落,就要认真检查原先的评估是否被污染。

评估结果下降不是坏消息。它只是把隐藏的亏损提前暴露在纸面上,而不是等账户在实盘里替你缴学费。

从单一指标转向路径分布与交易结果

金融模型的性能不能只看分类准确率。标签定义、交易成本、持仓期限和仓位管理都会改变最终收益。

在CPCV产生的多条样本外路径上,至少应当同时观察:

  • 累计收益;
  • 最大回撤;
  • 夏普比率;
  • 胜率与盈亏比;
  • 连续亏损次数;
  • 换手率;
  • 手续费和滑点;
  • 不同波动状态下的表现;
  • 信号数量是否集中在少数测试块;
  • 交易结果是否依赖个别极端行情。

尤其是滑点。模型在标签层面预测正确,不代表订单能按回测价格成交。高频策略一旦把盘口冲击、排队位置和成交延迟加入,收益可能被直接击穿。净化交叉验证解决的是统计评估污染,不会替你解决执行层问题。别把统计洁净误认为交易可行。

一套没有经过净化和禁运的回测,连入场逻辑都尚未获得审判资格,更谈不上用收益曲线证明自己。

路径分布还可以帮助你识别策略依赖。若模型只在某几个测试块盈利,其他路径持续亏损,说明它可能在吃单一市场状态。若模型在不同路径中收益有限但回撤结构接近,后续再叠加成本和容量分析,才有继续研究的价值。

一条实战上的判断原则

面对任何金融时序模型,我会先问五个问题:

1. 标签从什么时候开始,到什么时候真正闭合;

2. 训练样本和测试样本的标签窗口是否发生交叉;

3. 测试块之后是否设置了与序列相关性匹配的禁运区间;

4. 特征处理、标准化和选择过程是否只在训练集内拟合;

5. 模型是否通过多条完全样本外路径接受检验。

这五个问题有一个答不上来,就不要急着讨论网络层数、学习率或特征重要性。连数据边界都没守住,继续调参只是给泄露的模型抛光。

净化交叉验证的底层数学机理并不玄妙:把每个样本看成带有时间起点和结束点的区间,遇到测试区间就净化重叠部分,测试结束后再留出禁运缓冲,最后用多种测试块组合构造样本外路径。

真正难的是执行。你必须承认金融数据不是IID样本,承认标签不是一个点,承认模型训练流程的每一层都可能泄露,承认一条漂亮曲线不能替代统计分布。

金融市场不会因为你的回测报告写得完整,就给你更好的成交价。标签窗口没有清干净,模型就会带着未来信息进场;禁运没有设置,序列相关性就会贴着测试边界逼空;路径没有拆开,单次盈利就可能只是行情给你的诱饵。

先把泄露击穿,再谈预测。否则下一次被扫损的,不是模型,而是你的本金。

相关阅读: 交易员决策疲劳应对策略:减少日内无效交易的系统化方案全职交易员的社交孤立:享受孤独还是走向心理失衡?.

常见问题

为什么传统K折交叉验证在金融时序建模中会失效?
传统K折假设样本独立同分布,但金融数据存在显著的时间依赖性。随机打散样本会导致训练集和测试集的标签窗口发生重叠,使模型在训练时“偷看”到未来信息。
什么是净化(Purging)?它与禁运(Embargoing)有何区别?
净化是指剔除训练集中与测试集标签窗口重叠的样本,解决前瞻性泄露问题;禁运则是在测试块结束后额外封锁一段缓冲区,以消除测试块后仍存在的序列相关性影响。
如何确定标签的结束时间?
标签结束时间应根据实际触发条件动态记录,例如三屏障标注法中,止盈、止损或时间限制中哪一个先触发,该时刻即为标签结束时间。
为什么不能在全量数据上进行标准化处理?
在全量数据上计算均值和标准差会引入测试集分布信息,导致数据泄露。正确的做法是在每一折中仅使用训练集计算参数,再应用到测试集。
组合式净化交叉验证(CPCV)的优势是什么?
CPCV通过将时间序列划分为多个数据块并进行多种组合回测,能生成多条样本外路径,帮助识别模型在不同市场状态下的表现分布,而非仅依赖单一回测曲线。