3、数据获取:交易所API介绍(Binance/OKX)、REST与WebSocket协议、历史K线数据下载、数据存储格式(CSV/Parquet)

做高频回测,第一步就是搞定数据。

数据拿不到,或者拿不准,后面所有策略都是空中楼阁。我见过太多人花几个月写策略,最后发现数据源有问题,全白干了。所以这一章,咱们把数据获取这件事彻底讲透。

3.1 交易所API:Binance vs OKX

目前全球流动性最好的两家交易所,就是币安和欧易。我个人习惯两个都接,但如果你刚入门,先搞定一个就行。

特性 Binance OKX
REST限频 1200次/分钟(权重制) 600次/分钟(IP维度)
WebSocket订阅 单连接最多1024个stream 单连接最多200个channel
历史K线深度 最早2017年 最早2018年
文档质量 ★★★★★ ★★★★☆

嗯,这里要注意一点:Binance的权重制限频比OKX的IP限频更灵活。什么意思呢?你发一个深度请求可能消耗50权重,但查个账户余额只消耗1权重。我建议你写代码时,一定要把权重消耗打日志,不然很容易被临时封禁。

我的经验:做高频回测,数据源最好用Binance。原因很简单——它的历史数据最全,而且API文档写得最清楚。OKX的文档有时候会漏掉一些字段,你得自己去试。

3.2 REST vs WebSocket:什么时候用哪个?

说白了,REST就是你去问交易所要数据,WebSocket是交易所主动推给你。

REST协议:

  • 适合一次性请求,比如下载历史K线
  • 请求-响应模式,简单直接
  • 延迟一般在50-200ms,不适合高频

WebSocket协议:

  • 适合实时行情订阅,比如tick级数据
  • 全双工通信,延迟可以做到10ms以内
  • 需要维护心跳,断线重连是必修课

为什么会这样?因为REST每次都要建立HTTP连接,握手开销摆在那里。WebSocket建立一次连接后,数据直接推过来,省掉了握手时间。

避坑指南:我曾经在项目里只用REST拿实时行情,结果回测出来的策略实盘完全跑不动。后来换成WebSocket,延迟从150ms降到了8ms。所以记住:回测用REST,实盘用WebSocket。

3.3 历史K线数据下载:实战代码

下载历史K线,我推荐用Binance的REST接口。代码很简单,但有几个坑要避开。

import requests
import pandas as pd
from datetime import datetime

def download_klines(symbol='BTCUSDT', interval='1m', limit=1000):
    """
    下载历史K线数据
    interval: 1m, 5m, 15m, 30m, 1h, 4h, 1d
    limit: 最大1000根
    """
    url = 'https://api.binance.com/api/v3/klines'
    params = {
        'symbol': symbol,
        'interval': interval,
        'limit': limit
    }
    
    resp = requests.get(url, params=params)
    data = resp.json()
    
    # 解析数据
    klines = []
    for k in data:
        klines.append({
            'timestamp': datetime.fromtimestamp(k[0]/1000),
            'open': float(k[1]),
            'high': float(k[2]),
            'low': float(k[3]),
            'close': float(k[4]),
            'volume': float(k[5])
        })
    
    return pd.DataFrame(klines)

# 下载最近1000根1分钟K线
df = download_klines('BTCUSDT', '1m', 1000)
print(df.head())

你想想看,一次只能拿1000根,如果要拿一年的1分钟K线怎么办?循环请求,每次用上次最后一条的时间戳作为下次的startTime。

注意:Binance的K线时间戳是毫秒级的,而且开收盘时间都是UTC+0。我刚开始做的时候没注意时区,回测结果差了8小时,找了半天bug。建议你统一用UTC时间存储,展示时再转本地时间。

3.4 数据存储格式:CSV vs Parquet

数据拿到手了,存成什么格式?这是个好问题。

特性 CSV Parquet
可读性 ★★★★★ 文本,随便打开 ★★☆☆☆ 二进制,需要工具
压缩率 ★★☆☆☆ 大 ★★★★★ 小(通常压缩80%)
读取速度 ★★☆☆☆ 慢 ★★★★★ 快(列式存储)
类型支持 ★★☆☆☆ 只有字符串 ★★★★★ 原生支持时间、浮点等

我个人习惯是这样:

  • 开发调试阶段:用CSV。方便用Excel打开看数据对不对。
  • 生产回测阶段:用Parquet。速度快,省硬盘,而且pandas直接读。

我的经验:有一次回测跑了3天,结果发现CSV文件里有一行数据格式错了,导致整个回测结果偏差。换成Parquet后,类型检查严格,再也没出过这种问题。说白了,Parquet就是帮你省心。

# CSV存储
df.to_csv('btcusdt_1m.csv', index=False)

# Parquet存储
df.to_parquet('btcusdt_1m.parquet', index=False)

# 读取对比
import time

start = time.time()
df_csv = pd.read_csv('btcusdt_1m.csv')
print(f'CSV读取耗时: {time.time()-start:.2f}秒')

start = time.time()
df_pq = pd.read_parquet('btcusdt_1m.parquet')
print(f'Parquet读取耗时: {time.time()-start:.2f}秒')

嗯,这里要注意:Parquet文件虽然小,但如果你要频繁追加数据,它不如CSV方便。我建议的做法是:每天下载完数据后,先存成CSV做备份,然后转成Parquet供回测使用。

小技巧:用Parquet时,可以按日期分区存储。比如每天一个文件,这样回测时只加载需要的时间段,内存占用小很多。

好了,数据获取这块就讲这么多。下一章咱们聊聊数据清洗——你拿到的数据,远没有你想的那么干净。