深圳融克迪特科技有限公司 Logo,金融科技,量化交易,软件开发

深圳融克迪特科技有限公司

RONG CREDIT TECHNOLOGY CO., LTD.

基础入门

【vectorbt 系列 第2讲】准备最小回测数据

本讲把 vectorbt 第 1 讲的价格对象压成一张更适合继续加工的最小回测表,覆盖单资产收窄、列名整理、顺序编号和工作副本维护。目标是为后续均线、信号和回测入口准备一份结构稳定、可解释、可复查的底表。

2026-04-23 智铨研究 阅读时长 8 分钟

目录

  1. 本节目标
  2. 为什么要先准备“最小回测数据”
  3. 复用第 1 讲的vbt_price_data
  4. 四步整理出最小回测数据
  5. 先看形状和列名
  6. 先选定一列做单资产练习
  7. 重命名成更直接的工作表字段
  8. 增加一列顺序编号辅助查看
  9. 确认这份最小回测数据已经够用
  10. 它仍然是 DataFrame
  11. 至少保住了价格列
  12. 价格列可以继续用于数值计算
  13. 行顺序清楚
  14. 为什么第 2 讲要先压成单资产表
  15. 明明 vectorbt 支持多资产,为什么先退回一列
  16. 为什么要改成close这么普通的列名
  17. reset_index 会不会破坏原数据
  18. 哪些情况仍然算本讲完成
  19. 写一个最小准备函数
  20. 完成标准
  21. 为什么“最小”比“完整”更重要
  22. 最小回测表到底应该服务什么
  23. 保留原对象,同时维护工作副本
  24. 为什么顺序编号对新手尤其有用
  25. 把第 2 讲做成一份“后面可以一直沿用”的底表
  26. 第 2 讲在整个 vectorbt 短课里的作用
  27. 系列衔接
  28. 风险揭示与免责声明

1. 本节目标

第 2 讲的任务很纯粹:把第 1 讲里构造出来的价格序列对象,整理成一份最小回测数据集,并明确后面几讲会一直围绕哪些列和哪些形状继续往下走。如果第 1 讲解决的是“vectorbt 环境和 PriceData 能不能成立”,那第 2 讲解决的就是“这份数据能不能真正拿来做均线、信号和回测”。

学完这一讲后,先把下面几件事做扎实:

  1. 知道最小回测数据集应该保留哪些列。
  2. 会把当前价格对象整理成后续计算更方便的 DataFrame 视图。
  3. 理解为什么在真正开始做信号前,先把输入表看清楚比什么都重要。

2. 为什么要先准备“最小回测数据”

vectorbt 很强,但也正因为强,新手很容易一开始就把问题做复杂,比如同时上多资产、多指标、多参数扫描。第 2 讲故意反过来:先只保留最小输入,把后面真的要用到的数据对象准备清楚。这样做有三个直接好处:

  1. 后面算均线时不会被无关列分散注意力。
  2. 构造买卖信号时,输入表结构是稳定的。
  3. 你能把“回测结果不对”先和“输入数据不清楚”分开。

在向量化框架里,输入表越明确,后面的调试成本越低。

3. 复用第 1 讲的vbt_price_data

本讲默认你已经完成第 1 讲,手上有一个可用的 vbt_price_data

import vectorbt as vbt
import pandas as pd
import numpy as np

close_df = vbt_price_data.close.copy()
print(close_df.head())

现在的关键不是重新造数据,而是把它整理成后面几讲反复会看的“最小回测表”。

4. 四步整理出最小回测数据

这一讲的目标是把原始价格表压成一张足够轻、但还能继续往后传递的工作表。下面四步分别处理结构、字段语义和查看便利性。

5. 先看形状和列名

print(close_df.shape)
print(close_df.columns.tolist())
print(close_df.index)

这一步的意义很简单:确认当前数据到底有几行几列、列名是谁、索引是什么。后面无论你算均线还是构造信号,本质上都在这些维度上继续加工。

6. 先选定一列做单资产练习

虽然第 1 讲构造了三列数据,但第 2 讲建议先只选一列当作最小练习对象:

asset_col = close_df.columns[0]
single_df = close_df[[asset_col]].copy()
print(single_df.head())

这不是在削弱 vectorbt 的多资产能力,而是在为入门者降低复杂度。单资产先走通,后面再回到多资产,会稳很多。

7. 重命名成更直接的工作表字段

single_df = single_df.rename(columns={asset_col: "close"})
print(single_df.head())

后面几讲如果一直带着原始资产名,新手很容易把“列名”和“字段语义”混在一起。先在单资产练习里把它改成 close,阅读和调试都会更直接。

8. 增加一列顺序编号辅助查看

single_df = single_df.reset_index().rename(columns={single_df.index.name or 'index': 'date'})
single_df['row_id'] = np.arange(len(single_df))
print(single_df.head())

这一步不是必须用于生产,而是非常适合入门调试。因为你后面在看均线、信号、入场出场时,经常需要快速对照“第几行发生了什么”。

9. 确认这份最小回测数据已经够用

验证不求花哨,只要确认这张表还能算、还能看、行顺序也清楚,就已经够支撑后面几讲。

10. 它仍然是 DataFrame

assert isinstance(single_df, pd.DataFrame)

11. 至少保住了价格列

assert 'close' in single_df.columns

12. 价格列可以继续用于数值计算

assert pd.to_numeric(single_df['close'], errors='coerce').notna().all()

13. 行顺序清楚

assert 'row_id' in single_df.columns
assert single_df['row_id'].iloc[0] == 0

14. 为什么第 2 讲要先压成单资产表

这里最容易出现的疑问,几乎都和“为什么不一步到位保留全部原始结构”有关。先把最小链路走通,比一开始追求完整更重要。

15. 明明 vectorbt 支持多资产,为什么先退回一列

因为入门阶段真正难的不是算力,而是看懂数据流。单列先走通,你才能看清后面 5 日均线、买入信号和回测净值之间的关系。

16. 为什么要改成close这么普通的列名

为了让你把注意力放在“这是价格列”上,而不是总被资产名分心。后面再回到多资产时,再保留原始列名也不迟。

17. reset_index 会不会破坏原数据

这里只是为了做一份更容易查看的练习表,不是在破坏底层 PriceData。第 2 讲的重点是“看清楚”,不是“保持最原汁原味的对象结构”。

18. 哪些情况仍然算本讲完成

  1. 你最终只保留一列 close 来练,完全没问题。
  2. 你也可以保留日期索引,不一定非要 reset_index(),只要自己能稳定看懂就行。
  3. 你整理的是单资产表,不代表后面不能回到多资产。
  4. row_id 只是调试辅助列,不是必须的业务字段。

19. 写一个最小准备函数

import numpy as np
import pandas as pd

def prepare_min_bt_frame(close_df: pd.DataFrame) -> pd.DataFrame:
    asset_col = close_df.columns[0]
    out = close_df[[asset_col]].copy().rename(columns={asset_col: 'close'})
    out = out.reset_index().rename(columns={out.index.name or 'index': 'date'})
    out['row_id'] = np.arange(len(out))
    return out

single_df = prepare_min_bt_frame(vbt_price_data.close)
print(single_df.head())

后面几讲只要这份表还在,你就能持续在同一份最小输入上往下推进。

20. 完成标准

如果你现在能说出“我已经把 vectorbt 的原始价格对象整理成一张只有日期、收盘价和顺序编号的最小回测表”,那第 2 讲就算真的完成了。

21. 为什么“最小”比“完整”更重要

很多人一接触 vectorbt,就会天然想把它当成一个“可以一下子做很多事”的工具。这种直觉没错,但对入门者来说,越早追求完整,越容易把自己绕进去。第 2 讲刻意强调“最小回测数据”,背后的考虑是:

  1. 当你只保留一列 close 时,后面的每一列衍生结果都更好解释。
  2. 当你把日期和顺序编号明确放出来时,后面一旦结果不对,很容易定位到具体哪一行。
  3. 当表足够小、足够干净时,你在脑子里也能把它想清楚,而不是只剩下一堆 API 名字。

对入门回测来说,这种“我脑子里能跟住数据流”的状态,比一开始就把所有能力都打开更重要。

22. 最小回测表到底应该服务什么

第 2 讲做出来的 single_df,重点不在于停留在展示层,更在于后面几讲持续复用。它至少要能服务下面几件事:

  1. 第 3 讲在 close 列上计算 5 日均线。
  2. 第 4 讲根据 close 和均线构造布尔信号。
  3. 第 5 讲继续在同一张表上构造卖出条件。
  4. 第 6 讲把这些列交给 vectorbt 的回测入口。

也就是说,这张表重点不在于“好看”,更在于“后面真的拿它干活”。只要你用这个标准回头检查,就会知道哪些列该留,哪些列可以先不引入。

23. 保留原对象,同时维护工作副本

入门阶段很推荐你把底层价格对象和工作表分开保存。比如:

raw_close_df = vbt_price_data.close.copy()
work_df = prepare_min_bt_frame(raw_close_df)
print(raw_close_df.head())
print(work_df.head())

这样做的价值很直接:当你后面不断往 work_df 里加均线、信号、收益列时,底层原始价格表仍然在。你随时可以回头对照“问题到底出在输入,还是出在中间加工步骤”。

24. 为什么顺序编号对新手尤其有用

很多有经验的人在看回测时,直接看日期索引就够了。但新手一开始常常会在“第几行”和“哪一天”之间来回找不到对应关系。所以第 2 讲特地建议你加一个 row_id。它不是什么高级字段,却非常适合做调试锚点。

例如后面你看到某个信号在第 12 行第一次变成 True,你可以立刻定位:

print(single_df.loc[single_df['row_id'] == 12])

这种直接按行追踪的方式,会明显降低你理解整条数据链路的难度。

25. 把第 2 讲做成一份“后面可以一直沿用”的底表

如果你愿意再稳一点,第 2 讲甚至可以顺手把这份底表保存一下:

single_df.to_csv('vectorbt_min_data.csv', index=False, encoding='utf-8-sig')

这样后面几讲你即使中途重开解释器,也能继续用同一份固定样本推进。它不会替代 PriceData,但会给你的入门练习提供一个稳定、可重复的起点。

26. 第 2 讲在整个 vectorbt 短课里的作用

如果把整个系列看成一条链,第 1 讲是“把环境和对象跑通”,第 2 讲则是“把对象变成后面能持续加工的底表”。很多入门学习之所以断掉,不是因为不会再多写一个函数,而是因为中间没有一张真正稳定、可解释、可复查的工作表。第 2 讲正是在补这块地基。

27. 系列衔接

本讲是《vectorbt回测入门短课》的第 2/8 讲,当前主题是《准备最小回测数据》。

上一讲:第 1 讲《安装vectorbt:创建第一组价格序列》。

下一讲:第 3 讲《计算5日均线》。

后续安排:第 4 讲《构造买入信号》;第 5 讲《构造卖出信号》。

28. 风险揭示与免责声明

风险揭示与免责声明

本页面内容仅用于量化研究与技术交流,旨在展示研究方法与流程,不构成对任何金融产品、证券或衍生品的要约、招揽、推荐或保证。

本文所涉历史数据、回测结果与示例参数不代表未来表现,也不应作为投资决策依据。

市场存在波动、流动性与执行偏差等不确定性,任何策略均可能出现收益波动或阶段性失效。

读者应结合自身风险承受能力进行独立判断,并在必要时咨询持牌专业机构意见。