你用收益率、波动率、盘口成交、宏观因子训练模型,交叉验证分数漂亮,样本外回测也一路上扬。模型像装上了瞄准镜,扫损变少,胜率抬头,净值曲线甚至顺滑得不像交易系统。可一旦接入实时行情,信号开始失真,滑点放大,连续止损,最后不是模型崩溃,就是仓位把账户绞杀。
问题往往不在算法本身。随机森林没有突然失灵,神经网络也没有凭空变蠢。真正的问题是:你把带有未来信息的金融标签拆开、打散,再塞进了传统交叉验证。训练集和测试集表面上互不相同,时间窗口却已经互相咬住。
这就是金融时序建模里最隐蔽、也最致命的数据泄露。净化交叉验证不是换一种评分方式,而是重新规定训练样本和测试样本之间的边界:哪些样本必须剔除,哪些时间段必须封锁,哪些回测路径才算真正的样本外检验。
金融模型的回测分数,先要证明自己没有偷看未来;否则那条漂亮的净值曲线,只是泄露信息画出来的幻觉。
传统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样本,承认标签不是一个点,承认模型训练流程的每一层都可能泄露,承认一条漂亮曲线不能替代统计分布。
金融市场不会因为你的回测报告写得完整,就给你更好的成交价。标签窗口没有清干净,模型就会带着未来信息进场;禁运没有设置,序列相关性就会贴着测试边界逼空;路径没有拆开,单次盈利就可能只是行情给你的诱饵。
先把泄露击穿,再谈预测。否则下一次被扫损的,不是模型,而是你的本金。
相关阅读: 交易员决策疲劳应对策略:减少日内无效交易的系统化方案 、 全职交易员的社交孤立:享受孤独还是走向心理失衡?.
