1. 事件驱动回测基础
1.1 事件驱动架构定义
事件驱动架构(Event-Driven Architecture, EDA)是量化回测系统的核心设计范式。与传统的向量化回测(一次性计算所有价格序列)不同,事件驱动回测将市场行为、订单执行、账户变动等抽象为离散的事件,通过事件的生产、检测、消费和响应来驱动回测逻辑的推进。
在事件驱动模型中,回测引擎不主动轮询市场数据,而是等待事件被触发并放入队列,再由对应的处理器按顺序消费。这种架构更贴近真实交易系统的运行方式,能够精确模拟订单簿变化、滑点、手续费以及多资产之间的依赖关系。
核心特征: 事件驱动回测的三大基石是“事件源”、“事件队列”和“事件处理器”。三者通过松耦合的方式协作,使得策略逻辑、风控逻辑和成交逻辑可以独立开发与测试。
1.2 回测引擎核心组件
一个完整的事件驱动回测引擎由以下六大核心组件构成,它们各自承担明确的职责,并通过事件总线相互通信:
| 组件名称 | 职责描述 | 典型事件输出 |
|---|---|---|
| 数据处理器 | 加载历史行情数据(Tick、1min、日线),按时间顺序生成市场事件 | MarketEvent |
| 策略模块 | 接收市场事件,根据交易信号逻辑生成买卖指令 | SignalEvent |
| 投资组合管理器 | 管理持仓、资金、风险敞口,将信号转化为可执行的订单 | OrderEvent |
| 执行处理器 | 模拟订单簿撮合,考虑滑点、手续费、流动性限制 | FillEvent |
| 风控模块 | 在订单执行前进行合规检查(仓位限制、最大回撤、杠杆约束) | RiskEvent / 拒绝事件 |
| 统计与记录器 | 记录每笔成交、权益曲线、交易日志,供后续绩效分析 | 无(被动接收) |
设计要点: 组件之间的依赖关系应尽量保持单向。例如,策略模块不应直接调用执行处理器,而是通过事件队列传递 SignalEvent,由事件循环统一调度。这种设计使得替换某个组件(如更换数据源或执行算法)时不会影响其他模块。
1.3 事件循环与队列机制
事件循环(Event Loop)是回测引擎的“心脏”,它按照严格的时间顺序从事件队列中取出事件并分发给对应的处理器。队列机制保证了事件的先进先出(FIFO)顺序,同时支持优先级调度(如风控事件优先于市场事件)。
下图展示了事件驱动回测引擎的完整架构与事件流转路径:
1.4 事件类型与处理流程
事件驱动回测中定义了明确的事件类型层级。每种事件携带特定的数据字段,并由对应的处理器消费。以下是标准事件类型及其处理逻辑:
- MarketEvent(市场事件):由数据处理器生成,包含时间戳、品种代码、最新价、成交量、买卖盘口等。策略模块订阅该事件以触发信号计算。
- SignalEvent(信号事件):由策略模块生成,包含方向(买入/卖出/平仓)、品种、信号强度、建议仓位比例。投资组合管理器根据当前持仓和资金状况决定是否接受该信号。
- OrderEvent(订单事件):由投资组合管理器生成,包含订单类型(市价单/限价单)、数量、价格(可选)、有效时间。执行处理器接收后模拟撮合。
- FillEvent(成交事件):由执行处理器生成,包含成交价格、成交数量、滑点成本、手续费、成交时间戳。该事件会更新投资组合的持仓和资金记录。
- RiskEvent(风控事件):由风控模块生成,用于拒绝违规订单、触发强制平仓或调整风险参数。该事件具有最高优先级。
常见陷阱: 初学者容易在事件处理中加入“等待”或“延迟”逻辑,试图模拟网络延迟。在回测中,所有事件应严格按时间戳顺序处理,不应引入非确定性的等待。如需模拟延迟,应通过时间戳偏移来实现,而非阻塞事件循环。
1.5 事件循环的伪代码实现
以下是一个简化的事件循环核心逻辑,展示了队列初始化、事件分发和循环终止条件:
class EventLoop:
def __init__(self):
self.queue = PriorityQueue() # 优先级队列,时间戳越早优先级越高
self.running = True
def add_event(self, event):
self.queue.put((event.timestamp, event))
def run(self):
while self.running and not self.queue.empty():
_, event = self.queue.get()
self.dispatch(event)
def dispatch(self, event):
if event.type == 'MARKET':
self.strategy.on_market(event)
elif event.type == 'SIGNAL':
self.portfolio.on_signal(event)
elif event.type == 'ORDER':
self.executor.on_order(event)
elif event.type == 'FILL':
self.portfolio.on_fill(event)
self.stats.on_fill(event)
elif event.type == 'RISK':
self.portfolio.on_risk(event)
# 事件处理过程中可能产生新事件,直接加入队列
# 新事件的时间戳必须 ≥ 当前事件时间戳,否则破坏因果律
上述代码中,PriorityQueue 保证了事件按时间顺序出队。每次事件处理过程中可能产生新事件(如成交事件触发止损信号),这些新事件会被立即加入队列,但必须确保其时间戳不小于当前正在处理的事件时间戳,以维护时间因果一致性。
1.6 事件驱动 vs 向量化回测对比
理解事件驱动与向量化回测的差异,有助于在实际开发中做出正确的技术选型:
| 对比维度 | 事件驱动回测 | 向量化回测 |
|---|---|---|
| 计算方式 | 逐事件顺序处理,模拟真实交易节奏 | 一次性数组运算,依赖 Pandas/NumPy |
| 订单模拟 | 支持限价单、止损单、冰山订单等复杂订单类型 | 通常仅支持市价单,难以模拟订单簿 |
| 滑点与手续费 | 可在成交时动态计算,支持基于流动性的滑点模型 | 通常使用固定滑点或比例手续费 |
| 多资产支持 | 天然支持,各资产事件独立入队 | 需要手动对齐时间轴,处理缺失值 |
| 执行速度 | 较慢,适合策略验证和精细回测 | 极快,适合大规模参数扫描 |
| 适用场景 | 高频策略、做市策略、复杂风控逻辑 | 趋势跟踪、均值回归等低频策略的初步筛选 |
实践建议: 在参数优化阶段,可以先使用向量化回测快速缩小参数空间,再使用事件驱动回测对候选参数进行精细验证。这种“先粗后精”的策略可以兼顾效率与精度。