第一章:数据获取与存储——WebSocket实时数据流接入、REST API历史数据获取、Tick数据的高效存储方案

做套利策略,说白了就是跟时间赛跑。你比别人快0.1秒,可能就多赚几个点。但这一切的前提是什么?数据。没有干净、及时、完整的数据,再牛的策略也是空中楼阁。

我个人习惯把数据这块分成三个环节:实时接入、历史回补、高效存储。今天咱们就一个一个拆开聊。

1.1 WebSocket实时数据流接入

为什么用WebSocket而不是轮询?你想想看,套利机会往往就出现在价格跳变的瞬间。如果用REST API每秒轮询一次,等你拿到数据,黄花菜都凉了。

WebSocket是全双工通信,交易所一有Tick变动,立马推送到你本地。延迟通常在10-50毫秒级别,比轮询快一个数量级。

核心要点:WebSocket连接建立后,需要维护心跳。交易所一般要求每30秒发一次ping,否则会断开连接。我在项目中遇到过因为心跳超时导致数据断流,回测时发现少了一堆Tick,排查了半天才发现是这个问题。

下面是一个简单的WebSocket接入示例,以币安为例:

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 解析Tick数据
    tick = {
        'symbol': data['s'],
        'price': float(data['c']),
        'volume': float(data['v']),
        'timestamp': data['E']
    }
    print(f"收到Tick: {tick}")

def on_error(ws, error):
    print(f"WebSocket错误: {error}")

def on_close(ws, close_status_code, close_msg):
    print("WebSocket连接关闭")

def on_open(ws):
    print("WebSocket连接已建立")
    # 订阅BTC/USDT的实时行情
    subscribe_msg = {
        "method": "SUBSCRIBE",
        "params": ["btcusdt@ticker"],
        "id": 1
    }
    ws.send(json.dumps(subscribe_msg))

# 启动WebSocket
ws = websocket.WebSocketApp(
    "wss://stream.binance.com:9443/ws",
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close
)
ws.run_forever()

避坑指南:我曾经在生产环境里只开了一个WebSocket连接,结果订阅了20多个交易对,数据量一大直接卡死。后来改成每个交易对单独开一个连接,或者用连接池管理,问题就解决了。建议一个连接不要订阅超过10个交易对。

1.2 REST API历史数据获取

实时数据有了,但回测怎么办?你需要历史数据。REST API就是干这个的。

大部分交易所都提供历史K线或Tick数据接口。但要注意,历史Tick数据通常只保留最近几天,更早的数据需要付费或者自己攒。

我一般这样设计数据获取流程:

  1. 首次运行:用REST API拉取最近7天的历史数据
  2. 日常运行:WebSocket实时接收,同时每5分钟用REST API做一次数据校验
  3. 数据补全:如果发现缺失,从REST API补拉

下面是一个获取历史K线的示例:

import requests
import pandas as pd

def fetch_historical_klines(symbol, interval, limit=1000):
    url = "https://api.binance.com/api/v3/klines"
    params = {
        'symbol': symbol,
        'interval': interval,
        'limit': limit
    }
    response = requests.get(url, params=params)
    data = response.json()
    
    # 转换为DataFrame
    df = pd.DataFrame(data, columns=[
        'timestamp', 'open', 'high', 'low', 'close', 'volume',
        'close_time', 'quote_asset_volume', 'number_of_trades',
        'taker_buy_base_vol', 'taker_buy_quote_vol', 'ignore'
    ])
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    return df

# 获取BTC/USDT最近1000根1分钟K线
df = fetch_historical_klines('BTCUSDT', '1m', 1000)
print(df.head())

注意:REST API有频率限制。币安的限制是每分钟1200次请求。我曾经一次性拉取100个交易对的历史数据,结果被限流了半小时。建议加个请求队列,控制请求频率在每秒20次以内。

1.3 Tick数据的高效存储方案

数据拿到了,存哪儿?这是个大学问。

Tick数据的特点是:高频、海量、时间序列。一个交易对一天就能产生几十万条Tick,多个交易对加起来轻松上亿。普通的关系型数据库(比如MySQL)根本扛不住。

我推荐两种方案:

方案 适用场景 优点 缺点
InfluxDB 实时查询、监控 写入快、查询快、自带时间序列优化 存储成本高、不适合复杂分析
Parquet 历史回测、数据分析 压缩率高、列式存储、适合批量处理 不支持实时写入

我个人习惯是:实时数据用InfluxDB,历史数据转存Parquet。这样既保证了实时查询的速度,又节省了存储成本。

InfluxDB写入示例

from influxdb import InfluxDBClient
import json

client = InfluxDBClient(host='localhost', port=8086)
client.create_database('tick_data')

def write_tick_to_influx(tick):
    json_body = [
        {
            "measurement": "tick",
            "tags": {
                "symbol": tick['symbol']
            },
            "time": tick['timestamp'],
            "fields": {
                "price": tick['price'],
                "volume": tick['volume']
            }
        }
    ]
    client.write_points(json_body)

# 示例:写入一条Tick
tick = {'symbol': 'BTCUSDT', 'price': 50000.0, 'volume': 1.5, 'timestamp': '2024-01-01T00:00:00Z'}
write_tick_to_influx(tick)

Parquet存储示例

import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq

def save_tick_to_parquet(tick_list, file_path):
    df = pd.DataFrame(tick_list)
    table = pa.Table.from_pandas(df)
    pq.write_table(table, file_path, compression='snappy')

# 示例:保存1000条Tick到Parquet文件
tick_list = [
    {'symbol': 'BTCUSDT', 'price': 50000.0, 'volume': 1.5, 'timestamp': '2024-01-01T00:00:00Z'}
    # ... 更多数据
]
save_tick_to_parquet(tick_list, 'btc_tick_20240101.parquet')

经验之谈:Parquet文件建议按日期分区存储,比如 data/2024/01/01/btc_tick.parquet。这样查询某一天的数据时,只需要加载对应的文件,效率高很多。我刚开始把所有数据塞到一个文件里,结果回测时加载一次要等5分钟,后来改成按天分区,加载时间降到了10秒以内。

1.4 整体架构图

下面这张图展示了数据获取与存储的整体流程。你可以看到,WebSocket负责实时数据,REST API负责历史数据,两者最终汇入InfluxDB和Parquet。

Tick数据获取与存储架构 交易所A 交易所B 交易所C WebSocket实时数据流 数据处理器(清洗、校验、格式化) InfluxDB(实时查询) Parquet文件(历史回测) 数据源 实时流 处理器 存储

嗯,到这里数据获取与存储的核心内容就讲完了。记住一句话:数据质量决定策略上限。WebSocket保证实时性,REST API保证完整性,InfluxDB和Parquet保证存储效率。这三板斧用好了,你的套利策略就成功了一半。


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