2. 回测系统架构设计:核心模块、事件驱动与数据流

做量化做市策略,最怕什么?

怕策略在实盘里跑得好好的,突然给你来个滑点爆仓。或者回测时收益曲线漂亮得像艺术品,一上实盘就变成灾难现场。

我见过太多人,花几个月写策略,结果回测系统本身就有bug。说白了,回测系统才是整个量化交易的地基。地基不稳,上面盖多高的楼都没用。

2.1 回测系统的核心模块

一个靠谱的回测系统,至少得包含这几个模块。我习惯把它们分成四块:

模块 职责 我踩过的坑
数据引擎 管理历史行情数据,提供时间序列切片 数据对齐没做好,导致买卖信号错位
策略引擎 执行做市逻辑,生成报价和订单 策略状态没重置,回测结果被污染
模拟撮合 模拟订单簿,处理成交与滑点 撮合逻辑太理想,忽略了排队延迟
风控与绩效 监控风险指标,计算收益曲线 风控阈值设得太松,回测里没暴露问题

这四个模块缺一不可。你想想看,数据引擎如果给错了时间戳,策略引擎再牛也是白搭。我曾经在一个项目里,因为数据引擎的时区没处理好,导致所有订单都提前了15分钟发出——回测结果漂亮得离谱,实盘直接亏到怀疑人生。

2.2 事件驱动架构:为什么非它不可?

做市策略和普通趋势策略不一样。趋势策略可能每天只交易几次,但做市策略每秒都在跟订单簿打交道。

这时候,事件驱动架构就派上用场了。

什么叫事件驱动?说白了就是:系统不主动轮询,而是等事情发生了再响应。比如:

  • 新的tick数据来了 → 触发行情事件
  • 订单被成交了 → 触发成交事件
  • 风控阈值触发了 → 触发风控事件

我习惯把事件驱动架构画成下面这样:

行情数据源 订单反馈源 定时器事件 事件队列 TickEvent OrderFillEvent TimerEvent RiskEvent ... ... 事件循环 1. 取出事件 2. 分发事件 3. 调用处理器 4. 生成新事件 做市策略模块

看到这个图了吗?事件队列就像一个邮局,所有事件都先扔进去。事件循环就是邮递员,按顺序把信送到对应的处理器手里。

这样做的好处很明显:

  • 解耦:数据源和策略逻辑互不依赖
  • 可扩展:加一个新的事件类型,不影响现有代码
  • 可回放:把历史数据变成事件流,回测和实盘用同一套代码
我的经验:事件队列最好用优先级队列,而不是普通FIFO。比如风控事件的优先级应该高于普通行情事件。我曾经因为没做优先级,导致风控事件被堵在队列后面,等处理时已经爆仓了。

2.3 数据流设计:从tick到信号

做市策略的数据流,跟普通策略不太一样。普通策略可能只看1分钟K线,但做市策略需要看逐笔tick数据。

数据流大概是这样的链路:

  1. 原始tick数据 → 交易所推送的逐笔成交和报价
  2. 数据清洗 → 去重、补全、时间对齐
  3. 订单簿重建 → 根据tick数据实时构建买卖盘口
  4. 特征计算 → 价差、深度、订单不平衡等指标
  5. 策略信号 → 根据特征生成报价决策

这里有个关键点:订单簿重建。做市策略的核心就是看订单簿,你得知道买一卖一在哪里,深度够不够。

# 一个简化的订单簿重建逻辑
class OrderBook:
    def __init__(self):
        self.bids = {}  # 价格 -> 数量
        self.asks = {}
    
    def update(self, tick):
        # tick包含:价格、数量、买卖方向
        if tick.side == 'buy':
            self.bids[tick.price] = tick.volume
        else:
            self.asks[tick.price] = tick.volume
    
    def get_spread(self):
        best_bid = max(self.bids.keys())
        best_ask = min(self.asks.keys())
        return best_ask - best_bid
    
    def get_mid_price(self):
        return (best_bid + best_ask) / 2

嗯,这里要注意。订单簿重建的精度直接影响策略表现。我见过有人用1秒快照代替逐笔更新,结果价差计算偏差了30%以上。做市策略对价差极其敏感,差一个tick可能就是盈亏的分界线。

2.4 订单流设计:从决策到成交

订单流是回测系统的另一个核心。做市策略的订单流,跟普通策略最大的区别在于:做市策略是双向报价的

也就是说,你同时挂买单和卖单,赚取价差。订单流大概是这样的:

  • 报价决策 → 策略决定在什么价格挂单、挂多少
  • 订单发送 → 把订单提交到模拟撮合引擎
  • 撮合处理 → 模拟对手方是否吃掉你的订单
  • 成交反馈 → 更新持仓和资金
  • 撤单重报 → 如果市场变了,撤销旧单重新报价

这里有个容易忽略的点:撤单重报的频率。做市策略需要频繁调整报价,但撤单本身也有成本(交易所收撤单费,或者模拟时的时间成本)。

避坑指南:我曾经在回测里把撤单延迟设成0,结果实盘时发现撤单根本没那么快。交易所的撤单确认至少需要几十微秒,高频做市时这个延迟会累积。建议在回测里加入至少1-2个tick的撤单延迟。

2.5 模拟撮合:最容易被低估的模块

模拟撮合是回测系统里最复杂的部分。很多人随便写个「价格到了就成交」,结果回测和实盘差距巨大。

做市策略的撮合,需要考虑:

因素 说明 我的建议
排队位置 你的订单在队列里排第几 按价格-时间优先级模拟
部分成交 对手方只吃掉你一部分订单 必须支持,否则高估成交率
滑点模型 成交价格和报价之间的偏差 用历史数据拟合滑点分布
冰山订单 大单隐藏真实数量 至少支持基础版本

我个人习惯用「事件驱动+状态机」来实现撮合。每个订单都是一个状态机:

订单状态流转:
NEW → PARTIALLY_FILLED → FILLED
    → CANCELLED
    → REJECTED

每次tick进来,撮合引擎遍历所有活跃订单,检查是否满足成交条件。满足就更新状态,生成成交事件。

你想想看,如果撮合逻辑太简单,比如假设所有订单都能立即成交,那回测出来的夏普比率可能比实盘高出一倍。我见过有人因为这个原因,实盘直接亏了30%才发现问题。

2.6 数据流与订单流的协同

最后,把数据流和订单流串起来看:

行情数据流 订单簿重建 特征计算 做市策略 报价决策 风险管理 订单发送 模拟撮合 成交反馈 持仓与资金更新反馈

看到这个闭环了吗?数据流从左到右进入策略,策略生成订单流向右进入撮合,撮合结果再反馈回策略。这个闭环必须完整,缺一环都不行。

我见过最离谱的回测系统,数据流和订单流是分开跑的——数据流跑完了再跑订单流。结果就是策略明明应该根据最新行情调整报价,但回测里报价是固定的,完全失真。

我的建议:回测系统的数据流和订单流一定要在同一个事件循环里处理。每个tick进来,先更新订单簿,再检查是否有订单需要撤单重报,最后生成新的报价。顺序不能乱。

好了,以上就是回测系统架构设计的核心内容。说白了,就是四个模块、一个事件循环、两条流。把这套框架搭好了,后面的策略实现就是水到渠成的事。


公众号:蓝海资料掘金营,微信deep3321