第四章:数据获取与清洗——交易所API对接、实时行情订阅、数据对齐与清洗、缺失值处理
做跨市场套利,说白了就是跟数据打交道。我见过太多策略,逻辑上完美无缺,一上实盘就崩——十有八九是数据环节出了问题。今天咱们就把这块硬骨头啃下来。
4.1 交易所API对接:选对路子,少走弯路
不同交易所的API风格差异很大。我个人习惯把API分成三类:
| 类型 | 代表交易所 | 特点 | 适合场景 |
|---|---|---|---|
| RESTful | Binance、OKX | 请求-响应模式,简单稳定 | 历史数据获取、订单管理 |
| WebSocket | BitMEX、Deribit | 全双工通信,延迟低 | 实时行情订阅、高频套利 |
| FIX协议 | 传统期货交易所 | 金融行业标准,可靠性高 | 机构级套利、做市商 |
我在项目中遇到过最坑的事:某交易所的REST接口限频是每秒1次,但文档里写的是10次。结果策略跑起来直接触发风控,账户被冻结了半小时。所以我的建议是——永远不要相信文档里的限频说明,自己压测一遍。
核心原则:对接API时,先做三件事——测延迟、测限频、测断线重连。这三件事没做完,别上实盘。
4.2 实时行情订阅:别让数据成为你的瓶颈
实时行情订阅,听起来简单,做起来全是坑。你想想看,两个交易所的行情同时涌进来,延迟不一样,深度不一样,甚至价格更新的频率都不一样——怎么对齐?
我常用的做法是:
- 统一时间戳:所有行情数据到达后,立即打上本地时间戳。别信交易所的时间戳,那玩意儿可能差好几秒。
- 建立缓冲区:每个交易所维护一个100ms的滑动窗口,窗口内的数据取最新值。
- 心跳检测:如果某个交易所超过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-5秒)。
- 放弃该时刻:如果缺失超过5秒,直接放弃这个时间点的交易信号。
为什么会这样?因为套利策略对时效性要求极高。你补一个5秒前的价格,跟实际价格可能已经差了十万八千里。与其用错误的数据,不如不做这笔交易。
我的习惯:每次处理缺失值,我都会记录日志。如果某个交易所的缺失率超过0.1%,我会主动检查网络或API状态。别等到策略亏钱了才想起来查数据质量。
知识体系总览
下面这张图,是我做数据获取与清洗的完整流程。每次搭建新策略,我都会对着这张图检查一遍,确保每个环节都没漏掉。
数据获取与清洗,听起来是苦活累活,但恰恰是套利策略的基石。我见过太多人花90%的时间写策略逻辑,只花10%的时间处理数据——结果就是策略在回测里跑得漂亮,一上实盘就翻车。记住:数据质量决定了策略的天花板。
最后说一句:别嫌数据清洗麻烦。你在这上面花的时间,会在实盘里十倍百倍地赚回来。我自己的经验是,数据清洗代码和策略代码的比例,至少是1:1。有时候甚至更多。
公众号:蓝海数据掘金营 | 微信:deep3321