3. 市场数据事件模型:Tick数据、Bar数据、订单簿快照的事件定义与数据结构

在事件驱动回测框架中,市场数据是驱动策略逻辑运转的“燃料”。不同的数据粒度对应不同的事件类型,其数据结构直接影响策略的计算效率与信号精度。本章将系统定义三种核心市场数据事件:Tick、Bar 与 订单簿快照,并给出标准化的数据结构与事件触发规则。

3.1 事件模型总览

市场数据事件模型遵循“生产者-消费者”模式:数据源(交易所、数据文件)将原始行情封装为事件对象,推入事件队列;策略模块通过订阅特定事件类型,在事件循环中消费并响应。下表对比了三种事件的核心特征:

事件类型 数据粒度 触发频率 典型用途 数据量级
TickEvent 逐笔成交 毫秒级/随机 高频策略、盘口分析 极大
BarEvent 聚合K线 固定时间间隔 趋势跟踪、均值回归 中等
OrderBookEvent 深度快照 事件驱动/定时 流动性分析、订单簿冲击 大
设计原则:所有市场数据事件应继承自同一个基类 MarketEvent,包含 timestamp、symbol、event_type 三个公共字段,便于事件循环统一调度。

3.2 Tick 数据事件

Tick 数据是市场交易的最小粒度记录,每一条 Tick 代表一笔真实的成交或一次报价变动。在回测中,TickEvent 通常由逐笔成交数据生成,用于模拟最真实的交易环境。

3.2.1 数据结构定义

class TickEvent:
    def __init__(self, timestamp, symbol, last_price, volume, 
                 bid_price, ask_price, bid_size, ask_size):
        self.event_type = 'TICK'
        self.timestamp = timestamp      # datetime 对象
        self.symbol = symbol            # 合约代码
        self.last_price = last_price    # 最新成交价
        self.volume = volume            # 该笔成交量
        self.bid_price = bid_price      # 当前买一价
        self.ask_price = ask_price      # 当前卖一价
        self.bid_size = bid_size        # 买一挂单量
        self.ask_size = ask_size        # 卖一挂单量

3.2.2 事件触发规则

  • 逐笔触发:每读取一条原始 Tick 记录,立即生成一个 TickEvent 并推入队列。
  • 时间戳对齐:使用交易所原始时间戳,回测引擎不做插值或平滑。
  • 异常处理:对于价格为零、买卖价差为负等异常 Tick,应标记为无效事件并跳过。
实战注意:使用 Tick 数据回测时,务必考虑交易成本与滑点。Tick 级别的模拟可以精确计算冲击成本,但数据量巨大,建议对回测周期进行分段处理。

3.3 Bar 数据事件

Bar 数据是将一段时间内的 Tick 数据聚合而成的统计摘要,是量化策略中最常用的数据形态。BarEvent 支持多种时间周期(1分钟、5分钟、日线等),其数据结构需包含开盘、最高、最低、收盘、成交量等核心字段。

3.3.1 数据结构定义

class BarEvent:
    def __init__(self, timestamp, symbol, open_price, high_price,
                 low_price, close_price, volume, vwap=None):
        self.event_type = 'BAR'
        self.timestamp = timestamp      # Bar 的结束时间
        self.symbol = symbol
        self.open = open_price
        self.high = high_price
        self.low = low_price
        self.close = close_price
        self.volume = volume
        self.vwap = vwap                # 成交量加权均价(可选)

3.3.2 聚合方式对比

聚合方式 触发条件 优点 缺点
时间周期 固定时间间隔(如60秒) 实现简单,与策略逻辑对齐 交易稀疏时段数据失真
成交量周期 累计成交量达到阈值 反映真实交易活跃度 时间不固定,策略逻辑复杂
Tick 数周期 累计 Tick 笔数达到阈值 适合高频统计 受 Tick 频率波动影响

3.4 订单簿快照事件

订单簿快照(Order Book Snapshot)记录了某一时刻市场所有限价订单的分布情况,包括买盘和卖盘的多档价格与挂单量。OrderBookEvent 是构建高级交易策略(如做市、套利)的基础数据源。

3.4.1 数据结构定义

class OrderBookEvent:
    def __init__(self, timestamp, symbol, bids, asks):
        self.event_type = 'ORDER_BOOK'
        self.timestamp = timestamp      # 快照时间
        self.symbol = symbol
        self.bids = bids                # 列表,每项为 (price, size)
        self.asks = asks                # 列表,每项为 (price, size)
        # 示例:bids = [(100.5, 200), (100.4, 500), ...]
        # 示例:asks = [(100.6, 300), (100.7, 400), ...]

3.4.2 快照 vs 增量更新

  • 全量快照:每次事件包含完整的买卖盘口数据,数据冗余但处理简单,适合回测场景。
  • 增量更新:只记录相对于上一次快照的变化(新增、删除、修改),节省存储空间,但需要维护状态机。
回测限制:大多数回测框架仅支持全量快照模式。若使用增量更新,需确保状态机在回测启动时从第一个全量快照开始重建,否则会导致盘口数据错乱。

3.5 事件模型架构图

下图展示了三种市场数据事件在回测引擎中的流转关系:数据源经过解析器生成事件对象,事件队列统一调度,策略模块通过事件类型订阅消费。

市场数据事件模型架构 数据源 数据解析器 事件队列 策略模块 TickEvent BarEvent OrderBookEvent 订阅机制 策略通过 event_type 订阅特定事件 类型

3.6 事件订阅与分发机制

为了实现灵活的策略组合,事件模型需要支持多策略订阅与选择性消费。以下是一个简化的事件分发流程:

  1. 注册:策略在初始化时向事件总线注册感兴趣的事件类型(如 subscribe('TICK', handler))。
  2. 分发:事件循环从队列中取出事件,根据 event_type 查找所有已注册的回调函数,依次调用。
  3. 过滤:策略可在回调函数内部根据 symbol 字段进一步过滤,只处理自己关注的合约。
  4. 性能优化:对于高频 Tick 事件,建议使用 symbol 级别的预过滤,避免不必要的函数调用开销。
代码示例:事件订阅与分发核心逻辑
class EventBus:
    def __init__(self):
        self.subscribers = {}  # event_type -> [handler1, handler2, ...]

    def subscribe(self, event_type, handler):
        if event_type not in self.subscribers:
            self.subscribers[event_type] = []
        self.subscribers[event_type].append(handler)

    def publish(self, event):
        handlers = self.subscribers.get(event.event_type, [])
        for handler in handlers:
            handler(event)

3.7 数据质量与异常处理

市场数据在采集、传输、存储过程中可能引入各种异常。回测框架必须对数据事件进行严格校验,避免“垃圾进,垃圾出”。

异常类型 典型表现 处理策略
价格异常 价格为0、负数或超出合理范围 丢弃该事件,记录日志
时间戳乱序 后一个事件的时间戳早于前一个 按时间戳排序后重放,或丢弃乱序事件
买卖价差为负 bid_price > ask_price 标记为无效,不用于策略计算
成交量异常 单笔成交量超过市场合理阈值 截断或丢弃,根据交易所规则处理
数据缺失 某段时间内无任何事件 策略应具备空值处理逻辑,避免除零错误
重要提示:回测中使用的数据应尽量与实盘数据源保持一致。如果回测数据经过了清洗或插值,务必在策略文档中说明,否则回测结果可能产生误导。

3.8 本章小结