数据源接入:交易所API对接、行情数据格式解析、交易数据格式解析

做市业务里,数据源接入是第一步,也是最容易踩坑的一步。

我见过不少团队,策略模型写得漂漂亮亮,结果一上线就亏钱。查到最后,发现是API对接时数据就错了。嗯,数据源不对,后面全是白搭。

交易所API对接:别小看这层通信

每个交易所的API风格都不一样。有的用REST,有的用WebSocket。我个人习惯是:行情走WebSocket,交易走REST。为什么?

  • WebSocket:全双工通信,适合高频行情推送。延迟低,带宽省。
  • REST:请求-响应模式,适合下单、撤单、查余额。逻辑清晰,容易调试。

我在项目中遇到过一个问题:某交易所的WebSocket每隔30秒会发一个ping帧,如果客户端没及时回复pong,连接就被断开了。当时我们的重连逻辑没写好,导致行情断了整整3分钟。嗯,那3分钟里策略还在按旧数据下单,亏了不少。

避坑指南:我曾经因为没处理好WebSocket的心跳机制,导致生产环境频繁断连。后来我统一封装了一个连接管理模块,自动处理ping/pong、断线重连、消息去重。建议你也这么做。

行情数据格式解析:别被字段名骗了

行情数据,说白了就是交易所告诉你「现在市场上发生了什么」。常见的格式有:

数据类型 典型字段 说明
深度数据 bids, asks, price, size 买卖盘口,按价格排序
Ticker last, volume, high, low 最新成交价、成交量等
K线 open, high, low, close, volume 时间窗口内的聚合数据
逐笔成交 price, size, side, timestamp 每一笔真实的成交记录

你想想看,深度数据里的bids和asks,不同交易所的排序方向可能相反。有的bids从高到低,有的从低到高。如果你没注意,直接拿过来用,策略算出来的中间价可能就是错的。

我的经验:解析行情数据时,一定要做一次「格式校验」。比如检查bids[0].price是否大于bids[1].price,如果不是,说明排序方向反了,需要反转。

交易数据格式解析:订单状态机是关键

交易数据比行情数据复杂得多。因为它涉及状态变化。一个订单从提交到成交,中间可能经历:

  1. NEW:刚提交,还没进撮合引擎
  2. PARTIALLY_FILLED:部分成交
  3. FILLED:完全成交
  4. CANCELED:被撤销
  5. REJECTED:被拒绝

我在项目中遇到过最头疼的事:某交易所的订单状态更新有延迟。订单明明已经成交了,但API返回的还是PARTIALLY_FILLED。如果你这时候去撤单,可能会把已经成交的部分也撤掉——嗯,那就会产生「撤单失败」的报错。

建议:维护一个本地订单状态机。每次收到交易所的推送,先对比本地状态和推送状态。如果状态跳跃了(比如从NEW直接到FILLED),要能正确处理。不要假设交易所的推送顺序是可靠的。

数据清洗:脏数据比你想的要多

数据接入后,第一件事不是存起来,而是清洗。我总结了几类常见脏数据:

  • 重复数据:同一个成交记录被推送了两次。需要根据交易ID去重。
  • 乱序数据:后发生的成交先到达。需要根据时间戳重新排序。
  • 缺失数据:某段时间的行情断了。需要做插值或标记为无效。
  • 异常数据:价格突然跳变100倍。可能是交易所的bug,也可能是数据解析错误。

说白了,数据清洗就是「把垃圾挡在门外」。我见过有人把脏数据直接喂给模型,结果模型学了一堆错误规律。你想想看,那能赚钱吗?

实战:一个简单的行情解析示例

下面是一个简化版的深度数据解析代码。注意,我用了异常处理和格式校验:

def parse_orderbook(raw_data):
    try:
        bids = raw_data['bids']
        asks = raw_data['asks']
        
        # 校验排序方向
        if bids[0][0] < bids[1][0]:
            bids = sorted(bids, key=lambda x: x[0], reverse=True)
        if asks[0][0] > asks[1][0]:
            asks = sorted(asks, key=lambda x: x[0])
        
        # 提取价格和数量
        bid_prices = [b[0] for b in bids[:10]]
        bid_sizes = [b[1] for b in bids[:10]]
        ask_prices = [a[0] for a in asks[:10]]
        ask_sizes = [a[1] for a in asks[:10]]
        
        return {
            'bid_prices': bid_prices,
            'bid_sizes': bid_sizes,
            'ask_prices': ask_prices,
            'ask_sizes': ask_sizes,
            'timestamp': raw_data.get('timestamp', 0)
        }
    except Exception as e:
        log.error(f"解析深度数据失败: {e}")
        return None

这段代码看起来简单,但我在生产环境里加了不少细节。比如排序校验、异常捕获、日志记录。嗯,这些细节往往决定了系统的稳定性。

数据存储:选对工具省一半力气

数据解析完了,存哪里?我个人推荐分层存储:

层级 存储工具 用途
热数据 Redis / 内存 当前盘口、最新成交,毫秒级访问
温数据 InfluxDB / TimescaleDB 分钟级K线、历史深度快照
冷数据 Parquet / HDFS 全量历史数据,用于回测和分析

我在项目中遇到过一个问题:把全量逐笔成交数据都存到关系型数据库里,结果查询一次要等几十秒。后来改成用Parquet按天分区存储,查询速度提升了100倍。说白了,选对工具比优化代码更重要。

本章小结

数据源接入这件事,看起来是体力活,其实藏着不少门道。API对接要注意心跳和重连,行情解析要注意格式和排序,交易解析要注意状态机,数据清洗要防脏数据。每一步都马虎不得。

我记得刚入行时,觉得这些基础工作没什么技术含量。后来吃了不少亏才明白:数据质量决定了策略的上限。如果你的数据源是错的,再牛的模型也救不了你。

一句话总结:数据源接入不是「接上就行」,而是「接对、接稳、接干净」。

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