功能
前向滚动与交叉验证
通过滚动、扩展和清除重叠信息的切分,在样本外测试优化参数
优化后的策略在从未见过的数据上能否成立?VBT 在 Python 中运行前向滚动优化和时间序列交叉验证:将历史切分为训练和测试区间,在各训练区间选择参数,并仅在之后区间评估。
from itertools import product
data = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2025-01-01")
splitter = vbt.Splitter.from_rolling(
data.index,
length=455,
split=365,
offset=90,
offset_anchor="prev_start",
set_labels=["train", "test"],
)
splitter.n_splits16pairs = [(f, s) for f, s in product(range(10, 55, 5), range(60, 220, 20))]
fast_windows, slow_windows = zip(*pairs)
fast = vbt.MA.run(data.close, window=list(fast_windows), short_name="fast")
slow = vbt.MA.run(data.close, window=list(slow_windows), short_name="slow")
entries = fast.ma_above(slow)
exits = fast.ma_below(slow)
bounds = splitter.get_bounds(index_bounds=True)
rows, oos_returns = [], []
for split in range(splitter.n_splits):
train_start, train_end = bounds.loc[(split, "train")]
test_start, test_end = bounds.loc[(split, "test")]
train = slice(train_start, train_end - pd.Timedelta(days=1))
test = slice(test_start, test_end - pd.Timedelta(days=1))
train_pf = vbt.PF.from_signals(
data.loc[train], entries.loc[train], exits.loc[train], fees=0.001
)
best = train_pf.sharpe_ratio.idxmax()
test_pf = vbt.PF.from_signals(
data.loc[test], entries.loc[test, best], exits.loc[test, best], fees=0.001
)
rows.append(dict(
test_start=test_start.date(),
fast=best[0],
slow=best[1],
train_sharpe=train_pf.sharpe_ratio[best],
test_sharpe=test_pf.sharpe_ratio,
))
oos_returns.append(test_pf.returns)
summary = pd.DataFrame(rows)
summary.head(6).round(2) test_start fast slow train_sharpe test_sharpe
0 2020-12-31 10 200 3.19 3.56
1 2021-03-31 10 200 3.42 -2.00
2 2021-06-29 20 60 2.83 0.98
3 2021-09-27 20 60 2.97 0.12
4 2021-12-26 20 80 1.86 -2.01
5 2022-03-26 20 80 0.80 -1.74summary[["train_sharpe", "test_sharpe"]].mean().round(2)train_sharpe 1.92
test_sharpe 0.24
dtype: float64oos = pd.concat(oos_returns)
hold = data.close.loc[oos.index] / data.close.loc[oos.index].iloc[0]
print(round((1 + oos).prod(), 3), round(hold.iloc[-1], 3))2.744 3.36每个训练年中表现最佳的参数,样本内平均夏普比率为 1.92,而随后季度仅为 0.24。拼接后,样本外策略增长至 2.7 倍,持有比特币增长至 3.4 倍。揭示这种样本内外差距,正是前向滚动测试的目的。
前向滚动优化
前向滚动测试重复同一循环:在训练窗口优化,将结果应用于下一窗口,再向前移动。连接测试收益,评估重复选择过程。上方每个测试窗口独立模拟,若快均线高于慢均线,可在第一根 K
线入场。如果策略要求每个边界必须空仓,请在每个测试窗口最后一根 K
线添加退出;如果状态必须延续,请按实时模拟页面的方法,从一个窗口继续投资组合到下一个窗口。测试期投资组合可通过
vbt.PF.row_stack 沿时间连接,形成具备全部指标和图表的单个样本外投资组合。
您选择每次决策使用多少历史。滚动窗口保留固定长度的近期数据,例如最近一年。扩展窗口随新观测到来保留较早历史。按日历切分可匹配研究计划,例如每月选择参数或每季重新训练模型。
切分器
vbt.Splitter 构建切分:
| 构造器 | 切分形式 | 用途 |
|---|---|---|
from_rolling、from_n_rolling | 向前移动的固定长度窗口 | 在近期历史上重新优化 |
from_expanding、from_n_expanding | 从固定起点增长的窗口 | 保留全部可用训练历史 |
from_grouper | 月、季等日历区间 | 按时期比较绩效 |
from_n_random | 用于稳健性检查的随机窗口 | 测试不同历史起点 |
from_ranges、from_splits | 您定义的准确区间 | 自定义研究计划 |
from_purged_walkforward、from_purged_kfold | 清除重叠信息的切分 | 分离重叠的标签信息 |
每次切分可包含训练、测试等多个集合,以 K 线数、比例或时长指定大小,并在其间留出间隔。splitter.plot() 在时间轴上显示每次切分,get_coverage 和
get_overlap_matrix 衡量各集合覆盖多少数据,以及切分之间重叠多少,如下方切分器功能介绍所示。
在训练与测试之间留出间隔
扩展窗口可以在增长训练历史的同时,保留固定测试期及两者之间的间隔。这里每次测试覆盖三天,开始前留出两天。表格显示各集合第一个和最后一个包含的日期。
index = pd.date_range("2025-01-01", periods=18, freq="D")
splitter = vbt.Splitter.from_expanding(
index,
min_length=12,
offset=3,
split=(1.0, vbt.RelRange(length=2, is_gap=True), 3),
split_range_kwargs=dict(backwards=True),
set_labels=["train", "test"],
)
splitter.get_bounds(index_bounds=True, right_inclusive=True)bound start end
split set
0 train 2025-01-01 2025-01-07
test 2025-01-10 2025-01-12
1 train 2025-01-01 2025-01-10
test 2025-01-13 2025-01-15
2 train 2025-01-01 2025-01-13
test 2025-01-16 2025-01-18is_gap=True 将这些日期排除在两个集合之外。反向切分先保留测试期和间隔,再将剩余历史分配给训练。使用同一切分器,从价格、特征和标签取得匹配切片。
应用切分
splitter.take(obj) 将数据、指标或投资组合切成各区间,splitter.apply 在每个区间运行函数。@vbt.split
装饰器为任意函数添加切分,@vbt.cv_split 将切分与参数搜索结合:一次调用在每个训练集优化、选择最佳组合,并在测试集评估,如下方
CV 装饰器和切分装饰器功能介绍所示。结果按切分和集合标注,可直接比较。默认 cv_split
保留结果最高的组合,因此函数应为每种组合返回一个数字,例如夏普比率。设置 selection="min" 则保留最低者;基于网格结果的模板可应用其他任意规则,例如由多个指标构成的评分。
检查完整参数网格
使用 return_grid=True 保留训练网格,或用 return_grid="all"
在每个集合评估完整网格。这样可比较周围参数,了解强劲训练结果来自广泛区域还是孤立组合。选择仍来自训练结果。额外测试结果可用于研究稳健性。
跨资产与时间周期验证
在 cv_split 外层再包一层参数化函数,就能为每个资产独立运行验证流程。外层搜索选择资产,内层搜索在每个训练窗口选择策略参数。结果保留标签,方便比较哪些设置跨标的有效,哪些依赖单一市场。
切分边界也可在一个时间索引上定义,再应用于其他频率的数据。例如,定义日频训练和测试边界,再切片回测使用的分钟 K 线。
保留指标历史
测试窗口可使用更早价格预热均线,而不将这些早期 K 线计入绩效。像开头示例一样,预先计算只使用当前及历史价格的指标,或为函数提供历史缓冲区,在模拟前裁剪。如果长回看期会使较短测试窗口的大部分时间没有信号,这尤其有用。
清除重叠信息与组合交叉验证
当标签像许多机器学习目标一样使用未来信息时,靠近测试期的训练样本可能包含该测试期信息。清除(purging)移除这类样本,禁运期(embargo)按照 López de Prado
的方法,在每个测试期之后留出间隔。PurgedWalkForwardCV 对前向滚动折叠清除重叠信息,PurgedKFoldCV
运行组合清除交叉验证:选定数量折叠的每种组合都成为测试集,因此一段历史产生多次样本外测试,而非仅一次,如下方清除重叠信息的 CV功能介绍所示。
对于结果在不同时间才可获知的标签,请提供预测时间和评估时间,让清除过程考虑每个样本的信息区间。按时间重新训练使用清除重叠信息的前向滚动切分,比较多种训练测试安排则使用组合清除切分。下方十折中取两折测试的示例产生 45 种组合。
scikit-learn
切分器与 scikit-learn 双向兼容。vbt.SplitterCV 将 VBT 切分器转换为用于 GridSearchCV 或 cross_val_score 的
scikit-learn 交叉验证器,vbt.Splitter.from_sklearn 则将 scikit-learn 切分器转换为 VBT 切分器,以便绘图和分析。
这也为现有 scikit-learn 模型接入流程提供途径。一起切分特征和标签,在训练数据拟合模型,再在匹配测试集上评估预测。如果拟合模型、生成信号和回测预测需要独立步骤,您可将相同切片传入自己的 Python 流水线。
教程
会员专属的交叉验证(英文)教程通过切分器和装饰器演示完整验证流程。
✅ 基于 Marcos Lopez de Prado 的《金融机器学习进阶》,新增支持带清除重叠信息的前向滚动交叉验证(CV),以及带清除与禁运期的组合 CV。
splitter = vbt.Splitter.from_purged_kfold(
vbt.date_range("2024", "2025"),
n_folds=10,
n_test_folds=2,
purge_td="3 days",
embargo_td="3 days"
)
splitter.plots().show()✅ 大多数交叉验证任务都需要在训练数据上测试参数组合网格、选择最佳组合,再在测试数据验证。每次切分都要重复此过程。交叉验证装饰器将参数化与切分装饰器组合,以自动完成这一任务。
@vbt.cv_split(
splitter="from_rolling",
splitter_kwargs=dict(length=365, split=0.5, set_labels=["train", "test"]),
takeable_args=["data"],
parameterized_kwargs=dict(random_subset=100, seed=42),
merge_func="concat"
)
def sma_crossover_cv(data, fast_period, slow_period, metric):
fast_sma = data.run("sma", fast_period, hide_params=True)
slow_sma = data.run("sma", slow_period, hide_params=True)
entries = fast_sma.real_crossed_above(slow_sma)
exits = fast_sma.real_crossed_below(slow_sma)
pf = vbt.PF.from_signals(data, entries, exits, direction="both")
return pf.deep_getattr(metric)
sma_crossover_cv(
vbt.YFData.pull("BTC-USD", start="4 years ago"),
vbt.Param(np.arange(20, 50), condition="x < slow_period"),
vbt.Param(np.arange(20, 50)),
"trades.expectancy"
)split set fast_period slow_period
0 train 20 25 8.015725
test 20 23 0.573465
1 train 40 48 -4.356317
test 39 40 5.666271
2 train 24 45 18.253340
test 22 36 111.202831
3 train 20 31 54.626024
test 20 25 -1.596945
4 train 25 48 41.328588
test 25 30 6.620254
5 train 26 32 7.178085
test 24 29 4.087456
6 train 22 23 -0.581255
test 22 31 -2.494519
dtype: float64教程
更多内容请参阅交叉验证教程。
✅ 通常,要在每个切分上运行函数,必须针对传给函数的输入数据构建专用切分器。这意味着每次输入变化都要重建切分器。切分装饰器通过封装函数,获得全部参数并按需决定切分方式,从而自动化这一过程。它可以为任意 Python 函数“感染”上切分能力 🦠
@vbt.split(
splitter="from_grouper",
splitter_kwargs=dict(by="Q"),
takeable_args=["data"],
merge_func="concat"
)
def get_quarter_return(data):
return data.returns.vbt.returns.total()
data = vbt.YFData.pull("BTC-USD")
get_quarter_return(data.loc["2021"])Date
2021Q1 1.005805
2021Q2 -0.407050
2021Q3 0.304383
2021Q4 -0.037627
Freq: Q-DEC, dtype: float64get_quarter_return(data.loc["2022"])Date
2022Q1 -0.045047
2022Q2 -0.572515
2022Q3 0.008429
2022Q4 -0.143154
Freq: Q-DEC, dtype: float64教程
更多内容请参阅交叉验证教程。
✅ scikit-learn 中的切分器并不完全适合验证基于机器学习或规则的交易策略。VBT
提供强大类,支持许多对回测安全的切分方案,包括滚动窗口、扩展窗口、时间锚定窗口、用于分块自助法的随机窗口,甚至 Pandas 原生 groupby、resample
指令,例如表示月频的“M”。此外,生成的切分可轻松分析和可视化!例如,可检测切分或集合重叠,将全部切分转换为单一布尔掩码以自定义分析,对切分和集合分组,并分析它们的相互分布。这个类的代码行数超过整个
backtesting.py 软件包,别小看这位新王者!🦏
data = vbt.YFData.pull("BTC-USD", start="4 years ago")
splitter = vbt.Splitter.from_rolling(
data.index,
length="360 days",
split=0.5,
set_labels=["train", "test"],
freq="daily"
)
splitter.plots().show()教程
更多内容请参阅交叉验证教程。
版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。