事件驱动(Event Driven)属于量化投资之中的一个重要类别,涵盖投资机会广泛。广义上说,市场上任何发生的有可能与股票市场相关的新闻、事件、公告均有可能成为事件驱动的投资机会。 目前我国业界事件驱动策略中包括的常用重大事件有:业绩预告、业绩快报、分红送转、大股东增减持、高管增减持、定向增发、限售股解禁、股权激励、重组并购、ST摘和评级上调等,如下图所示。 可以看出,目前市场经过验证有效的事件已经不少,涵盖了影响股票价格的多个方面。事件驱动策略由于其策略逻辑的独特性,因此与其他常规股票策略相关性很低,再加上事件众多,资金容量大这一特点,使得事件驱动策略成为国外对冲基金非常大类的投资策略。 BigQuant回测引擎能够快速验证事件的有效性,从而开发事件驱动策略。为方便小伙伴顺利开发事件驱动策略,本文以业绩快报中净利润大幅增长为事件,验证该事件是否可以带来超额收益,未来我们会发布更多的基于其他事件的策略,敬请耐心等待。 在回测之前,我们先看策略的完整介绍:
我们的策略流程是:每日更新数据,查看当日发布财务报表并且(归属母公司)净利润季度同比增长率超过30%的公司,如果出现这样的事件,就买入该股票。策略回测结果为: 从测试结果来看,该事件驱动策略为长期正收益系统。因为财报公布事件会有一个期限规定(年度报告是每年结束后4个月内,半年度是上半年结束后2个月内,季度报告是季度结束后1个月内),所以某些时间段不会有公司公布财务报表,当然那段时间就不会出现业绩快报净利润大幅增长的事件,因此仓位很多时候并不是100%。 备注:上市公司财报披露时间一般是财报发布日期的前一天晚上8点,因此策略回测中订单生成时间是财报公布日前一天,目的是便于回测和实盘保持一致性。 想要查看完整策略,点击 克隆策略一键搞定。 def prepare(context):
instruments = context.instruments
start_date = context.start_date
end_date = context.end_date
# 获取数据
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] = []
context.event = event def initialize(context):
context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
context.daily_buy_stock = context.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.trade.v2(
instruments=D.instruments(market='CN_STOCK_A'),
start_date='2010-01-01',
end_date='2017-08-01',
prepare=prepare,
initialize=initialize,
handle_data=handle_data,
order_price_field_buy='open',
order_price_field_sell='open',
capital_base=1000000,
benchmark='000300.INDX',
)
|