ZVT 项目研究站 · 取数与查询契约
ZVT 取数与查询:record_data 写进本地,query_data 从本地查出来
ZVT 和「每次调一次接口」的工具最本质的差别,就在这两个方法上:所有数据先通过 record_data 落到本地库(默认增量更新),再由 query_data 在同一套参数下查出来。理解这个顺序,才能理解为什么它适合做可复现的因子研究,也才能避开「不传 code 就是全市场」这类默认行为。
先写库,再查询
src/zvt/contract/ 的接口命名自绘(非官方架构图);参数名逐字取自 README 示例。ZVT 的 record_data:写进本地库的统一入口
所有 schema 共用这一个方法。下表把 README 里出现过的参数与行为集中起来——官方是分散在示例里的。
| 参数 / 行为 | 含义 | 适用场景 | 注意点 |
|---|---|---|---|
provider | 选择用哪个数据源写入 | 某个源不可用、或想换一家口径时 | 同一个 schema 可能注册多个 provider,不传时用第一个;各家数据口径不完全一致 |
code / codes | 写单只 / 多只标的 | 小范围验证、补单只数据 | 不传就写全市场,第一次跑全市场耗时长且容易触发限流 |
entity_ids | 用实体 id 指定标的(如 stock_sz_000001) | 标的来自其它表的查询结果时 | 与 code 二选一,混用时以实体 id 为准 |
sleeping_time | 每次请求之间的间隔秒数 | 批量写库时降速,避免被数据源限流 | README 的策略示例里用的是 1 秒;全市场建议适当放大 |
| 增量更新 | 已存在的数据不重复拉取,只补新增部分 | 日常定时更新 | 想强制重写需要另行处理;不要靠重复运行来「修复」脏数据 |
| 本地落盘 | 数据写入 zvt_home/data 等路径下 | 想在另一台机器复用、或做大范围备份时 | 路径由 zvt_env 决定,可配置;换机器后要迁移整个数据目录 |
from zvt.domain import Stock, Stock1dHfqKdata
Stock.record_data(provider="em")
Stock1dHfqKdata.record_data(provider="em",
entity_ids=["stock_sz_000001", "stock_sz_000338"],
sleeping_time=1)ZVT 的 query_data:把条件交给本地库,而不是交给网络
查询全部打在本地数据上,所以可以放心写复杂条件与排序。
| 参数 | 含义 | 用法示例 | 注意点 |
|---|---|---|---|
filters | 条件列表,元素为「字段 比较 值」的表达式 | [FinanceFactor.roe>0.08, FinanceFactor.report_period=='year'] | 多个条件是并列关系;字段名以 schema 定义为准,Schema.help() 可以看到全部列 |
start_timestamp | 起始时间,支持字符串日期 | start_timestamp='2019-01-01' | 对财报类数据,这里筛的是「公告/记录时间」而不是报告期,口径差异会直接改变结果 |
order | 排序,支持升序与降序 | order=FinanceFactor.roe.desc() | 与 limit 搭配就是「取前 N」;不排序时取前 N 没有意义 |
limit | 限制返回行数 | limit=20 | 只影响返回,不影响库里的数据 |
columns | 指定返回列 | columns=["code"] + FinanceFactor.important_cols() | 用 important_cols() 可以只取关键列,避免宽表拖慢分析 |
index | 把某一列设为返回 DataFrame 的索引 | index='code' 或 index='timestamp' | 不同 index 会影响后续与价格表对齐的方式,做因子前先想清楚 |
provider | 按数据来源过滤 | 查询时限定某一家的记录 | 同一张表可能混有多个 provider 写入的数据,做一致性检查时要用它拆开 |
df = FinanceFactor.query_data(
filters=[FinanceFactor.roe > 0.08,
FinanceFactor.report_period == 'year',
FinanceFactor.op_income_growth_yoy > 0.08],
start_timestamp='2019-01-01',
order=FinanceFactor.roe.desc(),
limit=20,
columns=["code"] + FinanceFactor.important_cols(),
index='code')ZVT 的数据落在哪里:zvt_env 的五个路径
README 的 Env settings 章节给出了一台示例机器上的五个路径设置。换机器、换盘符、做备份时都要先看它。
| 键 | 含义 | 会用到的场景 | 注意点 |
|---|---|---|---|
zvt_home | 框架的工作根目录 | 迁移数据、做整目录备份 | 它是下面四个路径的父级,迁移时整个目录一起搬 |
data_path | 数据文件目录 | 确认数据是否真的落盘 | 写库后最直接的验证点;空目录说明写库没成功 |
tmp_path | 临时文件目录 | 排查磁盘占用 | 异常中断后可能留残留文件 |
ui_path | 界面相关资源目录 | 界面起不来时检查 | 与 zvt 命令有关,只用 API 时可忽略 |
log_path | 日志目录 | 取数失败时看真实报错 | 排错第一现场:报错原文通常在这里而不是在控制台 |
init_config(current_config=zvt_config, jq_username='xxx', jq_password='yyy') 这样的写法,可写聚宽账号、SMTP、微信相关配置。这些值最终落在框架自己的配置文件里,不要把真实密钥提交到代码仓库。一次完整取数:从空库到可查询的五步
按这个顺序走,可以把「网络问题」和「代码问题」分开定位。
先看配置:代理与 provider 凭据
打开框架的
config.json,确认http_proxy/https_proxy是否符合本机情况(默认值是本地代理 127.0.0.1:1087),以及要用到的 provider 是否需要账号。预期:明确知道这次取数走的是哪家源、需不需要登录信息。写标的清单:
Stock.record_data(provider="em")先拿小样本验证:给
code或entity_ids传一只标的,确认链路通,再考虑全市场。预期:data_path下出现数据文件,日志目录没有异常堆栈。写行情:
Stock1dHfqKdata.record_data(...)周期与复权在表名里选(
1d与hfq的含义见 K 线与复权页)。预期:同一只标的可以查到连续日期序列。查询验证:
query_data加条件与排序用
filters加一个明确条件(例如某只标的),再用index='timestamp'看时间轴是否连续。预期:返回行数与预期交易日数接近;缺口说明写库没完成。再放大到全市场
确认链路与时间成本后,再去掉 code 参数写全市场,并在批量写库时设置
sleeping_time。预期:耗时显著变长;期间应关注日志里的限流与重试信息。
什么时候该重跑、什么时候不该重跑
增量更新是这套设计的优点,也是误解最多的地方。
| 情形 | 建议动作 | 理由 | 注意点 |
|---|---|---|---|
| 每天新增一个交易日的数据 | 直接重跑同一行 record_data | 增量更新会只补新增部分 | 建议交给定时任务,见标签与任务页的 runner |
| 发现某段历史数据缺失 | 先用 query_data 确认缺口范围,再针对性补写 | 盲目全量重跑既慢又可能覆盖其它来源的数据 | 同一张表可能混有多个 provider 的记录 |
| 换了一家 provider | 按 provider 分别查询后再对比 | 两家口径不同,混在一起做因子会失真 | 用 provider 参数拆分,不要直接看平均值 |
| 升级了框架版本 | 先备份整个 zvt_home 再升级 | README 明确不保证向后兼容,表结构可能变化 | 仓库里的 sql/ 目录有几个手工维护脚本,说明存在需要人工干预的库变更 |
| 数据源接口变更 | 等 provider 侧修复或自行改 recorder | 取数依赖第三方站点结构 | 这类问题不在框架可控范围内,先看 issue 再动手 |
取数与查询常见问题
答案以 README 与 src/zvt/contract/ 的接口定义为准;本站未运行过这些代码。
不传 code 会怎样?
按 README 的说明,不传 code 或 codes 时会记录全市场。这在标的清单这类小表上没问题,但在日线这类大表上会跑很久并可能触发数据源限流。建议先传单只标的验证链路,确认无误后再放开。
数据存在哪?能换盘吗?
默认在框架自己的 zvt_home 下,数据文件放在 data_path。README 的 Env settings 展示了这些路径是可配置的,换盘或迁移时把整个目录一起搬走即可。具体配置方式以源码为准。
为什么查出来的行数和交易日对不上?
常见三种原因:写库没跑完(增量只补了一部分)、该标的期间停牌、或者 start_timestamp 筛的是记录时间而不是行情日期。先按 index='timestamp' 看时间轴的缺口位置,再决定补数据还是改查询条件。
能像数据库一样直接写 SQL 吗?
框架暴露的是 record_data / query_data 这一层,底层是 SQLAlchemy 2.0。理论上可以绕过框架直接用引擎,但那样会失去增量更新与统一口径,升级时也更容易踩到表结构变化。sql/ 目录里的脚本是官方自己用于库维护的,不是给用户日常使用的接口。
取数很慢怎么办?
三件事按顺序做:①确认不是默认代理导致的重试;②给批量写库加 sleeping_time 降速,避免被限流后反复重试;③把「全市场」拆成多个小批次。框架本身没有分布式抓取能力,性能上限取决于数据源。
这套流程和 EasyClaw 的取数技能是什么关系?
两条并列路线。技能(akshare-finance、tushare-finance、mx-data 等)是即取即用、结果一次性返回,不建本地库;ZVT 是先落库再查询,适合反复跑因子与回测。两者无已证实集成,具体取舍见「对比」页。