1. 事件驱动架构基础

好,咱们直接进入正题。事件驱动架构,说白了就是一套「发生了什么事,就触发什么动作」的机制。我刚开始接触这个概念时,觉得它不就是个回调函数吗?后来在实盘系统里踩过坑才明白——没那么简单。

什么是事件驱动架构

想象一下你坐在交易大厅里。行情来了,你下单;成交了,你更新仓位;风控触发了,你暂停交易。每一件事都触发一个动作,这就是事件驱动。

传统程序是「你问我答」的模式:

# 轮询模式 - 低效
while True:
    price = get_price()
    if price < threshold:
        execute_order()

事件驱动是「你来了我响应」:

# 事件驱动模式 - 高效
def on_price_update(price):
    if price < threshold:
        execute_order()

event_bus.subscribe('price_update', on_price_update)

区别在哪?前者在空转,后者在等待。你想想看,回测跑十年数据,轮询模式得空转多少次?

为什么回测需要事件驱动

这个问题我问过不少同行。有人觉得「我就用for循环遍历K线,不也挺好?」嗯,对于简单策略确实够用。但一旦涉及以下场景,你就知道事件驱动的价值了:

  • 多资产同时交易:A股和港股的开盘时间不同,用循环你得自己管理时间轴
  • 订单执行细节:限价单可能部分成交、可能滑点,事件驱动能精确模拟
  • 外部事件干扰:比如新闻公告、分红除权,这些不是按K线节奏来的

核心观点:回测的本质是「模拟真实市场」。真实市场是事件驱动的,不是循环驱动的。你用循环做回测,等于用自行车模拟高铁——能跑,但失真。

我在项目中遇到过一件事。有个同事用循环回测跑得好好的,换到实盘就崩了。为什么?因为实盘里订单成交是异步的,他的循环逻辑根本处理不了「先下单、后成交、中间行情变了」这种场景。后来改成事件驱动,问题迎刃而解。

核心组件拆解

事件驱动架构有三个核心零件:事件、事件队列、事件处理器。咱们一个一个说。

1. 事件(Event)

事件就是「发生了什么」。在回测里,常见的事件类型有:

事件类型 触发时机 携带数据
BarEvent 新K线生成 时间、开盘价、收盘价、成交量...
TickEvent 新逐笔成交 时间、价格、成交量、买卖方向...
OrderEvent 策略下单 标的、方向、数量、价格类型...
FillEvent 订单成交 成交价、成交量、手续费...
SignalEvent 策略产生信号 标的、方向、置信度...

我个人习惯把事件设计成不可变对象。为什么?因为事件在队列里传递时,谁改了它都会出问题。我曾经被一个同事改了事件里的价格字段,排查了两天才发现——从那以后,所有事件我都用 namedtuple 或者 dataclass(frozen=True)

from dataclasses import dataclass
from datetime import datetime

@dataclass(frozen=True)
class BarEvent:
    symbol: str
    timestamp: datetime
    open: float
    high: float
    low: float
    close: float
    volume: float

2. 事件队列(Event Queue)

事件队列就是事件的「传送带」。它负责把事件从生产者送到消费者手里。在回测里,这个队列必须是有序的——时间不能乱。

我见过有人用 Python 的 queue.Queue 做回测队列。嗯,这里要注意:queue.Queue 是线程安全的,但回测通常是单线程的。用它的代价是性能损耗。我个人推荐用 collections.deque,或者干脆用 heapq 实现一个优先级队列——按时间戳排序。

import heapq
from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedEvent:
    timestamp: float
    event: Any = field(compare=False)

class EventQueue:
    def __init__(self):
        self._queue = []
    
    def push(self, event):
        heapq.heappush(self._queue, event)
    
    def pop(self):
        return heapq.heappop(self._queue) if self._queue else None
    
    def peek(self):
        return self._queue[0] if self._queue else None
    
    def is_empty(self):
        return len(self._queue) == 0

避坑指南:我曾经用 list.sort() 来维护队列顺序,数据量小还行,跑到百万级事件时直接卡死。用堆(heap)是 O(log n) 的插入复杂度,能扛住十年 tick 数据。

3. 事件处理器(Event Handler)

事件处理器就是「谁来处理这个事件」。在回测架构里,每个组件都是一个处理器:

  • 数据处理器:把原始行情转成标准事件
  • 策略处理器:收到行情事件,产生交易信号
  • 风控处理器:检查信号是否合规
  • 执行处理器:把信号转成订单,模拟成交
  • 组合处理器:更新持仓和资金

每个处理器只关心自己订阅的事件类型。这种解耦设计,说白了就是「各扫门前雪」。策略不用管订单怎么成交,执行不用管策略怎么想的——出了问题也好排查。

class EventHandler:
    def __init__(self, event_bus):
        self.event_bus = event_bus
        self.event_bus.subscribe('BarEvent', self.on_bar)
    
    def on_bar(self, event):
        # 子类实现具体逻辑
        raise NotImplementedError

架构全景图

说了这么多,咱们用一张图把整个流程串起来。下面这张 SVG 展示了事件在回测系统中的流转路径:

事件驱动回测架构 - 核心流程 数据源 行情数据 / 历史数据 事件生成器 BarEvent / TickEvent 事件队列 优先级队列 按时间戳排序 事件分发器 按类型分发 事件处理器 策略处理器 风控处理器 执行处理器 新事件(订单、信号) 回测结果 成交记录 / 资金曲线 数据源 事件生成 事件队列 事件分发 事件处理 结果输出

这张图里,数据从左边进来,变成事件,进队列,按时间顺序分发,处理器们各司其职。处理过程中产生的新事件(比如策略下单)会重新进入队列。整个系统就像一条流水线——每个环节只做一件事,但组合起来就能模拟出完整的交易生命周期。

注意:事件驱动架构最大的坑是「事件循环」。如果处理器在处理事件时又产生了同类型事件,可能造成无限递归。我见过一个策略在 on_bar 里又触发了 on_bar,直接栈溢出。解决方案是:新事件先入队列,等当前事件处理完再处理下一个。

好了,事件驱动架构的基础就这些。说白了就是三件事:定义事件、排队、处理。但就是这三件事,决定了你的回测系统能不能跑得稳、跑得快、跑得真。下一节咱们会深入事件队列的实现细节,包括如何处理不同时间粒度的事件对齐问题。


专注资料整理