背景介绍 刚开始炒股的时候,心中一直有个疑问:什么是大盘?为什么大家都在看大盘而不是看自己的股票呢?大盘的涨跌和个股的走势有什么关联吗?
然后看新闻资讯的时候,也常常听到什么拉动所在板块上升之类的话。某个行业的利多利空消息是否对所有股票都适用?哪些个股比较随大流,它涨我也涨?又是哪些股总是带头冲锋,苟利国家生死以,岂因祸福避趋之?
这篇文章,尝试从数据出发,挖掘股票价格之间的关系,寻找那些生死与共的好股友们。
概念扫盲 在继续深入之前,先对一些名词有个简单的了解:
大盘:通常指上证综合指数,以上海证券交易所挂牌上市的全部股票为样本,以发行量为权数,以加权平均法计算,以1990年12月19日为基日,基日指数定为100点的股价指数。
板块:由股票组成的群体,因为有某一共同特征而被归类在一起。板块特征可能是地理上的,例如江苏板块、浦东板块;可能是业绩上的,如绩优板块;可能是上市公司经营行为方面的,如购并板块;可能行业分类方面的,如钢铁板块、科技板块、金融板块、房地产板块等。
收集数据 可以通过 tushare 下载历年的数据:
import tushare as tsimport pandas as pddef download (code, name, index=False) : filename = 'get_h_2000/' +code+'-' +name+str('.csv' ) if os.path.exists(filename): os.remove(filename) print '%s已存在,删除源文件' % filename df = ts.get_h_data(code, start='2000-01-01' , end='2016-01-01' , index=index) df = df.sort_index(ascending=True ).reset_index() df.to_csv(filename, mode='a' , index=False ) download('000001' ,'上证指数' ,index=True )
有需要的朋友可以在 Gist 下载:000001-上证指数-2000-2015.csv 。
观察数据 通过 plot
方法可以很方便的绘制上证指数这15年的走势:
可以用 Pandas 自带的 describe()
方法观察一下目前的数据:
pd.set_option('display.float_format' , lambda x: '%.02f' % x) df.describe().transpose()
数据统计如下:
数字看着比较抽象,我们可以通过箱线图展示统计信息:
ax = df.boxplot(column=['close' ,'high' ,'low' ,'open' ],return_type='axes' )
箱线图展示结果如下:
从图中可以很清晰的看到各列的最小值、第一四分位数、中位数、第三四分位数、最大值等等。
接下来再来看看每天的涨幅如何。这里用『收盘价-开盘价』来表示每天的涨跌幅,并且将涨跌幅除以开盘价,计算并绘制涨跌率:
df['price_change' ] = df['close' ] - df['open' ] df['price_change_p' ] = df['price_change' ] / df['open' ] fig,ax = plt.subplots(figsize=(16 ,6 )) ax.bar(df.index,df['price_change_p' ]) plt.show()
绘制结果如下:
可以看到,08年和15年有比较大的起伏,这分别对应着07年和15年的两次牛市。
通过 hist
方法可以绘制涨跌率的频率分布直方图:
fig,ax = plt.subplots(figsize=(16 ,6 )) ax.hist(df['price_change_p' ],bins=100 ) plt.show()
绘图如下:
可以看到,大盘的涨跌率基本呈正态分布,涨的部分靠近零轴,跌的部分则略为分散。翻译成大白话大致是:涨的天数多,但是磨磨蹭蹭,幅度不大。跌的天数少,但是来势凶猛,幅度较大。
接下来看下银行板块的个股股价。通过前面的 download
函数下载好数据之后,通过 read
函数读取数据:
def read (filename) : path = 'get_h_2000/%s.csv' % filename df = pd.read_csv(path) df['date' ] = pd.to_datetime(df['date' ]) df = df.set_index('date' ) df['price_change' ] = df['close' ] - df['open' ] df['price_change_p' ] = df['price_change' ] / df['open' ] return df
然后列出所有银行股的数据名,并随机选择三个股票作为对比参照,依次读取数据:
stocks = np.array([ '000001-平安银行' ,'002142-宁波银行' ,'600000-浦发银行' ,'600015-华夏银行' , '600016-民生银行' ,'600036-招商银行' ,'601009-南京银行' ,'601166-兴业银行' , '601169-北京银行' ,'601288-农业银行' ,'601328-交通银行' ,'601398-工商银行' , '601818-光大银行' ,'601939-建设银行' ,'601988-中国银行' ,'601998-中信银行' ]) dfs = {} for name in stocks: dfs[name] = read(name) stocks_o = np.array(['601233-桐昆股份' ,'002134-天津普林' ,'600362-江西铜业' ]) dfs_o = {} for name in stocks_o: dfs_o[name] = read(name)
由于各股上市时间不一致,我们只取2010年往后的数据做展示::
temp_stocks = np.concatenate((stocks, stocks_o)) stock_count = len(temp_stocks) f, axes = plt.subplots(stock_count, 1 , sharex=True , figsize=(16 ,4 *stock_count)) useds_axes = [] for name in temp_stocks: df = dfs[name] if dfs.has_key(name) else dfs_o[name] df = df[df.index > '2010-01-01' ] ax = axes[len(useds_axes)] ax.plot(df.index, df['close' ], label=name) ax.plot(df_000001.index, df_000001['close' ]/(df_000001['close' ][0 ]/df['close' ][0 ]), label='上证指数' ) ax.set_title(name) ax.legend(loc='upper left' ) useds_axes.append(ax) plt.show()
绘图结果如下:
从图中来看,大部分银行股的起伏十分相似,而且和大盘的走势相近。不过,民生银行、浦发银行、江西铜业,这三个股票和大盘似乎有些不相关。
接下来可以从数据的角度具体分析一下它们之间的关联程度。
寻找相关 在找到相关的特征之前,我们首先要了解有哪些量化关联度的方法。比如三个比较常见度量方法:
欧式距离:两点间的直线距离,不同特征的量级差异对结果影响较大。
皮尔逊相关系数:用于度量两个变量 X 和 Y 之间的线性相关,值介于-1与1之间,对量级不敏感。
余弦相似度:计算两个向量的夹角,注重向量在方向上的差异,对量级不敏感。
在这里我选择的是皮尔逊相关系数做实验。
scipy
中自带了 pearsonr
函数,输入 X 和 Y 返回皮尔逊相关系数和 p-value 。p-value 简单来说,就是在假设原假设正确时,出现现状或更差的情况的概率。
一个小例子演示一下如何使用:
np.random.seed(0 ) size = 1000 x = np.random.normal(0 , 1 , size) print stats.pearsonr(x, x + np.random.normal(0 , 1 , size))print stats.pearsonr(x, x + np.random.normal(0 , 100 , size))
运行结果是:
(0.7029971477605752 , 6.8891376644515863e-150 ) (-0.027349877570630021 , 0.38761043588940391 )
可以看到,噪音较小的那一串数列的相关性更高,p 值也更低。接下来我们尝试用皮尔逊相关系数来探索股价之间的相关性。
大盘的影响 先来看下各个银行股和大盘之间的相关性。为了有所参照,我们随机生成了一个数字序列作为参照,完整代码如下:
p_result = {} start_time = '2010-01-01' print '----------随机数列----------' np.random.seed(0 ) x1 = df_000001[df_000001.index > start_time]['close' ].values x0 = np.random.normal(0 ,100 ,len(x1)) r = stats.pearsonr(x1, x0) p_result['000000-随机数列' ] = r print '000000-随机数列:{0}' .format(r)print '----------随机个股----------' for i in stocks_o: df = dfs_o[i] df = df[df.index > start_time] x = df['close' ].values x1 = [df_000001.loc[si]['close' ] for si in df.index.values] r = stats.pearsonr(x1, x) p_result[i] = r print '{0}:{1}' .format(i ,r) print '----------银行板块----------' for i in stocks: df = dfs[i] df = df[df.index > start_time] x = df['close' ].values x1 = [df_000001.loc[si]['close' ] for si in df.index.values] r = stats.pearsonr(x1, x) p_result[i] = r print '{0}:{1}' .format(i ,r)
运算结果:
----------随机数列---------- 000000 -随机数列:(-0.018379229354344717 , 0.48345058165702948 )----------随机个股---------- 601233 -桐昆股份:(0.82394024147340494 , 1.4846471908590122e-276 )002134 -天津普林:(0.87070295523388863 , 0.0 )600362 -江西铜业:(0.29074317824804269 , 1.1115439379128497e-29 )----------银行板块---------- 000001 -平安银行:(0.83230166489115542 , 0.0 )002142 -宁波银行:(0.95071984964658407 , 0.0 )600000 -浦发银行:(0.83177504090683074 , 0.0 )600015 -华夏银行:(0.86944980096838431 , 0.0 )600016 -民生银行:(0.47921539730721252 , 6.7671691831972994e-84 )600036 -招商银行:(0.88739800637271515 , 0.0 )601009 -南京银行:(0.89395777699351697 , 0.0 )601166 -兴业银行:(0.79369148744309614 , 6.16023876965882e-313 )601169 -北京银行:(0.91317333851684301 , 0.0 )601288 -农业银行:(0.83442809587466238 , 0.0 )601328 -交通银行:(0.92295526522170779 , 0.0 )601398 -工商银行:(0.84993313219224631 , 0.0 )601818 -光大银行:(0.96153251745347235 , 0.0 )601939 -建设银行:(0.84982759873987823 , 0.0 )601988 -中国银行:(0.88002650449792652 , 0.0 )601998 -中信银行:(0.88759530896504257 , 0.0 )
为了更好的展示相关性的结果,可以通过柱状图把结果绘制出来:
prs = [] pvs = [] for name in p_result.keys(): prs.append(p_result[name][0 ]) pvs.append(p_result[name][1 ]) fig, ax = plt.subplots(figsize=(16 ,5 )) ind = np.arange(len(prs)) width = 0.35 rects1 = ax.bar(ind, prs, width, color='b' ) rects2 = ax.bar(ind + width, pvs, width, color='r' ) ax.set_ylabel('Value' ) ax.set_title('股票与大盘的相关性系数' ) ax.set_xticks(ind + width) ax.set_xticklabels(p_result.keys(), rotation='vertical' ) legend = ax.legend((rects1[0 ], rects2[0 ]), ('Pearson\'s' , 'P-Value' ), frameon=True , shadow=True ) plt.show()
绘制结果如下:
可以看到,随机数列的相关性明显很低,另外相关性比较差的还有民生银行、江西铜业、浦发银行、桐昆股份、平安银行。这个和之前的肉眼观测结果是相同的。
不过,光看价格的相关性并不是很准确,因为不同股票的价位不同,相关性的计算结果可能也会受到影响。其实我们应该关注的是股价涨跌幅的相关性。调整一下代码:
temp_stocks = np.concatenate((stocks, stocks_o)) stock_count = len(temp_stocks) f, axes = plt.subplots(stock_count, 1 , sharex=True , figsize=(16 ,4 *stock_count)) useds_axes = [] for name in temp_stocks: df = dfs[name] if dfs.has_key(name) else dfs_o[name] start_time = '2015-10-01' df = df[df.index > start_time] df0 = df_000001[df_000001.index > start_time] ax = axes[len(useds_axes)] ax.bar(df.index, df['price_change_p' ], label=name, alpha=0.5 , color='r' ) ax.bar(df0.index, df0['price_change_p' ], label='上证指数' , alpha=0.5 , color='g' ) ax.set_title(name) ax.legend(loc='upper left' ) useds_axes.append(ax) plt.show()
为了方便展示,只取了2015年最后三个月的数据,展示如下:
红色超出绿色的部分为个股超出大盘的涨跌幅,绿色超出的部分为大盘超出个股的涨跌幅。从图表来看,相关性不是很明显。
用代码计算一下相关性:
p_result = {} start_time = '2010-01-01' print '----------随机数列----------' np.random.seed(0 ) x1 = df_000001[df_000001.index > start_time]['price_change_p' ].values x0 = np.random.normal(-1 ,1 ,len(x1)) r = stats.pearsonr(x1, x0) p_result['000000-随机数列' ] = r print '000000-随机数列:{0}' .format(r)print '----------随机个股----------' for i in stocks_o: df = dfs_o[i] df = df[df.index > start_time] x = df['price_change_p' ].values x1 = [df_000001.loc[si]['price_change_p' ] for si in df.index.values] r = stats.pearsonr(x1, x) p_result[i] = r print '{0}:{1}' .format(i ,r) print '----------银行板块----------' for i in stocks: df = dfs[i] df = df[df.index > start_time] x = df['price_change_p' ].values x1 = [df_000001.loc[si]['price_change_p' ] for si in df.index.values] r = stats.pearsonr(x1, x) p_result[i] = r print '{0}:{1}' .format(i ,r)
计算结果如下:
----------随机数列---------- 000000 -随机数列:(-0.01170938101429754 , 0.65528444998797009 )----------随机个股---------- 601233 -桐昆股份:(0.5926213844273559 , 1.3768416303873001e-106 )002134 -天津普林:(0.55037020105007528 , 1.9031317037476984e-115 )600362 -江西铜业:(0.76021449109055472 , 7.0259968513985565e-274 )----------银行板块---------- 000001 -平安银行:(0.7051512083759307 , 6.6891609215087728e-209 )002142 -宁波银行:(0.72612012412782501 , 2.4079706518655618e-237 )600000 -浦发银行:(0.7167360945341158 , 3.4557547788647287e-225 )600015 -华夏银行:(0.69668869104482156 , 4.1601778321466699e-209 )600016 -民生银行:(0.62750442079223367 , 3.9126027918308307e-159 )600036 -招商银行:(0.67017991271658717 , 1.208446093582849e-187 )601009 -南京银行:(0.70304459154675281 , 5.5004160013777059e-215 )601166 -兴业银行:(0.72086996034908235 , 2.2042255646943599e-231 )601169 -北京银行:(0.70007100804010169 , 1.8158330878777684e-208 )601288 -农业银行:(0.63124751196123652 , 4.2460567821792627e-148 )601328 -交通银行:(0.67157367944285129 , 7.08859346030525e-190 )601398 -工商银行:(0.59367541046236361 , 3.7205953256769839e-138 )601818 -光大银行:(0.66338500488771768 , 6.6439318291648056e-164 )601939 -建设银行:(0.64247755967824627 , 6.6203452504774864e-169 )601988 -中国银行:(0.60765522028795782 , 2.1780341191010662e-146 )601998 -中信银行:(0.62940968812629339 , 1.688815329830977e-159 )
涨跌幅的相关性明显不如价格的相关性。绘制图表如下:
最高的不到0.8,最低0.5,江西铜业反而成了相关性最高的股了,感觉难以置信。不妨计算一下股票和大盘的涨跌一致的比例:
start_time = '2010-01-01' def count_p (name) : if name == '000000-随机数列' : return 0 df = dfs[name] if dfs.has_key(name) else dfs_o[name] df = df[df.index > start_time] df.loc[:, 'A' ] = pd.Series([df_000001.loc[si]['price_change_p' ] for si in df.index.values], index=df.index) df.loc[:, 'same_change_p' ] = (df['price_change_p' ] * df['A' ]) > 0 vc = df['same_change_p' ].value_counts() return vc[True ] * 1.0 / (vc[False ] + vc[True ]) result = map(count_p, p_result.keys())
将计算结果绘制在前面的一张图上:
在过去的六年中,江南铜业有76%的概率涨跌与大盘一致,相关性高也可以理解了。
板块的影响 通过前面的探索,基本可以确定板块内的股票也是有一定相关性的,这里就不啰嗦了,直接贴出最后的运算结果:
小结 以上就是本次股票相关性分析的全部内容了。回顾一下前面的分析,主要有以下问题:
皮尔逊相关系数只对线性相关敏感,而且结果和数据量有关,用于股票涨跌趋势的相关性分析其实并不合适。
对股票价格的分析,光看价格的涨跌趋势是远远不够,股票的分析难点在于特征值的提取。
考虑使用时间序列模型(ARIMA、GARCH)对股票价格进行分析。
股海茫茫,涨涨跌跌,若即若离,如梦如幻。一首《刀剑如梦》,献给各位股票,以及它们的爱恨情仇。
我剑 何去何从 爱与恨 情难独钟 我刀 割破长空 是与非 懂也不懂 我醉 一片朦胧 恩和怨 是幻是空 我醒 一场春梦 生与死 一切成空
来也匆匆 去也匆匆 恨不能相逢 爱也匆匆 恨也匆匆 一切都随风
狂笑一声 长叹一声 快活一生 悲哀一生 谁与我生死与共