日线:最常见也最稳
日线 OHLCV 是绝大多数中低频策略的默认选择:数据易得、噪声相对小,成交价假设的争议也最少。缺点是信号滞后一天,对短线策略的刻画能力有限。
Data · OHLCV 接入
Backtesting.py 对数据的要求很小:一张以时间为索引、含 Open / High / Low / Close(Volume 可选)列的 pandas DataFrame,就能驱动一次回测。真正花时间的不是「怎么喂数据」,而是把数据源口径、列名大小写、时间对齐与复权口径统一——这些做错不会报错,只会让结论悄悄失真。本页按「格式要求 → 数据源对照 → 分步接入 → 检查清单 → 质量排查」整理。
数据格式是硬要求:列名大小写、索引类型、是否单序列,任何一项不符都会在运行时报错或静默出错。下表把每一项的作用与「不满足时会怎样」放在一起看。
| 项 | 是否必需 | 作用 | 不满足时的现象 | 注意点 |
|---|---|---|---|---|
| Open | 必需 | 开盘价,撮合与成交价假设的基准之一 | 运行时报列缺失类错误 | 大小写敏感,需与官方要求完全一致 |
| High | 必需 | 区间内的价格高点,用于止损止盈与波动度量 | 同上;部分策略逻辑失效 | 必须 ≥ Low,需自查高低价关系 |
| Low | 必需 | 区间内的价格低点 | 同上 | 同上 |
| Close | 必需 | 收盘价,多数指标与信号的输入 | 指标算不出或全为空值 | 指标基于收盘价计算时尤其关键 |
| Volume | 可选 | 成交量,量能类策略与指标需要 | 依赖量能的策略无法运行或全为空 | 没有量能数据时省略即可,不要填 0 冒充 |
| DatetimeIndex | 必需 | 时间轴,决定 bar 顺序与逐 bar 迭代 | 列名对但顺序错,结果失真且不报错 | 须为时间类型且单调递增 |
| 单条价格序列 | 必需 | 一次回测对应一个标的的价格序列 | 多标的宽表会被当成一列多值处理 | 多标的需分别回测后自行汇总 |
| 自定义附加列 | 可选 | 预先把外部指标算好挂进 DataFrame | 不影响运行,但会增加数据维护成本 | 列名不要与 OHLC 冲突 |
import pandas as pd # 目标形态:index = DatetimeIndex,列 = Open/High/Low/Close[/Volume] df = pd.DataFrame(raw, columns=['Open', 'High', 'Low', 'Close', 'Volume']) df.index = pd.to_datetime(df.index) df = df.sort_index()
Backtesting.py 只内置一份示例数据用于跑通流程;真实研究的数据要靠你自己准备。下表按「取数方式 → 覆盖范围 → 适用场景 → 限制与注意点」对照常见来源。
| 取数方式 | 覆盖范围 | 适用场景 | 限制与注意点 |
|---|---|---|---|
| 内置示例数据 | 官方随包提供的一只美股示例 | 跑通流程、验证代码路径 | 只适合试跑,不能用于任何绩效结论 |
| 本地 CSV / Parquet + pandas | 取决于你的数据文件 | 离线复现、归档、内网环境 | 需自行整理列名与时间索引,并记录数据来源与采集日期 |
| tushare 等 A 股接口 | A 股行情与部分基本面 | 国内 A 股日线级研究 | 通常需要 Token 与积分,字段名与限频以数据源官方文档为准 |
| akshare 等开源接口 | A 股、基金、期货、指数等多类数据 | 个人研究、快速取数 | 接口稳定性与字段随上游变化,取数后必须做质量自检 |
| yfinance 等海外接口 | 美股等海外市场 | 海外标的研究 | 境内网络可达性不稳定,字段与复权口径以数据源文档为准 |
| 券商 / 终端导出 | 与你账户权限一致的市场 | 已付费数据的复现研究 | 导出格式各异,注意时间字段与时区 |
| 合成数据(随机游走等) | 无真实市场含义 | 调试指标与信号逻辑 | 不能用于任何绩效结论,仅验证代码是否正确 |
把数据交给 Backtesting.py 的五步按顺序做,每步给出真实代码、一句话说明与可验证的预期结果。以一张本地 CSV 为例,换成接口取数时只需替换取数那一步。
先用 pandas 把数据读进来看看长什么样:字段名、时间列位置、是否有代码列。先观察再改名,能省掉后面反复对不上的时间。
import pandas as pd
raw = pd.read_csv('600519.csv')
print(raw.head())
print(raw.columns.tolist())预期输出:前几行数据与列名清单。若时间列是字符串、或混着股票代码列,下一步统一处理。
把列名重命名为 Open/High/Low/Close(Volume 可选),并把时间列设为 DatetimeIndex——这一步决定了后面所有指标与信号能不能算。
df = raw.rename(columns={'日期': 'index', '开盘': 'Open', '高点': 'High',
'低点': 'Low', '收盘': 'Close', '成交量': 'Volume'})
df['index'] = pd.to_datetime(df['index'])
df = df.set_index('index').sort_index()
print(df.dtypes)预期输出:各列为数值类型、索引为时间类型。若价格为字符串,需要先转数值再继续。
在把数据交给任何回测之前先跑一遍检查:缺失值、重复时间、价格是否为 0 或负、时间是否单调。数据问题越早发现,返工成本越低。
print(df.index.is_monotonic_increasing, df.index.duplicated().sum()) print(df[['Open', 'High', 'Low', 'Close']].isna().sum().to_dict()) print(df.index.min(), df.index.max())
预期输出:True / 0 / 各列缺失数全为 0 / 起止日期覆盖你要回测的区间。任一项不符先回到上一步修数据。
这个库一次回测对应一条价格序列:多标的要先拆开分别回测,不要塞进一张宽表。同时确认区间足够长,能覆盖不同市场环境。
price = df['Close'] print(len(price), price.index[0], price.index[-1])
预期输出:样本条数与首尾日期。样本过少时,任何指标结论都不可信(区间选择标准以你自己的研究设计为准)。
把 DataFrame 按位置传给回测对象(它是回测的必要输入),跑一次最小回测确认数据能被正确读取,再进入策略与参数环节。
from backtesting import Backtest from backtesting.test import SMA bt = Backtest(df, MyStrategy, cash=10_000, commission=.002) stats = bt.run() print(stats['# Trades'])
预期输出:打印出成交笔数(具体数值随数据与策略变化)。若成交数为 0,先查列名、时间顺序与信号条件,而不是先怀疑策略逻辑。
下面这些检查都是 pandas 通用写法,不需要额外依赖;建议固化成脚本,每次换数据源都跑一次。表里的「预期结果」是基本要求,不是充分条件。
| 检查项 | 怎么查 | 预期结果 | 不合格怎么处理 |
|---|---|---|---|
| 列名是否齐全且大小写正确 | df.columns.tolist() | 含 Open/High/Low/Close(Volume 视情况) | 用 rename 统一列名,不要用大小写不一致的列 |
| 索引是否为时间类型 | df.index.dtype | datetime64 类时间类型 | 用 pd.to_datetime() 转换后再设为索引 |
| 时间是否单调递增 | df.index.is_monotonic_increasing | True | sort_index() 重新排序;乱序会让回测结果失真 |
| 是否有重复时间点 | df.index.duplicated().sum() | 0 | 定位重复行后去重,并确认数据源是否分多次拼接 |
| 关键列是否有缺失值 | df[['Open','High','Low','Close']].isna().sum() | 全为 0 | 先确认缺失成因(停牌/未收盘/接口缺失),再决定删除或补齐 |
| 价格是否为 0 或负值 | (df[['Open','High','Low','Close']] <= 0).sum() | 全为 0 | 多为复权或字段错位导致,需回数据源核对口径 |
| 高低价关系是否成立 | (df['High'] < df['Low']).sum() | 0 | 列错位或数据源异常,需回源核对,不能靠数值变换掩盖 |
| 时间区间是否覆盖回测区间 | df.index.min(), df.index.max() | 覆盖你设定的起止日期 | 补齐区间或缩小回测区间,避免「样本外其实是空」 |
| 成交量量级是否合理 | df['Volume'].describe()(若有) | 量级与标的规模相符,无异常 0 值 | 量能缺失时宁可不做量能类策略,也不要填 0 |
Backtesting.py 里数据问题最麻烦的地方在于「不报错但结论错」。下表按「现象 → 成因 → 处理方式 → 注意点」整理,处理顺序建议是先对齐时间轴、再处理缺失、最后才计算指标与信号。
| 现象 | 成因 | 处理方式 | 注意点 |
|---|---|---|---|
| 回测区间内出现大段空白 | 停牌或数据源本身缺失交易日 | 确认是真实停牌还是漏数据,再决定保留缺口或剔除区间 | 不要用「向前填充价格」掩盖停牌,会伪造出可成交的假象 |
| 指标跳空、收益率异常 | 复权口径不一致(不复权/前复权/后复权混用) | 同一回测内统一口径,并在记录里写明用的是哪种 | 复权算法与字段名以数据源官方文档为准 |
| 分红送转日价格断崖 | 未复权导致价格跳空 | 改用复权数据,或明确接受不复权口径并在结论里说明 | 口径不同会让收益率与回撤都不可比 |
| 把多个标的塞进一张表后结果混乱 | Backtesting.py 一次回测对应单资产价格序列 | 拆成多份单资产数据,分别回测后自行汇总 | 多资产组合能力不在这个库的能力范围内 |
| 策略在历史上表现异常好 | 用了当时不可能知道的信息(未来数据) | 检查信号是否用到当日收盘后才知道的数据,必要时整体后移一根 bar | 这是最容易被忽略、也最伤结论的一类错误 |
| 依赖量能的策略没有信号 | Volume 列缺失或全为 0 | 补齐量能数据,或换不依赖量能的策略 | Volume 本就可选,缺它就别用需要它的指标 |
| 时间对不上、交易日错位 | 时区或时间戳格式不一致 | 统一到同一时区与同一时间粒度后再对齐 | 跨市场数据尤其容易踩这一条 |
| 样本区间太短 | 只用了一小段行情 | 拉长区间覆盖不同市场环境,或明确标注结论适用范围 | 短样本下的参数结论通常不可复述 |
Backtesting.py 以逐 bar 迭代运行,bar 的粒度完全由你喂进来的数据频率决定——也就是说,能不能做日内回测,取决于你手上有没有分钟级数据。
日线 OHLCV 是绝大多数中低频策略的默认选择:数据易得、噪声相对小,成交价假设的争议也最少。缺点是信号滞后一天,对短线策略的刻画能力有限。
把分钟级 OHLCV 喂进来就能做日内回测,但成交时点、滑点与撮合假设的重要性会急剧上升;同一份数据换一个成交价假设,结论可能完全不同,需谨慎验证。
在策略里同时用日线与更长周期指标是可行的,但不同频率的数据对齐、重采样与「哪一根 bar 才可见」都需要你自己处理,Backtesting.py 不会替你完成频率转换。
把真实数据接进 Backtesting.py 时,下面这些现象出现频率较高。按「现象 → 可能原因 → 处理 → 提前规避」四列整理,报错原文以官方与解释器实际提示为准。
| 现象 | 可能原因 | 处理 | 提前规避 |
|---|---|---|---|
| 报列缺失类错误 | 列名大小写不一致或缺列 | 按官方要求统一为 Open/High/Low/Close | 取数后立刻打印列名清单核对 |
| 指标全是空值、没有成交 | 窗口长度超过数据长度或关键列有缺失 | 检查窗口与数据长度、补齐缺失值 | 先跑一次最小回测确认数据可用 |
| 结果明显好得不真实 | 前视偏差、区间过短或成本未计入 | 检查信号可见性、拉长区间、补上手续费与滑点 | 把成本假设固定写进回测参数并留档 |
| 换一段数据结论就翻转 | 参数是在同一段历史上挑出来的 | 做样本内外的切分再复核 | 数据准备好当天就规划好样本切分方式 |
| 图表打不开或显示异常 | 可视化依赖缺失或版本不匹配 | 按官方安装说明补齐依赖后重试 | 先把依赖装全再开始研究,避免中途卡住 |
| 同一份数据两次结果不同 | 数据文件被更新、或时间顺序被改动 | 固定数据快照并记录采集日期 | 首次取数成功后就落盘保存,回测只读本地文件 |
是,这个库按 Open/High/Low/Close(Volume 可选)这样的大小写识别列;写成小写或中文列名会导致运行时报列缺失。具体列名要求以官方 README 与文档为准,其余自定义列可以保留但要避免与 OHLC 冲突。
可以,Volume 是可选的:不依赖量能的指标与策略完全可以不带这一列。但要注意两点——不要用 0 填充冒充「无成交量」,也不要省略后仍去用需要量能的指标,否则指标会算不出来或全为空值。
可以,思路统一:接口取回数据后,把列名改成 Open/High/Low/Close(量能按需保留)、把时间列转成 DatetimeIndex 并排序,就能传进回测。但各接口的字段名、复权口径、是否需要 Token 与限频规则都不一样,一律以该数据源官方文档为准;国内接口的可用性与稳定性也需你自己评估。
不行。Backtesting.py 一次回测对应一条价格序列,不支持多资产组合,也不支持分数股——把多只股票塞进一张宽表,会被当成结构异常的数据处理,结果不可信。正确做法是每个标的单独回测,再自行汇总组合层面的结论。
能,bar 的粒度由数据频率决定,喂分钟级数据就按分钟推进。但日内回测对成交时点、滑点与撮合假设极其敏感:同一份数据换一个成交价假设可能得出相反结论。建议先用日线验证策略逻辑,再考虑是否需要下降到日内,且结论以你自己的成本假设为准。
可以省掉一部分取数工作:本机已核验的技能里有覆盖财经行情数据与技术指标分析的方向,一句话即可发起取数与基础分析;但拿到的数据仍需你自己做字段与口径校验才能喂进回测,两者是相互独立的两条路线,没有已证实集成关系。是否值得取决于你想把时间花在数据工程还是策略本身。