事件循环与队列:量化交易系统的“心脏”
做量化交易开发这些年,我越来越觉得,一个系统的核心不是策略有多复杂,而是它怎么处理“事件”。说白了,行情来了你得接,信号到了你得执行,风控触发了你得立刻停。这些动作如果乱成一锅粥,再好的策略也白搭。
今天我们就聊聊事件循环和队列。这是事件驱动型系统的基石。我个人习惯把这两样东西比作交易系统的“心脏”和“血管”——事件循环负责泵血,队列负责让血液有序流动。
Python事件循环基础:别让CPU空转
先说说事件循环。你想想看,一个交易系统大部分时间在干嘛?等。等行情数据,等成交回报,等定时任务。如果让CPU一直轮询检查,那效率太低了。事件循环就是来解决这个问题的——它让程序在“没事干”的时候休眠,有事件来了再醒过来处理。
Python里最基础的事件循环,其实可以用一个简单的while循环模拟:
import time
import random
class SimpleEventLoop:
def __init__(self):
self.events = []
self.running = False
def add_event(self, event):
self.events.append(event)
print(f"事件已加入: {event}")
def run(self):
self.running = True
while self.running:
if self.events:
event = self.events.pop(0)
self.handle_event(event)
else:
# 没有事件时,让出CPU
time.sleep(0.01)
def handle_event(self, event):
print(f"处理事件: {event}")
# 模拟处理耗时
time.sleep(random.uniform(0.1, 0.5))
def stop(self):
self.running = False
# 使用示例
loop = SimpleEventLoop()
loop.add_event("行情数据到达")
loop.add_event("信号触发")
loop.add_event("订单成交回报")
# loop.run() # 实际运行时会阻塞
嗯,这里要注意。上面这个例子虽然简单,但它暴露了一个问题:如果事件处理太慢,新事件就会堆积。我在早期做回测系统时就踩过这个坑——行情处理不过来,导致信号延迟,回测结果完全失真。
queue.Queue的使用:让事件有序流动
Python标准库里的queue.Queue,说白了就是一个线程安全的先进先出队列。它解决了两个核心问题:一是多线程环境下的数据竞争,二是生产者-消费者模型的解耦。
我一般这样用:
import queue
import threading
import time
# 创建一个事件队列
event_queue = queue.Queue(maxsize=1000)
def producer():
"""模拟行情推送"""
for i in range(10):
event = {
'type': 'tick',
'symbol': 'BTC/USDT',
'price': 50000 + i * 10,
'timestamp': time.time()
}
try:
event_queue.put(event, timeout=1)
print(f"生产事件: {event['type']} - {event['price']}")
except queue.Full:
print("队列满了,丢弃事件")
time.sleep(0.1)
def consumer():
"""模拟策略处理"""
while True:
try:
event = event_queue.get(timeout=5)
print(f"消费事件: {event['type']} - 价格: {event['price']}")
# 模拟处理
time.sleep(0.2)
event_queue.task_done()
except queue.Empty:
print("队列空了,等待中...")
break
# 启动生产者和消费者
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
t1.start()
t2.start()
t1.join()
t2.join()
这里有个细节:maxsize参数很关键。我见过不少新手不设上限,结果内存被撑爆。设个合理的上限,配合timeout参数,就能优雅地处理流量高峰。
多线程事件处理:别让一个任务拖垮整个系统
单线程处理事件有个致命问题:如果某个事件处理耗时很长,后面的所有事件都得等着。这在量化交易里是不能接受的——行情延迟几毫秒,可能就错过最佳买卖点。
多线程方案很直接:一个线程负责接收事件,多个工作线程负责处理。我常用的模式是线程池:
from concurrent.futures import ThreadPoolExecutor
import queue
import time
class MultiThreadEventHandler:
def __init__(self, num_workers=4):
self.event_queue = queue.Queue()
self.executor = ThreadPoolExecutor(max_workers=num_workers)
self.running = False
def start(self):
self.running = True
# 启动事件分发线程
self.dispatch_thread = threading.Thread(target=self._dispatch)
self.dispatch_thread.start()
def _dispatch(self):
while self.running:
try:
event = self.event_queue.get(timeout=1)
# 提交到线程池处理
self.executor.submit(self._process_event, event)
except queue.Empty:
continue
def _process_event(self, event):
"""实际的事件处理逻辑"""
print(f"线程 {threading.current_thread().name} 处理: {event}")
time.sleep(0.1) # 模拟处理
def add_event(self, event):
self.event_queue.put(event)
def stop(self):
self.running = False
self.executor.shutdown()
# 使用
handler = MultiThreadEventHandler(4)
handler.start()
for i in range(10):
handler.add_event(f"事件-{i}")
time.sleep(2)
handler.stop()
为什么用线程池而不是直接开线程?因为创建线程是有开销的。线程池复用线程,避免了频繁创建销毁。我做过测试,在同样负载下,线程池比每次新建线程快30%以上。
事件优先级:重要的事先做
不是所有事件都同等重要。举个例子:
- P0(最高优先级):风控事件、止损触发、系统错误
- P1:行情数据、信号生成
- P2:订单状态更新、成交回报
- P3:日志记录、统计指标计算
Python的queue.PriorityQueue就是干这个的。它会根据优先级自动排序,优先级高的先出队:
import queue
class PriorityEvent:
def __init__(self, priority, event_type, data):
self.priority = priority # 数值越小优先级越高
self.event_type = event_type
self.data = data
def __lt__(self, other):
return self.priority < other.priority
def __repr__(self):
return f"[P{self.priority}] {self.event_type}: {self.data}"
# 创建优先级队列
pq = queue.PriorityQueue()
# 添加不同优先级的事件
pq.put(PriorityEvent(1, "风控", "止损触发"))
pq.put(PriorityEvent(3, "日志", "系统启动完成"))
pq.put(PriorityEvent(0, "错误", "内存不足"))
pq.put(PriorityEvent(2, "行情", "BTC 50000"))
# 按优先级处理
while not pq.empty():
event = pq.get()
print(f"处理: {event}")
输出结果会是:
处理: [P0] 错误: 内存不足
处理: [P1] 风控: 止损触发
处理: [P2] 行情: BTC 50000
处理: [P3] 日志: 系统启动完成
你看,错误事件虽然最后加入,但因为优先级最高,最先被处理。这在实盘中至关重要——系统出错了,你得先处理错误,而不是继续处理行情。
知识体系总览
说了这么多,我们来梳理一下整个事件驱动系统的核心逻辑。下面这张图能帮你快速理解:
这张图展示了一个完整的事件驱动流程。事件从各个源头发起,进入队列排队,事件循环不断从队列中取出事件,分发给多线程处理器,最后输出结果。每个环节都有它的设计考量——队列解决并发问题,优先级解决重要性问题,多线程解决性能问题。
我个人觉得,理解这个流程比背代码更重要。因为不同的交易场景,你需要调整的只是参数和具体实现,但这个架构骨架是通用的。
好了,事件循环和队列的基础就聊到这里。这些东西看起来简单,但用好了,你的交易系统会稳定很多。下次遇到系统卡顿或者事件丢失的问题,不妨先从队列大小和线程数量入手排查——八成问题出在这里。
公众号:蓝海资料掘金营,微信deep3321