请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  数理科学 帖子:3353187 新帖:0

业绩预告的市场响应及相关内幕交易的验证

外汇老法师发表于:7 月 18 日 18:00回复(1)

业绩预告是投资者判断上市公司经营情况的重要参考,市场通常对业绩预告的信息会迅速做出反应。本文通过研究股价在业绩预告日前后的表现,验证二级市场对于业绩预告的反应情况和可能存在的相关内幕交易。在各种预告类型中,本文取2014-2018各年预告类型为“业绩大幅提升”的数据分别进行汇总。

from jqdata import *
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import datetime as dt
import matplotlib.pyplot as plt
import seaborn as sns

首先,为避免次新股行情对结论的影响,我们需要过滤掉数据中的次新股。于是,定义如下次新股过滤函数:

#过滤次新股函数
def delnew(df):
    for i in df.index:
        #获取证券代码
        security = df.loc[i,'code']
        #获取预告日期(字符串类型)
        pub_date = df.loc[i,'pub_date']
        #将预告日期先转化为日期时间类型,再转为日期类型
        pub_date = dt.datetime.strptime(pub_date,'%Y-%m-%d').date()
        #获取证券的上市日期(日期类型)
        try:
            list_date = get_security_info(security).start_date
        except:
            continue
        #上市不超过1年,则置证券代码为空值
        if pub_date-list_date<dt.timedelta(365):
            df.loc[i,'code']=np.nan
    df = df.dropna()
    return df

由于需要计算股票相对于基准指数的收益,而不同板块的股票比较基准不同,我们定义如下基准指数获取函数:

#定义获取基准指数函数
def index(stock):
    #深圳主板证券代码前3位是000,基准指数为深证成指399001
    if stock[0:3]=='000':
        reference = '399001.XSHE'
    #中小板证券代码前3位是002,基准指数为中小板指399005
    elif stock[0:3]=='002':
        reference = '399005.XSHE'
    #创业板证券代码前3位是300,基准指数为创业板指399006
    elif stock[0:3]=='300':
        reference = '399006.XSHE'
    #上海主板证券代码首位是6,基准指数为上证综指000001
    elif stock[0]=='6':
        reference = '000001.XSHG'
    #其它情形的比较基准设为沪深300指数000300
    else:
        reference = '000300.XSHG'
    return reference

#函数运行示例:浦发银行(证券代码600000)对应的指数为上证综指(证券代码000001)
index('600000.XSHG')
'000001.XSHG'

我们比较的是预告发布前后8个交易日的股价表现。在使用数据获取函数时,我们需要知道从预告日起第8个交易日期,故定义如下函数:

#定义函数,获取某日开始第n个交易日期
def later(date,n=8):
    start = dt.datetime.strptime(date,'%Y-%m-%d')
    end = start+dt.timedelta(n+14) 
    days = get_trade_days(start,end)
    return days[n-1]

#函数运行示例:'2015年6月9日开始的第8个交易日为2015年6月18日
later('2015-06-09')
datetime.date(2015, 6, 18)

在计算事件响应时间时,需要计算预告前后的最高价距离预告日的时间间隔,故定义如下函数:

#计算前后两个日期的交易日间隔(返回天数)
def delta(start_date,end_date):
    days = get_trade_days(start_date,end_date)
    return len(days)

#函数运行示例:2019年7月1日到2019年7月11日共9个交易日
delta('2019-07-01','2019-07-11')
9

1、公告发布前后,上涨的股票占比;2、公告发布前后的涨幅(绝对和相对);3、公告发布前后最高收盘价出现的日期距离公告日的时长(响应时间)。

#研究的年份
year_list = [2014,2015,2016,2017,2018]
#定义公告前后的周期
n=8
#初始化数据指标
#p1:公告前上涨比例(%)
#p2:公告后上涨比例(%)
#a1:公告前平均绝对涨幅(%)
#a2:公告后平均绝对涨幅(%)
#r1:公告前平均相对涨幅(%)
#r2:公告后平均相对涨幅(%)
#l1:公告前最高收盘价当天距离公告日的平均时长(天)
#l2:公告后最高收盘价当天距离公告日的平均时长(天)
#insider:内幕交易比例(%)
#range:公告首日平均高开幅度
#retutn:策略平均年化收益
table = pd.DataFrame(np.zeros([5,13]),
                     index=year_list,
                     columns=['p1','p2','a1','a2','r1','r2','l1','l2','h1','h2','insider','range','strategy'])
#遍历所有年份
for year in year_list:
    date1 = date1 = dt.date(year,1,1)
    date2 = dt.date(year+1,1,1)
    #获取全年业绩预告类型为“业绩大幅上升”的数据
    q = query(finance.STK_FIN_FORCAST).filter(finance.STK_FIN_FORCAST.type=='业绩大幅上升',
                                          finance.STK_FIN_FORCAST.pub_date>date1,
                                          finance.STK_FIN_FORCAST.pub_date<date2,)
    forcast = finance.run_query(q)[['code','pub_date','type']]
    #过滤次新股
    forcast = delnew(forcast)
    #在数据中加入股票对应的基准指数
    forcast['reference'] = forcast['code'].map(index)
    #展示每年数据的最后5行
    print('Year:'+str(year))
    print(forcast.tail())
    #遍历当年数据的所有行
    for i in forcast.index:
        stock = forcast.loc[i,'code']
        reference = forcast.loc[i,'reference']
        date = forcast.loc[i,'pub_date']
        end = later(date)
        #首个交易日单独用作计算公告前最高涨幅的基准,故取2n+1个数据(公告前n+1个数据,公告后n个数据)
        try:
            prices = get_price([stock,reference],count=2*n+1,end_date=end,fields=['open','close','high'])
        #部分代码可能查不到数据,该行其余数据为空
        except:
            continue
        #公告前8日的最高价(首个交易日单独作为计算涨幅的基准,不参与最高价计算)
        highest1 = prices['high'][stock][1:n+1].max()
        #公告后8日的最高价
        highest2 = prices['high'][stock][n+1:].max()
        #公告前最高价出现的日期
        date_high1 = prices['high'][stock][1:n+1].idxmax()
        #公告后最高价出现的日期
        date_high2 = prices['high'][stock][n:].idxmax()
        #记录公告前最高涨幅(比较基准为公告前倒数第9个交易日收盘价)
        forcast.loc[i,'abs1'] = highest1/prices['close'][stock][0]-1
        #记录公告后最高涨幅(比较基准为公告前一日的收盘价)
        forcast.loc[i,'abs2'] = highest2/prices['close'][stock][n]-1
        #计算公告前最高涨幅的同期指数涨幅(比较基准为公告前9日的第一个交易日收盘价)
        ind_ret1 = prices['high'].loc[date_high1,reference]/prices['close'][reference][0]-1
        #计算公告后最高涨幅的同期指数涨幅(比较基准为公告前一日的收盘价)
        ind_ret2 = prices['high'].loc[date_high2,reference]/prices['close'][reference][n]-1
        #记录公告前最高价的相对涨幅(股价涨幅-指数涨幅)
        forcast.loc[i,'rel1'] = forcast.loc[i,'abs1']-ind_ret1
        #记录公告后最高价的相对涨幅(股价涨幅-指数涨幅)
        forcast.loc[i,'rel2'] = forcast.loc[i,'abs2']-ind_ret2
        #记录公告前最高收盘价当天距离公告日的时长
        forcast.loc[i,'len1'] = delta(date_high1,date)-1
        #记录公告后最高收盘价当天距离公告日的时长
        forcast.loc[i,'len2'] = delta(date,date_high2)
        #记录公告前最高价相对于公告日前一天收盘价的倍数
        forcast.loc[i,'high1'] = highest1/prices['close'][stock][n]
        #记录公告后最高价相对于公告日前一天收盘价的倍数
        forcast.loc[i,'high2'] = highest2/prices['close'][stock][n]
        
        #为减少运行时间,下面将后续策略提前放到循环中计算收益
        #开仓价(公告日开盘价,公告前n+1个数据,公告日为第n+2数据)
        open_price = prices['open'][stock][n+1]
        #平仓价(公告后第4个交易日开盘价)
        close_price = prices['open'][stock][n+4]
        #记录公告首日高开幅度
        forcast.loc[i,'range'] = open_price/prices['close'][stock][n]-1
        #记录策略收益
        forcast.loc[i,'strategy'] = close_price/open_price-1
    
    #删除空值
    forcast = forcast.dropna()
    
    #记录当年公告前上涨概率(%)
    table.loc[year,'p1'] = round(len(forcast[forcast.abs1>0])/len(forcast)*100,1)
    #记录当年公告后上涨概率(%)
    table.loc[year,'p2'] = round(len(forcast[forcast.abs2>0])/len(forcast)*100,1)
    #记录当年公告前平均绝对涨幅(%)
    table.loc[year,'a1'] = round(forcast['abs1'].mean()*100,1)
    #记录当公告后平均绝对涨幅(%)
    table.loc[year,'a2'] = round(forcast['abs2'].mean()*100,1)
    #记录当年公告前平均相对涨幅(%)
    table.loc[year,'r1'] = round(forcast['rel1'].mean()*100,1)
    #记录当年公告后平均相对涨幅(%)
    table.loc[year,'r2'] = round(forcast['rel2'].mean()*100,1)
    #记录公告前最高收盘价当天距离公告日的平均时长(天)
    table.loc[year,'l1'] = round(forcast['len1'].mean(),1)
    #记录公告后最高收盘价当天距离公告日的平均时长(天)
    table.loc[year,'l2'] = round(forcast['len2'].mean(),1)
    #记录公告前最高收盘价相对于公告日前一天收盘价的平均倍数
    table.loc[year,'h1'] = round(forcast['high1'].mean(),2)
    #记录公告后最高收盘价相对于公告日前一天收盘价的平均倍数
    table.loc[year,'h2'] = round(forcast['high2'].mean(),2)
    #公告日前跑赢指数,公告日后跑输指数,则认定内幕交易存在
    condition = (forcast.rel1>0)&(forcast.rel2<0)
    #记录内幕交易比例(%)
    table.loc[year,'insider'] = round(len(forcast[condition])/len(forcast)*100,1)
    #记录公告首日平均高开幅度(%)
    table.loc[year,'range'] = round(forcast['range'].mean()*100,1)
    #记录策略的平均年化收益
    table.loc[year,'strategy'] = round(forcast['strategy'].mean()*100*240/3,1)
table
Year:2014
             code    pub_date    type    reference
1506  600832.XSHG  2014-12-25  业绩大幅上升  000001.XSHG
1508  002699.XSHE  2014-12-27  业绩大幅上升  399005.XSHE
1509  300201.XSHE  2014-12-30  业绩大幅上升  399006.XSHE
1510  300296.XSHE  2014-12-31  业绩大幅上升  399006.XSHE
1511  300311.XSHE  2014-12-31  业绩大幅上升  399006.XSHE
Year:2015
             code    pub_date    type    reference
1687  300219.XSHE  2015-12-25  业绩大幅上升  399006.XSHE
1688  300237.XSHE  2015-12-30  业绩大幅上升  399006.XSHE
1689  000568.XSHE  2015-12-30  业绩大幅上升  399001.XSHE
1690  300032.XSHE  2015-12-31  业绩大幅上升  399006.XSHE
1691  300296.XSHE  2015-12-31  业绩大幅上升  399006.XSHE
Year:2016
             code    pub_date    type    reference
2162  600400.XSHG  2016-12-27  业绩大幅上升  000001.XSHG
2163  300058.XSHE  2016-12-28  业绩大幅上升  399006.XSHE
2165  600568.XSHG  2016-12-28  业绩大幅上升  000001.XSHG
2166  000059.XSHE  2016-12-28  业绩大幅上升  399001.XSHE
2168  300342.XSHE  2016-12-30  业绩大幅上升  399006.XSHE
Year:2017
             code    pub_date    type    reference
2988  002016.XSHE  2017-10-31  业绩大幅上升  399005.XSHE
2989  002031.XSHE  2017-10-31  业绩大幅上升  399005.XSHE
2990  000786.XSHE  2017-11-01  业绩大幅上升  399001.XSHE
2998  002088.XSHE  2017-11-28  业绩大幅上升  399005.XSHE
2999  300038.XSHE  2017-11-28  业绩大幅上升  399006.XSHE
Year:2018
             code    pub_date    type    reference
2748  300568.XSHE  2018-12-14  业绩大幅上升  399006.XSHE
2749  300567.XSHE  2018-12-20  业绩大幅上升  399006.XSHE
2750  300476.XSHE  2018-12-25  业绩大幅上升  399006.XSHE
2751  300285.XSHE  2018-12-26  业绩大幅上升  399006.XSHE
2752  300365.XSHE  2018-12-28  业绩大幅上升  399006.XSHE
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
p1 p2 a1 a2 r1 r2 l1 l2 h1 h2 insider range strategy
2014 89.6 89.5 6.3 7.7 5.1 6.1 4.5 3.7 1.05 1.08 11.0 0.9 49.5
2015 79.4 81.7 10.3 13.0 5.8 8.5 4.6 3.6 1.12 1.13 12.1 0.6 126.7
2016 90.4 89.0 6.9 7.4 5.1 5.8 4.6 3.5 1.07 1.07 9.6 0.5 37.5
2017 88.0 88.2 4.4 5.7 3.6 5.0 5.1 3.2 1.05 1.06 10.1 0.5 -5.2
2018 90.3 89.5 5.2 7.2 3.5 5.6 4.9 3.3 1.07 1.07 11.9 0.7 -33.8

首先绘制预告前后上涨比例的并列直方图:

#为正常显示汉字和负号,需进行以下两行设置
plt.rcParams['font.family'] = ['sans-serif']
plt.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
#设置图标大小
fig = plt.figure(figsize=(7,4))
#设置柱宽
w = 0.35
#设置自变量和因变量
t = table.index
p1 = table.p1
p2 = table.p2
#绘制公告前上涨概率柱状图
plt.bar(t,p1,color='c',label='公告前上涨概率',width=w,alpha=0.7)
#绘制公告后上涨概率柱状图
plt.bar(t+w,p2,color='m',label='公告后上涨概率',width=w,alpha=0.7)
#绘制网格线
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
#设置坐标刻度
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,110,20),fontsize=14)
#设置坐标标签
plt.xlabel('年份',fontsize=14)
plt.ylabel('概率(%)',fontsize=14)
#在每一柱子上标注数值
for a,b in zip(t,p1):
    plt.text(a,b+1,b,ha='center', va= 'bottom',fontsize=12,color='c')
for a,b in zip(t,p2):
    plt.text(a+w,b+1,b,ha='center', va= 'bottom',fontsize=12,color='m')
#绘制图例    
plt.legend(bbox_to_anchor=(1.02, 1),fontsize=14)
#显示标题
plt.title('股价上涨概率',fontsize=15)
#显示图表
plt.show()

绘制股价在预告前后的最高涨幅:

#在上子图绘制股价在预告前后的最高涨幅
a1 = table.a1
a2 = table.a2
fig = plt.figure(figsize=(7,10))
plt.subplot(2,1,1)
plt.bar(t,a1,color='blue',label='公告前平均最高涨幅',width=w,alpha=0.7)
plt.bar(t+w,a2,color='orangered',label='公告后平均最高涨幅',width=w,alpha=0.7)
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,15,2),fontsize=14)
plt.xlabel('年份',fontsize=14)
plt.ylabel('涨幅(%)',fontsize=14)
for a,b in zip(t,a1):
    plt.text(a,b+0.1,b,ha='center', va= 'bottom',fontsize=12,color='blue')
for a,b in zip(t,a2):
    plt.text(a+w,b+0.1,b,ha='center', va= 'bottom',fontsize=12,color='orangered')
plt.legend(fontsize=13)
plt.title('股价8日内平均最高涨幅',fontsize=15)
#显示图像
plt.show()

#在下子图绘制股价在预告前后的最高涨幅
r1 = table.r1
r2 = table.r2
fig = plt.figure(figsize=(7,10))
plt.subplot(2,1,2)
plt.bar(t,r1,color='deepskyblue',label='公告前平均最高相对涨幅',width=w,alpha=0.7)
plt.bar(t+w,r2,color='chocolate',label='公告后平均最高相对涨幅',width=w,alpha=0.7)
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,11,2),fontsize=14)
plt.xlabel('年份',fontsize=14)
plt.ylabel('涨幅(%)',fontsize=14)
for a,b in zip(t,r1):
    plt.text(a,b+0.1,b,ha='center', va= 'bottom',fontsize=12,color='deepskyblue')
for a,b in zip(t,r2):
    plt.text(a+w,b+0.1,b,ha='center', va= 'bottom',fontsize=12,color='chocolate')
plt.legend(fontsize=13)
plt.title('股价8日内平均最高相对涨幅',fontsize=15)
#显示图像
plt.show()

绘制公告首日平均高开幅度:

#设置因变量
r = table.range
#设置图标大小
fig = plt.figure(figsize=(7,4))
#绘制柱状图
plt.bar(t,r,color='red',label='公告首日平均高开幅度',width=0.5,alpha=0.7)
#绘制网格线
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
#设置坐标刻度
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,2.1,0.5),fontsize=14)
#设置坐标标签
plt.xlabel('年份',fontsize=14)
plt.ylabel('涨幅(%)',fontsize=14)
#在柱子上标注数值
for a,b in zip(t,r):
    plt.text(a,b+0.1,b,ha='center', va= 'bottom',fontsize=13,color='red')
#绘制图例    
plt.legend(fontsize=14)
#显示标题
plt.title('公告首日平均高开幅度',fontsize=15)
#显示图表
plt.show()

绘制公告前后的响应时间:

#设置因变量
l1 = table.l1
l2 = table.l2
#设置图标大小
fig = plt.figure(figsize=(7,4))
#绘制公告前最高涨幅距离公告日的时长
plt.bar(t,l1,color='olive',label='公告前平均响应时间',width=w,alpha=0.7)
#绘制公告后最高涨幅距离公告日的时长
plt.bar(t+w,l2,color='deeppink',label='公告后平均响应时间',width=w,alpha=0.7)
#绘制网格线
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
#设置坐标刻度
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,7,1),fontsize=14)
#设置坐标标签
plt.xlabel('年份',fontsize=14)
plt.ylabel('天数',fontsize=14)
#在每一柱子上标注数值
for a,b in zip(t,l1):
    plt.text(a,b+0.1,b,ha='center', va= 'bottom',fontsize=12,color='olive')
for a,b in zip(t,l2):
    plt.text(a+w,b+0.1,b,ha='center', va= 'bottom',fontsize=12,color='deeppink')
#绘制图例    
plt.legend(bbox_to_anchor=(1.02, 1),fontsize=14)
#显示标题
plt.title('平均响应时间',fontsize=15)
#显示图表
plt.show()

绘制公告前后平均最高价倍数:

#设置因变量
h1 = table.h1
h2 = table.h2
#设置图标大小
fig = plt.figure(figsize=(7,4))
#绘制公告前平均最高价倍数
plt.bar(t,h1,color='teal',label='公告前平均最高价倍数',width=w,alpha=0.7)
#绘制公告后平均最高价倍数
plt.bar(t+w,h2,color='darkorange',label='公告后平均最高价倍数',width=w,alpha=0.7)
#绘制网格线
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
#设置坐标刻度
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,1.5,0.2),fontsize=14)
#设置坐标标签
plt.xlabel('年份',fontsize=14)
plt.ylabel('倍数',fontsize=14)
#在每一柱子上标注数值
for a,b in zip(t,h1):
    plt.text(a,b+0.03,b,ha='center', va= 'bottom',fontsize=12,color='teal')
for a,b in zip(t,h2):
    plt.text(a+w,b+0.03,b,ha='center', va= 'bottom',fontsize=12,color='darkorange')
#绘制图例    
plt.legend(bbox_to_anchor=(1.02, 1),fontsize=14)
#显示标题
plt.title('平均最高价倍数',fontsize=15)
#显示图表
plt.show()

绘制内幕交易比例:

#设置因变量
insider = table.insider
#设置图标大小
fig = plt.figure(figsize=(7,4))
#绘制柱状图
plt.bar(t,insider,color='dimgrey',label='内幕交易比例',width=0.5,alpha=0.7)
#绘制网格线
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
#设置坐标刻度
plt.xticks(fontsize=14)
plt.yticks(np.arange(0,35,5),fontsize=14)
#设置坐标标签
plt.xlabel('年份',fontsize=14)
plt.ylabel('比例(%)',fontsize=14)
#在柱子上标注数值
for a,b in zip(t,insider):
    plt.text(a,b+0.5,b,ha='center', va= 'bottom',fontsize=14,color='dimgrey')
#绘制图例    
plt.legend(fontsize=14)
#显示标题
plt.title('内幕交易比例',fontsize=15)
#显示图表
plt.show()

根据事件的响应时间可得,股价大约在公告后第3到4个交易日达到高峰。故提出相应策略:公告日开盘买入,公告发布后第4个交易日开盘卖出。绘制策略平均年化收益:

#设置因变量
s = table.strategy
#设置图标大小
fig = plt.figure(figsize=(7,4))
#绘制柱状图
plt.bar(t,s,color='darkorange',label='平均年化收益',width=0.5,alpha=0.7)
#绘制网格线
plt.grid(linestyle='-',linewidth=1,axis='y',alpha=0.5)
#设置坐标刻度
plt.xticks(fontsize=14)
plt.yticks(np.arange(-60,155,30),fontsize=14)
#设置坐标标签
plt.xlabel('年份',fontsize=14)
plt.ylabel('收益率(%)',fontsize=14)
#在柱子上标注数值
for a,b in zip(t,s):
    if b>=0:
        plt.text(a,b+5,b,ha='center', va= 'bottom',fontsize=14,color='darkorange')
    else:
        plt.text(a,b-15,b,ha='center', va= 'bottom',fontsize=14,color='darkorange')
#绘制图例    
plt.legend(fontsize=14)
#显示标题
plt.title('策略平均年化收益',fontsize=15)
#显示图表
plt.show()

从上图看出,业绩预告信号在2017年以后失效。

全部回复

0/140

量化课程

    移动端课程