01 引言
手把手教你用Python搭建自己的量化回测框架【均值回归策略】 是使用矢量化方法(pandas)建立的基于研究的量化回测框架,不考虑交易的委托成交行为,与真实市场情况差距比较大。今天为大家介绍的是基于事件驱动的回测框架,这是一种十分复杂的回测系统,力图模拟实盘交易,搭建一种仿真的回测环境。
与矢量化方法相比,事件驱动的系统具有许多优点,一是事件驱动回测可以用于历史回测和实时交易,而矢量化的回测必须一次获得所有数据才能进行统计分析;二是使用事件驱动的回测不会出现前瞻性偏见,因为将市场数据接收视为“事件”,可以用市场数据“滴灌”来复制订单管理和投资组合系统的行为方式;三是事件驱动的回测允许对如何执行订单和产生交易成本进行定制。由于可以构建自定义交易处理程序,因此可以轻松处理基本的市场和限价订单。
尽管事件驱动的回测系统具有许多优点,但与简单的矢量化系统相比,两大缺点也比较突出:一是实施和测试要复杂得多,有更多的“活动组建”(模块),导致引入错误的机会更大;二是执行速度较慢,进行数学计算时,无法利用最佳的矢量化运算。下面仍然与均值回归交易策略为例,为大家展示Python基于事件驱动回测框架的构建思路,回测代码主要参考了《Mastering Python for Finance》Chapter 9 Backtesting,对市场数据获取使用了tushare作为替代,公众号后台回复"finance"可获取本书的电子书(英文版)。
02 回测框架与Python代码
基于事件驱动的回测框架一般包括以下几个模块,(1)数据采集,数据采集模块通过接口获取行情数据和历史数据(这里使用tushare),产生市场数据事件。(2)事件模块,一般是设定一个事件基类,然后在事件的基类下面生成很多子事件,如市场数据事件、交易信号事件、委托下单事件和订单成交事件等。(3)策略模块,一般先设定一个策略基类,然后通过基类衍生很多子策略,该模块通过输入数据,生成交易信号(signal),即产生信号事件。(4)交易执行模块,接收信号事件,确定需要开仓和平仓的头寸数量,输出委托下单事件,根据委托下单事件进行模拟或者真实的交易,当订单成交事件完成时更新持有资产头寸以及其他相关数据。(5)资产头寸,记录资金、仓位、仓位市值等信息。最后,所有事件通过事件队列进行管理,当一个事件完成后,由下一个事件开始任务,不断循环。
Python基于事件驱动的回测系统主要使用面向对象(class)来编写,因此需要对类的基础要求比较高,关于Python的面向对象编程可以参考推文:【手把手教你】Python面向对象编程入门及股票数据管理应用实例。
#先引入后面可能用到的包(package)importpandasaspdimportnumpyasnpimportmatplotlib.pyplotasplt%matplotlibinline#正常显示画图时出现的中文和负号frompylabimportmplmpl.rcParams["font.sans-serif"]=["SimHei"]mpl.rcParams["axes.unicode_minus"]=False
将每一个时间戳(timestamp)内的数据作为输入参数,构建类TickData。
classMeanRevertingStrategy(Strategy):def__init__(self,symbol,lookback_intervals=20,buy_threshold=-1.5,sell_threshold=1.5):Strategy.__init__(self)self.symbol=symbolself.lookback_intervals=lookback_intervalsself.buy_threshold=buy_thresholdself.sell_threshold=sell_thresholdself.prices=pd.DataFrame()self.is_long,self.is_short=False,Falsedefevent_position(self,positions):ifself.symbolinpositions:position=positions[self.symbol]self.is_long=>0elseFalseself.is_short=<0elseFalsedefevent_tick(self,market_data):self.store_prices(market_data)iflen(self.prices)<self.lookback_intervals:returnsignal_value=self.calculate_z_score()timestamp=market_data.get_timestamp(self.symbol)ifsignal_value<self.buy_threshold:self.on_buy_signal(timestamp)elifsignal_value>self.sell_threshold:self.on_sell_signal(timestamp)defstore_prices(self,market_data):timestamp=market_data.get_timestamp(self.symbol)self.prices.loc[timestamp,"close"]=\market_data.get_last_price(self.symbol)self.prices.loc[timestamp,"open"]=\market_data.get_open_price(self.symbol)defcalculate_z_score(self):self.prices=self.prices[-self.lookback_intervals:]returns=self.prices["close"].pct_change().dropna()z_score=((returns-returns.mean())/returns.std())[-1]returnz_scoredefon_buy_signal(self,timestamp):ifnotself.is_long:self.send_market_order(self.symbol,100,True,timestamp)defon_sell_signal(self,timestamp):ifnotself.is_short:self.send_market_order(self.symbol,100,False,timestamp)
根据数据要求,生成市场数据事件,这里主要获取收盘价、开盘价、成交量和时间戳。
importdatetimeasdtimportpandasaspdclassBacktester:def__init__(self,symbol,start_date,end_date):self.target_symbol=symbolself.start_dt=start_dateself.end_dt=end_dateself.strategy=Noneself.unfilled_orders=[]self.positions=dict()self.current_prices=Noneself.rpnl,self.upnl=pd.DataFrame(),pd.DataFrame()defget_timestamp(self):returnself.current_prices.get_timestamp(self.target_symbol)defget_trade_date(self):timestamp=self.get_timestamp()returntimestamp.strftime("%Y-%m-%d")defupdate_filled_position(self,symbol,qty,is_buy,price,timestamp):position=self.get_position(symbol)position.event_fill(timestamp,is_buy,qty,price)self.strategy.event_position(self.positions)self.rpnl.loc[timestamp,"rpnl"]=position.realized_pnlprint(self.get_trade_date(),\"成交:","买入"ifis_buyelse"卖出",\qty,symbol,"价格",price)defget_position(self,symbol):ifsymbolnotinself.positions:position=Position()position.symbol=symbolself.positions[symbol]=positionreturnself.positions[symbol]defevthandler_order(self,order):self.unfilled_orders.append(order)print(self.get_trade_date(),\"收到指令:",\"买入"iforder.is_buyelse"卖出",order.qty,\order.symbol)defmatch_order_book(self,prices):iflen(self.unfilled_orders)>0:self.unfilled_orders=\[orderfororderinself.unfilled_ordersifself.is_order_unmatched(order,prices)]defis_order_unmatched(self,order,prices):symbol=order.symboltimestamp=prices.get_timestamp(symbol)iforder.is_market_orderandtimestamp>order.timestamp:#Orderismatchedandfilled.order.is_filled=Trueopen_price=prices.get_open_price(symbol)order.filled_timestamp=timestamporder.filled_price=open_priceself.update_filled_position(symbol,order.qty,order.is_buy,open_price,timestamp)self.strategy.event_order(order)returnFalsereturnTruedefevthandler_tick(self,prices):self.current_prices=pricesself.strategy.event_tick(prices)self.match_order_book(prices)defstart_backtest(self):self.strategy=MeanRevertingStrategy(self.target_symbol)self.strategy.event_sendorder=self.evthandler_ordermds=MarketDataSource()mds.event_tick=self.evthandler_tickmds.ticker=self.target_symbolmds.start,mds.end=self.start_dt,self.end_dtprint("Backtestingstarted...")mds.start_market_simulation()print("Completed.")
获取市场数据并搭建市场模拟交易的状态。
backtester=Backtester("600000","0101","")backtester.start_backtest()
交易指令和头寸管理。
classOrder:def__init__(self,timestamp,symbol,qty,is_buy,is_market_order,price=0):self.timestamp=timestampself.symbol=symbolself.qty=qtyself.price=priceself.is_buy=is_buyself.is_market_order=is_market_orderself.is_filled=Falseself.filled_price=0self.filled_time=Noneself.filled_qty=0classPosition:def__init__(self):self.symbol=Noneself.buys,self.sells,=0,0,0self.realized_pnl=0self.unrealized_pnl=0self.position_value=0defevent_fill(self,timestamp,is_buy,qty,price):ifis_buy:self.buys+=qtyelse:self.sells+==self.buys-self.sellschanged_value=qty*price*(-1ifis_buyelse1)self.position_value+===0:self.realized_pnl=self.position_valuedefupdate_unrealized_pnl(self,price):==0:self.unrealized_pnl=0else:self.unrealized_pnl=price*+self.position_valuereturnself.unrealized_pnl
策略的基类,其他策略都基于该策略进行编写。classStrategy:def__init__(self):self.event_sendorder=Nonedefevent_tick(self,market_data):passdefevent_order(self,order):passdefevent_position(self,positions):passdefsend_market_order(self,symbol,qty,is_buy,timestamp):ifnotself.event_sendorderisNone:order=Order(timestamp,symbol,qty,is_buy,True)self.event_sendorder(order)
下面以均值回归模型为例进行回测,关于均值回归模型更详细的内容可参考推文:手把手教你用Python搭建自己的量化回测框架【均值回归策略】 。
classMeanRevertingStrategy(Strategy):def__init__(self,symbol,lookback_intervals=20,buy_threshold=-1.5,sell_threshold=1.5):Strategy.__init__(self)self.symbol=symbolself.lookback_intervals=lookback_intervalsself.buy_threshold=buy_thresholdself.sell_threshold=sell_thresholdself.prices=pd.DataFrame()self.is_long,self.is_short=False,Falsedefevent_position(self,positions):ifself.symbolinpositions:position=positions[self.symbol]self.is_long=>0elseFalseself.is_short=<0elseFalsedefevent_tick(self,market_data):self.store_prices(market_data)iflen(self.prices)<self.lookback_intervals:returnsignal_value=self.calculate_z_score()timestamp=market_data.get_timestamp(self.symbol)ifsignal_value<self.buy_threshold:self.on_buy_signal(timestamp)elifsignal_value>self.sell_threshold:self.on_sell_signal(timestamp)defstore_prices(self,market_data):timestamp=market_data.get_timestamp(self.symbol)self.prices.loc[timestamp,"close"]=\market_data.get_last_price(self.symbol)self.prices.loc[timestamp,"open"]=\market_data.get_open_price(self.symbol)defcalculate_z_score(self):self.prices=self.prices[-self.lookback_intervals:]returns=self.prices["close"].pct_change().dropna()z_score=((returns-returns.mean())/returns.std())[-1]returnz_scoredefon_buy_signal(self,timestamp):ifnotself.is_long:self.send_market_order(self.symbol,100,True,timestamp)defon_sell_signal(self,timestamp):ifnotself.is_short:self.send_market_order(self.symbol,100,False,timestamp)
最后定义一个回测类,将上述模块串联到一起。回测系统中只是对策略交易的已实现收益(未实现收益)进行回测,并未加入收益率、夏普比率、最大回撤等策略评价指标,关于这方面内容可以参考手把手教你用Python搭建自己的量化回测框架【均值回归策略】 。
importdatetimeasdtimportpandasaspdclassBacktester:def__init__(self,symbol,start_date,end_date):self.target_symbol=symbolself.start_dt=start_dateself.end_dt=end_dateself.strategy=Noneself.unfilled_orders=[]self.positions=dict()self.current_prices=Noneself.rpnl,self.upnl=pd.DataFrame(),pd.DataFrame()defget_timestamp(self):returnself.current_prices.get_timestamp(self.target_symbol)defget_trade_date(self):timestamp=self.get_timestamp()returntimestamp.strftime("%Y-%m-%d")defupdate_filled_position(self,symbol,qty,is_buy,price,timestamp):position=self.get_position(symbol)position.event_fill(timestamp,is_buy,qty,price)self.strategy.event_position(self.positions)self.rpnl.loc[timestamp,"rpnl"]=position.realized_pnlprint(self.get_trade_date(),\"成交:","买入"ifis_buyelse"卖出",\qty,symbol,"价格",price)defget_position(self,symbol):ifsymbolnotinself.positions:position=Position()position.symbol=symbolself.positions[symbol]=positionreturnself.positions[symbol]defevthandler_order(self,order):self.unfilled_orders.append(order)print(self.get_trade_date(),\"收到指令:",\"买入"iforder.is_buyelse"卖出",order.qty,\order.symbol)defmatch_order_book(self,prices):iflen(self.unfilled_orders)>0:self.unfilled_orders=\[orderfororderinself.unfilled_ordersifself.is_order_unmatched(order,prices)]defis_order_unmatched(self,order,prices):symbol=order.symboltimestamp=prices.get_timestamp(symbol)iforder.is_market_orderandtimestamp>order.timestamp:#Orderismatchedandfilled.order.is_filled=Trueopen_price=prices.get_open_price(symbol)order.filled_timestamp=timestamporder.filled_price=open_priceself.update_filled_position(symbol,order.qty,order.is_buy,open_price,timestamp)self.strategy.event_order(order)returnFalsereturnTruedefevthandler_tick(self,prices):self.current_prices=pricesself.strategy.event_tick(prices)self.match_order_book(prices)defstart_backtest(self):self.strategy=MeanRevertingStrategy(self.target_symbol)self.strategy.event_sendorder=self.evthandler_ordermds=MarketDataSource()mds.event_tick=self.evthandler_tickmds.ticker=self.target_symbolmds.start,mds.end=self.start_dt,self.end_dtprint("Backtestingstarted...")mds.start_market_simulation()print("Completed.")
开始回测
backtester=Backtester("600000","0101","")backtester.start_backtest()
Backtestingstarted...-01-31收到指令:卖出100600000-02-01成交:卖出100600000价格10.82-02-15收到指令:买入100600000-02-18成交:买入100600000价格10.75......-03-09收到指令:买入100600000-03-10成交:买入100600000价格10.71Completed.backtester.rpnl.plot(figsize=(16,6))plt.show()
策略已实现收益:
03 结语
本文以均值回归模型为例,展示了基于事件驱动回测系统的Python实现过程。当然,上述回测系统仍然是一个简化版的系统,还存在很多需要完善的地方,比如没有加入关于策略的量化评价指标和可视化模块,没有考虑交易手续费等,这些都留待读者自己去思考和进一步完善。现实的市场交易比回测系统要复杂更多,因此回测系统再怎么完美也很难完全复现真实交易的场景。量化回测是量化交易的重要组成部分,回测系统的好坏会直接影响对策略的评估。目前很多量化平台和Python量化回测开源框架都提供了相应的回测系统,大家也没必要都自己去重新造轮子,但是对于Python基础比较扎实,从事个人量化交易的来说,了解回测系统的运作过程,构建自己的量化交易系统还是很有必要的。后续推文将会介绍Python量化回测开源框架的应用。
参考资料:Weiming J M, Weiming J M. Mastering Python for Finance[M]. .