事件驱动分析基本流程

摘要

事件驱动是一类传统的投资策略,其将离散化的事件时间对齐,来看其是否具有的超额收益。这里所说的事件可以是公告,如业绩预告,定增公告,高转送等,也可以是技术指标,如CCI小于-200,DEA穿0等等。本文旨在完成一个简易的事件驱动分析基本流程,以及由事件驱动分析转化为投资策略的一些思考。

xxxxxxxxxx9

1

import pandas as pd

2

import numpy as np

3

import datetime

4

from dateutil.parser import parse

5

import scipy.stats as ss

6

import matplotlib.pyplot as plt

7

from CAL.PyCAL import *

8

cal = Calendar('China.SSE')

9

font.set_size(15)

查看全部

一、事件列表获取

首先我们要获取事件数据,如下调用业绩预告API,对于事件分析来说,一定要知道的是股票代码和事件发生的时间字段,保留forecastType字段,方便以后对不同业绩预告做分组分析。

xxxxxxxxxx8

1

def get_events(begin_date,end_date,universe):

2

events = DataAPI.FdmtEfGet(secID=universe,reportType=u"",forecastType=u"",publishDateBegin=begin_date,

3

publishDateEnd=end_date,field=u"secID,actPubtime,forecastType",pandas="1")

4

events['actPubtime'] = events['actPubtime'].map(lambda x : x[0:10] if cal.isBizDay(x) else cal.advanceDate(x[0:10], '-1B',BizDayConvention.Unadjusted).strftime('%Y-%m-%d'))

5

events = events[events['actPubtime']

6

events['secID_actPubtime'] = events['secID'] +'_'+ events['actPubtime']

7

events = events.drop_duplicates()

8

return events

查看全部

xxxxxxxxxx1

1

events = get_events('20080101','20160520',set_universe('A'))

查看全部

二、行情数据获取

事件分析需要用到一个窗口内的行情数据,但通过离散的时间点反推拿数据调用API太多,并且也是会冗余的。故我们先将所有可能用到的行情数据全部拿出,生产一张日期-股票的二维表。

xxxxxxxxxx10

1

def get_quotation(start_date,end_date,universe):

2

df_eq = pd.DataFrame()

3

for i in range(30,len(universe),30):

4

df_tmp = DataAPI.MktEqudAdjGet(secID=universe[i-30:i],beginDate=start_date,endDate=end_date,

5

field=u"secID,tradeDate,openPrice",isOpen=1,pandas="1")

6

df_eq = pd.concat([df_eq,df_tmp],axis=0)

7

df_tmp = DataAPI.MktEqudAdjGet(secID=universe[i:],beginDate=start_date,endDate=end_date,

8

field=u"secID,tradeDate,openPrice",isOpen=1,pandas="1")

9

df_eq = pd.concat([df_eq,df_tmp],axis=0)

10

return df_eq

查看全部

xxxxxxxxxx1

1

df_eq = get_quotation('20071101','20160920',set_universe('A'))

查看全部

xxxxxxxxxx1

1

df_eq_unstack = df_eq.set_index(['tradeDate','secID']).unstack()

查看全部

业绩预告公布通常是收盘后发生,此处我们计算每日收益采用的更接近实际的开盘至开盘的收益。

xxxxxxxxxx1

1

open2open_yield = df_eq_unstack['openPrice'].pct_change().dropna(how='all')

查看全部

xxxxxxxxxx1

1

open2open_yield.head()

查看全部

secID 000001.XSHE 000002.XSHE 000004.XSHE 000005.XSHE 000006.XSHE 000007.XSHE 000008.XSHE 000009.XSHE 000010.XSHE 000011.XSHE ... 603969.XSHG 603979.XSHG 603986.XSHG 603988.XSHG 603989.XSHG 603993.XSHG 603996.XSHG 603997.XSHG 603998.XSHG 603999.XSHG

tradeDate

2007-11-02 -0.019308 -0.007703 -0.073746 -0.016667 -0.033029 NaN -0.003281 -0.017812 NaN 0.049726 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-05 -0.026032 0.002554 0.029724 -0.011299 -0.000332 NaN -0.042798 0.034046 NaN -0.046039 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-06 -0.042206 -0.092762 0.010309 0.002857 -0.052247 NaN 0.079106 -0.010920 NaN -0.006396 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-07 0.019781 0.032651 -0.021429 0.007123 0.009450 NaN 0.016733 -0.017564 NaN 0.008661 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-08 0.006754 0.000000 0.027112 -0.032532 -0.012309 NaN -0.014890 0.029116 NaN 0.040149 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 2920 columns

三、基准收益选取

我们分析结果是要看最后事件是否具有超额收益,故要设置一个基准收益,看处于事件的股票的收益是否比基准收益更高。此处我们将股票同行业的平均收益做为基准收益,更细致的分析,可以将市值因素考虑进来,出于运算时间的考虑此处没有加上。

xxxxxxxxxx4

1

def get_industry(into_date,universe):

2

df_industry = DataAPI.EquIndustryGet(industryVersionCD=u"010303",secID=universe,

3

intoDate=into_date,field=u"secID,industryName1",pandas="1")

4

return df_industry

查看全部

xxxxxxxxxx1

1

df_industry = get_industry('20161101',set_universe('A'))

查看全部

xxxxxxxxxx6

1

ind_ret_list = []

2

for ind in df_industry.industryName1.drop_duplicates().tolist():

3

ind_ret = open2open_yield.ix[:,df_industry[df_industry['industryName1']==ind]['secID'].tolist()].mean(axis=1)

4

ind_ret.name = ind

5

ind_ret_list.append(ind_ret)

6

ind_ret_df = pd.concat(ind_ret_list,axis=1)

查看全部

xxxxxxxxxx3

1

def get_benchmark(x):

2

ind = df_industry[df_industry['secID']==x]['industryName1'].values[0]

3

return ind_ret_df[ind]

查看全部

xxxxxxxxxx1

1

benchmarkYield = open2open_yield.apply(lambda x: get_benchmark(x.name))

查看全部

xxxxxxxxxx1

1

benchmarkYield.head()

查看全部

secID 000001.XSHE 000002.XSHE 000004.XSHE 000005.XSHE 000006.XSHE 000007.XSHE 000008.XSHE 000009.XSHE 000010.XSHE 000011.XSHE ... 603969.XSHG 603979.XSHG 603986.XSHG 603988.XSHG 603989.XSHG 603993.XSHG 603996.XSHG 603997.XSHG 603998.XSHG 603999.XSHG

tradeDate

2007-11-02 -0.017547 -1.672790e-02 -0.052213 -1.672790e-02 -0.031043 -0.039814 -0.052747 -0.047344 -0.050576 -1.672790e-02 ... -0.046136 -0.031043 -0.050595 -0.052133 -0.050595 -0.052747 -0.055699 -0.053349 -0.052213 -0.049244

2007-11-05 -0.018039 -1.526467e-02 -0.017383 -1.526467e-02 -0.029716 -0.017486 -0.031141 -0.008446 -0.009109 -1.526467e-02 ... -0.013924 -0.029716 -0.003476 -0.013211 -0.003476 -0.031141 -0.004119 -0.016371 -0.017383 -0.011453

2007-11-06 -0.017563 2.556436e-07 0.024740 2.556436e-07 -0.010207 0.029414 0.007403 0.014627 0.013087 2.556436e-07 ... 0.025786 -0.010207 0.033150 0.018414 0.033150 0.007403 0.033471 0.025504 0.024740 0.025682

2007-11-07 0.010099 8.917162e-03 0.011993 8.917162e-03 0.026226 0.017989 0.006096 0.009628 0.005059 8.917162e-03 ... 0.003906 0.026226 0.008568 0.006212 0.008568 0.006096 0.007304 0.009932 0.011993 0.011487

2007-11-08 0.006309 -5.831206e-03 -0.014400 -5.831206e-03 -0.001473 -0.006418 -0.007260 -0.000318 -0.009066 -5.831206e-03 ... -0.015505 -0.001473 -0.017592 -0.010173 -0.017592 -0.007260 -0.006330 -0.008882 -0.014400 -0.009428

5 rows × 2920 columns

四、时间窗口分析

事件驱动分析将时间发生点设置为T+0,将所有事件发生对齐到T+0点,并设置T-n到T+n为事件影响期,看事件的超额收益情况。

xxxxxxxxxx18

1

def get_window_yield(unstack_yield,events,window):

2

#获取事件窗口收益

3

events = events.reset_index()

4

def get_time_list(window,events):

5

# 得到每个事件的时间窗口

6

minus_period = '-' + str(window) + 'B'

7

period = str(window) + 'B'

8

time_list = [[cal.advanceDate(events['actPubtime'][i],minus_period,BizDayConvention.Unadjusted),cal.advanceDate(events['actPubtime'][i],period,BizDayConvention.Unadjusted)] for i in range(len(events['actPubtime']))]

9

return time_list

10

11

time_list = get_time_list(window,events)

12

offset = range(-1*window,window+1,1)

13

event_equity_list = []

14

for i in range(len(events)):

15

df_tmp = pd.DataFrame(unstack_yield.ix[time_list[i][0].strftime('%Y-%m-%d'):time_list[i][1].strftime('%Y-%m-%d'),events.iloc[i]['secID']].values,columns=[events.iloc[i]['secID_actPubtime']],index = offset)

16

event_equity_list.append(df_tmp)

17

event_equity_window = pd.concat(event_equity_list,axis=1)

18

return event_equity_window

查看全部

xxxxxxxxxx10

1

def get_mean_outperform_yield(event_equity_window_yield,event_benchmark_window_yield):

2

#获取窗口内平均超额收益

3

cols = list(event_equity_window_yield[(event_equity_window_yield > 0.4) | (event_equity_window_yield

4

for col in cols:

5

del event_equity_window_yield[col]

6

del event_benchmark_window_yield[col]

7

event_equity_window_cumyield = (1 + event_equity_window_yield).cumprod(axis=0) / (1 + event_equity_window_yield).cumprod(axis=0).ix[0] # 累计收益,对齐T+0点(这里之前的版本把event_equity_window_yield写成了event_benchmark_window_yield,导致了后面的分析结果不对)

8

event_benchmark_window_cumyield = (1 + event_benchmark_window_yield).cumprod(axis=0) / (1 + event_benchmark_window_yield).cumprod(axis=0).ix[0]

9

event_outperform_window_yield = (event_equity_window_cumyield - event_benchmark_window_cumyield).mean(axis=1) #超额收益累计平均值

10

return event_outperform_window_yield

查看全部

接下来我们对几类常见的业绩预告进行时间窗口分析,首先看下有哪些分类,以及其样本数量。

xxxxxxxxxx1

1

events['forecastType'].value_counts()

查看全部

22 1713024 780011 770251 493021 383023 302441 275213 148412 29931 19732 11014 4729 1Name: forecastType, dtype: int64

可以看出这个API将业绩预告分为13类,我们将样本点过少的状态剔除,分析样本点大于2000的业绩预告事件。它们分别是,22——业绩预增;11——预计亏损;24——盈利下降;51——经营预期及其他(该条描述不明也不予分析);21——预计盈利;23——盈利减缓;41——基本持平

xxxxxxxxxx13

1

def event_window_plot(open2open_yield,benchmarkYield,events,forecastType,window,legend):

2

## 画出各事件时间窗口收益

3

event_equity_window_yield = get_window_yield(open2open_yield,events[events['forecastType']==forecastType],window)

4

event_benchmark_window_yield = get_window_yield(benchmarkYield,events[events['forecastType']==forecastType],window)

5

event_outperform_window_yield = get_mean_outperform_yield(event_equity_window_yield,event_benchmark_window_yield)

6

fig = plt.figure(figsize=(10,6))

7

ax = fig.add_subplot(111)

8

ax.plot(event_outperform_window_yield.index,event_outperform_window_yield.values,'dodgerblue',linewidth=2.5)

9

plt.xlim(-25,25)

10

plt.ylim(event_outperform_window_yield.min(),event_outperform_window_yield.max())

11

ax.plot([0,0],[event_outperform_window_yield.min(),event_outperform_window_yield.max()],'crimson',linewidth=1.5,linestyle ='--')

12

ax.plot([1,1],[event_outperform_window_yield.min(),event_outperform_window_yield.max()],'crimson',linewidth=1.5,linestyle ='--')

13

ax.set_title(legend, fontproperties=font, fontsize=18)

查看全部

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,22,25,u"盈利预增事件时间窗口分析")

查看全部

360doc

从上图我们可以看出,第一,在业绩预增公告发布时有一个明显的开盘至开盘的超额收益(两条红虚线之间),但这个收益在公开信息的情况下是没法抓到的;第二,业绩预增公告发布之前,股票存在明显的超额收益,表明确实存在公告发布之前就有很大的涨幅;第三,业绩预增公告发布之后,第五天超额收益达到最大,随后超额收益开始下降。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,11,25,u"预计亏损事件时间窗口分析")

查看全部

360doc

从上图可以看出,预计亏损事件在公告发布之前就有明显的负的超额收益,并在预计亏损事件公布之后快速下跌,不过最后最后下跌基本止住。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,24,25,u"盈利下降事件时间窗口分析")

查看全部

360doc

从上图可以看出,盈利下降事件在公告发布之前就有明显的负的超额收益,并在盈利下降事件公布之后快速下跌,并在25个时间窗口内持续下跌。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,21,25,u"预计盈利事件时间窗口分析")

查看全部

360doc

从上图中可以看出,预计盈利事件表现出在公告发布之前有一定的超额收益,在公告发布时有一个显著的超额收益;但在随后表现平稳,并没有超额收益,也没有出现下跌。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,23,25,u"盈利减缓事件时间窗口分析")

查看全部

360doc

从上图可知,盈利减缓事件,呈现出公告发布前几乎没有超额收益,公告发布当日有明显的超额收益,不过在随后25个交易日内出现大幅下跌。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,41,25,u"基本持平事件时间窗口分析")

查看全部

360doc

从上图可知,基本持平事件,呈现出公告发布前几乎没有超额收益,公告发布的两日呈现出负的超额收益,随后超额收益继续小幅下跌。

五、事件驱动分析转化为投资策略

上面的事件分析给了我们另一个角度来看股票的超额的收益,然而事件的发生实际上是离散的,上述分析无法给出我们一个投资策略。本文提供一个简易的思路来将事件驱动分析转化为投资策略。

我们知道我们上面的分析是将每一个处于事件的股票累计收益的平均,故一个直接的想法便是将所有处于事件的股票等权买入,但因为事件是离散的,我们在一个持有期会面临着新的处于事件的股票需要买入,而导致实际上我们对每一个股票并不都是同一权重的。此处给出的解决办法是,首先计算出处于事件窗口期的最大股票个数,然后以这个最大股票个数的倒数做为每一只处于事件的股票的权重,这样就能保证所有处于事件的股票以同一权重跑完整个窗口期。

xxxxxxxxxx16

1

def get_max_event_stock(events):

2

##获得窗口期有交叉的最大股票个数

3

events['minute'] = events['actPubtime'].map(lambda x:x[11:20])

4

events['actpubdate'] = events['actPubtime'].map(lambda x: datetime.datetime.strptime(x[0:10],'%Y-%m-%d'))

5

events['ii'] = range(len(events))

6

events['buytime'] = events['ii'].map(lambda x: cal.advanceDate(events.iloc[x]['actpubdate'],'1B') if events.iloc[x]['minute'] != '00:00:00' else Date.fromDateTime(events.iloc[x]['actpubdate']))

7

calendar_date = DataAPI.TradeCalGet(exchangeCD=u"XSHG",beginDate=u"20071227",endDate=u"",field=u"",pandas="1")['calendarDate']

8

calendar_date = pd.DataFrame(calendar_date)

9

calendar_date['calendarDate'] = calendar_date['calendarDate'].map(lambda x:Date.parseISO(x))

10

?

11

def get_event_stock(trade_date):

12

event_stock = []

13

begin_date = cal.advanceDate(trade_date,'-5B')

14

return events[(events['buytime'] = begin_date)]['secID'].tolist()

15

calendar_date['stk_list'] = calendar_date['calendarDate'].map(lambda x: get_event_stock(x))

16

return max([len(item) for item in calendar_date['stk_list'].values])

查看全部

xxxxxxxxxx1

1

test = events[events['forecastType']==22]

查看全部

xxxxxxxxxx1

1

get_max_event_stock(test)

查看全部

355

如上我们得到了盈利预增事件的5天窗口期有交叉的最大股票个数,355;故我们编写如下策略。

xxxxxxxxxx45

1

import numpy as np

2

import pandas as pd

3

from CAL.PyCAL import *

4

start = '2007-12-27' # 回测起始时间

5

end = '2016-11-03' # 回测结束时间

6

benchmark = 'HS300' # 策略参考标准

7

universe = set_universe('A') #证券池,支持股票和基金

8

capital_base = 100000000 # 起始资金

9

freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测

10

refresh_rate = 1 # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd'时间间隔的单位为交易日,若freq = 'm'时间间隔为分钟

11

cal = Calendar('China.SSE')

12

period = '-1B'

13

?

14

def initialize(account): # 初始化虚拟账户状态

15

account.day_count ={}

16

for stk in universe:

17

account.day_count[stk] = 0

18

?

19

def handle_data(account): # 每个交易日的买入卖出指令

20

?

21

yestoday = cal.advanceDate(account.current_date, period, BizDayConvention.Unadjusted)

22

begin_date = cal.advanceDate(account.current_date, '-1Y', BizDayConvention.Unadjusted)

23

profit_up = DataAPI.FdmtEfGet(secID=account.universe,publishDateBegin=begin_date.strftime('%Y%m%d'),publishDateEnd=account.current_date.strftime('%Y%m%d'),

24

forecastType="22",field=['secID','actPubtime',],pandas="1")

25

26

fil_profit = profit_up[profit_up['actPubtime'].map(lambda x: x[0:10]) == yestoday.strftime('%Y-%m-%d')]

27

28

buy_list = fil_profit['secID'].tolist()

29

30

total_money = account.reference_portfolio_value

31

prices = account.reference_price

32

33

for stk in account.avail_security_position:

34

if account.day_count[stk] == 5:

35

order_to(stk,0)

36

account.day_count[stk] = 0

37

?

38

for stk in buy_list:

39

if stk not in account.security_position:

40

if np.isnan(prices[stk]) or prices[stk] == 0:

41

continue

42

order_pct_to(stk,1.0/355)

43

44

for stk in account.security_position:

45

account.day_count[stk] = account.day_count[stk] + 1

查看全部

年化收益率2.5%

基准年化收益率-5.1%

阿尔法-1.0%

贝塔0.09

夏普比率-0.34

收益波动率5.6%

信息比率0.13

最大回撤9.2%

换手率43.08

Created with Highstock 1.3.10累计收益率策略基准[***********][***********]0.00%-75.00%-50.00%-25.00%25.00%50.00%2010-08-09策略: -3.95%基准: -44.57%

查看回测详情,以及回测曲线可以看出,盈利预增事件自带择时属性;牛市时,该事件相对较多,而在熊市时,该事件相对较少。

六、总结

本文以业绩预告为例,给出了一个事件驱动的基本分析过程,并给出最后的demo策略。通过上面的分析过程,我们可从另一个角度来看股票的超额收益,将离散的事件规整到同一分析维度,来看这类事件是否是存在超额收益的。但最后我们做策略时,我们还是要面对离散的事件,如果各事件窗口不存在交叉的话,我们可以很简单使用凯利公式来得到我们对每个事件的最优仓位。然而大部分时候,事件的窗口都存在很多的交叉,如上策略我们做了一个最大股票个数平均仓位的处理,但是这样资金利用率非常低,而且存在事件个数在各年份不一定稳定的问题,所以在真实情况下,我们对未来该事件发生的次数做一个预测或许是一个更好的方式。

摘要

事件驱动是一类传统的投资策略,其将离散化的事件时间对齐,来看其是否具有的超额收益。这里所说的事件可以是公告,如业绩预告,定增公告,高转送等,也可以是技术指标,如CCI小于-200,DEA穿0等等。本文旨在完成一个简易的事件驱动分析基本流程,以及由事件驱动分析转化为投资策略的一些思考。

xxxxxxxxxx9

1

import pandas as pd

2

import numpy as np

3

import datetime

4

from dateutil.parser import parse

5

import scipy.stats as ss

6

import matplotlib.pyplot as plt

7

from CAL.PyCAL import *

8

cal = Calendar('China.SSE')

9

font.set_size(15)

查看全部

一、事件列表获取

首先我们要获取事件数据,如下调用业绩预告API,对于事件分析来说,一定要知道的是股票代码和事件发生的时间字段,保留forecastType字段,方便以后对不同业绩预告做分组分析。

xxxxxxxxxx8

1

def get_events(begin_date,end_date,universe):

2

events = DataAPI.FdmtEfGet(secID=universe,reportType=u"",forecastType=u"",publishDateBegin=begin_date,

3

publishDateEnd=end_date,field=u"secID,actPubtime,forecastType",pandas="1")

4

events['actPubtime'] = events['actPubtime'].map(lambda x : x[0:10] if cal.isBizDay(x) else cal.advanceDate(x[0:10], '-1B',BizDayConvention.Unadjusted).strftime('%Y-%m-%d'))

5

events = events[events['actPubtime']

6

events['secID_actPubtime'] = events['secID'] +'_'+ events['actPubtime']

7

events = events.drop_duplicates()

8

return events

查看全部

xxxxxxxxxx1

1

events = get_events('20080101','20160520',set_universe('A'))

查看全部

二、行情数据获取

事件分析需要用到一个窗口内的行情数据,但通过离散的时间点反推拿数据调用API太多,并且也是会冗余的。故我们先将所有可能用到的行情数据全部拿出,生产一张日期-股票的二维表。

xxxxxxxxxx10

1

def get_quotation(start_date,end_date,universe):

2

df_eq = pd.DataFrame()

3

for i in range(30,len(universe),30):

4

df_tmp = DataAPI.MktEqudAdjGet(secID=universe[i-30:i],beginDate=start_date,endDate=end_date,

5

field=u"secID,tradeDate,openPrice",isOpen=1,pandas="1")

6

df_eq = pd.concat([df_eq,df_tmp],axis=0)

7

df_tmp = DataAPI.MktEqudAdjGet(secID=universe[i:],beginDate=start_date,endDate=end_date,

8

field=u"secID,tradeDate,openPrice",isOpen=1,pandas="1")

9

df_eq = pd.concat([df_eq,df_tmp],axis=0)

10

return df_eq

查看全部

xxxxxxxxxx1

1

df_eq = get_quotation('20071101','20160920',set_universe('A'))

查看全部

xxxxxxxxxx1

1

df_eq_unstack = df_eq.set_index(['tradeDate','secID']).unstack()

查看全部

业绩预告公布通常是收盘后发生,此处我们计算每日收益采用的更接近实际的开盘至开盘的收益。

xxxxxxxxxx1

1

open2open_yield = df_eq_unstack['openPrice'].pct_change().dropna(how='all')

查看全部

xxxxxxxxxx1

1

open2open_yield.head()

查看全部

secID 000001.XSHE 000002.XSHE 000004.XSHE 000005.XSHE 000006.XSHE 000007.XSHE 000008.XSHE 000009.XSHE 000010.XSHE 000011.XSHE ... 603969.XSHG 603979.XSHG 603986.XSHG 603988.XSHG 603989.XSHG 603993.XSHG 603996.XSHG 603997.XSHG 603998.XSHG 603999.XSHG

tradeDate

2007-11-02 -0.019308 -0.007703 -0.073746 -0.016667 -0.033029 NaN -0.003281 -0.017812 NaN 0.049726 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-05 -0.026032 0.002554 0.029724 -0.011299 -0.000332 NaN -0.042798 0.034046 NaN -0.046039 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-06 -0.042206 -0.092762 0.010309 0.002857 -0.052247 NaN 0.079106 -0.010920 NaN -0.006396 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-07 0.019781 0.032651 -0.021429 0.007123 0.009450 NaN 0.016733 -0.017564 NaN 0.008661 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

2007-11-08 0.006754 0.000000 0.027112 -0.032532 -0.012309 NaN -0.014890 0.029116 NaN 0.040149 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 2920 columns

三、基准收益选取

我们分析结果是要看最后事件是否具有超额收益,故要设置一个基准收益,看处于事件的股票的收益是否比基准收益更高。此处我们将股票同行业的平均收益做为基准收益,更细致的分析,可以将市值因素考虑进来,出于运算时间的考虑此处没有加上。

xxxxxxxxxx4

1

def get_industry(into_date,universe):

2

df_industry = DataAPI.EquIndustryGet(industryVersionCD=u"010303",secID=universe,

3

intoDate=into_date,field=u"secID,industryName1",pandas="1")

4

return df_industry

查看全部

xxxxxxxxxx1

1

df_industry = get_industry('20161101',set_universe('A'))

查看全部

xxxxxxxxxx6

1

ind_ret_list = []

2

for ind in df_industry.industryName1.drop_duplicates().tolist():

3

ind_ret = open2open_yield.ix[:,df_industry[df_industry['industryName1']==ind]['secID'].tolist()].mean(axis=1)

4

ind_ret.name = ind

5

ind_ret_list.append(ind_ret)

6

ind_ret_df = pd.concat(ind_ret_list,axis=1)

查看全部

xxxxxxxxxx3

1

def get_benchmark(x):

2

ind = df_industry[df_industry['secID']==x]['industryName1'].values[0]

3

return ind_ret_df[ind]

查看全部

xxxxxxxxxx1

1

benchmarkYield = open2open_yield.apply(lambda x: get_benchmark(x.name))

查看全部

xxxxxxxxxx1

1

benchmarkYield.head()

查看全部

secID 000001.XSHE 000002.XSHE 000004.XSHE 000005.XSHE 000006.XSHE 000007.XSHE 000008.XSHE 000009.XSHE 000010.XSHE 000011.XSHE ... 603969.XSHG 603979.XSHG 603986.XSHG 603988.XSHG 603989.XSHG 603993.XSHG 603996.XSHG 603997.XSHG 603998.XSHG 603999.XSHG

tradeDate

2007-11-02 -0.017547 -1.672790e-02 -0.052213 -1.672790e-02 -0.031043 -0.039814 -0.052747 -0.047344 -0.050576 -1.672790e-02 ... -0.046136 -0.031043 -0.050595 -0.052133 -0.050595 -0.052747 -0.055699 -0.053349 -0.052213 -0.049244

2007-11-05 -0.018039 -1.526467e-02 -0.017383 -1.526467e-02 -0.029716 -0.017486 -0.031141 -0.008446 -0.009109 -1.526467e-02 ... -0.013924 -0.029716 -0.003476 -0.013211 -0.003476 -0.031141 -0.004119 -0.016371 -0.017383 -0.011453

2007-11-06 -0.017563 2.556436e-07 0.024740 2.556436e-07 -0.010207 0.029414 0.007403 0.014627 0.013087 2.556436e-07 ... 0.025786 -0.010207 0.033150 0.018414 0.033150 0.007403 0.033471 0.025504 0.024740 0.025682

2007-11-07 0.010099 8.917162e-03 0.011993 8.917162e-03 0.026226 0.017989 0.006096 0.009628 0.005059 8.917162e-03 ... 0.003906 0.026226 0.008568 0.006212 0.008568 0.006096 0.007304 0.009932 0.011993 0.011487

2007-11-08 0.006309 -5.831206e-03 -0.014400 -5.831206e-03 -0.001473 -0.006418 -0.007260 -0.000318 -0.009066 -5.831206e-03 ... -0.015505 -0.001473 -0.017592 -0.010173 -0.017592 -0.007260 -0.006330 -0.008882 -0.014400 -0.009428

5 rows × 2920 columns

四、时间窗口分析

事件驱动分析将时间发生点设置为T+0,将所有事件发生对齐到T+0点,并设置T-n到T+n为事件影响期,看事件的超额收益情况。

xxxxxxxxxx18

1

def get_window_yield(unstack_yield,events,window):

2

#获取事件窗口收益

3

events = events.reset_index()

4

def get_time_list(window,events):

5

# 得到每个事件的时间窗口

6

minus_period = '-' + str(window) + 'B'

7

period = str(window) + 'B'

8

time_list = [[cal.advanceDate(events['actPubtime'][i],minus_period,BizDayConvention.Unadjusted),cal.advanceDate(events['actPubtime'][i],period,BizDayConvention.Unadjusted)] for i in range(len(events['actPubtime']))]

9

return time_list

10

11

time_list = get_time_list(window,events)

12

offset = range(-1*window,window+1,1)

13

event_equity_list = []

14

for i in range(len(events)):

15

df_tmp = pd.DataFrame(unstack_yield.ix[time_list[i][0].strftime('%Y-%m-%d'):time_list[i][1].strftime('%Y-%m-%d'),events.iloc[i]['secID']].values,columns=[events.iloc[i]['secID_actPubtime']],index = offset)

16

event_equity_list.append(df_tmp)

17

event_equity_window = pd.concat(event_equity_list,axis=1)

18

return event_equity_window

查看全部

xxxxxxxxxx10

1

def get_mean_outperform_yield(event_equity_window_yield,event_benchmark_window_yield):

2

#获取窗口内平均超额收益

3

cols = list(event_equity_window_yield[(event_equity_window_yield > 0.4) | (event_equity_window_yield

4

for col in cols:

5

del event_equity_window_yield[col]

6

del event_benchmark_window_yield[col]

7

event_equity_window_cumyield = (1 + event_equity_window_yield).cumprod(axis=0) / (1 + event_equity_window_yield).cumprod(axis=0).ix[0] # 累计收益,对齐T+0点(这里之前的版本把event_equity_window_yield写成了event_benchmark_window_yield,导致了后面的分析结果不对)

8

event_benchmark_window_cumyield = (1 + event_benchmark_window_yield).cumprod(axis=0) / (1 + event_benchmark_window_yield).cumprod(axis=0).ix[0]

9

event_outperform_window_yield = (event_equity_window_cumyield - event_benchmark_window_cumyield).mean(axis=1) #超额收益累计平均值

10

return event_outperform_window_yield

查看全部

接下来我们对几类常见的业绩预告进行时间窗口分析,首先看下有哪些分类,以及其样本数量。

xxxxxxxxxx1

1

events['forecastType'].value_counts()

查看全部

22 1713024 780011 770251 493021 383023 302441 275213 148412 29931 19732 11014 4729 1Name: forecastType, dtype: int64

可以看出这个API将业绩预告分为13类,我们将样本点过少的状态剔除,分析样本点大于2000的业绩预告事件。它们分别是,22——业绩预增;11——预计亏损;24——盈利下降;51——经营预期及其他(该条描述不明也不予分析);21——预计盈利;23——盈利减缓;41——基本持平

xxxxxxxxxx13

1

def event_window_plot(open2open_yield,benchmarkYield,events,forecastType,window,legend):

2

## 画出各事件时间窗口收益

3

event_equity_window_yield = get_window_yield(open2open_yield,events[events['forecastType']==forecastType],window)

4

event_benchmark_window_yield = get_window_yield(benchmarkYield,events[events['forecastType']==forecastType],window)

5

event_outperform_window_yield = get_mean_outperform_yield(event_equity_window_yield,event_benchmark_window_yield)

6

fig = plt.figure(figsize=(10,6))

7

ax = fig.add_subplot(111)

8

ax.plot(event_outperform_window_yield.index,event_outperform_window_yield.values,'dodgerblue',linewidth=2.5)

9

plt.xlim(-25,25)

10

plt.ylim(event_outperform_window_yield.min(),event_outperform_window_yield.max())

11

ax.plot([0,0],[event_outperform_window_yield.min(),event_outperform_window_yield.max()],'crimson',linewidth=1.5,linestyle ='--')

12

ax.plot([1,1],[event_outperform_window_yield.min(),event_outperform_window_yield.max()],'crimson',linewidth=1.5,linestyle ='--')

13

ax.set_title(legend, fontproperties=font, fontsize=18)

查看全部

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,22,25,u"盈利预增事件时间窗口分析")

查看全部

360doc

从上图我们可以看出,第一,在业绩预增公告发布时有一个明显的开盘至开盘的超额收益(两条红虚线之间),但这个收益在公开信息的情况下是没法抓到的;第二,业绩预增公告发布之前,股票存在明显的超额收益,表明确实存在公告发布之前就有很大的涨幅;第三,业绩预增公告发布之后,第五天超额收益达到最大,随后超额收益开始下降。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,11,25,u"预计亏损事件时间窗口分析")

查看全部

360doc

从上图可以看出,预计亏损事件在公告发布之前就有明显的负的超额收益,并在预计亏损事件公布之后快速下跌,不过最后最后下跌基本止住。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,24,25,u"盈利下降事件时间窗口分析")

查看全部

360doc

从上图可以看出,盈利下降事件在公告发布之前就有明显的负的超额收益,并在盈利下降事件公布之后快速下跌,并在25个时间窗口内持续下跌。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,21,25,u"预计盈利事件时间窗口分析")

查看全部

360doc

从上图中可以看出,预计盈利事件表现出在公告发布之前有一定的超额收益,在公告发布时有一个显著的超额收益;但在随后表现平稳,并没有超额收益,也没有出现下跌。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,23,25,u"盈利减缓事件时间窗口分析")

查看全部

360doc

从上图可知,盈利减缓事件,呈现出公告发布前几乎没有超额收益,公告发布当日有明显的超额收益,不过在随后25个交易日内出现大幅下跌。

xxxxxxxxxx1

1

event_window_plot(open2open_yield,benchmarkYield,events,41,25,u"基本持平事件时间窗口分析")

查看全部

360doc

从上图可知,基本持平事件,呈现出公告发布前几乎没有超额收益,公告发布的两日呈现出负的超额收益,随后超额收益继续小幅下跌。

五、事件驱动分析转化为投资策略

上面的事件分析给了我们另一个角度来看股票的超额的收益,然而事件的发生实际上是离散的,上述分析无法给出我们一个投资策略。本文提供一个简易的思路来将事件驱动分析转化为投资策略。

我们知道我们上面的分析是将每一个处于事件的股票累计收益的平均,故一个直接的想法便是将所有处于事件的股票等权买入,但因为事件是离散的,我们在一个持有期会面临着新的处于事件的股票需要买入,而导致实际上我们对每一个股票并不都是同一权重的。此处给出的解决办法是,首先计算出处于事件窗口期的最大股票个数,然后以这个最大股票个数的倒数做为每一只处于事件的股票的权重,这样就能保证所有处于事件的股票以同一权重跑完整个窗口期。

xxxxxxxxxx16

1

def get_max_event_stock(events):

2

##获得窗口期有交叉的最大股票个数

3

events['minute'] = events['actPubtime'].map(lambda x:x[11:20])

4

events['actpubdate'] = events['actPubtime'].map(lambda x: datetime.datetime.strptime(x[0:10],'%Y-%m-%d'))

5

events['ii'] = range(len(events))

6

events['buytime'] = events['ii'].map(lambda x: cal.advanceDate(events.iloc[x]['actpubdate'],'1B') if events.iloc[x]['minute'] != '00:00:00' else Date.fromDateTime(events.iloc[x]['actpubdate']))

7

calendar_date = DataAPI.TradeCalGet(exchangeCD=u"XSHG",beginDate=u"20071227",endDate=u"",field=u"",pandas="1")['calendarDate']

8

calendar_date = pd.DataFrame(calendar_date)

9

calendar_date['calendarDate'] = calendar_date['calendarDate'].map(lambda x:Date.parseISO(x))

10

?

11

def get_event_stock(trade_date):

12

event_stock = []

13

begin_date = cal.advanceDate(trade_date,'-5B')

14

return events[(events['buytime'] = begin_date)]['secID'].tolist()

15

calendar_date['stk_list'] = calendar_date['calendarDate'].map(lambda x: get_event_stock(x))

16

return max([len(item) for item in calendar_date['stk_list'].values])

查看全部

xxxxxxxxxx1

1

test = events[events['forecastType']==22]

查看全部

xxxxxxxxxx1

1

get_max_event_stock(test)

查看全部

355

如上我们得到了盈利预增事件的5天窗口期有交叉的最大股票个数,355;故我们编写如下策略。

xxxxxxxxxx45

1

import numpy as np

2

import pandas as pd

3

from CAL.PyCAL import *

4

start = '2007-12-27' # 回测起始时间

5

end = '2016-11-03' # 回测结束时间

6

benchmark = 'HS300' # 策略参考标准

7

universe = set_universe('A') #证券池,支持股票和基金

8

capital_base = 100000000 # 起始资金

9

freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测

10

refresh_rate = 1 # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd'时间间隔的单位为交易日,若freq = 'm'时间间隔为分钟

11

cal = Calendar('China.SSE')

12

period = '-1B'

13

?

14

def initialize(account): # 初始化虚拟账户状态

15

account.day_count ={}

16

for stk in universe:

17

account.day_count[stk] = 0

18

?

19

def handle_data(account): # 每个交易日的买入卖出指令

20

?

21

yestoday = cal.advanceDate(account.current_date, period, BizDayConvention.Unadjusted)

22

begin_date = cal.advanceDate(account.current_date, '-1Y', BizDayConvention.Unadjusted)

23

profit_up = DataAPI.FdmtEfGet(secID=account.universe,publishDateBegin=begin_date.strftime('%Y%m%d'),publishDateEnd=account.current_date.strftime('%Y%m%d'),

24

forecastType="22",field=['secID','actPubtime',],pandas="1")

25

26

fil_profit = profit_up[profit_up['actPubtime'].map(lambda x: x[0:10]) == yestoday.strftime('%Y-%m-%d')]

27

28

buy_list = fil_profit['secID'].tolist()

29

30

total_money = account.reference_portfolio_value

31

prices = account.reference_price

32

33

for stk in account.avail_security_position:

34

if account.day_count[stk] == 5:

35

order_to(stk,0)

36

account.day_count[stk] = 0

37

?

38

for stk in buy_list:

39

if stk not in account.security_position:

40

if np.isnan(prices[stk]) or prices[stk] == 0:

41

continue

42

order_pct_to(stk,1.0/355)

43

44

for stk in account.security_position:

45

account.day_count[stk] = account.day_count[stk] + 1

查看全部

年化收益率2.5%

基准年化收益率-5.1%

阿尔法-1.0%

贝塔0.09

夏普比率-0.34

收益波动率5.6%

信息比率0.13

最大回撤9.2%

换手率43.08

Created with Highstock 1.3.10累计收益率策略基准[***********][***********]0.00%-75.00%-50.00%-25.00%25.00%50.00%2010-08-09策略: -3.95%基准: -44.57%

查看回测详情,以及回测曲线可以看出,盈利预增事件自带择时属性;牛市时,该事件相对较多,而在熊市时,该事件相对较少。

六、总结

本文以业绩预告为例,给出了一个事件驱动的基本分析过程,并给出最后的demo策略。通过上面的分析过程,我们可从另一个角度来看股票的超额收益,将离散的事件规整到同一分析维度,来看这类事件是否是存在超额收益的。但最后我们做策略时,我们还是要面对离散的事件,如果各事件窗口不存在交叉的话,我们可以很简单使用凯利公式来得到我们对每个事件的最优仓位。然而大部分时候,事件的窗口都存在很多的交叉,如上策略我们做了一个最大股票个数平均仓位的处理,但是这样资金利用率非常低,而且存在事件个数在各年份不一定稳定的问题,所以在真实情况下,我们对未来该事件发生的次数做一个预测或许是一个更好的方式。


相关内容

  • 什么是财务业务一体化
  • 什么是财务业务一体化 一.财务业务一体化内涵 财务业务一体化的基本思想是,在包括网络.数据库.管理软件平台等要素的IT环境下,将企业经营中的三大主要流程,即业务流程.财务会计流程.管理流程有机融合,将计算机的"事件驱动"概念引入流程设计,使财务数据和业务融为一体.在这一指导思想下 ...

  • 软件工程实验指导书--要求及参考范本
  • 软件工程课程 综合实验 验 指 导 计算机学院 2011年9月 实 书 <软件工程>综合实验指导书 目录(Contents) 第一章 概述(Overview) ................................................................ ...

  • 软件项目开发管理系统
  • 软件项目开发管理系统 摘 要 软件项目开发管理系统在相关企业进行生产的管理中有着广泛的应用,它有利于提高企业对软件项目开发过程中的信息管理.系统研究的目的.意义.现状和发展趋势都有所说明. 这里设计的系统所要实现的功能包括测试管理功能.验收管理功能.文档管理功能和用户管理功能.为了实现系统的各项信息 ...

  • 黑盒测试习题02
  • 习题02 0501 用等价类划分法设计8位长数字类型用户名登录操作的测试用例,应该分成( )个等价区间. 0502 0503 0504 某工厂招工,规定报名者年龄应在20周岁--39周岁之间,即出生年月不得早于1960年7月,不晚于1979年6月.报名程序具有自动检验输入数据的功能,如果出生年月不属 ...

  • 2011年系统架构设计师论文考试真题范文(四)
  • 2011年系统架构设计师论文考试真题范文(四) 系统架构设计师考试属于软考中的一项高级资格考试,考试分综合知识.案例分析和论文3个科目.对于很多考生来说论文是一个考试难关,怎么提高自己的论文写作水平,多看历年软考论文真题范文是一个很好的练习论文写作水平的方式,希赛小编为大家整理了2011年系统架构设 ...

  • 企业内控"失控"的五大原因
  • 企业内控"失控"的五大原因 得控则强,失控则弱,无控则乱.成功的企业离不开完善的内部控制.它是以专业管理制度为基础,以防范风险.有效监管为目的,通过全方位建立过程控制体系.描述关键控制点和以流程形式直观表达生产经营业务过程而形成的管理规范. 中国企业内部控制的规范化和体系化建设虽 ...

  • 信息科技风险"三道防线"良好实践研究
  • 信息科技风险"三道防线"良好实践研究 浙江稠州商业银行 陈小其 目录 信息科技风险"三道防线"良好实践研究 ............................................................................ ...

  • 2014年组织绩效管理考试重点
  • 一.提取绩效指标的方法P53[选择:根据概念区分方法] 1.工作分析法 ①工作分析是确定完成各项工作所需履行的责任和具备的知识及技能的系统工程: ②工作描述和任职资格是工作分析的两个直接结果: ③工作分析(任职者能力&工作职责)>>绩效指标>>相对重要性. 2.个案研 ...

  • 交互设计发展史
  • 交互设计发展概况 交互设计,又称互动设计,(英文Interaction Design, 缩写 IxD 或者 IaD),是定义.设计人造系统的行为的设计领域.人造物,即人工制成物品,例如,软件.移动设备.人造环境.服务.可佩带装置以及系统的组织结构.交互设计在于定义人造物的行为方式(the 从用户角度 ...