A股量化交易本地Tick数据落盘方案
作者:公子世无双2026.11.07 02:52浏览量:1简介:面向A股量化开发者,本文系统讲解如何突破行情接口的历史数据获取限制,通过本地化存储构建可持续复用的Tick数据库。内容涵盖全市场代码列表拉取、集合竞价数据订阅、字段含义解析、分区存储与增量更新策略,并给出可落地的代码示例与性能优化建议,帮助你为集合竞价与高频策略打下坚实的数据基础。
A股量化交易本地Tick数据落盘方案
对于个人开发者而言,参与A股量化交易时最先遇到的工程问题往往不是策略本身,而是数据从哪里来、如何稳定地留下来。多数行情接口对历史Tick数据的回溯窗口有明确限制,通常只能提供最近若干天的数据,超过窗口期的请求会被拒绝或返回空值。这意味着如果不在每个交易日及时落盘,一旦错过就再也无法补回。对于依赖集合竞价、盘口微结构、逐笔成交行为的策略而言,这种数据缺失是致命的。
本文围绕“如何把Tick数据稳定地保存到本地”这一核心问题展开,从接口限制、代码列表获取、订阅与读取、字段解析,到存储结构与增量更新策略,逐层给出可操作的方案。
一、为什么必须做本地化存储
行情接口的设计目标通常是“实时推送”而非“历史归档”。因此它一般具备两个特征:
- 回溯窗口有限:只能获取最近N个交易日的Tick数据,N通常较小。
- 订阅有状态:需要先订阅标的,才能通过接口读取对应数据。
这两个特征决定了本地存储不是可选项,而是必需项。Tick数据本身还包含集合竞价阶段(开盘前与收盘前的集中撮合)的委托与成交信息,这正是集合竞价策略的核心输入。若当天不保存,第二天再想复现当日的竞价结构,基本不可能。
因此推荐的做法是:每个交易日收盘后,自动把全市场Tick数据落盘一次,形成按日期分区的本地数据库。
二、获取全市场标的列表
第一步是拿到需要订阅的标的集合。A股市场包含沪深两市与北交所,接口一般通过“板块”来组织标的。常见做法是先下载板块信息,再从板块中取出成分股。
SECTOR_A_SHARE = "沪深A股"SECTOR_BEIJING = "BJ"def get_a_share_list() -> list[str]:"""获取沪深京A股列表。"""download_sector_info()stock_codes = set(xtdata.get_stock_list_in_sector(SECTOR_A_SHARE) or [])stock_codes.update(xtdata.get_stock_list_in_sector(SECTOR_BEIJING) or [])return sorted(stock_codes)
这里有几个细节值得注意:
- 先下载板块信息:板块成分并非静态,新股上市、退市、指数调整都会改变列表,因此每次运行前重新拉取是必要的。
- 使用集合去重:沪深与北交所板块可能存在交叉,用
set合并再去重可以避免重复订阅。 - 排序输出:排序后的列表便于日志比对与增量更新时定位差异。
如果标的数量较大(A股全市场约五千余只),一次性订阅全部Tick可能给客户端带来较大压力。实践中可以分批处理,例如按每批200~500只分组,处理完一批再进入下一批。
三、订阅与读取Tick数据
拿到标的列表后,逐个(或分批)订阅并读取。核心流程是“先订阅,再读取指定时间区间”。
xtdata.subscribe_quote(stock_code=stock_code,period="tick",count=-1,)data = xtdata.get_market_data_ex(stock_list=[stock_code],period="tick",start_time=f"{trading_day}091500",end_time=f"{trading_day}235959",)tick_df = data.get(stock_code)
几个关键点:
period="tick":指定获取的是Tick级别数据,而非分钟或日线。count=-1:表示不限制推送数量,交由时间区间过滤。- 时间区间:起始时间设为
091500,可以覆盖集合竞价阶段(9:15开始接收申报)。结束时间设为当日235959,确保收盘后的数据也被包含。 get_market_data_ex返回结构:通常是一个字典,键为标的代码,值为对应的 DataFrame。取用时需要用.get(stock_code)判空,避免标的无数据时抛异常。
需要特别提醒的是:Tick数据的实际采样粒度约为3秒一次,并非逐笔成交。也就是说,它是按固定时间间隔对盘口与成交状态做的快照。这一点直接影响策略假设——如果你的策略需要真正的逐笔委托流,那么快照式Tick并不等价,需要重新评估数据适用性。
四、Tick字段含义解析
落盘前必须理解每个字段的语义,否则存下来的只是一堆无意义的列名。常见字段及含义如下:
| 字段 | 含义 |
|---|---|
time |
时间戳 |
lastPrice |
最新价 |
open |
开盘价 |
high |
最高价 |
low |
最低价 |
lastClose |
前收盘价 |
amount |
成交总额 |
volume |
成交总量 |
pvolume |
原始成交总量 |
stockStatus |
证券状态 |
openInt |
持仓量 |
lastSettlementPrice |
前结算 |
askPrice |
委卖价 |
bidPrice |
委买价 |
askVol |
委卖量 |
bidVol |
委买量 |
transactionNum |
成交笔数 |
几点解读:
askPrice/bidPrice通常是数组:一般包含多档(如五档或十档)委卖委买价,落盘时需要决定是展平为多列,还是以序列化形式存入单列。stockStatus:用于判断标的当前是否处于可交易、停牌、涨跌停等状态,对过滤异常数据非常重要。pvolume与volume:前者为原始成交量(可能含未成交或特殊口径),后者为已成交量,两者在不同场景下用途不同。transactionNum:成交笔数,可用于估算成交活跃度与平均单笔规模。
建议在落盘时同时保存字段字典或注释文件,避免日后回溯时忘记语义。
五、落盘:从DataFrame到本地文件
最直接的保存方式是利用 DataFrame 的导出能力:
tick_df.to_csv(output_path, index=True, encoding="utf-8-sig")
utf-8-sig 编码可以保证在常见表格工具中打开时不出现中文乱码。index=True 会保留行索引,便于后续按序读取。
但在生产环境中,直接存CSV会带来几个问题:
- 体积膨胀:Tick数据列多、行数大,CSV文本格式占用空间远高于二进制格式。
- 读取缓慢:全市场Tick按日落盘后,CSV读取速度难以满足回测需求。
- 类型丢失:CSV读回后数值类型需要重新推断,容易出错。
因此更推荐的分层存储策略是:
- 热数据(近期):使用列式二进制格式(如 Parquet)保存,压缩率高、读取快、类型保留完整。
- 冷数据(历史):可归档到对象存储,按日期分区,需要时再拉取。
- 元数据:单独维护一份清单,记录每个交易日已落盘的标的、行数、时间范围,用于校验与增量补齐。
六、目录结构与增量更新
一个清晰、可扩展的目录结构能极大降低后续维护成本。推荐按“日期 + 标的”两级分区:
tick_data/2026-04-22/600000.parquet000001.parquet...2026-04-23/...
对应代码:
from pathlib import Pathdef build_output_path(base_dir: str, trading_day: str, stock_code: str) -> Path:day_dir = Path(base_dir) / trading_dayday_dir.mkdir(parents=True, exist_ok=True)return day_dir / f"{stock_code}.parquet"
增量更新逻辑建议如下:
- 每日收盘后运行一次全量落盘任务。
- 落盘前检查目标文件是否已存在,若存在则跳过,避免重复写入。
- 维护一份当日清单文件,记录成功与失败的标的。
- 对失败的标的进行重试,最多重试若干次。
- 次日开盘前,可对前一日清单做完整性校验。
七、性能与稳定性建议
- 分批订阅:全市场一次性订阅容易造成内存与连接压力,建议分批处理并在批次间释放引用。
- 异常隔离:单只标的失败不应中断整个任务,用
try/except包裹单标的处理逻辑。 - 日志记录:记录每只标的的落盘行数与耗时,便于发现异常标的。
- 时区与交易日:确保
trading_day来自交易日历而非自然日,避免节假日产生空数据。 - 数据校验:可对关键字段做空值率统计,若某字段空值率异常偏高,说明该标的当日数据可能不完整。
八、总结
个人开发者在A股做量化,数据自主可控是长期竞争力的基础。行情接口的历史回溯限制决定了Tick数据必须当天落盘,而集合竞价数据的不可复现性进一步放大了这件事的重要性。完整的落盘链路包括:拉取全市场标的、分批订阅、按时间区间读取、理解字段语义、以分区结构保存、并配合增量更新与校验机制。
把这套流程脚本化、定时化之后,你得到的不只是一堆文件,而是一个可以持续增长的本地行情数据库——它将成为后续策略研究、回测与实盘验证的共同地基。

登录后可评论,请前往 登录 或 注册