2. 数据源接入:CTP接口、易盛接口、以及第三方数据商(如万得、聚宽)的数据格式解析

做高频做市,第一步就是搞定数据源。这步要是没走稳,后面所有策略都是空中楼阁。

我见过不少团队,策略逻辑写得漂漂亮亮,结果一上线就崩——查到最后,是数据解析那层出了bug。说白了,数据接入是地基,地基不牢,地动山摇。

今天咱们就聊聊三种主流数据源:CTP、易盛、还有第三方数据商。我会结合我自己的踩坑经历,把每种接口的格式、解析要点、还有那些容易翻车的地方,都给你捋一遍。

2.1 CTP接口:国内期货的标配

CTP(综合交易平台)是上期技术开发的,国内绝大多数期货公司都用它。做高频做市,CTP几乎是绕不开的。

2.1.1 数据格式长什么样

CTP的行情数据,核心结构体是 CThostFtdcDepthMarketDataField。里面字段很多,但咱们做高频做市,重点关注这几个:

字段名 含义 我常用的处理方式
InstrumentID 合约代码 字符串,注意大小写
LastPrice 最新价 浮点数,精度到小数点后两位
BidPrice1 ~ BidPrice5 买一到买五价格 数组,用于构建订单簿
AskPrice1 ~ AskPrice5 卖一到卖五价格 同上
BidVolume1 ~ BidVolume5 买一到买五量 整数,注意单位是手
AskVolume1 ~ AskVolume5 卖一到卖五量 同上
UpdateTime 行情时间 字符串,需要转成时间戳
UpdateMillisec 毫秒 整数,和UpdateTime拼起来用

重点提醒:CTP的行情时间精度只到毫秒。对于高频做市来说,这其实有点不够用。我建议你在接收时,用本地时间戳做补充,尤其是做Tick级回测的时候。

2.1.2 解析代码示例

我用Python比较多,CTP的Python封装一般用 vnpy 或者 ctp-python。下面是我自己项目里用的解析片段:

class CTPDataParser:
    def __init__(self):
        self.order_book = {}
    
    def parse_market_data(self, data):
        """解析CTP行情数据"""
        tick = {
            'symbol': data.InstrumentID,
            'last_price': data.LastPrice,
            'bid_prices': [data.BidPrice1, data.BidPrice2, data.BidPrice3],
            'bid_volumes': [data.BidVolume1, data.BidVolume2, data.BidVolume3],
            'ask_prices': [data.AskPrice1, data.AskPrice2, data.AskPrice3],
            'ask_volumes': [data.AskVolume1, data.AskVolume2, data.AskVolume3],
            'timestamp': f"{data.UpdateTime}.{data.UpdateMillisec:03d}"
        }
        return tick

我的小技巧:CTP的行情推送频率是500毫秒一次。如果你需要更细粒度的数据,可以考虑用FPGA或者DMA网卡来抓取交易所的原始组播数据。当然,这成本就上去了。

2.2 易盛接口:商品期权的利器

易盛(Esunny)在商品期权和部分商品期货上用得比较多。它的数据格式和CTP有些不同,但核心逻辑类似。

2.2.1 数据格式特点

易盛的行情数据,核心结构体是 ESAPIDepthMarketData。和CTP最大的区别在于:

  • 深度支持更多档位:CTP只给5档,易盛可以给到10档甚至更多
  • 时间精度更高:易盛支持微秒级时间戳
  • 期权特有字段:比如隐含波动率、希腊字母等

我记得有一次做豆粕期权的做市策略,CTP拿不到足够的深度数据,换成易盛接口后,订单簿的厚度明显不一样了。

2.2.2 解析要点

易盛的API是C++写的,Python封装相对少一些。我一般用 ctypes 直接调用动态库:

import ctypes

class ESAPIDepthMarketData(ctypes.Structure):
    _fields_ = [
        ("InstrumentID", ctypes.c_char * 31),
        ("UpdateTime", ctypes.c_char * 9),
        ("UpdateMillisec", ctypes.c_int),
        ("BidPrice", ctypes.c_double * 10),
        ("BidVolume", ctypes.c_int * 10),
        ("AskPrice", ctypes.c_double * 10),
        ("AskVolume", ctypes.c_int * 10),
        ("LastPrice", ctypes.c_double),
        ("OpenInterest", ctypes.c_double),
    ]

注意:易盛的行情数据是组播方式推送的,网络环境要求比较高。我曾经在机房调试时,因为交换机配置问题,丢包率高达5%——那数据根本没法用。建议用专门的组播接收卡,或者至少做好丢包重传机制。

2.3 第三方数据商:万得、聚宽

除了直接接交易所接口,很多团队也会用第三方数据商。原因很简单:省事。

2.3.1 万得(Wind)

万得的数据覆盖面广,从期货到股票到债券都有。但它的数据格式偏「金融分析」风格,不太适合高频场景。

万得的数据一般通过 WindPy 接口获取:

from WindPy import w

w.start()

# 获取期货行情
data = w.wsq("rb2401.SHF", "rt_last,rt_bid1,rt_ask1,rt_vol")
print(data.Data)

说实话,万得的行情数据延迟比较高,大概在几百毫秒到几秒不等。做回测分析可以,做实时做市就不太行了。

2.3.2 聚宽(JoinQuant)

聚宽的数据格式更贴近量化交易场景。它的Tick数据长这样:

{
    'symbol': 'rb2401',
    'current': 3850.0,
    'bid': [3849.0, 3848.0, 3847.0],
    'ask': [3851.0, 3852.0, 3853.0],
    'bid_vol': [100, 200, 150],
    'ask_vol': [80, 120, 90],
    'time': '2024-01-15 14:30:00.123'
}

聚宽的数据格式比较规整,解析起来很舒服。但要注意,聚宽的数据是经过清洗的,和原始交易所数据可能会有细微差异。

我的建议:如果你做的是高频做市策略,尽量用CTP或易盛的原生接口。第三方数据商适合做策略回测和辅助分析。我自己的做法是:实盘用CTP,回测用聚宽,两边数据做交叉验证。

2.4 数据格式对比总结

为了让你看得更清楚,我画了一张对比图:

数据源格式对比 CTP接口 • 5档行情 • 毫秒级时间戳 • 结构体格式 • TCP推送 • 延迟约500ms 适合:实盘交易 易盛接口 • 10档+行情 • 微秒级时间戳 • 组播推送 • 期权字段丰富 • 延迟约100μs 适合:期权做市 第三方数据商 • JSON/CSV格式 • 秒级延迟 • 数据已清洗 • 覆盖面广 • 延迟不稳定 适合:回测分析

2.5 数据清洗的通用要点

不管用哪种数据源,有几个清洗要点是通用的:

  1. 时间对齐:不同数据源的时间格式不一样,统一转成时间戳
  2. 去重:CTP偶尔会推送重复的Tick,需要根据时间戳去重
  3. 异常值过滤:比如价格突然跳空、买卖价差为负等
  4. 缺失值处理:行情中断时的数据填充策略

避坑指南:我曾经在CTP数据里发现过「负的成交量」——这明显是数据异常。后来排查发现,是行情服务器重启后,某些字段被重置了。所以,一定要加一层数据校验逻辑,别盲目相信原始数据。

好了,数据源接入这块就聊到这儿。每种接口都有自己的脾气,摸透了就好办了。下一节咱们聊聊数据清洗的具体操作,到时候我会拿真实数据来演示。


公众号:蓝海资料掘金营,微信deep3321