D1
引言
从系统论的角度来看,股票市场是一个复杂系统,市场的涨跌是由资金流(市场资金存量、流入量、流出量)和回路(市场信息与交易者行为形成的各种反馈、调节、增强回路)非线性作用下的结果。换句话说,牛市是资金流入和正向反馈(赚钱效应吸引更多资金流入)占主下的系统演化过程,熊市则相反。而影响市场资金流向和反馈回路形成的驱动因素主要有政策、经济、交易情绪、流动性、技术面和外围环境等。市场上对这些驱动因素的研究和把握最强的是“聪明资金”(Smart money),在A股上则是那些游资主力,而不是基金。龙虎榜是这些游资的战场,而涨停板则是游资主力释放的最重要的操盘信号。
涨停板制度是我国借鉴国外早期证券市场,为防止交易价格暴涨暴跌,抑制过度投机的制度,却也成了游资主力吸引跟风盘的重要手段。利用概念题材炒作,快速封涨停板,通过类似饥饿营销地方式吸引各路跟风资金,再拉高出货赚取价差。当然涨停板不代表一出现就会上涨,也可能是昙花一现,也可能是主力挖的坑,但是游资主力发动进攻一般以涨停板出现。因此涨停板是实盘操作中值得深入分析和挖掘的重要信号。本文使用Python对A股市场2016-2021年涨停板个股数据进行探索性分析,为读者挖掘涨停股、深入认识市场提供一个量化视角。
D2
数据获取
本文数据来源于tushare,数据期间为2016年2月15日-2021年4月23日,包含74300个样本。tushare pro中的limit_list函数可直接获取A股中每日涨跌停个股信息。但由于该数据需积分达到2000才能获取,所以本文也提供了csv格式数据供大家学习,有需要的在公众号后台回复 “涨停板数据” 即可获取下载链接。
import pandas as pd
import numpy as np
#画图
import matplotlib.pyplot as plt
#正确显示中文和负号
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
#处理时间
from dateutil.parser import parse
from datetime import datetime,timedelta
#使用tushare获取数据
import tushare as ts
token='输入你在tushare上注册的token'
pro=ts.pro_api(token)
#获取最新交易日期
#获取交易日历
cals=pro.trade_cal(exchange='SSE')
cals=cals[cals.is_open==1].cal_date.values
def get_now_date():
#获取当天日期时间
d=datetime.now().strftime('%Y%m%d')
while d not in cals:
d1=parse(d)
d=(d1-timedelta(1)).strftime('%Y%m%d')
return d
d1=get_now_date()
n1=np.argwhere(cals==d1)[0][0]+1
#获取最近6年的交易日行情
#实际上tushare只能获取2016后的涨跌停数据
dates=cals[-250*6:n1]
df=pro.limit_list(trade_date=dates[0], limit_type='U')
for date in dates[1:]:
df_tem=pro.limit_list(trade_date=date, limit_type='U')
df=pd.concat([df,df_tem])
#查看前几行数据
#实际上tushare只能获取2016后的涨跌停数据
#数据下载3-4分钟左右
df.head()
其中,fl_ratio 为封单手数/流通股本;amp是振幅;fc_ratio是封单金额/日成交金额;fl_ratio为封单手数/流通股本;fd_amount为封单金额;first_time代表首次涨停时间;last_time代表最后封板时间;open_times是打开次数;strth是涨跌停强度。
#保存数据到本地
#df.to_csv('up_limit_data.csv')
#读取数据#df=pd.read_csv('up_limit_data.csv',index_col=0)
D3
市场涨停整体情况
描述性统计
df.iloc[:,1:].describe().round(2)
从描述性统计来看,涨停股价格大都在25元及以下(75%分位数),其他几个变量波动标准差均较大,反映个股涨停的特征差别较大。下面使用可视化的方式展现不同价格期间个股涨停情况。
涨停股价格区间
先构建一个价格区间标记函数,将个股收盘价划分为10元以下、10-30元、30-50元、50-100元以及100元以上,价格区间的划分主要是根据经验和A股市场情况而定。
def dy_zh(data, cut_points, labels=None):
min_num = data.min()
max_num = data.max()
break_points = [min_num] + cut_points + [max_num]
if not labels:
labels = range(len(cut_points)+1)
else:
labels=[labels[i] for i in range(len(cut_points)+1)]
dataBin = pd.cut(data,bins=break_points,
labels=labels,include_lowest=True)
return dataBin
cut_points = [10,30,50,100]
labels=['10元以下', '10-30元','30-50元','50-100元','100元以上']
#调用函数dy_zh,增加新列
df['价格区间'] = dy_zh(df['close'], cut_points, labels)
#查看标签列,取值范围前面加上了序号,是便于后面生成表格时按顺序排列
#df.head()
使用柱状图展示不同价格区间下涨停个股数量分布。
group_price=df.groupby('价格区间')['trade_date'].count()
plt.figure(figsize=(12,5))
colors=['#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd','#8c564b']
fig=plt.bar(group_price.index,group_price.values,color=colors[:5]);
#自动添加标签
def autolabel(fig):
for f in fig:
h=f.get_height()
plt.text(f.get_x()+f.get_width()/2,1.02*h,
f'{int(h)}',ha='center',va='bottom')
autolabel(fig)
涨停板排名
下面对2016-2021年期间个股出现涨停次数进行排序,前二十名中有十二个是ST(含*)股,ST股一直是市场短线资金炒作的对象,容易暴涨暴跌,特别是有摘帽预期的个股,在资金的推动下短期可能出现连续几十个涨停,当然炒作过后往往也一地鸡毛,如*ST天马。
def plot_bar(group_data):
plt.figure(figsize=(16,5))
fig=plt.bar(group_data.index,group_data.values);
autolabel(fig)
plt.title('2016-2021涨停板排名前20',size=15);
group_name=df.groupby('name')['ts_code'].count().sort_values(ascending=False)[:20]
plot_bar(group_name)
剔除*ST/ST/N股后排名
下面是剔除*ST/ST/N股后的情况,其中诚迈科技在2019年和2020年2月短短一年期间以大量涨停的方式实现了二三十倍的涨幅。
#分别剔除ST、*ST和新股(N开头)
df_st=df[-(df.name.str.startswith('ST') | df.name.str.startswith('*ST')|df.name.str.startswith('N'))]
group_name_st=df_st.groupby('name')['ts_code'].count().sort_values(ascending=False)[:20]
plot_bar(group_name_st)
每日涨停统计
每日涨停个数在一定程度上反映了市场的交投热情,当涨停个股超过100个时,预示着市场赚钱效应较高。
#使用0.5.11版本的pyecharts
from pyecharts import Bar
count_=df.groupby('trade_date')['trade_date'].count()
attr=count_.index
v1=count_.values
bar=Bar('每日涨停板个数','2016-2021',title_text_size=15)
bar.add('',attr,v1,is_splitline_show=False,is_datazoom_show=True,linewidth=2)
bar
D4
行业涨停分布
细分行业
tushare pro的stock_basic可以获取个股所在的细分行业,将该数据与涨停数据合并,然后按照行业进行聚合,可以得到各细分行业的涨停个股分布情况。
#获取股票列表
stocks=pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,area,industry,list_date')
#排除新股
stocks=stocks[stocks.list_date<(parse(get_now_date())-timedelta(60)).strftime('%Y%m%d')]
dff=pd.merge(df[['trade_date','ts_code','name','close','pct_chg','fc_ratio','fl_ratio']],stocks[['ts_code','name','industry','list_date']])
#dff.head()
(dff.groupby('industry')['name'].count().sort_values(ascending=False)[:10]
.plot.bar(figsize=(14,5),rot=0));
plt.title('2016-2021涨停板行业排名前十',size=15);
dff['year']=(pd.to_datetime(dff['trade_date'].astype(str))).dt.strftime('%Y')
#对每年行业涨停板个数排名进行可视化
#生成2*3六个子图
plot_pos=list(range(321,327))
#每个子图颜色
colors=['#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd','#8c564b']
fig=plt.figure(figsize=(18,14))
fig.suptitle('2016-2021行业涨停排名前十',size=15)
years=sorted(dff['year'].unique())
for i in np.arange(len(plot_pos)):
ax=fig.add_subplot(plot_pos[i])
(dff[dff.year==years[i]].groupby('industry')['name']
.count()
.sort_values(ascending=False)[:10]
.plot.bar(rot=0,color=colors[i]));
ax.set_title(years[i])
ax.set_xlabel('')
plt.show()
大类行业
上述行业分类过细,对部分相关细分行业进行合并,最后得到28个大类行业。
new_name=['汽车','电力','有色金融','钢铁','农林牧渔','医药生物','房地产','交通运输','煤炭','金融','食品饮料',
'石油','公用事业','计算机','电子','通信','休闲服务','纺织服装','商业贸易','建筑装饰','机械设备','轻工制造','化工']
old_name=[('汽车配件', '汽车整车','汽车服务','摩托车',),('火力发电','新型电力', '水力发电'),('黄金', '铝','小金属','铅锌','铜',),
('普钢','特种钢','钢加工',),( '渔业', '种植业','林业','农业综合','饲料', '农药化肥','橡胶', ),('医疗保健','生物制药','医药商业','中成药','化学制药',),
('房产服务', '区域地产','全国地产','园区开发',),('公路','铁路','水运', '航空','空运','公共交通','路桥','港口','船舶', '仓储物流', ),
('煤炭开采','焦炭加工',),('证券','保险','多元金融','银行'),('啤酒','食品', '乳制品', '红黄酒','白酒','软饮料',),
('石油开采','石油加工','石油贸易'),('供气供热','水务','环境保护', ),
('互联网', '软件服务', 'IT设备', ),('半导体', '元器件',),('通信设备','电信运营',),( '文教休闲','旅游服务','旅游景点','酒店餐饮','影视音像','出版业',),
('染料涂料','服饰','纺织','纺织机械','家居用品'), ('商品城','百货', '批发业', '超市连锁','电器连锁', '其他商业','商贸代理','广告包装'),
('建筑工程','装修装饰','其他建材','水泥'),('专用机械','轻工机械','化工机械','机械基件','运输设备','机床制造','农用机械','工程机械', '电器仪表'),
('造纸','陶瓷','玻璃', '塑料','矿物制品',),('化工原料','化纤','日用化工')]
合并成大类板块后,数据显示,4月23日医药生物板块涨停个股最多,此外,机械设备、电子、纺织服装、汽车和休闲服务(含旅游)等板块最近一周涨停股较多,与近期热点密切相关,如印度疫情复发、新能源、五一旅游等。
#将某些细分行业合并成大类
for i in range(len(old_name)):
for j in old_name[i]:
dff.replace(j,new_name[i],inplace=True)
industry_up=pd.DataFrame()
#获取最近10日各行业涨停板数据
for d in dates[-10:]:
industry_up[d]=dff[dff.trade_date==d].groupby('industry')['name'].count()
industry_up.fillna(0).sort_values(dates[-1],ascending=False).astype(int)
使用滚动5日累计板块涨停个数,可以一定程度反映近期板块题材的资金的关注情况,排在前面的是汽车、医药生物、机械设备和电子。
#近期滚动5天行业涨停个股数
(industry_up.fillna(0).T.rolling(5).sum()).T.dropna(axis=1).sort_values(dates[-1],ascending=False)
D5
个股连板情况
下面构建函数统计和分析个股连续涨停的概率以及获取某日连板股票池。由于代码较长,此处省略,完整版见Python金融量化知识星球。
def up_con_pro(df,ddd):
pass
连板概率
ddd=sorted(df.trade_date.unique()[-60:],reverse=True)
up_con_pro(df,ddd).round(4).T.head(10)
数据显示,个股第一次涨停后,第二天连续涨停的概率接近30%,连续7-10板的概率接近0。剔除st股后连板的概率更低。
#剔除st股后
#up_con_pro(df_st,ddd).round(4).T.head(10)
up_con_pro(df,ddd).T.describe().round(4)
#剔除st股
#up_con_pro(df_st,ddd).T.describe().round(4)
获取某日连板个股
下面使用get_con_up_stocks获取指定日期连板的个股名单,如2021年4月23日,锦泓集团、ST岩石、*ST节能实现四连板。
def get_con_up_stocks(date):
#代码较长,此处省略,完整版见Python金融量化知识星球
#获取当前交易日最新连板个股
#参数可输入指定日期
result=get_con_up_stocks(dates[-1])
print(result[result>=2])
603518.SH 4;600696.SH 4;000820.SZ 4;002175.SZ 3;002575.SZ 3;600462.SH 3;002997.SZ 2;002592.SZ 2;600518.SH 2;002885.SZ 2;603538.SH 2;002630.SZ 2;002071.SZ 2;002587.SZ 2;002021.SZ 2;600581.SH 2;601127.SH 2
#stock_plot为个人画图脚本文件
stock_plot(result.index[0]).kline_plot(ktype=0)
D6
结语
美国著名投机家杰西·利弗莫尔(《股票作手回忆录》)说,如果你不能在领头羊上赚钱,就不可能在股市上赚钱。在A股市场,领头羊个股往往是以涨停的形式开启一波大行情,涨停板是反映主力进攻(或撤退)的重要操盘信号。市场上一度流行着“有三必有五,有五必成妖”的涨停股说法。当然从数据统计的概率来看,一直连五板及更多的概率是较低的,大部分妖股是连续涨停后经过猛烈调整和洗盘再继续拉升的。本文从量化的角度对A股涨停个股数据进行了探索性分析,为读者通过量化的手段挖掘涨停个股提供参考框架。对于驱动个股涨停的具体反馈回路还有待深入探讨,如存在哪些闭合因果关系链驱动资金流向某个板块题材和个股,或者说影响个股涨停和持续性的关键因素是什么?能否通过量化的手段构建涨停板交易策略,历史回测效果如何?这些问题留给读者进一步思考。
关于Python金融量化
专注于分享Python在金融量化领域的应用。加入知识星球,可以免费获取30多g的量化投资视频资料、量化金融相关PDF资料、公众号文章Python完整源码、量化投资前沿分析框架,与博主直接交流、结识圈内朋友等。