文章来源:博文视点Broadview
作者:博文视点
作为一种技术手段,预测在金融、证券领域的应用非常广泛,尤其是对股票价格的预测。我们介绍一下获得股票数据的方法,并基于此对数据进行预处理,接着使用数据分析方法,建立基础特征,进一步构建预测模型,且基于新数据验证模型效果。拟使用VAR及LSTM两种算法建立预测模型。
获取股票数据
股票数据通常可从新浪股票、雅虎股票等网页上获取,此外还有一些炒股软件,如同花顺、通达信等都提供了非常清楚的股票数据展示和图表呈现。如果要获得实时的股票数据,可以考虑使用新浪股票提供的接口获取数据。以大秦铁路(股票代码:601006)为例,如果要获取它的最新行情,只需访问新浪的股票数据接口(具体可以百度),该接口会返回一串文本,例如:
1 var hq_str_sh601006="大秦铁路,6.980,6.960,7.010,7.070,6.950,7.010,7.020,121033256,847861533.000,18900, 7.010,214867,7.000,66500,6.990,386166,6.980,336728,6.970,273750,7.020,836066,7.030,630800,7.040,936306,7.050,579400,7.060,2016-03-18,15:00:00,00";
这个字符串由许多数据拼接在一起,不同含义的数据用逗号隔开了,按照程序员的思路,顺序号从0开始。
0:<大秦铁路>,股票名字
1:<< span="">6.980>,今日开盘价
2:<< span="">6.960>,昨日收盘价
3:<< span="">7.010>,当前价格
4:<< span="">7.070>,今日最高价
5:<< span="">6.950>,今日最低价
6:<< span="">7.010>,竞买价,即“买一”报价
7:<< span="">7.020>,竞卖价,即“卖一”报价
8:<< span="">121033256>,成交的股票数,由于股票交易以一百股为基本单位,所以在使用时,通常把该值除以一百
9:<< span="">847861533.000>,成交金额,单位为“元”,为了一目了然,通常以“万元”为成交金额的单位,所以通常把该
值除以一万
10:<< span="">18900>,“买一”申请4695股,即47手
11:<< span="">7.010>,“买一”报价
12:<< span="">214867>,“买二”
13:<< span="">7.000>,“买二”
14:<< span="">66500>,“买三”
15:<< span="">6.990>,“买三”
16:<< span="">386166>,“买四”
17:<< span="">6.980>,“买四”
18:<< span="">336728>,“买五”
19:<< span="">6.970>,“买五”
20:<< span="">273750>,“卖一”申报3100股,即31手
21:<< span="">7.020>,“卖一”报价
(22,23),(24,25),(26,27),(28,29)分别为“卖二”至“卖四的情况”
30:<< span="">2016-03-18>,日期
31:<< span="">15:00:00>,时间
这个接口对于JavaScript程序非常方便,如果要查看该股票的日K线图,可访问新浪股票的K线图接口(具体可百度),便可得到日K线图。
日K线图
如果要查看该股票的分时线,可访问链接新浪股票的分时线图接口(具体可百度),便可得到分时线图。
分时线图
对于周K线和月K线的查询,可分别访问新浪股票的周K线图和月K线图的接口(具体可百度)。Python中我们可以使用pandas_datareader库来获取股票数据,默认是访问yahoofinance的数据,其中包括上证和深证的股票数据,还有港股数据,该库只能获取股票的历史交易记录信息:如最高价、最低价、开盘价、收盘价以及成交量,无法获取个股的分笔交易明细历史记录。上证代码是ss,深证代码是sz,港股代码是hk,比如茅台:6000519.ss,万科000002.sz,长江实业0001.hk。这里以贵州茅台股票为例,说明pandas_datareader库中股票数据的获取方法及简单的可视化,代码如下:
1import pandas as pd
2import pandas_datareader.data as web
3import datetime as dt
4data = web.DataReader('600519.ss','yahoo', dt.datetime(2019,8,1),dt.datetime(2019,8,31))
5data.head()
6 High Low Open Close Volume Adj Close
7# Date
8# 2019-08-01 977.000000 953.020020 976.51001 959.299988 3508952 959.299988
9# 2019-08-02 957.979980 943.000000 944.00000 954.450012 3971940 954.450012
10# 2019-08-05 954.000000 940.000000 945.00000 942.429993 3677431 942.429993
11# 2019-08-06 948.000000 923.799988 931.00000 946.299988 4399116 946.299988
12# 2019-08-07 955.530029 945.000000 949.50000 945.000000 2686998 945.000000
13
14kldata=data.values[:,[2,3,1,0]] # 分别对应开盘价、收盘价、最低价和最高价
15from pyecharts import options as opts
16from pyecharts.charts import Kline
17
18kobj = Kline().add_xaxis(data.index.strftime("%Y-%m-%d").tolist()).add_yaxis("贵州茅台-日K线图",kldata.tolist()).set_global_opts(
19 yaxis_opts=opts.AxisOpts(is_scale=True),
20 xaxis_opts=opts.AxisOpts(is_scale=True),
21 title_opts=opts.TitleOpts(title=""))
22kobj.render()
贵州茅台股票日K线图如图。
为给定时间序列的财务图表,代码中对象data包含6个属性,依次为Open(开盘价)、High(最高价)、Low(最低价)、Close(收盘价)、Volume(成交量)、Adjusted(复权收盘价)。基于收盘价的重要性,可从收盘价的历史数据中分割训练集、验证集、测试集,使用适当的特征,建立预测模型,并实施预测。
基于VAR算法的预测
向量自回归(VAR)模型就是非结构化的多方程模型,它的核心思想不考虑经济理论,而直接考虑经济变量时间时序之间的关系,避开了结构建模方法中需要对系统中每个内生变量关于所有内生变量滞后值函数建模的问题,通常用来预测相关时间序列系统和研究随机扰动项对变量系统的动态影响。VAR模型类似联立方程,将多个变量包含在一个统一的模型中,共同利用多个变量信息,比起仅使用单一时间序列的ARIMA等模型,其涵盖的信息更加丰富,能更好地模拟现实经济体,因而用于预测时能够提供更加贴近现实的预测值。此处拟基于贵州茅台股票数据,建立VAR的预测模型。使用后30天的数据作为验证集,剩余的数据用于建立预测模型。本节从VAR模型的平稳性检验出发,依次完成VAR模型的定阶及建模预测,最终通过分析验证集上的准确率来评估预测效果。
▊ 平稳性检验
只有平稳的时间序列才能够直接建立VAR模型,因此在建立VAR模型之前,首先要对变量进行平稳性检验。通常可利用序列的自相关分析图来判断时间序列的平稳性,如果序列的自相关系数随着滞后阶数的增加很快趋于0,即落入随机区间,则序列是平稳的;反之,序列是不平稳的。另外,也可以对序列进行ADF检验来判断平稳性。对于不平稳的序列,需要进行差分运算,直到差分后的序列平稳后,才能建立VAR模型。此处首先提取用于建立预测模型的基础数据,并对其进行单位根检验,对应的Python代码如下:
1import statsmodels.tsa.stattools as stat
2import pandas_datareader.data as web
3import datetime as dt
4import pandas as pd
5import numpy as np
6
7data = web.DataReader('600519.ss','yahoo', dt.datetime(2014,1,1),dt.datetime(2019,9,30))
8subdata = data.iloc[:-30,:4]
9for i in range(4):
10 pvalue = stat.adfuller(subdata.values[:,i], 1)[1]
11 print("指标 ",data.columns[i]," 单位根检验的p值为:",pvalue)
12# 指标 High 单位根检验的p值为:0.9955202280850401
13# 指标 Low 单位根检验的p值为:0.9942509439755689
14# 指标 Open 单位根检验的p值为:0.9938548193990323
15# 指标 Close 单位根检验的p值为:0.9950049124079876
可以看到,p值都大于0.01,因此都是不平稳序列。现对subdata进行1阶差分运算,并再次进行单位根检验,对应的Python代码如下:
1subdata_diff1 = subdata.iloc[1:,:].values - subdata.iloc[:-1,:].values
2for i in range(4):
3 pvalue = stat.adfuller(subdata_diff1[:,i], 1)[1]
4 print("指标 ",data.columns[i]," 单位根检验的p值为:",pvalue)
5# 指标 High 单位根检验的p值为:0.0
6# 指标 Low 单位根检验的p值为:0.0
7# 指标 Open 单位根检验的p值为:0.0
8# 指标 Close 单位根检验的p值为:0.0
如结果所示,对这4个指标的1阶差分单独进行单位根检验,其p值都不超过0.01,因此可以认为是平稳的。
▊ VAR模型定阶
接下来就是为VAR模型定阶,可以让阶数从1逐渐增加,当AIC值尽量小时,可以确定最大滞后期。我们使用最小二乘法,求解每个方程的系数,并通过逐渐增加阶数,为模型定阶,Python代码如下:
1# 模型阶数从1开始逐一增加
2rows, cols = subdata_diff1.shape
3aicList = []
4lmList = []
5
6for p in range(1,11):
7 baseData = None
8 for i in range(p,rows):
9 tmp_list = list(subdata_diff1[i,:]) + list(subdata_diff1[i-p:i].flatten())
10 if baseData is None:
11 baseData = [tmp_list]
12 else:
13 baseData = np.r_[baseData, [tmp_list]]
14 X = np.c_[[1]*baseData.shape[0],baseData[:,cols:]]
15 Y = baseData[:,0:cols]
16 coefMatrix = np.matmul(np.matmul(np.linalg.inv(np.matmul(X.T,X)),X.T),Y)
17 aic = np.log(np.linalg.det(np.cov(Y - np.matmul(X,coefMatrix),rowvar=False))) + 2*(coefMatrix.shape[0]-1)**2*p/baseData.shape[0]
18 aicList.append(aic)
19 lmList.append(coefMatrix)
20
21#对比查看阶数和AIC
22pd.DataFrame({"P":range(1,11),"AIC":aicList})
23# P AIC
24# 0 1 13.580156
25# 1 2 13.312225
26# 2 3 13.543633
27# 3 4 14.266087
28# 4 5 15.512437
29# 5 6 17.539047
30# 6 7 20.457337
31# 7 8 24.385459
32# 8 9 29.438091
33# 9 10 35.785909
如上述代码所示,当p=2时,AIC值最小为13.312225。因此VAR模型定阶为2,并可从对象lmList[1]中获取各指标对应的线性模型。
▊ 预测及效果验证
基于lmList[1]中获取各指标对应的线性模型,对未来30期的数据进行预测,并与验证数据集进行比较分析,Python代码如下:
1p = np.argmin(aicList)+1
2n = rows
3preddf = None
4for i in range(30):
5 predData = list(subdata_diff1[n+i-p:n+i].flatten())
6 predVals = np.matmul([1]+predData,lmList[p-1])
7 # 使用逆差分运算,还原预测值
8 predVals=data.iloc[n+i,:].values[:4]+predVals
9 if preddf is None:
10 preddf = [predVals]
11 else:
12 preddf = np.r_[preddf, [predVals]]
13 # 为subdata_diff1增加一条新记录
14 subdata_diff1 = np.r_[subdata_diff1, [data.iloc[n+i+1,:].values[:4] - data.iloc[n+i,:].values[:4]]]
15
16#分析预测残差情况
17(np.abs(preddf - data.iloc[-30:data.shape[0],:4])/data.iloc[-30:data.shape[0],:4]).describe()
18# High Low Open Close
19# count 30.000000 30.000000 30.000000 30.000000
20# mean 0.010060 0.009380 0.005661 0.013739
21# std 0.008562 0.009968 0.006515 0.013674
22# min 0.001458 0.000115 0.000114 0.000130
23# 25% 0.004146 0.001950 0.001653 0.002785
24# 50% 0.007166 0.007118 0.002913 0.010414
25# 75% 0.014652 0.012999 0.006933 0.022305
26# max 0.039191 0.045802 0.024576 0.052800
从上述代码第17行可以看出这4个指标的最大百分误差率分别为3.9191%、4.5802%、2.4576%、5.28%,最小百分误差率分别为0.1458%、0.0115%、0.0114%、0.013%,进一步,绘制二维图表观察预测数据与真实数据的逼近情况,Python代码如下:
1import matplotlib.pyplot as plt
2plt.figure(figsize=(10,7))
3for i in range(4):
4 plt.subplot(2,2,i+1)
5 plt.plot(range(30),data.iloc[-30:data.shape[0],i].values,'o-',c='black')
6 plt.plot(range(30),preddf[:,i],'o--',c='gray')
7 plt.ylim(1000,1200)
8 plt.ylabel("$"+data.columns[i]+"$")
9plt.show()
10v = 100*(1 - np.sum(np.abs(preddf - data.iloc[-30:data.shape[0],:4]).values)/np.sum(data.iloc[-30:data.shape[0],:4].values))
11print("Evaluation on test data: accuracy = %0.2f%% \n" % v)
12# Evaluation on test data: accuracy = 99.03%
该预测效果如下图,其中黑色实线为真实数据,灰色虚线为预测数据,使用VAR模型进行预测的效果总体还是不错的,平均准确率为99.03%。针对多元时间序列的情况,VAR模型不仅考虑了其他指标的滞后影响,计算效率还比较高,从以上代码可以看到,对于模型的拟合,直接使用的最小二乘法,这增加了该模型的适应性。
预测效果
基于LSTM算法的预测
本节主要基于LSTM算法对贵州茅台股票数据进行预测,该算法非常擅长序列数据的建模,由于引入了遗忘门等更为复杂的内部处理单元来处理上下文信息的存储与更新,这样既可以消除梯度问题的困扰,也可以对存在短期或长期依赖的数据建模,该算法在文本、语音等序列数据模型中广泛使用。本节从LSTM建模的数据要求及网络结构设计讲起,通过设置合理的参数,通过训练得到模型,并基于该模型进行预测,最后将结果与真实数据进行比较,评估预测效果。
▊ 数据要求
本节使用LSTM算法对贵州茅台股票数据进行预测,可基于前N条样本对当前样本进行预测,因此该模型不需要像DNN那样,将历史数据进行复杂转换,将基础数据稍加处理就能用于训练模型。对基础数据的处理即为对该数据进行重新封装,将样本前N期的集合与当前样本对应上,分别得到训练数据的输入与输出。
所示数据对应关系(具体数据为示意)
▊ 数据预处理
首先,需要将基础数据重构为包含历史3周特征数据的基础数据,以预测日的High(最高价)、Low(最低价)、Open(开盘价)、Close(收盘价)4个指标作为输出数据。这里我们使用2014年1月1日至2019年8月31日的贵州茅台股票数据作为训练数据,使用2019年整个9月的数据作为测试数据,来验证模型效果。用Python将对全体数据进行标准化,并将基础数据的特征进行重构,代码如下:
1SEQLEN = 21
2dim_in = 4
3dim_out = 4
4pred_len = 30
5vmean = data.iloc[:,:4].apply(lambda x:np.mean(x))
6vstd = data.iloc[:,:4].apply(lambda x:np.std(x))
7t0 = data.iloc[:,:4].apply(lambda x:(x-np.mean(x))/np.std(x)).values
8X_train = np.zeros((t0.shape[0]-SEQLEN-pred_len, SEQLEN, dim_in))
9Y_train = np.zeros((t0.shape[0]-SEQLEN-pred_len, dim_out),)
10X_test = np.zeros((pred_len, SEQLEN, dim_in))
11Y_test = np.zeros((pred_len, dim_out),)
12for i in range(SEQLEN, t0.shape[0]-pred_len):
13 Y_train[i-SEQLEN] = t0[i]
14 X_train[i-SEQLEN] = t0[(i-SEQLEN):i]
15for i in range(t0.shape[0]-pred_len,t0.shape[0]):
16 Y_test[i-t0.shape[0]+pred_len] = t0[i]
17 X_test[i-t0.shape[0]+pred_len] = t0[(i-SEQLEN):i]
如上述代码所示,SEQLEN表示使用前期数据的长度,dim_in表示输入数据的维度,dim_out表示输出数据的维度,pred_len表示预测数据的长度。第5~7行代码对数据进行zscore标准化,将数据映射到标准正态分布。第12~17行代码对基础数据进行重构,分别得到训练数据X_train、Y_train以及测试数据X_test、Y_test。
▊ 网络结构设计
经尝试,我们使用近3周的历史数据来训练LSTM模型,同时,设置隐含层神经元的数量为64。因此,我们可以将LSTM神经网络按下面的结构进行设计(图中N可取21,即3周对应的天数)。
LSTM神经网络结构
▊ 建立模型
现基于Keras搭建LSTM神经网络,并基于训练集对模型进行训练,Python代码如下:
1from keras.layers import LSTM, Dense
2from keras.models import Sequential
3model = Sequential()
4model.add(LSTM(64, input_shape=(SEQLEN, dim_in),activation='relu',recurrent_dropout=0.01))
5model.add(Dense(dim_out,activation='linear'))
6model.compile(loss = 'mean_squared_error', optimizer = 'rmsprop')
7history = model.fit(X_train, Y_train, epochs=200, batch_size=10, validation_split=0)
8# Epoch 1/200
9# 1350/1350 [==============================] - 1s 1ms/step - loss: 0.0447
10# Epoch 2/200
11# 1350/1350 [==============================] - 1s 737us/step - loss: 0.0059
12# Epoch 3/200
13# 1350/1350 [==============================] - 1s 743us/step - loss: 0.0043
14# ......
15# Epoch 200/200
16# 1350/1350 [==============================] - 1s 821us/step - loss: 9.2794e-04
如上述代码所示,我们使用rmsprop算法来优化模型。由于当前的建模场景是数值预测,因此使用MSE(均方误差)来定义损失函数。算法经过200次迭代,loss从0.0447降到了9.2794e-04。我们可以基于得到的模型进行进一步预测。
▊ 预测实现
基于上文得到的模型,进一步编写Python代码,对X_test对应的输出数据进行预测。需要注意的是,直接得到的预测结果是处于标准化的数据空间中的,需要将其还原成原始数据空间的值,结果才有意义。对应的Python代码如下:
1preddf=model.predict(X_test)*vstd.values+vmean.values
如上述代码所示,将模型的预测结果pred_y乘以vstd再加上vmean,即可对数据进行还原。preddf即是最终得到的预测数据,可打印其值,代码如下:
1preddf
2# array([[1069.35781887, 1038.57915742, 1056.77147186, 1053.83827734],
3# [1070.65142282, 1039.58533719, 1057.34561875, 1054.85567074],
4# [1083.58529328, 1052.70457308, 1070.78824637, 1067.49741882],
5#
6# [1186.19297789, 1161.52758381, 1172.33666591, 1170.44623263],
7# [1181.42680223, 1155.14778501, 1166.5726204 , 1165.00336968],
8# [1186.75600881, 1160.84733425, 1172.37636963, 1170.09819923]])
9
10preddf.shape
11# (30, 4)
如上述代码所示,preddf是一个的二维数据,包含了2019年9月整月的预测结果。
▊ 效果评估
对贵州茅台股票数据预测的效果评估可以采用两种方法。一种方法是对预测的结果与真实结果进行绘图比较,通过直观观察可以知道预测效果,如果预测曲线与真实曲线完全重合或相当接近,则说明预测效果较好;反之,则说明预测模型还需要改进。另一种方法是基于贵州茅台股票数据预测的误差累计值来计算一个误差率,从而得到平均精度水平,该值越大说明整体预测效果也就越好,该值越小说明预测模型还存在优化空间。编写Python代码,同时实现预测结果与真实数据的对比图,以及计算累计误差,从而全面地评估预测效果,代码如下:
1import matplotlib.pyplot as plt
2plt.figure(figsize=(10,7))
3for i in range(4):
4 plt.subplot(2,2,i+1)
5 plt.plot(range(30),data.iloc[-30:data.shape[0],i].values,'o-',c='black')
6 plt.plot(range(30),preddf[:,i],'o--',c='gray')
7 plt.ylim(1000,1200)
8 plt.ylabel("$"+data.columns[i]+"$")
9plt.show()
10v = 100*(1 - np.sum(np.abs(preddf - data.iloc[-30:data.shape[0],:4]).values)/np.sum (data.iloc[-30:data.shape[0],: 4].values))
11print("Evaluation on test data: accuracy = %0.2f%% \n" % v)
12# Evaluation on test data: accuracy = 99.01%
预测评估对比图如下。
我们可以看到,黑色实线为真实数据,灰色虚线为预测数据,横坐标为日期下标,纵坐标为对应的股票价格。使用LSTM模型进行预测的效果总体还是不错的,平均准确率为99.01%。对于多元时间序列数据,可尝试使用LSTM模型,该模型能够记忆历史较长的重要信息,可有效识别历史数据中存在的规律和模式,如今广泛应用于包含大量序列数据的场景中。
本文节选自《Python预测之美:数据分析与算法实战》一书。
近期推荐阅读:
【1】整理了我开始分享学习笔记到现在超过250篇优质文章,涵盖数据分析、爬虫、机器学习等方面,别再说不知道该从哪开始,实战哪里找了【2】【终篇】Pandas中文官方文档:基础用法6(含1-5)
如果你觉得文章不错的话,分享、收藏、在看、留言666是对老表的最大支持。