在前面的几篇文章中,我们利用 Python 和 pandas 回测了各种交易策略。pandas 的向量化特性确保了对大型数据集的操作非常迅速。然而,我们目前所研究的向量化回测方法在模拟交易执行方面存在一些缺陷。在本文中,我们将通过使用 Python 构建一个事件驱动的回测环境,来讨论一种更符合实际的历史策略模拟方法。
事件驱动型软件
在深入探讨此类回测系统的开发之前,我们首先需要理解事件驱动系统的概念。简单举个例子,视频游戏就是一种事件驱动软件的天然应用场景。视频游戏包含多个组件,它们在高帧率的实时环境中相互交互。这是通过在一个称为“事件循环”或“游戏循环”的“无限”循环中运行整套计算来实现的。
在游戏循环的每个周期中,系统会调用一个函数来接收最新的事件,该事件将由游戏中先前发生的某些相应动作生成。根据事件的性质(例如按键或鼠标点击),系统会采取响应的后续操作,这些操作可能会终止循环或生成更多的事件。然后,这个过程将继续。以下是一些示例伪代码:
while True: # Run the loop forever new_event = get_new_event() # Get the latest event # Based on the event type, perform an action if new_event.type == "LEFT_MOUSE_CLICK": open_menu() elif new_event.type == "ESCAPE_KEY_PRESS": quit_game() elif new_event.type == "UP_KEY_PRESS": move_player_north() # ... and many more events redraw_screen() # Update the screen to provide animation tick(50) # Wait 50 milliseconds
这段代码在不断地检查新事件,然后根据这些事件执行操作。特别是,它通过代码不断循环和检查事件,营造出实时响应处理的假象。显而易见,这正是我们进行高频交易模拟所需要的。
为什么要使用事件驱动型回测系统?
与向量化方法相比,事件驱动系统有诸多优势:
代码复用性:从设计上讲,事件驱动型回测系统可用于历史回测和实盘交易,且组件更换极少。而向量化回测系统则不然,后者必须一次性获取所有数据才能进行统计分析。避免前视偏差:在事件驱动型回测系统中,由于市场数据的接收被视为必须处理的“事件”,从而避免了前视偏差。因此,可以向事件驱动型回测系统“滴灌式”地提供市场数据,真实模拟订单管理和投资组合系统的运行方式。逼真性:事件驱动型回测系统允许对订单执行方式和交易成本发生方式进行高度自定义。通过构建自定义交易所处理系统,可以处理基本的市价单和限价单,以及开盘市价单和收盘市价单。尽管事件驱动系统优势显著,但与更简单的向量化系统相比,也存在两个主要缺点。首先,它们的实现和测试要复杂得多。更多的“活动部件”导致引入错误的可能性更大。为了缓解这个问题,可以采用适当的软件测试方法,如测试驱动开发。 其次,与向量化系统相比,它们的执行速度更慢。在进行数学计算时,无法利用最优的向量化操作。我们将在后面的文章中讨论克服这些限制的方法。
事件驱动型回测系统概述
要将事件驱动方法应用于回测系统,需要定义处理特定任务的组件或对象:
事件:事件是事件驱动系统的基本类单元。它包含一个类型(如“市场MARKET”、“信号SIGNAL”、“订单ORDER”或“成交FILL”),该类型决定了它在事件循环中的处理方式。事件队列:事件队列是一个内存中的 Python 队列对象,用于存储由软件其他部分生成的所有事件子类对象。数据处理器:数据处理器是一个抽象基类(ABC),它为处理历史或实时市场数据提供接口。这使得策略和投资组合模块能在两种方式间复用,具有极大的灵活性。数据处理器在系统的每一次“心跳”(见下文)时生成一个新的市场事件。策略:是一个抽象基类,它提供了一个接口,用于获取市场数据并生成相应的信号事件,这些信号最终被投资组合对象利用。信号事件包含代码符号、方向(做多或做空)和时间戳。投资组合:这是一个抽象基类,它处理与策略的当前和后续头寸相关的订单管理。它还对投资组合进行风险管理,包括行业敞口和头寸规模控制。在更复杂的实现流程中,这部分功能可以委托给专门的风险管理类。投资组合从队列中获取信号事件并生成添加到队列中的订单事件。执行处理器:执行处理器模拟与券商的连接。处理器的工作是获取队列中的订单事件并执行它们,执行方式可以是模拟的,也可以是连接到真实券商的实盘接口。订单执行后,处理器会创建成交事件,描述实际交易详情,包括费用、佣金和滑点(如果建模的话)。循环:所有这些组件都封装在一个事件循环中,该循环正确处理所有事件类型,并将它们路由到相应的组件。这是一个相当基础的交易引擎模型。还有很大的扩展空间,尤其是在投资组合的使用方式。此外,不同的交易成本模型也可以抽象为独立的类层次结构。在现阶段,本文中引入这些会增加不必要的复杂性,因此我们暂时不作进一步讨论。在以后的教程中,我们可能会扩展系统以增加更多现实性。
下面是一段 Python 代码片段,演示了回测系统在实践中的工作原理。代码中存在两个循环。外层循环用于给回测系统一个“心跳”。对于实盘交易,这是轮询新市场数据的频率。对于策略回测,这并非严格必需,因为回测系统使用以“滴灌”形式提供的市场数据(参见 bars.update_bars() 行)。
内层循环实际处理来自事件队列对象的事件。特定的事件被委派给相应的组件,随后新的事件被添加到队列中。当事件队列为空时,循环继续:
# Declare the components with respective parametersbars = DataHandler(..)strategy = Strategy(..)port = Portfolio(..)broker = ExecutionHandler(..)while True: # Update the bars (specific backtest code, as opposed to live trading) if bars.continue_backtest == True: bars.update_bars() else: break # Handle the events while True: try: event = events.get(False) except Queue.Empty: break else: if event is not None: if event.type == 'MARKET': strategy.calculate_signals(event) port.update_timeindex(event) elif event.type == 'SIGNAL': port.update_signal(event) elif event.type == 'ORDER': broker.execute_order(event) elif event.type == 'FILL': port.update_fill(event) # 10-Minute heartbeat time.sleep(10*60)
这就是事件驱动型回测系统的基本设计框架。在下一篇文章中,我们将讨论事件类的层级结构。