市场数据模型:Tick数据、Order Book数据、K线数据

做量化交易,尤其是高频策略,第一道坎就是数据。

我见过太多人,策略逻辑写得天花乱坠,结果一跑回测就崩。为什么?数据模型没搞对。说白了,你连市场长什么样都没搞清楚,怎么赚钱?

今天咱们就把这块硬骨头啃下来。我会把Tick、Order Book、K线这三种核心数据模型,掰开揉碎了讲清楚。

1. Tick数据:市场的原子单位

Tick数据是什么?就是每一笔成交的原始记录。它是最细粒度的数据,没有之一。

我个人习惯把Tick数据叫做「市场的呼吸」。每一笔成交,都是市场的一次呼吸。高频策略吃的就是这口「气」。

1.1 Tick数据字段定义

一个标准的Tick数据,至少包含以下字段:

字段名 类型 说明
symbol string 合约代码,比如 "BTCUSDT"
timestamp int64 成交时间戳,精确到毫秒或微秒
price float64 成交价格
volume float64 成交量(币的数量)
side int8 成交方向:1=主动买,-1=主动卖,0=未知
trade_id int64 交易所唯一成交ID
注意: 时间戳的精度问题,我踩过坑。有些交易所给的是秒级,有些是毫秒级。回测时如果不统一,你的策略会「穿越时空」——用未来的数据预测过去,这很致命。

1.2 Tick数据标准化

不同交易所,字段名不一样,格式也不一样。比如币安叫 "t",OKX叫 "instId"。怎么办?标准化。

# 这是我常用的Tick标准化函数
def normalize_tick(raw_tick, exchange):
    """
    将不同交易所的Tick数据统一为标准格式
    """
    if exchange == 'binance':
        return {
            'symbol': raw_tick['s'],
            'timestamp': int(raw_tick['T']),
            'price': float(raw_tick['p']),
            'volume': float(raw_tick['q']),
            'side': 1 if raw_tick['m'] else -1,
            'trade_id': int(raw_tick['t'])
        }
    elif exchange == 'okx':
        return {
            'symbol': raw_tick['instId'],
            'timestamp': int(raw_tick['ts']),
            'price': float(raw_tick['px']),
            'volume': float(raw_tick['sz']),
            'side': 1 if raw_tick['side'] == 'buy' else -1,
            'trade_id': int(raw_tick['tradeId'])
        }
    # 其他交易所类似处理

你看,标准化之后,你的策略代码就不用关心底层数据源了。这是基本功。

2. Order Book数据:市场的深度与博弈

Order Book,也就是订单簿。它告诉你:现在有多少人在买,多少人在卖,价格挂在哪。

我当年做做市商策略时,Order Book就是我的「眼睛」。没有它,你根本不知道市场的真实流动性在哪。

2.1 Order Book数据结构

一个完整的Order Book快照,包含以下部分:

  • bids:买单列表,按价格从高到低排序
  • asks:卖单列表,按价格从低到高排序
  • timestamp:快照时间
  • sequence:序列号,用于检测数据是否连续

每个订单条目包含:价格、数量、订单数量(可选)。

# Order Book快照示例
order_book_snapshot = {
    'symbol': 'BTCUSDT',
    'timestamp': 1625000000000,
    'bids': [
        [35000.0, 1.5],   # 价格35000,数量1.5个BTC
        [34999.5, 2.0],
        [34999.0, 0.8]
    ],
    'asks': [
        [35001.0, 1.2],
        [35001.5, 3.0],
        [35002.0, 0.5]
    ],
    'sequence': 123456789
}

2.2 增量更新 vs 全量快照

这里有个关键点:Order Book是实时变化的。每次有人下单、撤单,订单簿都会变。

交易所通常提供两种数据:

  • 全量快照:每隔一段时间(比如100ms)给你整个订单簿
  • 增量更新:每次变化时,只告诉你变了什么
我的建议: 高频策略一定要用增量更新。全量快照太慢了,等你拿到数据,市场早变了。我曾经用全量快照做回测,结果策略在实盘时完全跟不上节奏。

2.3 Order Book的深度指标

从Order Book里,我们可以算出很多有用的指标:

指标 计算方式 用途
买卖价差 ask[0].price - bid[0].price 衡量市场流动性
深度加权价格 按数量加权平均 判断真实成交价格
订单不平衡度 (bid_vol - ask_vol) / (bid_vol + ask_vol) 预测短期价格方向
价格冲击成本 吃掉N层订单需要的成本 评估大单执行成本

3. K线数据:时间维度的压缩

K线,也叫OHLCV。它是把一段时间内的交易数据,压缩成四个价格:开盘、最高、最低、收盘,外加成交量。

说实话,K线是给中低频策略用的。高频策略很少直接用K线,因为信息损失太大了。但做回测时,K线可以用来做快速验证。

3.1 K线字段定义

字段 说明
open 开盘价
high 最高价
low 最低价
close 收盘价
volume 成交量
timestamp K线开始时间

3.2 从Tick合成K线

有时候你只有Tick数据,但需要K线做分析。那就自己合成。

def ticks_to_kline(ticks, interval_ms=60000):
    """
    将Tick数据合成为K线
    interval_ms: K线周期,默认1分钟
    """
    klines = []
    current_kline = None
    
    for tick in ticks:
        # 计算当前Tick属于哪个K线周期
        kline_time = (tick['timestamp'] // interval_ms) * interval_ms
        
        if current_kline is None or current_kline['timestamp'] != kline_time:
            # 新K线开始
            if current_kline is not None:
                klines.append(current_kline)
            current_kline = {
                'timestamp': kline_time,
                'open': tick['price'],
                'high': tick['price'],
                'low': tick['price'],
                'close': tick['price'],
                'volume': tick['volume']
            }
        else:
            # 更新当前K线
            current_kline['high'] = max(current_kline['high'], tick['price'])
            current_kline['low'] = min(current_kline['low'], tick['price'])
            current_kline['close'] = tick['price']
            current_kline['volume'] += tick['volume']
    
    return klines
核心要点: 合成K线时,开盘价是周期内第一笔成交价,收盘价是最后一笔。最高最低就是周期内的极值。这个逻辑很简单,但很多人写错。

4. 数据标准化:统一战场

数据标准化,说白了就是让所有数据「说同一种语言」。

我做过一个项目,同时接入5个交易所的数据。每个交易所的字段名、时间格式、价格精度都不一样。如果不标准化,你的策略代码会变成一团乱麻。

4.1 标准化的几个维度

  • 时间标准化:统一为UTC时间戳,精确到毫秒
  • 价格标准化:统一为float64,注意精度问题
  • 数量标准化:统一为float64,注意小数位数
  • 方向标准化:买卖方向用+1/-1表示
  • 缺失值处理:用NaN或-1填充,不要用0

4.2 数据质量检查

数据标准化之后,一定要做质量检查。我吃过这个亏——数据里有重复的Tick,导致回测结果虚高。

def check_data_quality(ticks):
    """
    检查Tick数据质量
    """
    issues = []
    
    # 检查时间戳是否递增
    for i in range(1, len(ticks)):
        if ticks[i]['timestamp'] <= ticks[i-1]['timestamp']:
            issues.append(f"时间戳异常: index {i}")
    
    # 检查价格是否合理
    for tick in ticks:
        if tick['price'] <= 0:
            issues.append(f"价格异常: {tick}")
    
    # 检查是否有重复trade_id
    trade_ids = [t['trade_id'] for t in ticks]
    if len(trade_ids) != len(set(trade_ids)):
        issues.append("存在重复的trade_id")
    
    return issues

5. 知识体系总览

下面这张图,是我自己总结的市场数据模型知识体系。你看一眼,就能明白今天讲的所有内容是怎么串起来的。

市场数据模型知识体系 Tick数据 Order Book数据 K线数据 核心字段 • 时间戳 (timestamp) • 价格 (price) • 成交量 (volume) • 成交方向 (side) 核心结构 • 买单队列 (bids) • 卖单队列 (asks) • 增量更新机制 • 深度指标计算 OHLCV • 开盘价 (Open) • 最高价 (High) • 最低价 (Low) • 收盘价 (Close) 数据标准化 时间标准化 价格标准化 方向标准化 缺失值处理

这张图把今天的内容都串起来了。你看,Tick是源头,Order Book是深度,K线是压缩。而标准化,是贯穿始终的「通用语言」。

一个小建议: 刚开始做回测系统时,别贪多。先把Tick数据模型做扎实。Tick搞定了,Order Book和K线都是衍生品。我当年就是先啃下Tick,后面就顺了。

好了,市场数据模型这块,咱们就聊到这。记住一句话:数据模型是回测系统的地基。地基不稳,楼盖得再高也得塌。

专注资料整理