向量化与广播
行情与策略状态都以数组形式保存,多标的、多参数沿新增维度一次性参与运算,避免了 Python 逐事件循环里每个对象、每次函数调用的开销。代价是参数维度越多,占用的内存也越大,扫描前要先估算数据量。
vectorbt 的网格扫描五步按顺序做,每步给出真实代码、一句话说明和可验证的预期结果。示例沿用了双均线场景,参数命名与默认值以官方 API 文档为准。
扫描的输入仍然是「索引为时间、值为价格」的一列序列;数据来源可以是内置下载,也可以是自备 CSV(见数据接入页)。
import vectorbt as vbt
price = vbt.YFData.download('AAPL', start='2020-01-01').get('Close')
print(price.tail())
预期:打印出末尾若干行「日期 → 收盘价」。若这一步报网络类错误,先换本地 CSV 或国内数据源再继续——能不能下载到数据取决于数据源可达性,与扫描逻辑无关。
同一个访问器传入窗口列表,就一次性得到多组均线,而不是写循环逐个算。这一步决定了参数网格的横向长度。
fast = vbt.MA.run_combs(price, window=[5, 10, 20])
print(fast.window.values) # 每个组合对应的窗口参数
预期:返回的结果对象带上了「窗口」这一参数维度,属性名与返回值结构以官方 API 文档为准;若打印报属性不存在的错,说明该版本命名不同,改用文档中的对应属性即可,不影响后续流程。
双均线需要快线与慢线两组参数。分别扫描后,两者会按广播规则组合成一张「快线 × 慢线」的参数矩阵——网格的规模就是两个列表长度的乘积。
slow = vbt.MA.run_combs(price, window=[30, 60])
# 两套参数广播组合后,一次得到「快线 × 慢线」的全部组合
# 组合数 = 3 × 2 = 6 组,逐条交叉判断即可得到每组参数各自的信号
预期:结果带上了两个参数维度。注意组合数增长很快:各加 3 个取值就是 9 倍算量,所以网格要「先粗后细」,不要一上来就铺满。
把价格与整片信号矩阵交给组合回测入口,所有参数组合在同一批数据上跑完。成本假设在这一步就要定下来,否则不同组合之间不可比。
pf = vbt.Portfolio.from_signals(price, entries, exits)
print(pf.stats())
预期:返回一个组合对象,并打印出按参数维度分组的统计结果;口径与字段名以官方实现输出为准,不同版本可能增删字段。结果只反映这段历史区间的表现,不构成投资建议。
把某个绩效指标按两个参数维度铺成热力图,才能一眼看出「哪种参数区域整体表现接近」。
pf.total_return().vbt.heatmap()
预期:得到以快线、慢线窗口为坐标的热力图。看的是连成片的稳定区,而不是颜色突出一格——孤立高点通常来自个别行情偶然配合。
| 环节 / 参数 | 作用 | 取值怎么定 | 适用场景 | 注意点 |
|---|---|---|---|---|
| 窗口列表 | 决定要算哪些周期的指标 | 先取一个量级跨度较大的粗列表 | 均线类、趋势类策略的起步扫描 | 列表越长组合数增长越快,网格密度直接决定算量与过拟合程度 |
| 参数组合数 | 决定扫描的横向宽度 | 等于各参数列表长度的乘积 | 双参数及以上的策略 | 组合数上千以后必须先想清楚「证据够不够」,而不是直接跑 |
| 参数维度命名 | 让结果能被区分与排序 | 按参数角色命名(如快线 / 慢线) | 结果需要按参数分组查看时 | 名称只用于标识,不影响计算;命名混乱会让后续复盘无法进行 |
| 批量回测入口 | 把整片信号一次转成组合 | 输入买入、卖出两组布尔序列 | 买卖条件已明确、只差参数选择 | 成本假设必须一次定好并固定,中途改动会让整片结果失去可比性 |
| 费率与滑点 | 让结果贴近真实交易 | 按你自己的实际费率与滑点设定 | 信号频率偏高的参数组合 | 不计成本跑出来的突出组合,绝大多数在真实成本下会失效 |
| 网格区间与步长 | 决定扫描的粗细与范围 | 先粗后细,分两轮收窄 | 不确定有效参数区间时 | 把区间外推到极端取值得到的「好结果」没有解释力 |
| 结果指标 | 决定用哪个口径排序 | 总收益、夏普、回撤等交叉看 | 不同研究目标下的筛选 | 换一个指标排序往往会换出一组不同的「突出参数」,单一指标不足以定论 |
同样是「多组参数批量回测」,vectorbt 的做法与逐条循环差别很大,原因就在下面四件事上。
行情与策略状态都以数组形式保存,多标的、多参数沿新增维度一次性参与运算,避免了 Python 逐事件循环里每个对象、每次函数调用的开销。代价是参数维度越多,占用的内存也越大,扫描前要先估算数据量。
核心计算路径会用即时编译(JIT)把数值循环编成接近机器码的形式执行,因此同一段逻辑不必改写成 C 扩展。首次运行存在编译预热,之后复用编译结果,小任务上反而可能感觉更慢。
官方另提供 Rust 引擎作为可选扩展,用预编译替代部分即时编译开销,适合反复跑大规模网格的场景。它属于可选安装项,需要额外的工具链支持;装不上时退回基础安装仍可正常使用。
指标、信号与结果都以 pandas 对象为主,能直接沿用你熟悉的索引对齐、切片与聚合方式,也能与 pandas / NumPy 生态里的其他工具混用。好处是迁移成本低,代价是仍要遵守 pandas 的对齐规则——索引不一致时会静默出错而不是报错。
| 加速手段 | 作用 | 代价 / 边界 | 注意点 |
|---|---|---|---|
| 数组化与广播 | 把多标的、多参数一次算完 | 参数维度上升时内存占用同步上升 | 先估算网格总规模,再决定要不要一次跑完 |
| 即时编译(JIT)关键路径 | 让数值循环接近编译语言速度 | 首次调用有编译预热 | 小网格上预热成本占比高,别用一次小测试判断整体速度 |
| 可选预编译引擎 | 免去部分预热开销,适合反复扫描 | 需要额外安装与工具链 | 属于可选扩展,装不上可退回基础安装继续研究 |
| 按列组织任务 | 贴合连续内存布局,减少跨列随机访问 | 要求数据结构与缺失值口径统一 | 多标的混用不同列结构会让对齐成本悄悄吃掉收益 |
| 扫描阶段不生成图表 | 省下渲染与输出时间 | 过程中看不到中间结果 | 最终仍要抽样看图,全程不看容易把错误数据带入结论 |
| 控制数值精度与字段 | 降低内存占用与计算量 | 精度取舍会带来细微差异 | 关键结论要用原始精度复核一遍 |
| 分批跑大网格 | 避免一次性内存溢出 | 需要自行合并结果并保证口径一致 | 分批时成本假设、数据区间必须完全相同,否则结果不可比 |
vectorbt 参数扫描同样如此:有的场景能提高判断质量,有的场景只会得到无法解释的答案。扫描用于敏感性检查,不是自动寻找未来有效策略的工具。
逻辑已经确定,想知道结论对参数是否敏感:看某个参数小幅变化时收益是否平滑过渡。这类扫描的价值在于验证稳健性,而不是找到收益突出的那一格,输出通常是一段区间而不是一个点。
同一套逻辑在几十上百个标的上批量回测,看结论是否普遍成立。它能有效排除「只在某个标的上成立」的偶然结果,但前提是所有标的的数据区间、复权口径与缺失值处理方式完全一致。
为机器学习流程批量生成特征、标签与滚动切分,把向量化能力用在数据准备而不是策略选择上。这一环最容易出错的地方是信息泄漏:切分点之后的信息一旦进入特征,样本外评估就失去了意义。
| 研究场景 | 该不该做参数扫描 | 建议做法 | 注意点 |
|---|---|---|---|
| 逻辑明确,只想知道参数敏感度 | 该做 | 用粗网格看邻域是否成片 | 目的是稳健性体检,不追求收益突出的单点 |
| 逻辑说不清,指望扫出能赚钱的参数 | 不该做 | 先回头把买卖条件的逻辑写清楚 | 没有逻辑支撑的扫描结果无法解释,也无法在行情变化后判断何时失效 |
| 单标的、样本不足三年 | 谨慎 | 拉长样本,或换成多标的验证 | 样本太短时统计量不稳,扫出的差异多数来自噪声 |
| 多标的横截面策略 | 该做 | 统一口径,按标的与时间双重切分 | 横截面策略要求同一时点可比,缺失值处理必须一致 |
| 机器学习特征与标签准备 | 该做 | 切分训练与验证段,严格按时间推进 | 最怕标签泄漏,切分之后的数据不能出现在特征里 |
| 准备实盘、要定最终参数 | 谨慎 | 用样本外段与前向段确认 | 可以接受「不突出但稳定」的参数,而不是样本内表现特别好的那一组 |
| 组合数远超本机算力 | 该做但要收窄 | 先粗网格定位,再在局部加密 | 被算力限制逼着收窄网格,本身就是一次合理的筛选 |
VectorBT 网格扫描能给出候选参数区域,不能替代独立样本评估。Walk-forward 按时间推进:每轮只用过去窗口选参,再冻结参数跑后续测试窗,最后汇总所有测试窗及失败轮次。
| 风险点 | 现象 | 规避做法 | 注意点 |
|---|---|---|---|
| 多重比较(扫的组数太多) | 网格里总有一格收益明显高于其余 | 记录扫描组数,对该结果打折看待 | 组数越多,出现运气好的组合几乎是必然事件,与策略有效性无关 |
| 参数尖峰 | 相邻参数表现断崖式下滑 | 只选连成片的稳定区 | 尖峰通常由个别极端行情贡献,换一段数据就消失 |
| 样本内挑参数 | 样本内表现突出,样本外明显回落 | 训练段选参、样本外段复核 | 不能用整段样本挑参数、再拿整段样本汇报结果 |
| 区间敏感 | 换一个起止日期结论反转 | 在多段行情上分别复核 | 单一行情阶段(如单边上涨)的结果不能外推 |
| 忽略交易成本 | 高频进出组合回报异常高 | 统一施加费率与滑点再排序 | 事后挑选是否计成本,等于变相的参数择优 |
| 未做 walk-forward | 整段历史选参后只报告同一段结果 | 按时间滚动训练、选参和前向测试,汇总所有测试窗 | 测试窗不得参与前一轮选参;窗口与成本口径需预先固定 |
| 未做 walk-forward | 整段历史选参后只报告同一段结果 | 按时间滚动训练、选参和前向测试,汇总所有测试窗 | 测试窗不得参与前一轮选参,窗口和成本口径需预先固定 |
| 样本长度不足 | 只靠几十笔交易就下结论 | 拉长样本或换更活跃的标的 | 交易笔数太小时绩效统计量本身就不稳定 |
| 参数随行情漂移 | 早期有效、近期失效 | 改为滚动或前向验证 | 参数不是一次选定就长期有效,需要定期复核 |
| 标的池选择偏差 | 结论只在现存标的上成立 | 明确标的池的构建规则与纳入时点 | 标的池的构建方式本身就是一种选择,会系统性影响结论 |
vectorbt 的性能基准不引用“快 100 倍”这类脱离环境的宣传值。循环、Numba 与可选 Rust 路径的比较必须固定输入、机器和版本,并把首次 JIT 预热与稳态重复分开记录;未在同机实测前只说明测量方法。
| 记录项 | 至少保留的值 | 为何影响比较 | 报告建议 |
|---|---|---|---|
| 数据规模 | bar 数、资产数、参数组数、数据类型 | 规模决定矩阵与临时数组大小 | 报告总元素规模,不只写“若干标的” |
| 运行环境 | CPU、内存、操作系统、Python 与依赖版本 | 平台和编译器会改变计算路径 | 保存锁定依赖与完整执行命令 |
| 首次调用 | 进程冷启动及 JIT 编译耗时 | 预热可能主导小任务的耗时 | 与稳态重复结果分开列示 |
| 稳态重复 | 多次耗时与中位数、离散程度 | 单次计时易被缓存或后台任务干扰 | 说明重复次数、清理规则与统计方式 |
| 峰值内存与分块 | 扫描峰值、是否分块、是否中断 | 大矩阵可能先受内存限制而非算力限制 | 记录分块策略、批次参数范围与进程峰值 |
| 计算等价性 | 相同输入、信号、成本与输出字段 | 撮合语义不同则耗时不可比 | 对输出摘要做一致性校验并留档 |
| Rust 路径 | 实际安装版本、启用方式与回退情况 | 装了 extras 不代表所有代码路径都走 Rust | 按官方说明逐项核对,不据 extras 猜结论 |
| 分批等价性 | 整批与分批各跑一次的小样本对照 | 边界或广播顺序变化会改变结果 | 逐字段对账通过后再扩展到完整网格 |
vectorbt 跑完网格只是拿到候选结果,还需复核稳定性、基准、成本和未见时间段。
| 看什么 | 怎么判断 | 常见误读 | 下一步 |
|---|---|---|---|
| 热力图整体形态 | 颜色是否连成成片的区域 | 只盯颜色突出的一格 | 取稳定区中心附近的参数,而不是孤点 |
| 参数邻域梯度 | 平缓过渡优于陡峭断崖 | 把陡峭当成「参数精准」 | 降低网格密度或换区间再验一次 |
| 绩效指标组合 | 收益类与风险类指标交叉看 | 只看总收益排序 | 用回撤、风险调整后收益等口径复核 |
| 与基准对比 | 是否明显优于对照基准 | 忽略基准、只看绝对收益 | 计算相对表现,确认超额来自策略还是行情 |
| 交易笔数 | 样本量是否足以支撑判断 | 把笔数多当成策略可靠 | 笔数过少时结论只能算线索,不能当作证据 |
| 成本敏感性 | 提高成本假设后是否仍然成立 | 用零成本结果下结论 | 在更高成本下重跑,看稳定区是否还存在 |
| 结果的时间分布 | 收益是否高度集中在某一段行情 | 只看区间总收益 | 按子区间分别统计,识别对特定行情的依赖 |
会。参数和尝试次数越多,越容易在历史数据中找到偶然突出的组合。查看相邻参数区域、样本外窗口、walk-forward 和成本敏感性只能降低风险,不能消除数据挖掘偏差;实现与统计口径以官方文档为准。
run 面向单一参数组,算出一条指标序列;run_combs 面向参数列表,一次算出多组并带上参数维度,结果可直接进入批量回测与热力图。参数名、支持写法和默认行为以官方 API 文档为准,不同版本可能有差异;如果不确定,先用 run 在小样本上验证逻辑,再换 run_combs 做批量扫描。
没有通用数字,判断依据是你打算怎么解释结果:如果只是做敏感度体检,数百组通常就够看出邻域是否成片;如果需要精细定位稳定区,可以在粗扫后再局部加密。要注意组合数随参数个数增长很快,两组参数各取 10 个点就是 100 组,再加上多标的会成倍放大;同时网格越密,对样本外检验的要求也越高;网格上限以官方文档与本机资源为准
按顺序排查四件事:网格总规模是否已经过大、内存是否成为瓶颈(表现为频繁换页或直接中断)、是否在扫描过程中生成了多余的图表与报告、是否装了可选的预编译引擎(可选扩展,需要额外工具链)。常见做法是先粗后细、分批运行、扫描阶段不出图。实际速度取决于数据长度、网格规模与机器配置,以你本机实测为准;若只是想先验证一个想法、不打算维护这套环境,可以用本机技能Claw 本机已核验的量化类技能走一句话研究路线,两者是独立路线,没有已证实集成;性能表现以官方文档与本机实测为准
热力图以参数为坐标展示某个绩效指标,看的是「成片」而不是「一格」:相邻参数表现接近的区域才算稳定区,说明策略对该参数不敏感;孤立的高点通常由个别行情贡献。另外要固定热力图的口径——同一张图里混用不同数据区间或不同成本假设,会得出错误结论。指标口径与绘制参数以官方实现为准。
不建议直接使用。样本内表现突出只能说明「在这段历史上曾经有效」,落地前至少要做样本外与前向验证、确认成本敏感性、检查交易笔数是否足够。实盘还涉及资金与执行风险,需自行评估。至于分工:需要自己控制数据、成本与参数口径的研究走本页这条自建路线;只想先用自然语言问清研究问题、快速看行情与指标,可以看本机已核验的量化研究类技能。两条路线相互独立,无已证实集成,具体可用能力以各自官方说明为准。