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时,可以按日期分区存储。比如每天一个文件,这样回测时只加载需要的时间段,内存占用小很多。
好了,数据获取这块就讲这么多。下一章咱们聊聊数据清洗——你拿到的数据,远没有你想的那么干净。