第四章:数据获取与清洗——交易所API对接、实时行情订阅、数据对齐与清洗、缺失值处理

做跨市场套利,说白了就是跟数据打交道。我见过太多策略,逻辑上完美无缺,一上实盘就崩——十有八九是数据环节出了问题。今天咱们就把这块硬骨头啃下来。

4.1 交易所API对接:选对路子,少走弯路

不同交易所的API风格差异很大。我个人习惯把API分成三类:

类型 代表交易所 特点 适合场景
RESTful Binance、OKX 请求-响应模式,简单稳定 历史数据获取、订单管理
WebSocket BitMEX、Deribit 全双工通信,延迟低 实时行情订阅、高频套利
FIX协议 传统期货交易所 金融行业标准,可靠性高 机构级套利、做市商

我在项目中遇到过最坑的事:某交易所的REST接口限频是每秒1次,但文档里写的是10次。结果策略跑起来直接触发风控,账户被冻结了半小时。所以我的建议是——永远不要相信文档里的限频说明,自己压测一遍

核心原则:对接API时,先做三件事——测延迟、测限频、测断线重连。这三件事没做完,别上实盘。

4.2 实时行情订阅:别让数据成为你的瓶颈

实时行情订阅,听起来简单,做起来全是坑。你想想看,两个交易所的行情同时涌进来,延迟不一样,深度不一样,甚至价格更新的频率都不一样——怎么对齐?

我常用的做法是:

  1. 统一时间戳:所有行情数据到达后,立即打上本地时间戳。别信交易所的时间戳,那玩意儿可能差好几秒。
  2. 建立缓冲区:每个交易所维护一个100ms的滑动窗口,窗口内的数据取最新值。
  3. 心跳检测:如果某个交易所超过500ms没推送数据,立刻标记为异常。
# 伪代码:实时行情订阅与缓冲
class MarketDataBuffer:
    def __init__(self, window_ms=100):
        self.buffer = deque(maxlen=100)
        self.window_ms = window_ms
        self.last_heartbeat = time.time()
    
    def on_tick(self, tick):
        tick.local_ts = time.time_ns()
        self.buffer.append(tick)
        self.last_heartbeat = time.time()
    
    def get_latest(self):
        # 只取窗口内的最新数据
        cutoff = time.time_ns() - self.window_ms * 1_000_000
        valid = [t for t in self.buffer if t.local_ts > cutoff]
        return valid[-1] if valid else None

避坑指南:我曾经因为没做心跳检测,某个交易所断线了整整3分钟都没发现。那3分钟里策略一直在用过期数据做套利,亏了6位数。从那以后,我每个交易所都单独开一个监控线程,每秒检查一次心跳。

4.3 数据对齐:套利策略的命门

数据对齐,是跨市场套利最容易被忽视的环节。为什么?因为两个交易所的行情到达时间天然不同步。你看到A交易所的价格是100.05,B交易所是100.10——但这两个价格可能差了200毫秒。这200毫秒里,价差可能已经变了。

我的对齐策略分三步:

  • 时间戳对齐:以本地时间戳为基准,把两个交易所的数据对齐到同一个时间轴上。
  • 插值处理:如果某个时间点只有A的数据,没有B的数据,用线性插值补上。
  • 延迟补偿:根据历史延迟数据,估算每个交易所的平均延迟,做偏移校正。

一个血的教训:有次我发现策略的胜率突然从60%掉到40%,查了两天,最后发现是交易所升级了服务器,延迟从50ms降到了20ms。我的对齐逻辑没更新,导致数据一直错位。嗯,从那以后我把延迟补偿做成了动态的,每5分钟重新估算一次。

4.4 数据清洗:脏数据比没数据更可怕

数据清洗,说白了就是给数据「洗澡」。我见过最离谱的脏数据:某交易所某天的BTC价格突然跳到0.01,然后又跳回来。如果策略没做清洗,那天直接爆仓。

我常用的清洗规则:

规则 说明 阈值
价格合理性 价格不能为负,不能为0 price > 0
价格突变检测 单笔价格变化不能超过X% 通常设为5%
成交量异常 成交量不能为负,不能超过日均量的10倍 动态计算
时间戳乱序 后一条数据的时间戳不能早于前一条 严格检查
def clean_tick(tick, prev_tick):
    # 价格合理性检查
    if tick.price <= 0:
        return None
    
    # 价格突变检测
    if prev_tick and abs(tick.price / prev_tick.price - 1) > 0.05:
        return None  # 超过5%的突变,直接丢弃
    
    # 成交量检查
    if tick.volume < 0:
        return None
    
    # 时间戳检查
    if prev_tick and tick.timestamp < prev_tick.timestamp:
        return None
    
    return tick

注意:清洗规则不是越严格越好。太严格会丢掉正常数据,导致策略信号延迟。我一般先用宽松规则跑一段时间,统计一下异常数据的分布,再调整阈值。

4.5 缺失值处理:别让「空档」毁了你的策略

缺失值处理,是数据清洗的最后一道关。交易所偶尔会断流,网络偶尔会抖动,这些都会造成数据缺失。怎么补?

我的处理优先级:

  1. 前向填充:用上一个有效值填充。适用于短时间缺失(< 1秒)。
  2. 线性插值:用前后两个有效值做线性插值。适用于中等时间缺失(1-5秒)。
  3. 放弃该时刻:如果缺失超过5秒,直接放弃这个时间点的交易信号。

为什么会这样?因为套利策略对时效性要求极高。你补一个5秒前的价格,跟实际价格可能已经差了十万八千里。与其用错误的数据,不如不做这笔交易。

我的习惯:每次处理缺失值,我都会记录日志。如果某个交易所的缺失率超过0.1%,我会主动检查网络或API状态。别等到策略亏钱了才想起来查数据质量。

知识体系总览

下面这张图,是我做数据获取与清洗的完整流程。每次搭建新策略,我都会对着这张图检查一遍,确保每个环节都没漏掉。

数据获取与清洗流程 交易所API数据源 实时行情订阅与缓冲 数据对齐(时间戳+插值+延迟补偿) 数据清洗(合理性+突变+成交量) 缺失值处理(前向填充/插值/放弃) 关键检查点 ✓ 限频压测 ✓ 断线重连 ✓ 延迟估算 ✓ 突变阈值 ✓ 缺失率监控 ✓ 日志记录 ✓ 动态阈值调整 ✓ 异常告警 ✓ 数据质量报告 ✓ 回测验证 每个环节都做日志记录,方便事后复盘

数据获取与清洗,听起来是苦活累活,但恰恰是套利策略的基石。我见过太多人花90%的时间写策略逻辑,只花10%的时间处理数据——结果就是策略在回测里跑得漂亮,一上实盘就翻车。记住:数据质量决定了策略的天花板

最后说一句:别嫌数据清洗麻烦。你在这上面花的时间,会在实盘里十倍百倍地赚回来。我自己的经验是,数据清洗代码和策略代码的比例,至少是1:1。有时候甚至更多。


公众号:蓝海数据掘金营 | 微信:deep3321