可以看出,目前市场经过验证有效的事件已经不少,涵盖了影响股票价格的多个方面。事件驱动策略由于其策略逻辑的独特性,因此与其他常规股票策略相关性很低,再加上事件众多,资金容量大这一特点,使得事件驱动策略成为国外对冲基金非常大类的投资策略。
BigQuant回测引擎能够快速验证事件的有效性,从而开发事件驱动策略。为方便小伙伴顺利开发事件驱动策略,本文以业绩快报中净利润大幅增长为事件,验证该事件是否可以带来超额收益,未来我们会发布更多的基于其他事件的策略,敬请耐心等待。
在回测之前,我们先看策略的完整介绍:
- 策略逻辑:认为业绩快报中净利润大幅增长为利好消息,会导致价格在一定期限内上涨
- 事件定义:当业绩快报中公布净利润同比增增长超过30%
- 股票持有不超过50只,仅当持有数量小于50只时,才买入股票
- 持有时间:40个交易日
我们的策略流程是:每日更新数据,查看当日发布财务报表并且(归属母公司)净利润季度同比增长率超过30%的公司,如果出现这样的事件,就买入该股票。因此这里的分析和"选股系列"以及"大师系列"的策略依然相似,大家可以结合来看。
策略回测结果为:
从测试结果来看,该事件驱动策略为长期正收益系统。因为财报公布事件会有一个期限规定(年度报告是每年结束后4个月内,半年度是上半年结束后2个月内,季度报告是季度结束后1个月内),所以某些时间段不会有公司公布财务报表,当然那段时间就不会出现业绩快报净利润大幅增长的事件,因此仓位很多时候并不是100%。
备注:上市公司财报披露时间一般是财报发布日期的前一天晚上8点,因此策略回测中订单生成时间是财报公布日前一天,目的是便于回测和实盘保持一致性。
源码,原文请参考BigQuant人工智能量化投资平台的社区:
- instruments = D.instruments()
- start_date = '2010-01-01'
- end_date = '2017-02-16'
- # 获取数据
- data = D.financial_statements(instruments, start_date, end_date,
- fields=['instrument','fs_publish_date','fs_quarter_year',
- 'fs_quarter_index','fs_net_profit_yoy'])
- # 选择净利润同比增长率大于30%的股票
- selected = data[data['fs_net_profit_yoy'] > 30]
- # 获取交易日历
- date = D.trading_days(start_date=start_date,end_date=end_date)
- date = date['date'].apply(lambda x : x.strftime('%Y-%m-%d'))
- # 为尽量接近实盘,事件日期应为财报公布日的前一天
- publish_date = date.shift(-1)
- shift = dict(zip(date,publish_date))
- # 建立事件表
- event = {}
- for dt in date:
- if type(shift[dt]) is str:
- event[dt] = list(selected[selected['fs_publish_date'] == shift[dt]].sort_values(
- 'fs_net_profit_yoy',ascending=False ).instrument)
- else:
- event[dt] = []
- def initialize(context):
-
- context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
- context.daily_buy_stock = event
- context.hold_periods = 40 # 持有40天
- context.stock_max_num = 50 # 最大持仓数量为50只
- context.hold_days = {}
-
- def handle_data(context,data):
-
- date = data.current_dt.strftime('%Y-%m-%d')
-
- # 目前仓位里面的股票列表
- equities = {e.symbol: e for e, p in context.perf_tracker.position_tracker.positions.items()}
-
- for k in equities.keys():
- # 如果持仓时间大于40天
- if context.trading_day_index - context.hold_days[k] >= context.hold_periods
- and data.can_trade(context.symbol(k)):
- # 卖完
- context.order_target_percent(context.symbol(k),0)
-
- # 还允许建仓的股票数目
- stock_can_buy_num = context.stock_max_num - len(equities)
- # 获取当日买入股票的代码
- stock_to_buy = context.daily_buy_stock[date][:stock_can_buy_num]
-
- # 等权重买入
- weight = 1 / context.stock_max_num
-
- # 买入
- for stock in stock_to_buy:
- if data.can_trade(context.symbol(stock)):
- context.order_target_percent(context.symbol(stock), weight)
- # 记录建仓时间的日期索引
- context.hold_days[stock] = context.trading_day_index
- m=M.backtest.v4(
- instruments=instruments,
- start_date=start_date,
- end_date=end_date,
- initialize=initialize,
- handle_data=handle_data,
- order_price_field_buy='open',
- order_price_field_sell='open',
- capital_base=float("1.0e7"),
- benchmark='000300.INDX',
- )