关于事件驱动回测系统的讨论,此前已涵盖事件循环、事件类层次结构以及数据处理组件等内容。本文将重点阐述策略类层次结构的设计。策略对象以市场数据作为输入,并输出交易信号事件。
一个策略对象封装了所有基于市场数据的计算逻辑,这些计算为投资组合对象生成建议信号。在事件驱动回测系统的当前开发阶段,尚未引入技术分析中常见的指标或过滤器概念。这些概念也适合构建独立的类层次结构,但本文暂不展开讨论。
策略类层次结构相对简单,主要包含一个抽象基类,该类具有生成信号事件SignalEvent对象的单一纯虚方法。为创建该层次结构,需要导入NumPy、pandas、队列对象、抽象基类工具以及信号事件类SignalEvent:
# strategy.pyimport datetimeimport numpy as npimport pandas as pdimport Queuefrom abc import ABCMeta, abstractmethodfrom event import SignalEvent
策略抽象基类仅定义了一个纯虚方法calculate_signals。在派生类中,该方法用于根据市场数据更新来生成信号事件对象:# strategy.pyclass Strategy(object): """ Strategy is an abstract base class providing an interface for all subsequent (inherited) strategy handling objects. The goal of a (derived) Strategy object is to generate Signal objects for particular symbols based on the inputs of Bars (OLHCVI) generated by a DataHandler object. This is designed to work both with historic and live data as the Strategy object is agnostic to the data source, since it obtains the bar tuples from a queue object. """ __metaclass__ = ABCMeta @abstractmethod def calculate_signals(self): """ Provides the mechanisms to calculate the list of signals. """ raise NotImplementedError("Should implement calculate_signals()")
策略抽象基类的定义非常简明扼要。我们首个策略子类的实例采用了买入并持有的策略来构建BuyAndHoldStrategy类。该策略在特定日期对某个证券建立多头仓位并长期持有,因此每个证券仅生成一次交易信号。该类的构造函数(__init__)需要接收市场数据处理器对象和事件队列对象作为参数:
# strategy.pyclass BuyAndHoldStrategy(Strategy): """ This is an extremely simple strategy that goes LONG all of the symbols as soon as a bar is received. It will never exit a position. It is primarily used as a testing mechanism for the Strategy class as well as a benchmark upon which to compare other strategies. """ def __init__(self, bars, events): """ Initialises the buy and hold strategy. Parameters: bars - The DataHandler object that provides bar information events - The Event Queue object. """ self.bars = bars self.symbol_list = self.bars.symbol_list self.events = events # Once buy & hold signal is given, these are set to True self.bought = self._calculate_initial_bought()
在初始化BuyAndHoldStrategy时,bought中的元素会为每个交易代码创建键值且初始值均设为 False。一旦该资产被“做多”,对应键值将变为True。这本质上让策略能够追踪自身是否已"入场":# strategy.py def _calculate_initial_bought(self): """ Adds keys to the bought dictionary for all symbols and sets them to False. """ bought = {} for s in self.symbol_list: bought[s] = False return bought
在此类中具体实现了calculate_signals这个纯虚方法。该方法会遍历代码列表中的所有交易品种,从数据处理器中获取每个品种的最新 K 线数据,然后检查该品种是否已被"买入"(即是否已对该品种建仓)。若尚未买入,则创建一个信号事件SignalEvent对象并将其放入事件events队列,同时将该品种在bought字典中的对应键值更新为True:# strategy.py def calculate_signals(self, event): """ For "Buy and Hold" we generate a single signal per symbol and then no additional signals. This means we are constantly long the market from the date of strategy initialisation. Parameters event - A MarketEvent object. """ if event.type == 'MARKET': for s in self.symbol_list: bars = self.bars.get_latest_bars(s, N=1) if bars is not None and bars != []: if self.bought[s] == False: # (Symbol, Datetime, Type = LONG, SHORT or EXIT) signal = SignalEvent(bars[0][0], bars[0][1], 'LONG') self.events.put(signal) self.bought[s] = True
这虽然是一个简单的策略,但足以展示事件驱动策略层次结构的基本特性。在后续文章中,我将会带大家构建更复杂的策略,例如配对交易策略。下一篇我们将讨论如何创建投资组合层次结构,以跟踪持仓及其盈亏状况("PnL")。