第三章:Tick数据解析——Tick数据的结构、采集与清洗方法

各位同学,欢迎来到第三章。

前两章我们聊了套利交易的基本逻辑和订单流的核心概念。从这章开始,我们要真正动手了。今天要啃的这块骨头,叫Tick数据。

说白了,Tick数据就是市场最原始的脉搏。每一笔成交、每一次报价变动,都被记录成一条Tick。我做了这么多年量化,可以负责任地告诉你:没有干净的Tick数据,一切高阶策略都是空中楼阁

3.1 Tick数据长什么样?

先看一个最直观的例子。这是我从某交易所拉下来的一条真实Tick记录:

timestamp, price, volume, side, symbol
2024-01-15 09:30:00.123456, 100.25, 200, B, AAPL

嗯,就这么一行。但你别小看它。每个字段都有讲究:

  • timestamp:精确到微秒的时间戳。我见过不少新手直接用秒级数据做套利,结果回测曲线漂亮得不行,实盘一跑就崩。为什么?因为套利拼的就是毫秒甚至微秒级的价差变化。
  • price:成交价格。注意,有些交易所给的是成交价,有些给的是买卖报价。这个坑我后面会讲。
  • volume:成交量。这里有个细节——是单笔成交量还是累计成交量?不同数据源定义不同。
  • side:买卖方向。B代表买方主动吃单,S代表卖方主动砸盘。这是订单流分析的核心。
  • symbol:合约代码。跨品种套利时,你得同时盯着好几个symbol。

重要提醒:Tick数据的时间戳精度,直接决定了你的套利策略能否捕捉到真实的价差机会。我个人习惯,低于毫秒级的数据直接弃用。

3.2 数据采集——从哪来,怎么拿?

数据采集这块,我踩过的坑比你们走过的路还多。先说说常见的几个来源:

数据源 优点 缺点 我的建议
交易所API直连 实时、原始、无延迟 需要自己维护连接,容易断 实盘首选,但要做好重连机制
第三方数据商 稳定、清洗过、有历史数据 贵,而且可能有延迟 回测用这个,省心
开源数据(如Polygon) 免费或低价 质量参差不齐,字段不全 练手可以,实盘别用

我曾经在一个项目中,为了省点钱用了某免费数据源。结果回测时发现价差套利策略年化收益30%,开心得不行。实盘跑了三天,亏了5%。后来一查,原来是数据源的时间戳精度不够,导致我计算的价差全是错的。嗯,从那以后,我再也不敢在数据源上省钱。

采集代码其实不复杂。我给你一个简单的WebSocket订阅示例:

import websocket
import json

def on_message(ws, message):
    tick = json.loads(message)
    # 这里你可以把tick写入数据库或文件
    print(f"收到Tick: {tick['symbol']} @ {tick['price']}")

def on_error(ws, error):
    print(f"连接出错: {error}")
    # 自动重连逻辑要写在这里

def on_close(ws, close_status_code, close_msg):
    print("连接关闭,准备重连...")

def on_open(ws):
    # 订阅你需要的合约
    subscribe_msg = {
        "type": "subscribe",
        "symbol": ["BTCUSDT", "ETHUSDT"]
    }
    ws.send(json.dumps(subscribe_msg))

ws = websocket.WebSocketApp(
    "wss://你的交易所地址",
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close
)
ws.run_forever()

小技巧:采集Tick数据时,建议同时记录本地时间戳和交易所时间戳。为什么?因为网络延迟会导致你收到数据的时间比实际发生时间晚几毫秒。做套利时,这个差异足以让你做出错误决策。

3.3 数据清洗——脏数据是量化交易的头号杀手

数据采集回来,你以为就能直接用了吗?太天真了。我见过太多人,数据都没洗干净就开始跑策略,结果亏得底裤都不剩。

常见的脏数据问题,我列了个清单:

  • 重复数据:同一笔成交被记录了两次。你想想看,如果你基于成交量做分析,重复数据会让你误判市场深度。
  • 缺失数据:某段时间内完全没有Tick。可能是网络断了,也可能是交易所宕机。套利策略遇到这种情况,很容易产生虚假信号。
  • 异常价格:比如突然出现一个1000美元的成交,而前后价格都是100美元。这明显是错误数据。
  • 时间戳错乱:后发生的交易时间戳反而更早。这在分布式系统中很常见。

怎么清洗?我给你一个实战中验证过的流程:

import pandas as pd
import numpy as np

def clean_ticks(df):
    """
    清洗Tick数据
    df: DataFrame,包含timestamp, price, volume, side, symbol
    """
    # 1. 去重
    df = df.drop_duplicates(subset=['timestamp', 'price', 'volume', 'symbol'])
    
    # 2. 按时间排序
    df = df.sort_values('timestamp')
    
    # 3. 检查时间戳连续性
    time_diff = df['timestamp'].diff()
    # 如果时间间隔超过5秒,标记为缺失
    df['gap_flag'] = (time_diff > pd.Timedelta(seconds=5)).astype(int)
    
    # 4. 价格异常检测(3倍标准差法)
    price_mean = df['price'].rolling(window=100).mean()
    price_std = df['price'].rolling(window=100).std()
    df['price_anomaly'] = (np.abs(df['price'] - price_mean) > 3 * price_std)
    
    # 5. 剔除异常数据
    df_clean = df[~df['price_anomaly']].copy()
    
    # 6. 填充缺失的时间戳(用前一个有效值填充)
    df_clean = df_clean.set_index('timestamp').resample('1ms').ffill().reset_index()
    
    return df_clean

# 使用示例
raw_data = pd.read_csv('ticks_raw.csv')
clean_data = clean_ticks(raw_data)
print(f"清洗前: {len(raw_data)} 条, 清洗后: {len(clean_data)} 条")

警告:清洗数据时,千万不要用全局均值填充异常值。我曾经犯过这个错,结果把一次真实的闪崩事件给洗掉了,导致策略完全没捕捉到那次套利机会。正确的做法是用局部滚动窗口的统计量来判断。

3.4 实战中的几个坑

最后,分享几个我在实战中遇到的坑,希望能帮你们少走弯路:

  1. 不同交易所的时间同步问题:做跨交易所套利时,两个交易所的时间戳可能相差几十毫秒。我建议你用NTP协议同步本地时间,然后以本地时间戳为准。
  2. Tick数据的存储:一天的数据量可能上亿条。用CSV存?别闹了。我推荐用Parquet格式或者时序数据库(如InfluxDB)。
  3. 回测时的数据偏差:用历史Tick数据回测时,要注意数据中可能包含了未来信息。比如,你用的数据源可能已经对数据做了后处理,导致某些信息被提前泄露。

好了,这一章的内容就到这里。Tick数据是套利交易的地基,地基不稳,房子再漂亮也得塌。下一章,我们会基于这些干净的Tick数据,构建真正的订单流指标。

记住:数据质量决定策略上限。别急着跑策略,先把数据搞干净。

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