Blankly / Keyless 与数据读取器

Keyless 模式:把你的价格数据喂进去,六列少一列就报错

Blankly 的离线回测能力靠 blankly.data 下的读取器实现。它们一共五类,覆盖价格、逐笔、事件与资金费率四种输入。这一页的重点不是 API 列表,而是每类读取器的字段硬要求与推断规则——因为校验失败时的报错信息比较简短,很多人会在「数据格式」上反复试错,而问题往往只是列名或时间格式。

依据:发行版 data_reader.py依据:本机离线回测实测非官方中文文档本站未实机交易
PriceReaderOHLCV 价格,CSV / JSON / DataFrame
TickReader逐笔数据,只需 time + price
EventReader自定义事件,time + data 字典
JsonEventReader从 JSON 文件读事件
数据读取器分类示意(依据发行版 blankly/data/data_reader.py)。第五类是 FundingRateEventReader,它需要期货接口在运行期拉取资金费率历史,因此不适合离线使用,详见正文。
五类读取器

每一类读取器要求什么、产出什么

Blankly 读取器的选择由你的输入形态决定,不由策略决定。先确定「我手上有什么数据」,再选读取器。

读取器接受的输入必需的列 / 字段典型用途与注意点
PriceReaderCSV 文件、JSON 文件、pandas DataFrame,或它们的列表必须包含 openhighlowclosevolumetime 六列;至少 2 行最常用的一类,配合 KeylessExchange 做离线回测;自动按 time 排序
TickReaderCSV 文件必须包含 timeprice 两列逐笔成交类数据;列要求比 PriceReader 少,但只适合 tick 级推进
EventReader直接传入 {时间: 数据} 字典timedata 两列零散事件用,不需要落盘文件
JsonEventReader.json 文件(扩展名必须是 json)文件内容为 {事件类型: {时间: 数据}} 结构把新闻、情绪、自定义信号以 JSON 形式喂进回测;文件不以 json 结尾会直接断言失败
FundingRateEventReader不接文件,需要符号、起止时间与一个期货接口对象由接口返回的 timerate用运行时拉取资金费率;需要一个可用且已授权的期货接口,因此不适合纯离线场景
一个关键设计细节:PriceReader 的分辨率不是由你指定的,而是从时间列的差分里取众数推断出来的。这意味着如果你的数据里混了不同频率的段落,或者时间列是字符串,推断结果会和你预期不同,甚至推断为 0 而直接报错。
字段规则

校验规则逐条列出:错哪条会报哪个错?

读取器的校验是断言式的,没有容错。下表把规则、违反后的报错原文与自查方法对应起来。

规则违反后的报错自查方法适用读取器
列名必须齐全(六列)AssertionError: Must have at least these columns: ('open', 'high', 'low', 'close', 'volume', 'time')pandas.read_csv 读一遍,打印列名与要求集合取差集PriceReader
行数至少 2 行AssertionError: Must give data with at least 2 rows in <文件>.len(df) > 2PriceReader / TickReader
文件扩展名必须匹配类型LookupError: Unknown filetype for <路径>确认结尾是 .csv.json,不要用 .txtPriceReader
同一次构造里不能混用 csv 与 jsonLookupError: Cannot pass both csv files and json files into a single constructor.同一批文件统一格式,或拆成多个读取器PriceReader
文件路径数量与标的数量必须一致LookupError: Mismatching symbol & file path lengths打印两个列表的长度PriceReader / TickReader
同一个读取器内标的不能重复AssertionError: Cannot use duplicate symbols for one price reader.去重 symbol 列表;同标的多段数据改用多个读取器PriceReader
分辨率不能推断为 0LookupError: Resolution is 0 for <标的>, this is not allowed.打印时间列差分并看众数;常见成因是时间用了科学计数法或行数太少PriceReader
事件文件必须是 jsonAssertionError: The filepath did not have a 'json' ending改扩展名JsonEventReader
最容易忽略的一条是「分辨率推断为 0」。它的报错文案里提到了「科学计数法」,这是源码作者留下的提示——如果 time 被 pandas 解析成浮点并在写出时用了指数形式,差分就会失真。把时间写成整数 epoch 秒是最稳的做法。
分步流程

怎么把自己的数据接进回测?四步流程

下面用一份日线 CSV 举例,每一步都给出命令、说明与预期结果。如果你手上是分钟线或 tick 数据,只需替换文件与分辨率。

  1. 把数据整理成六列并落盘为 csv

    命令示例:df[['time','open','high','low','close','volume']].to_csv('mydata.csv', index=False)。其中 time 用整数 epoch 秒。预期结果:文件头部一行为 time,open,high,low,close,volume,且没有任何单元格为空。

  2. 构造读取器并确认分辨率被正确推断

    命令示例:from blankly.data import PriceReader; pr = PriceReader('mydata.csv', 'MY-USD'); print(pr.prices_info)。预期结果:打印出的字典里该标的有 resolutionstart_timestop_time 三个键,且 resolution 与你的数据频率一致(日线为 86400)。

  3. 接进 KeylessExchange 并注册策略

    命令示例:ex = blankly.KeylessExchange(price_reader=pr),然后 s = blankly.Strategy(ex)s.add_price_event(fn, symbol='MY-USD', resolution='1d', init=init)。预期结果:没有异常;如果报 settings.json 缺失,先运行一次 blankly init

  4. 用数据区间内的绝对时间跑回测

    命令示例:s.backtest(start_date=..., end_date=..., initial_values={'USD': 10000}),其中两个时间取自上一步打印的 start_time / stop_time 范围。预期结果:输出 No cached data found ...Backtesting...,最后返回结果对象。

为什么建议先打印 prices_info它是读取器对你数据的「理解结果」。如果分辨率被推断错了,后面所有信号频率都会跟着错,而回测不会报错——只会给出一个看起来正常的错误结论。先看这一步能省下大量排查时间。
自定义事件

怎么把新闻、情绪或外部信号接进回测?

这是 Blankly 相对多数轻量回测库更有意思的一点:它把「非价格信息」也当成一等输入。不过要用好它,需要理解事件与价格的时间对齐方式。

做法数据形态适用场景注意点
字典直接传入{时间戳: 数据}事件数量少、在代码里就能写出来时间戳与价格数据必须使用同一时间基准
JSON 文件{'事件类型': {时间戳: 数据}}事件由外部流程产出并落盘文件扩展名必须是 .json,内容按事件类型分组
在策略里注册事件回调回调签名形如 event(type_, data)需要按事件内容做决策与价格回调共用同一份状态容器,注意时序假设
模拟延迟用类似 self.sleep(秒数) 的调用测试对延迟敏感的策略回测中的 sleep 是模拟时间推进,与真实网络延迟不是一回事
资金费率事件由期货接口在运行期拉取期货持仓的成本测算需要可用且已授权的期货接口;官方文档说明该能力仍在开发中
事件类数据最容易做出「未来函数」。如果你的事件时间戳是「事件被报道的时间」而价格用的是当日收盘价,那么当事件发生在收盘之后时,你的策略就用了当时还不知道的信息。建议的做法是:事件时间戳一律用「你在真实交易中能获得该信息的最早时刻」,并用整体延后一期的方式回归验证结果是否大幅衰减。
数据准备排错

哪七种数据准备失败最常见?

离线回测失败几乎都发生在数据进入引擎之前。下表按「你看到什么」组织,最后两列给出成因与处置。

你看到的现象最可能的成因处置验证是否修好
断言失败,提示需要六列列名大小写不符,或缺少 volume 这类被忽略的列统一改成全小写六列;缺少的列可用占位值补齐但要在记录中说明重新读文件并打印列名
Must give data with at least 2 rows过滤后数据几乎为空检查筛选条件是否把数据全滤掉了打印 len(df)
Resolution is 0时间列不是整数 epoch(字符串日期或科学计数法)转换时间列类型并避免指数写法打印时间列差分的众数
No cached or downloaded data available请求区间与数据区间不重叠(常见于用了 to='1y'改成数据区间内的绝对起止时间能打印出 Backtesting...
settings.json 找不到工作目录缺少配置文件,与数据格式无关运行 blankly init 或在工作目录放一份最小配置构造交易所不再抛异常
回测跑完但成交远少于预期数据频率或分辨率被推断错,导致事件触发次数不对核对 prices_info 的分辨率对比事件触发次数与理论预期
多标的回测里某个标的没有任何数据文件路径与标的列表顺序不匹配按索引逐个核对两个列表打印每个标的的行数与时间范围
FAQ

关于 Keyless 与数据接入的高频问题

回答依据发行版 data_reader.py 的校验逻辑与本机实测;数据结构细节以官方源码为准。

Keyless 模式需要联网吗?

不需要。价格全部来自你传入的文件,回测在本地完成。不过要注意三个前提:工作目录要有 settings.json(否则构造交易所就失败);回测起止时间必须落在你的数据区间内;如果你把 GUI_output 保持为默认的 true,会额外触发绘图流程。

为什么必须包含 volume 列?我的数据里没有成交量。

因为读取器的断言把六列作为一个整体校验,缺失任何一列都会失败。如果你的数据确实没有成交量,可以补一列占位值让校验通过——但这必须在方法论记录里写明,因为任何用到成交量的指标与逻辑都会因此失真。更稳妥的做法是补一列真实可得的量(如成交额换算),而不是填 0。

分辨率被推断错了怎么办?

读取器不提供「指定分辨率」的入参,它的分辨率来自时间列差分的众数。所以修正方式是改数据而不是改参数:确认 time 是等间隔的整数 epoch 秒;如果数据里混了多段不同频率(例如拼接了日线与小时线),就把它们拆成不同文件,用各自的读取器分别加载。

可以喂分钟级或秒级数据吗?

可以。读取器对频率没有硬编码限制,它按差分推断;你要做的是在 add_price_event 时把 resolution 写成与实际频率一致的值(例如 '1m')。频率越高,事件回调被调用的次数越多,回测耗时也越长——这是事件驱动引擎的固有代价。

JSON 和 CSV 该用哪个?

价格数据用 CSV 更省事(列名直观、便于目视检查)。事件类数据用 JSON 更自然,因为 JsonEventReader 的结构是按事件类型分组的,且它本身要求文件扩展名为 json。注意同一个 PriceReader 不能混用 csv 与 json。

自己准备的数据能用来证明策略有效吗?

能不能得出策略结论,取决于数据之外的三件事:是否有基准对照、是否计入了手续费与滑点、是否做了样本外检验。数据本身规范并不等于结论可信——这三件事都在回测引擎审计页里逐条展开。

下一步:数据进来之后,能用哪些现成指标

数据接入之后你会发现 Blankly 内置的指标与绩效函数比 README 展示的多得多。先把清单看一眼,能省下自己重复实现的时间。