4. 数据获取与处理:用ccxt搞定交易所行情

做市商系统里,数据就是你的眼睛。没有数据,报价就是瞎蒙。

我个人习惯把数据获取分成三个层次:Ticker(实时快照)、K线(历史切片)、深度数据(订单簿)。今天咱们重点讲前两个,深度数据后面单独开一章。

4.1 为什么选ccxt?

市面上有很多交易所的API SDK,但ccxt是唯一一个让我觉得「嗯,这玩意儿靠谱」的库。它统一了100多家交易所的接口,写法几乎一样。

你想想看,如果每个交易所都单独写一套对接代码,光维护就够你喝一壶的。我在项目中遇到过最夸张的情况——同时对接6家交易所,每家API风格都不一样。后来全换成ccxt,代码量直接砍掉70%。

核心优势:

  • 统一接口:Binance、OKX、Bybit、Coinbase……写法一致
  • 自动处理:签名、频率限制、重连机制
  • 活跃维护:几乎每周更新,跟得上交易所变化

4.2 安装与初始化

安装很简单,一行命令搞定:

pip install ccxt

初始化交易所对象时,我建议把API密钥放在环境变量里,别硬编码。曾经有个同事把密钥直接写在代码里提交到GitHub……嗯,后果你懂的。

import ccxt
import os

# 初始化交易所
exchange = ccxt.binance({
    'apiKey': os.getenv('BINANCE_API_KEY'),
    'secret': os.getenv('BINANCE_SECRET'),
    'enableRateLimit': True,  # 自动限速,很重要
    'options': {
        'defaultType': 'spot',  # 现货市场
    }
})

# 测试连接
try:
    exchange.load_markets()
    print(f"已连接 {exchange.name},支持 {len(exchange.markets)} 个交易对")
except Exception as e:
    print(f"连接失败: {e}")

我的小习惯:enableRateLimit一定要开。不开的话,交易所分分钟封你IP。我刚开始做的时候吃过这个亏,被Binance封了3次才长记性。

4.3 获取Ticker数据

Ticker就是当前市场的快照——最新价、24小时涨跌、成交量这些。做市商系统里,Ticker用来判断当前市场状态。

def get_ticker(symbol='BTC/USDT'):
    """获取实时Ticker"""
    ticker = exchange.fetch_ticker(symbol)
    
    # 提取关键字段
    data = {
        'symbol': ticker['symbol'],
        'last': ticker['last'],           # 最新成交价
        'bid': ticker['bid'],             # 买一价
        'ask': ticker['ask'],             # 卖一价
        'baseVolume': ticker['baseVolume'], # 24h成交量
        'quoteVolume': ticker['quoteVolume'], # 24h成交额
        'percentage': ticker['percentage'],   # 24h涨跌幅
        'timestamp': ticker['timestamp'],
    }
    return data

# 调用
ticker_data = get_ticker('BTC/USDT')
print(f"BTC最新价: {ticker_data['last']} USDT")

这里有个坑要注意——不同交易所返回的字段名可能不一样。ccxt虽然做了统一,但偶尔会有遗漏。我建议拿到数据后先打印看看结构,别直接信文档。

4.4 获取K线数据

K线是做策略的基础。做市商系统里,K线用来计算波动率、均线、布林带这些指标。

def get_ohlcv(symbol='BTC/USDT', timeframe='1h', limit=100):
    """
    获取K线数据
    :param symbol: 交易对
    :param timeframe: 时间周期 (1m, 5m, 15m, 1h, 4h, 1d)
    :param limit: 返回条数
    """
    ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
    
    # 转换成结构化数据
    candles = []
    for candle in ohlcv:
        candles.append({
            'timestamp': candle[0],   # 开盘时间
            'open': candle[1],        # 开盘价
            'high': candle[2],        # 最高价
            'low': candle[3],         # 最低价
            'close': candle[4],       # 收盘价
            'volume': candle[5],      # 成交量
        })
    return candles

# 获取最近100根1小时K线
btc_1h = get_ohlcv('BTC/USDT', '1h', 100)
print(f"获取到 {len(btc_1h)} 根K线")
print(f"最新收盘价: {btc_1h[-1]['close']}")

注意:不同交易所对limit的限制不一样。Binance最多1000根,OKX最多300根。如果你需要更多历史数据,得用循环分批拉取。我一般一次拉500根,够用又不触发限速。

4.5 数据清洗——别让脏数据毁了你的策略

数据拿到手,别急着用。交易所偶尔会返回异常数据——价格突变、时间戳错乱、成交量异常。不做清洗的话,你的策略可能会被一根假K线带偏。

我曾经遇到过一件事:某交易所的API在系统升级时返回了重复的K线数据,我的做市系统直接按那个数据调整了报价,结果亏了2个BTC……从那以后,数据清洗成了我的铁律。

def clean_ohlcv(candles):
    """清洗K线数据"""
    cleaned = []
    
    for i, candle in enumerate(candles):
        # 1. 检查价格是否合理(防止交易所bug)
        if candle['high'] <= 0 or candle['low'] <= 0:
            print(f"跳过异常K线 #{i}: 价格异常")
            continue
            
        # 2. 检查高低价关系
        if candle['high'] < candle['low']:
            print(f"跳过异常K线 #{i}: 最高价小于最低价")
            continue
            
        # 3. 检查时间戳是否递增(防止重复数据)
        if cleaned and candle['timestamp'] <= cleaned[-1]['timestamp']:
            print(f"跳过异常K线 #{i}: 时间戳重复或倒退")
            continue
            
        # 4. 检查成交量是否异常(防止插针)
        if cleaned and candle['volume'] > cleaned[-1]['volume'] * 10:
            print(f"警告: 成交量异常放大 #{i}")
            # 这里可以标记,但不一定删除
            
        cleaned.append(candle)
    
    return cleaned

# 使用
raw_data = get_ohlcv('BTC/USDT', '1h', 500)
clean_data = clean_ohlcv(raw_data)
print(f"清洗前: {len(raw_data)} 根, 清洗后: {len(clean_data)} 根")

4.6 数据存储——选对工具省一半力气

数据存哪里?这取决于你的使用场景。

存储方式 适用场景 我的评价
CSV文件 本地测试、小规模回测 简单粗暴,但别用于生产
SQLite 单机策略、个人项目 够用,但并发写入不行
InfluxDB 时序数据、高频场景 我的首选,查询快、压缩率高
PostgreSQL 团队协作、复杂查询 功能强大,但配置稍重

我个人习惯用InfluxDB存K线数据。原因很简单——时序数据库天生就是干这个的,写入快、查询快、磁盘占用小。

# 用CSV存储(最简单的方式)
import csv
import pandas as pd

def save_to_csv(data, filename='btc_1h.csv'):
    """保存K线到CSV"""
    df = pd.DataFrame(data)
    df.to_csv(filename, index=False)
    print(f"数据已保存到 {filename}")

def load_from_csv(filename='btc_1h.csv'):
    """从CSV加载K线"""
    df = pd.read_csv(filename)
    return df.to_dict('records')

# 使用
save_to_csv(clean_data, 'btc_1h_2024.csv')
loaded_data = load_from_csv('btc_1h_2024.csv')

我的建议:开发阶段用CSV就够了,方便调试。上线前再切换到InfluxDB或PostgreSQL。别一开始就上重型数据库,容易陷入「过度设计」的坑。

4.7 完整的数据流水线

把上面这些串起来,就是一个完整的数据获取与处理流程。我画了张图帮你理解:

数据获取与处理流水线 交易所 API Binance / OKX / Bybit ccxt 统一接口 Ticker / K线 / 深度 数据清洗 去重 / 校验 / 过滤 数据存储 CSV / DB / Influx 使用场景 实时报价决策 策略回测验证 风险监控告警 数据是做市商系统的血液,清洗是过滤血栓

4.8 避坑指南

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

  • 频率限制:别用单线程高频请求。我一般用asyncio异步,或者加个简单的sleep(0.1)。
  • 时间同步:交易所的时间戳是UTC,记得转成你本地时区。我吃过这个亏,策略在凌晨3点莫名其妙开仓。
  • 断线重连:网络波动是常态。写个重试机制,最多重试3次,每次间隔递增。
  • 数据对齐:不同交易所的K线起始时间可能差几秒。做多交易所策略时,一定要对齐时间戳。

曾经有一次:我在生产环境忘了处理交易所的维护公告时间。结果API返回了空数据,系统直接报错停了15分钟。后来我加了个健康检查——如果连续3次获取失败,就切换到备用数据源。

好了,数据获取与处理这块就讲这么多。代码都在上面了,你直接复制就能跑。记住一点:数据质量决定策略上限。花80%的时间在数据上,20%的时间写策略,这个比例不夸张。


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