业绩预告是投资者判断上市公司经营情况的重要参考,市场通常对业绩预告的信息会迅速做出反应。本文通过研究股价在业绩预告日前后的表现,验证二级市场对于业绩预告的反应情况和可能存在的相关内幕交易。在各种预告类型中,本文取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
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年以后失效。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程