硕士毕业之前曾经对基于LSTM循环神经网络的股价预测方法进行过小小的研究,趁着最近工作不忙,把其中的一部分内容写下来做以记录。
此次股票价格预测模型仅根据股票的历史数据来建立,不考虑消息面对个股的影响。曾有日本学者使用深度学习的方法来对当天的新闻内容进行分析,以判断其对股价正面性/负面性影响,并将其与股票的历史数据相结合,各自赋予一定的权重来对近日的股价进行预测[1]。该预测方法取得了一定的效果。
而这里我没有引入消息面的影响,主要出于以下几点考虑:
1.消息的及时性难以保证:很多时候,在一只股票的利好/利空消息出来之前,其股价已经有了较大幅度的增长/下跌。信息的不对称性导致普通群众缺乏第一手消息源。
2.消息的准确性难以保证:互联网上信息传播速度极快,媒体之间经常会出现相互抄袭新闻的情况,而这种抄来的新闻(非原创新闻)往往没有经过严格的审核,存在着内容虚假,夸大宣传的可能性。一旦分析模型错用了某条谣言或真实性不高的新闻,很有可能得出错误的预测结果。
3.语言的歧义性:一条新闻,其正面性/负面性往往存在着多种解读。例如“习主席宣布中国将裁军30万”——新华每日电讯2015.09.04。这条新闻一般意义上可以解读为:中央政府深入推进改革,精兵简政,大力发展国防军工事业。这是一种正面性的解读。而在使用机器学习模型时,如传统的奇异值分解算法(SVD),很有可能会判定其与“去年五大行裁员近3万”这种新闻具有较高的相似度,因而将其划分为负面新闻。
4.技术实现较为繁杂:这其实是一个非常重要的原因啦~,获取正确的信息并进行NLP操作,往往需要经过以下流程:人工浏览网页确定稳定可靠的信息源→设计爬虫实现有效信息的获取→设计新闻裁剪(填充)方案以应对不同长度的新闻→人工标注新闻的正/负性(也可以用当日股价涨跌来标注)→设计网络模型→训练及验证模型。其中的每一步都非常麻烦耗时,而且对于个股来说,并不是每天都会有新闻出现。
上面说了这么多,还没有开始对我这个预测模型进行介绍,下面开始进入正题。在决定排除消息面的考量之后,我开始思考股价涨跌的本质,我认为股价就是资金博弈结果的体现。这次建立的预测模型,朴素的想法是通过深度学习模型来洞悉庄家的操作规律,对拉升、砸盘的情况进行预测。为了达到以下目的,我决定选取以下七个特征来构建网络模型,即:
涨跌幅 最高涨幅 最低跌幅 大单净流入 中单净流入 小单净流入 换手率
使用这七个特征来对股票的涨跌情况以及资金的流动情况建立适当的模型。此外,其他的指标类似MACD、均线等也是通过一些基础数据的运算得出,在构建模型时并没有将其纳入考量范围。
一.源数据及其预处理
通过某股票交易软件,我获得的源数据约有20来个特征,包括:涨幅、现价、涨跌、买入、卖价、成交量等等。为了得到上面所述的七种特征,挑选出涨跌幅、大单净流入、中单净流入、小单净流入、换手率这5个特征,并计算最高涨幅、最高跌幅两个特征。通过下列公式计算获得。

经过处理的股票特征数据存储在 股票名.csv文件中,类似下图:

图中的特征顺序为:日期,大单净流入,中单净流入,小单净流入,涨跌幅,最高涨幅,最高跌幅,换手率,股价。股价在这里的用处是拼接训练样本输出时,计算多日的总涨跌幅。
注:在对源数据进行处理的时候,经常会遇到空值问题:即,有些特征值为0的时候,系统给出的源数据为”-”或”“。需要进行特殊处理。(经常遇见新股第一天的涨跌幅为空,或某交易日大单净流入为空。)
1 if fin_temp.ix[day,12]=='-' or fin_temp.ix[day,12]=='': # 新股的涨跌幅一栏会出现'','-',需要特殊处理2 raise_value = 0.03 else: 4 raise_value = float(fin_temp.ix[day,12])
二.训练样本拼接
首先设置一个滑动窗口,本次实验中将滑动窗口设置为50个交易日。每一个训练样本由50个连续的交易日组成,每个交易日的数据包含上述的七个特征,即一个50*7的矩阵,而一个样本的输出则是三个交易日之后的收盘价对比今日(即样本的输入中最后一个交易日)收盘价的涨跌幅,设置其上限为0.3,下限为-0.3(当然,连续三个涨停板的涨幅会超过0.3,这里将其统一视作0.3)。之所以选择三个交易日之后的涨跌幅作为训练样本的输出,是因为我国股市是T+1操作规则,当日买入不可卖出,预测的稍微靠后一些可留有操作空间;再有就是一天的拉升/砸盘偶然性太大,不易预测,对稍微长期一点的状况进行预测有着更高的稳定性。
归一化相关工作:因为神经网络激活函数的限制,需要在训练前将数据映射到0~1区间。本次试验中,对近两年的数据,获取其各项特征的最大值与最小值。设置归一化与函数,在样本拼接的同时将数据进行归一化。
样本 输入的归一化:
1 def normalize_oneday(stockN,fdata,day): 2 max_min = list(max_min_list[stockN]) 3 in_1 = (fdata.ix[day,1]-max_min[1])/(max_min[0]-max_min[1]) 4 in_2 = (fdata.ix[day,2]-max_min[3])/(max_min[2]-max_min[3]) 5 in_3 = (fdata.ix[day,3]-max_min[5])/(max_min[4]-max_min[5]) 6 in_4 = (fdata.ix[day,4]-max_min[7])/(max_min[6]-max_min[7]) 7 in_5 = (fdata.ix[day,5]-max_min[9])/(max_min[8]-max_min[9]) 8 in_6 = (fdata.ix[day,6]-max_min[11])/(max_min[10]-max_min[11]) 9 in_7 = (fdata.ix[day,7]-max_min[13])/(max_min[12]-max_min[13]) 10 return [in_1,in_2,in_3,in_4,in_5,in_6,in_7]
样本 输出的归一化与反归一化:
def normalize_raise(volume): norm_value = (volume+0.3)/0.6 if norm_value>1: norm_value

