使用python数据分析 python金融量化代码
D1简介
从系统论的角度来看,股票市场是一个复杂的系统,市场的兴衰是资本流动(股票、市场资金的流入和流出)和回路(市场信息和交易者行为形成的各种反馈、调节和增强回路)非线性作用的结果。换句话说,牛市是一个由资本流入和正反馈(获利效应吸引更多资本流入)主导的系统演化过程,而熊市则相反。影响市场资本流动和反馈回路形成的驱动因素主要包括政策、经济、交易情绪、流动性、技术层面和外部环境等。在市场上,“聪明钱”对这些驱动因素的研究和把握最强,而在a股,它是游资的主力军,而不是基金。龙虎榜是这些热钱的战场,日线涨停是热钱主力释放的最重要的交易信号。
每日限额制度是我国借鉴国外早期证券市场的一项制度,以防止交易价格的大幅上涨和下跌,抑制过度投机,但也成为游资主力吸引后续市场的重要手段。利用概念主题炒作,快速封住每日涨停,通过类似饥饿营销吸引各行各业资金,在拉船中赚取差价。当然,日线涨停并不意味着一出现就涨。也可能是昙花一现,也可能是主力挖的坑。然而,热钱主力通常以日线为限发起攻击。因此,跌停是实盘操作中值得深入分析和挖掘的重要信号。本文利用Python对2016-2021年a股市场的日涨停股票数据进行分析,为读者挖掘日涨停股票、深入了解市场提供了量化视角。
D2数据采集。
本文数据来源于tushare,数据期为2016年2月15日至2021年4月23日,共74300个样本。tushare pro中的limit_list函数可以直接获取a股个股的每日涨跌情况。但是这个数据只有分数达到2000才能得到,所以本文也提供了csv格式的数据供大家学习。如有需要,可在微信官方账号后台回复“涨停板数据”获取下载链接。
importpandassapdimportnummpyasnp #绘图importmapplotlib.plotasplt #正确显示中文和负数PLT . rcparams[' font . sans-serif ']=[' sim hei '。
]plt.rcParams['axes.unicode_minus']=False#处理时间from dateutil.parser import parsefrom 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.valuesdef 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 dd1=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版本的pyechartsfrom pyecharts import Barcount_=df.groupby('trade_date')['trade_date'].count()attr=count_.indexv1=count_.valuesbar=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节能实现四连板。
#stock_plot为个人画图脚本文件stock_plot(result.index[0]).kline_plot(ktype=0)
D6 结语
美国著名投机家杰西·利弗莫尔(《股票作手回忆录》)说,如果你不能在领头羊上赚钱,就不可能在股市上赚钱。在A股市场,领头羊个股往往是以涨停的形式开启一波大行情,涨停板是反映主力进攻(或撤退)的重要操盘信号。市场上一度流行着“有三必有五,有五必成妖”的涨停股说法。当然从数据统计的概率来看,一直连五板及更多的概率是较低的,大部分妖股是连续涨停后经过猛烈调整和洗盘再继续拉升的。本文从量化的角度对A股涨停个股数据进行了探索性分析,为读者通过量化的手段挖掘涨停个股提供参考框架。对于驱动个股涨停的具体反馈回路还有待深入探讨,如存在哪些闭合因果关系链驱动资金流向某个板块题材和个股,或者说影响个股涨停和持续性的关键因素是什么?能否通过量化的手段构建涨停板交易策略,历史回测效果如何?这些问题留给读者进一步思考。