3、数据获取与API使用:主流交易所API介绍(Binance/Coinbase)、获取历史K线数据、数据频率与时间周期选择

做量化交易,数据就是你的弹药库。没有数据,再牛的模型也是纸上谈兵。

我个人习惯把数据获取比作「做饭前的买菜」。菜买不好,厨艺再高也白搭。这一章,我们就来聊聊怎么「买菜」——从主流交易所拿到靠谱的历史K线数据。

3.1 主流交易所API:Binance vs Coinbase

市面上交易所很多,但真正适合做量化研究的,其实就那么几家。我主要用Binance和Coinbase,原因很简单:数据全、文档清晰、社区活跃。

核心观点:选交易所API,看三点——数据质量、请求限制、文档友好度。

3.1.1 Binance API

Binance是目前全球最大的加密货币交易所。它的API设计得很「工程师友好」。我个人觉得,Binance的REST API响应速度很快,而且历史数据覆盖了从2017年至今的所有交易对。

举个例子,获取BTC/USDT的日线数据,只需要一个HTTP请求:

import requests
import pandas as pd

# Binance API端点
url = "https://api.binance.com/api/v3/klines"

# 参数设置
params = {
    "symbol": "BTCUSDT",
    "interval": "1d",      # 日线
    "limit": 1000          # 最多1000根K线
}

# 发送请求
response = requests.get(url, params=params)
data = response.json()

# 解析成DataFrame
df = pd.DataFrame(data, columns=[
    "open_time", "open", "high", "low", "close", "volume",
    "close_time", "quote_asset_volume", "number_of_trades",
    "taker_buy_base_asset_volume", "taker_buy_quote_asset_volume", "ignore"
])

# 转换时间戳
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
print(df[["open_time", "open", "high", "low", "close", "volume"]].head())

小技巧:Binance的K线数据返回的是Unix毫秒时间戳。记得用pd.to_datetime(..., unit="ms")转换,不然你看到的就是一串数字,毫无意义。

3.1.2 Coinbase API

Coinbase Pro(现在叫Coinbase Advanced Trade)是另一家主流交易所。它的API风格偏RESTful,用起来也很顺手。不过,Coinbase的历史数据只保留最近300根K线,这点和Binance不太一样。

我曾经在项目中同时拉取Binance和Coinbase的数据做对比验证。结果发现,两家交易所的收盘价偶尔会有0.1%左右的偏差。为什么?因为撮合机制和流动性不同。嗯,这里要注意——做回测时,最好只用一家交易所的数据,别混着用。

import requests

# Coinbase API端点
url = "https://api.exchange.coinbase.com/products/BTC-USD/candles"

# 参数:粒度300秒(5分钟)
params = {
    "granularity": 300,
    "start": "2024-01-01T00:00:00Z",
    "end": "2024-01-02T00:00:00Z"
}

response = requests.get(url, params=params)
data = response.json()

# Coinbase返回格式:[time, low, high, open, close, volume]
df = pd.DataFrame(data, columns=["time", "low", "high", "open", "close", "volume"])
df["time"] = pd.to_datetime(df["time"], unit="s")
print(df.head())

避坑指南:我曾经因为Coinbase的API限频没处理好,导致IP被临时封禁。Coinbase对未认证的请求限制是每秒3次,认证后是每秒5次。Binance则是每分钟1200次权重。写代码时一定要加time.sleep()或者用异步请求。

3.2 获取历史K线数据:从原始数据到可用DataFrame

拿到原始数据只是第一步。你想想看,交易所返回的JSON里,时间戳是毫秒数,价格是字符串,成交量是浮点数——这些都需要清洗和转换。

我一般会封装一个通用的数据获取函数,方便后续复用:

def fetch_klines_binance(symbol, interval, start_str, end_str=None):
    """
    获取Binance历史K线数据
    :param symbol: 交易对,如"BTCUSDT"
    :param interval: 时间周期,如"1h", "4h", "1d"
    :param start_str: 开始时间,如"2024-01-01"
    :param end_str: 结束时间,可选
    :return: 清洗后的DataFrame
    """
    url = "https://api.binance.com/api/v3/klines"
    all_data = []
    
    # 转换为毫秒时间戳
    start_ts = int(pd.Timestamp(start_str).timestamp() * 1000)
    end_ts = int(pd.Timestamp(end_str).timestamp() * 1000) if end_str else None
    
    while True:
        params = {
            "symbol": symbol,
            "interval": interval,
            "startTime": start_ts,
            "limit": 1000
        }
        
        response = requests.get(url, params=params)
        data = response.json()
        
        if not data:
            break
            
        all_data.extend(data)
        
        # 更新起始时间戳为最后一条数据的时间
        start_ts = data[-1][0] + 1
        
        # 如果指定了结束时间,且已超过,则停止
        if end_ts and start_ts > end_ts:
            break
            
        # 礼貌性暂停,避免触发限频
        time.sleep(0.1)
    
    # 转换为DataFrame
    df = pd.DataFrame(all_data, columns=[
        "open_time", "open", "high", "low", "close", "volume",
        "close_time", "quote_asset_volume", "number_of_trades",
        "taker_buy_base_asset_volume", "taker_buy_quote_asset_volume", "ignore"
    ])
    
    # 数据类型转换
    df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
    df["close"] = df["close"].astype(float)
    df["open"] = df["open"].astype(float)
    df["high"] = df["high"].astype(float)
    df["low"] = df["low"].astype(float)
    df["volume"] = df["volume"].astype(float)
    
    # 设置时间索引
    df.set_index("open_time", inplace=True)
    
    return df[["open", "high", "low", "close", "volume"]]

个人经验:这个函数我用了两年多,从没出过问题。唯一要注意的是,Binance的startTime参数是包含关系,也就是说,如果你设置startTime=某时间戳,返回的数据会包含这个时间点之后的K线。所以循环里要加1毫秒,避免重复获取同一条数据。

3.3 数据频率与时间周期选择

数据频率的选择,直接决定了你的模型能看到什么「粒度」的市场行为。说白了,就像用显微镜还是用望远镜看市场。

我整理了一张表,方便你快速决策:

时间周期 K线数量/天 适用场景 我的建议
1分钟 (1m) 1440 高频交易、做市策略 数据量大,适合短线,但噪声也多
5分钟 (5m) 288 日内交易、网格策略 平衡了细节和噪声,我常用
1小时 (1h) 24 波段交易、趋势跟踪 适合大多数机器学习模型
4小时 (4h) 6 中长线、趋势判断 信号稳定,适合LSTM等序列模型
1天 (1d) 1 长期投资、基本面分析 数据少,适合做宏观判断

你可能会问:「那我到底该选哪个周期?」

我的回答是:看你的交易频率和模型复杂度。如果你做日内交易,5分钟或15分钟比较合适。如果你做趋势跟踪,4小时或日线更靠谱。我个人习惯先用1小时数据做特征工程,然后下采样到4小时做模型训练——这样既保留了日内细节,又减少了过拟合风险。

核心原则:数据频率越高,噪声越大,但信号也越丰富。频率越低,信号越平滑,但可能错过关键转折点。没有绝对正确的选择,只有适合你策略的选择。

3.4 本章知识体系:数据获取流程

下面这张图,是我自己画的数据获取流程。你看一遍,基本就明白整个链路了:

数据获取与API使用流程 选择交易所 Binance / Coinbase 确定参数 交易对 / 周期 / 时间范围 发送API请求 数据清洗 格式转换 JSON → DataFrame 存储到本地 CSV / Parquet / 数据库 循环获取更多数据(分页)

这张图的核心逻辑是:先选交易所,再定参数,然后发请求,拿到数据后清洗、转换、存储。如果数据量不够,就循环分页获取。嗯,就是这么简单。

3.5 避坑指南与最佳实践

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • 限频问题:我曾经一次性发了2000个请求,结果IP被封了24小时。后来我学乖了,每次请求之间至少间隔100毫秒,并且用requests.Session()复用连接。
  • 数据对齐:不同交易所的K线时间戳可能差几秒。做回测时,一定要统一时间基准。我一般以Binance的时间为准,因为它的数据最全。
  • 缺失值处理:有些冷门币种在早期可能没有交易数据。遇到这种情况,我直接填充为前一天的收盘价,或者干脆丢弃。
  • 存储格式:我建议用Parquet格式存储历史数据,比CSV快10倍以上,而且压缩率高。如果你用CSV,记得加上compression='gzip'

重要提醒:不要在生产环境中直接用API拉取大量历史数据。正确的做法是:先一次性拉取全量数据存到本地,然后每天增量更新。这样既快又稳。

好了,数据获取这部分就聊到这。下一章我们会深入特征工程,看看怎么从这些原始K线数据里挖出有用的信号。记住,数据是基础,但真正让模型赚钱的,是特征。


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