4、事件类型定义:OrderEvent、TradeEvent、BookUpdateEvent、ErrorEvent、自定义事件
事件驱动架构的核心是什么?说白了,就是「事件」本身。
你想想看,整个交易系统就像一条流水线。订单进来是一个事件,成交了是一个事件,盘口变了又是一个事件。每个事件都带着自己的数据,在系统的各个模块之间流转。我做了这么多年量化,见过太多系统因为事件定义混乱而崩掉的案例。
今天我们就来聊聊,在订单簿事件驱动框架里,事件类型到底该怎么定义。
4.1 事件基类:所有事件的爸爸
我个人习惯,先定义一个事件基类。所有具体的事件类型都继承它。这样有个好处——统一接口,方便扩展。
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, Optional
@dataclass
class Event:
"""事件基类"""
event_id: str
timestamp: datetime
event_type: str
data: Optional[Dict[str, Any]] = None
def to_dict(self) -> Dict[str, Any]:
return {
'event_id': self.event_id,
'timestamp': self.timestamp.isoformat(),
'event_type': self.event_type,
'data': self.data
}
嗯,这里要注意。我用的是 dataclass,不是普通类。为什么?因为 dataclass 自动生成 __init__、__repr__ 这些方法,省事。而且数据类在事件传递时更清晰,不容易出幺蛾子。
4.2 OrderEvent:订单的生命周期
订单事件,是交易系统里最核心的事件之一。从下单、撤单到订单状态变更,每一步都是一个 OrderEvent。
我在项目中遇到过一个问题:一开始只定义了「订单创建」和「订单成交」两个事件,结果后来要加「部分成交」和「订单修改」,代码改得我头皮发麻。所以,我建议你一开始就把事件类型定义全。
@dataclass
class OrderEvent(Event):
"""订单事件"""
order_id: str
symbol: str
side: str # 'buy' or 'sell'
order_type: str # 'limit', 'market', etc.
price: float
quantity: float
filled_quantity: float = 0.0
status: str = 'pending' # pending, open, filled, cancelled, rejected
def __post_init__(self):
self.event_type = 'order'
核心事件类型枚举:
ORDER_CREATED- 订单创建ORDER_FILLED- 订单完全成交ORDER_PARTIALLY_FILLED- 订单部分成交ORDER_CANCELLED- 订单撤销ORDER_REJECTED- 订单被拒绝ORDER_MODIFIED- 订单修改
4.3 TradeEvent:成交才是硬道理
TradeEvent 和 OrderEvent 有什么区别?很多人搞混。
OrderEvent 描述的是「订单」的状态变化。而 TradeEvent 描述的是「一笔成交」——也就是买方和卖方匹配成功的那一刻。
你可以这么理解:一个订单可能产生多笔成交(比如冰山订单),每一笔成交都是一个独立的 TradeEvent。
@dataclass
class TradeEvent(Event):
"""成交事件"""
trade_id: str
order_id: str
symbol: str
side: str
price: float
quantity: float
trade_time: datetime
maker_order_id: Optional[str] = None
taker_order_id: Optional[str] = None
def __post_init__(self):
self.event_type = 'trade'
避坑指南: 我曾经在记录成交事件时,忘了区分 maker 和 taker。结果做回测时,手续费计算全错了。记住,maker 和 taker 的手续费率通常不一样,这个字段一定要保留。
4.4 BookUpdateEvent:盘口变了
BookUpdateEvent 是订单簿更新的核心事件。每当有新的订单进入、撤单或者成交,盘口数据就会变化,这时候就需要触发 BookUpdateEvent。
我建议把 BookUpdateEvent 设计成增量更新模式。什么意思?就是只传递变化的部分,而不是整个盘口数据。这样能大幅减少网络传输和内存开销。
@dataclass
class BookUpdateEvent(Event):
"""订单簿更新事件"""
symbol: str
bids: List[Tuple[float, float]] # (price, quantity) 列表
asks: List[Tuple[float, float]]
update_type: str # 'snapshot' or 'delta'
sequence: int # 序列号,用于检测数据连续性
def __post_init__(self):
self.event_type = 'book_update'
注意: 增量更新模式下,一定要带序列号。我见过一个系统,因为网络丢包导致盘口数据错乱,整个策略亏了三天才发现。序列号就是你的「数据完整性检查器」。
4.5 ErrorEvent:出错了怎么办
没有不出错的系统。关键是出错后怎么处理。
ErrorEvent 不是用来「记录日志」的,而是用来「触发恢复机制」的。比如交易所连接断了、数据解析失败、订单被拒——这些都应该抛出 ErrorEvent。
@dataclass
class ErrorEvent(Event):
"""错误事件"""
error_code: str
error_message: str
source: str # 错误来源,如 'exchange', 'parser', 'strategy'
severity: str # 'info', 'warning', 'critical'
recoverable: bool = True # 是否可恢复
def __post_init__(self):
self.event_type = 'error'
我个人习惯把 ErrorEvent 的 severity 分成三级:
- info - 小问题,比如某次数据延迟,记录一下就行
- warning - 需要关注,比如连续三次重连失败
- critical - 必须停机,比如内存溢出、核心数据损坏
4.6 自定义事件:框架的扩展性
框架做得再好,也架不住业务需求千奇百怪。所以,一定要留出自定义事件的口子。
我的做法是定义一个 CustomEvent,里面放一个 event_name 字段,用户自己定义事件名称和数据内容。
@dataclass
class CustomEvent(Event):
"""自定义事件"""
custom_type: str # 用户自定义的事件名称
payload: Dict[str, Any] # 任意数据
def __post_init__(self):
self.event_type = f'custom_{self.custom_type}'
举个例子。假设你要监控系统延迟,可以定义一个 LatencyEvent:
# 使用自定义事件
latency_event = CustomEvent(
event_id='lat_001',
timestamp=datetime.now(),
custom_type='latency_check',
payload={
'exchange_latency_ms': 12.5,
'processing_latency_ms': 3.2,
'total_latency_ms': 15.7
}
)
4.7 事件类型关系图
下面这张图,展示了我们定义的所有事件类型之间的关系。你看一眼就能明白整个事件体系的架构。
4.8 事件注册与分发
事件定义好了,怎么用?我习惯用一个事件注册表来管理所有事件类型。
class EventRegistry:
"""事件注册表"""
_events: Dict[str, type] = {}
@classmethod
def register(cls, event_type: str, event_class: type):
cls._events[event_type] = event_class
@classmethod
def get(cls, event_type: str) -> type:
return cls._events.get(event_type)
@classmethod
def create_event(cls, event_type: str, **kwargs) -> Event:
event_class = cls.get(event_type)
if event_class is None:
raise ValueError(f"未知事件类型: {event_type}")
return event_class(**kwargs)
# 注册事件
EventRegistry.register('order', OrderEvent)
EventRegistry.register('trade', TradeEvent)
EventRegistry.register('book_update', BookUpdateEvent)
EventRegistry.register('error', ErrorEvent)
小技巧: 我建议在系统启动时,自动扫描所有事件类并注册。这样新增事件类型时,不用手动修改注册代码。用 Python 的 __init_subclass__ 或者元类都能实现。
4.9 总结一下
事件类型定义这件事,看着简单,但坑不少。我总结几个要点:
- 基类要稳:event_id、timestamp、event_type 这三个字段必须有
- 类型要全:订单、成交、盘口、错误,一个都不能少
- 扩展要活:留好自定义事件的接口,别把自己框死
- 注册要自动:别手动一个个注册,用自动化方式
事件类型定义好了,整个事件驱动框架的骨架就有了。接下来就是怎么让这些事件在系统里高效流转——那是下一节的内容了。
公众号:蓝海资料掘金营,微信deep3321