4、回测系统搭建:回测框架选择、数据准备与事件驱动引擎设计

做市策略跟普通趋势策略不一样。普通策略你拿日线数据跑一跑,差不多能看出个七七八八。但做市策略不行——它吃的是微观结构里的那点利润,订单簿的每一次跳动、每一笔成交的先后顺序,都直接影响你的盈亏。所以回测系统这块,我花了不少时间折腾。

今天咱们就聊聊,怎么搭一个能用的做市策略回测系统。说白了,就三件事:选框架、准备数据、写引擎。

4.1 回测框架怎么选?

市面上常见的回测框架就那几个。我挨个说说我的看法。

Backtrader

这个我用得最多。社区活跃,文档也全。它自带了一个事件驱动的架构,你只需要继承它的策略类,写几个回调函数就行。我个人习惯用它来做快速验证。

优点:上手快,社区插件多,支持多数据源。
缺点:对Tick级数据的处理效率一般,订单簿模拟需要自己扩展。

Zipline

Quantopian 留下的遗产。说实话,它更适合做因子研究和组合回测。做市策略里那些细碎的订单逻辑,Zipline 处理起来有点吃力。我试过一次,后来放弃了。

我的建议:如果你只是做分钟级别的做市策略验证,Zipline 勉强能用。但要是涉及Tick级数据,还是别折腾了。

自建框架

嗯,这是我最推荐的方案。为什么?因为做市策略的很多细节,通用框架根本覆盖不到。比如订单簿的深度管理、部分成交的处理、撤单重发的时机——这些东西在通用框架里要么没有,要么实现得不够灵活。

我曾经在一个项目里,用 Backtrader 跑了一个做市策略的回测,结果发现它的成交逻辑跟实盘差了十万八千里。后来我花了两周时间,自己写了一个轻量级的事件驱动引擎。从那以后,我再也没回头用过通用框架做做市策略的回测。

注意:自建框架虽然灵活,但开发成本不低。如果你只是刚入门,建议先用 Backtrader 跑通流程,再逐步替换核心模块。

4.2 数据准备:Tick级 vs 分钟级

数据是回测的命根子。做市策略对数据的要求,比普通策略高一个数量级。

Tick级数据

说白了,就是每一笔成交的记录。包括成交时间、价格、数量、买卖方向。有些交易所还会提供订单簿的快照数据。

我处理Tick级数据时,最头疼的是数据清洗。交易所的数据经常有缺失、重复、甚至时间戳错乱的情况。你想想看,如果时间戳错了,你的回测结果就全废了。

数据清洗要点:
  • 检查时间戳是否单调递增
  • 去除明显的异常价格(比如零价格、负价格)
  • 处理盘口数据的跳变(比如买卖价差突然拉大)

分钟级数据

如果你只是做初步的策略验证,分钟级数据也能凑合。但要注意,分钟级数据会丢失很多微观结构的信息。比如,你在分钟K线上看到的买卖价差,跟实际Tick级别的价差可能完全不一样。

我个人习惯是:先用分钟级数据跑一遍,看看策略逻辑有没有明显的问题。没问题了,再换Tick级数据做精细回测。

数据类型 适用场景 数据量 回测精度
Tick级 高频做市、订单簿策略 大(每天几百万条)
分钟级 初步验证、低频做市 小(每天几百条)

4.3 事件驱动回测引擎设计

这是整个回测系统的核心。事件驱动,说白了就是:市场发生什么,引擎就处理什么。比如,来了一个新的Tick,引擎就更新订单簿,检查是否有订单可以成交,然后触发策略的逻辑。

我设计的引擎,核心就三个组件:

  1. 事件队列:按时间顺序存放所有事件(Tick、订单状态变化、定时器事件等)。
  2. 事件处理器:从队列里取出事件,分发给对应的模块处理。
  3. 策略模块:接收事件,做出决策,然后提交订单。

下面是一个简化版的引擎代码框架:

class EventDrivenEngine:
    def __init__(self):
        self.event_queue = []
        self.strategy = None
        self.order_book = OrderBook()
        self.portfolio = Portfolio()

    def run(self, data_feed):
        for event in data_feed:
            self.event_queue.append(event)
            self._process_event()

    def _process_event(self):
        event = self.event_queue.pop(0)
        if event.type == 'TICK':
            self.order_book.update(event)
            self.strategy.on_tick(event)
        elif event.type == 'ORDER_FILL':
            self.portfolio.update(event)
            self.strategy.on_fill(event)
避坑指南:我曾经在事件队列里用了Python的list,结果回测几百万条数据时,性能惨不忍睹。后来换成了deque,速度快了十倍。嗯,这种细节一定要注意。

4.4 核心逻辑流程图

下面这张图,是我自己画的事件驱动引擎的核心流程。你可以看到,数据从左边进来,经过事件队列,然后分发给各个模块处理。

数据源 事件队列 (按时间排序) 事件处理器 (分发事件) 策略模块 订单簿 投资组合 提交订单 成交回报 事件驱动回测引擎核心流程 数据流从左到右,事件按时间顺序处理,策略与订单簿双向交互

4.5 一些实战中的坑

最后,分享几个我踩过的坑,希望能帮你省点时间。

  • 数据对齐问题:不同交易所的数据,时间戳可能不是同一个时区。我曾经因为这个,回测结果差了20%。
  • 订单簿重建:如果你只有成交数据,没有订单簿快照,那重建订单簿是个大工程。我建议至少保存每秒钟的订单簿快照。
  • 滑点模拟:做市策略的滑点影响特别大。别用固定滑点,最好根据订单簿的深度动态计算。
一个小技巧:在回测引擎里加一个「回放模式」。这样你可以逐笔查看策略的决策过程,调试起来特别方便。

好了,回测系统搭建这块,咱们就聊到这儿。下一节咱们会深入聊聊策略逻辑的具体实现,包括报价算法和风险管理。到时候见。


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