4、滑点数据采集:交易所API数据获取、订单簿数据解析、Tick级数据与K线数据、数据清洗与对齐

数据采集,说白了就是滑点分析的「原材料」环节。你想想看,再厉害的分析模型,喂进去的是脏数据,出来的结果肯定也是垃圾。我在这个坑里栽过不止一次,所以今天咱们好好聊聊怎么把数据这关把住。

4.1 交易所API数据获取

我个人习惯把API获取分成两类:REST和WebSocket。REST适合拉历史数据,WebSocket适合接实时流。别混着用,否则你会被限流搞疯。

核心要点:交易所API的限流策略各不相同。币安是每分钟1200次请求,OKX是每秒100次。我建议你写个请求队列,别一股脑全发出去。

下面是我常用的一个REST请求模板,加了重试和限流逻辑:

import requests
import time
from typing import Dict, List

class ExchangeAPI:
    def __init__(self, base_url: str, rate_limit: int = 10):
        self.base_url = base_url
        self.rate_limit = rate_limit  # 每秒请求数
        self.last_request_time = 0
    
    def _rate_limiter(self):
        """简单的速率限制"""
        elapsed = time.time() - self.last_request_time
        if elapsed < 1.0 / self.rate_limit:
            time.sleep(1.0 / self.rate_limit - elapsed)
        self.last_request_time = time.time()
    
    def get_orderbook(self, symbol: str, depth: int = 10) -> Dict:
        """获取订单簿数据"""
        self._rate_limiter()
        url = f"{self.base_url}/api/v3/depth"
        params = {"symbol": symbol, "limit": depth}
        
        for retry in range(3):  # 重试3次
            try:
                resp = requests.get(url, params=params, timeout=5)
                resp.raise_for_status()
                return resp.json()
            except Exception as e:
                print(f"请求失败,第{retry+1}次重试: {e}")
                time.sleep(1)
        return None

我的经验:WebSocket连接一定要加心跳检测。我曾经有个策略跑了三天,突然发现数据停了,就是因为连接断了没重连。嗯,从那以后我写了个自动重连的装饰器。

4.2 订单簿数据解析

订单簿数据长什么样?说白了就是一堆买单和卖单的集合。交易所返回的格式通常是这样的:

{
    "bids": [
        ["45000.00", "1.5"],  // [价格, 数量]
        ["44999.50", "2.3"],
        ...
    ],
    "asks": [
        ["45001.00", "0.8"],
        ["45001.50", "1.2"],
        ...
    ],
    "timestamp": 1698765432000
}

这里有个坑要注意:价格和数量都是字符串,不是浮点数。为什么?因为浮点数有精度问题。我建议你直接用Decimal来处理:

from decimal import Decimal

def parse_orderbook(raw_data: dict) -> dict:
    """解析订单簿,返回Decimal类型"""
    bids = [(Decimal(price), Decimal(qty)) 
            for price, qty in raw_data["bids"]]
    asks = [(Decimal(price), Decimal(qty)) 
            for price, qty in raw_data["asks"]]
    
    return {
        "bids": bids,
        "asks": asks,
        "timestamp": raw_data["timestamp"]
    }

避坑指南:我曾经直接用float解析价格,结果在计算滑点时出现了0.0000001的误差。别小看这点误差,高频交易里这就是真金白银的损失。

4.3 Tick级数据与K线数据

Tick数据和K线数据,这两个是滑点分析的核心数据源。我简单说说区别:

  • Tick数据:每一笔成交的记录,包含价格、数量、时间戳。数据量大,但最精细。
  • K线数据:聚合后的数据,比如1分钟K线包含开盘、最高、最低、收盘、成交量。数据量小,但丢失了细节。

做滑点分析,我个人更推荐用Tick数据。为什么?因为滑点发生在每一笔交易中,K线数据会把这些细节「抹平」了。你想想看,一根1分钟K线里可能有100笔成交,每笔的滑点都不一样,你用K线的均价去算,误差能不大吗?

下面是我常用的Tick数据采集代码:

import websocket
import json

def on_message(ws, message):
    """处理Tick数据"""
    data = json.loads(message)
    tick = {
        "symbol": data["s"],      # 交易对
        "price": float(data["p"]), # 成交价格
        "qty": float(data["q"]),   # 成交数量
        "time": int(data["T"]),    # 成交时间戳(毫秒)
        "side": "buy" if data["m"] else "sell"  # 买卖方向
    }
    # 这里可以写入数据库或队列
    save_tick(tick)

def start_tick_stream(symbol: str):
    """启动Tick数据流"""
    ws_url = f"wss://stream.binance.com:9443/ws/{symbol.lower()}@trade"
    ws = websocket.WebSocketApp(ws_url, on_message=on_message)
    ws.run_forever()

我的建议:如果你刚开始做,可以先从K线数据入手。等熟悉了流程,再切换到Tick数据。别一上来就搞Tick,数据量太大,存储和处理都是问题。

4.4 数据清洗与对齐

数据清洗,说白了就是「去伪存真」。我从交易所拿到的数据,经常会有这些问题:

  • 重复数据:同一个Tick被推送了两次
  • 缺失数据:网络波动导致某几秒的数据没收到
  • 异常数据:价格突然跳变,比如从45000跳到450000
  • 时间戳错乱:数据到达顺序和实际发生顺序不一致

我常用的清洗流程是这样的:

def clean_ticks(ticks: list) -> list:
    """清洗Tick数据"""
    # 1. 去重:基于时间戳和价格去重
    seen = set()
    unique_ticks = []
    for tick in ticks:
        key = (tick["time"], tick["price"], tick["qty"])
        if key not in seen:
            seen.add(key)
            unique_ticks.append(tick)
    
    # 2. 排序:按时间戳排序
    unique_ticks.sort(key=lambda x: x["time"])
    
    # 3. 异常检测:剔除价格偏离超过3个标准差的数据
    prices = [t["price"] for t in unique_ticks]
    mean = sum(prices) / len(prices)
    std = (sum((p - mean)**2 for p in prices) / len(prices))**0.5
    
    cleaned = []
    for tick in unique_ticks:
        if abs(tick["price"] - mean) < 3 * std:
            cleaned.append(tick)
    
    return cleaned

数据对齐呢?就是把不同来源的数据对齐到同一个时间轴上。比如你有币安的Tick数据和OKX的Tick数据,它们的时钟可能差了几毫秒。怎么对齐?

核心方法:以其中一个交易所的时间为基准,把另一个交易所的数据做线性插值。或者更简单点,直接取最近的时间戳匹配。我一般用后者,因为简单且够用。

下面是我画的一张数据采集流程图,帮你理清整体思路:

滑点数据采集流程 交易所API WebSocket实时流 历史数据文件 数据解析:订单簿解析 / Tick解析 / K线解析 数据清洗:去重 / 排序 / 异常检测 / 缺失填充 数据对齐:时间戳对齐 / 多源合并 / 插值处理 数据采集完成,进入滑点分析阶段

我的经验:数据清洗这一步,千万别偷懒。我见过太多人直接拿原始数据跑分析,结果滑点算出来是负的——因为数据里有重复的Tick,把价格拉偏了。嗯,花10%的时间做清洗,能省90%的调试时间。

最后说一句,数据对齐的时候,要注意交易所的时钟同步问题。有些交易所的服务器时间可能差了几十毫秒。我建议你每次采集数据前,先校准一下本地时钟,用NTP同步到毫秒级。

避坑指南:我曾经在回测时发现滑点异常大,查了半天,原来是数据对齐时用了不同的时间基准。一个用UTC,一个用本地时间,差了8小时。从那以后,我统一用UTC时间戳,再也没出过问题。

好了,数据采集这块就聊到这儿。记住一句话:数据质量决定了分析的上限。把数据搞干净了,后面的滑点分析才能站得住脚。

专注资料整理