数据采集与存储:API接口对接与高效落地

做高频交易,数据就是你的弹药。弹药质量不行,枪法再好也白搭。这一章,咱们聊聊怎么把CTP、易盛这些期货柜台的数据,稳稳当当地接到本地,再存成能快速读取的格式。

我个人习惯把数据采集分成三步:对接、落地、压缩。每一步都有坑,我踩过不少,今天一并说清楚。

API接口对接:CTP与易盛

CTP(综合交易平台)和易盛,是国内期货市场两大主流柜台。接口风格不太一样,但核心逻辑相通。

CTP接口对接要点

CTP的API是C++写的,Python调用得用封装库。我常用的是vnpy的封装,或者自己用ctypes直接调。

核心流程就三步:

  1. 初始化:创建交易和行情两个API实例
  2. 登录:填上你的经纪商账号、密码、交易服务器地址
  3. 订阅行情:传入合约代码,比如rb2401

这里有个坑——行情数据是异步推送的。你订阅后,数据会通过回调函数传回来。我曾经在项目里忘了处理回调队列,结果数据全丢了,排查了一下午才发现是队列满了没消费。

关键代码片段:CTP行情订阅

from vnpy_ctp import CtpMdApi

class MyMdApi(CtpMdApi):
    def on_tick(self, tick):
        # 这里拿到每笔行情
        print(tick.last_price, tick.volume)
        
api = MyMdApi()
api.connect("tcp://180.168.146.187:10131", "你的账号", "密码")
api.subscribe("rb2401")

易盛接口对接要点

易盛的API设计更现代一些,支持多账户同时登录。我个人觉得它的行情推送更稳定,但文档写得比较简略。

对接易盛时,注意它的合约编码规则。比如郑商所的合约,在易盛里可能是ZC401而不是ZC2401。我刚开始对接时,因为这个编码差异,折腾了两天才发现是合约代码写错了。

小技巧:建议写一个合约映射表,把不同柜台的合约代码统一映射成标准格式。这样切换柜台时,只需要改映射表,不用改业务代码。

数据落地:Parquet与Arrow格式

数据拿到手了,存成什么格式?CSV?别闹了。高频数据动辄每天几千万条,CSV读写慢、占空间大,根本扛不住。

我推荐两种格式:ParquetArrow

特性ParquetArrow
存储方式列式存储,压缩率高列式内存格式,可持久化
读取速度快(支持谓词下推)极快(零拷贝)
适用场景长期存储、数据分析实时计算、进程间通信
压缩比高(默认Snappy)中等

我的做法是:热数据用Arrow,冷数据用Parquet。当天交易的数据,用Arrow存内存或SSD上,方便回测时快速读取。历史数据,压缩成Parquet归档。

代码示例:将Tick数据写入Parquet

import pandas as pd
import pyarrow.parquet as pq

# 假设tick_data是DataFrame
tick_data = pd.DataFrame({
    'datetime': ['2024-01-15 09:30:00.123'],
    'last_price': [3985.0],
    'volume': [120],
    'symbol': ['rb2401']
})

# 写入Parquet,使用Snappy压缩
tick_data.to_parquet(
    'data/rb2401_20240115.parquet',
    compression='snappy',
    engine='pyarrow'
)

数据压缩策略

压缩不是随便选个算法就完事了。你得考虑读写速度压缩比的平衡。

我常用的压缩策略:

  • Snappy:压缩快,解压也快。适合高频场景,我90%的场景都用它
  • Zstd:压缩比高,但速度慢一些。适合归档数据
  • Gzip:不推荐。压缩慢,解压也慢,除非你有特殊需求

注意:不要对单个字段做压缩。Parquet和Arrow都是列式存储,它们会在列级别自动压缩。你手动压缩反而会破坏列式存储的优势。

另外,时间分区也是一种隐形的压缩策略。按天、按小时分区存储,查询时只扫描需要的分区,速度能快几十倍。

我的习惯:每天收盘后,把当天的Tick数据按合约+日期分区存储。比如data/rb2401/2024/01/15/。这样回测时只需要加载指定日期的数据,内存压力小很多。

知识体系总览

下面这张图,把数据采集到存储的整个链路串起来了。你可以对照着看,心里有个全局观。

数据采集与存储链路 CTP行情接口 易盛行情接口 其他数据源 数据采集层 异步回调 → 队列缓冲 → 数据清洗 → 字段标准化 存储决策:热数据 vs 冷数据 热数据(当天) Arrow格式 → SSD存储 → 实时回测 冷数据(历史) Parquet格式 → HDD/云存储 → 归档分析 压缩策略:Snappy(默认)| Zstd(归档)| 时间分区(隐式压缩)

你看,整个链路其实不复杂。关键是每个环节都要。接口对接要处理好异步回调,数据落地要选对格式,压缩策略要平衡速度和空间。

我曾经在一个项目里,因为压缩策略没选好,导致回测时I/O成了瓶颈。后来换成Snappy+时间分区,速度提升了5倍。嗯,这些细节,往往决定了你的策略能不能跑起来。