如何用人工智能预测股票(完整项目)

本文由 沈庆阳 所有,转载请与作者取得联系!

前言

十分钟实现人工智能股价预测,是一个深度学习的练习项目。其通过机器学习算法,根据过去几年与某只股票相关的K线走势、公司相关报道的情感分析作为数据集,通过训练来得到可以预测股价的机器学习模型,并用该模型对股价进行预测。
本项目使用几种不同的算法(线性回归、神经网络和随机森林)对股票进行预测,并对各自的效果进行比较。
运行本项目需要Python编程的基础,理解本项目的代码则需要对机器学习的相关知识。

自然人是如何投资股市的

在编写人工智能的程序之前,我们需要分析人类是怎样决定如何投资的。
有过炒股经历的人会更快地理解。投资股市的目的是盈利,因此在决定购买哪只股票之前我们会查阅与该公司相关的信息,搜索最近甚至之前与该公司有关的新闻,逛逛炒股方面的贴吧,看看微博上面与该公司有关的消息。如果这个公司的前景明朗(正面报道很多),那么投资该股票的回报率也许会高一些。


股票的K线

此外,投资股市,还需要会看各种数据,如K线等。有时我们看到某只股票持续走低,并且有上涨的势头了,也许此时是最佳的购入时机,因为该股票有很大可能会触底反弹了。
通过上述分析,我们明确了训练这样的一个机器学习模型需要哪些数据:
1、股价数据
2、对该股票(公司)的情感数据

获取历史数据并简单处理

数据对于机器学习十分重要。没有合适的数据,我们就无法训练机器学习模型,从而使其可以进行相应地预测。
在该项目中,我们需要获取2部分的数据。1:股价数据,2:情感数据。对于处理股价数据,我们需要
对于股价数据,需要使用Pandas进行分析。对于情感数据则使用NLTK(Natural Language Toolkit)来进行处理。

关于Pandas的使用入门,我曾写过一篇教程:从零开始机器学习-8 五分钟学会Pandas

首先,我们导入相应地Python包。

import numpy as np
import pandas as pd
import unicodedata
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

from nltk.sentiment.vader import SentimentIntensityAnalyzer

from treeinterpreter import treeinterpreter as ti
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import MLPClassifier

然后再读取往年的股价的数据,对其处理并生成Pandas的DataFrame格式。

df_stocks = pd.read_pickle('data/pickled_ten_year_filtered_data.pkl')
df_stocks['prices'] = df_stocks['adj close'].apply(np.int64)
df_stocks = df_stocks[['prices', 'articles']]
df_stocks['articles'] = df_stocks['articles'].map(lambda x: x.lstrip('.-'))

注:此处的数据是已经序列化成为文件的Python对象。
通过

print(df_stocks)

来查看我们的df_stocks DataFrame对象。其输出如下:

            prices                                           articles
2007-01-01   12469   What Sticks from '06. Somalia Orders Islamist...
2007-01-02   12472   Heart Health: Vitamin Does Not Prevent Death ...
2007-01-03   12474   Google Answer to Filling Jobs Is an Algorithm...
2007-01-04   12480   Helping Make the Shift From Combat to Commerc...
2007-01-05   12398   Rise in Ethanol Raises Concerns About Corn as...
2007-01-06   12406   A Status Quo Secretary General. Best Buy and ...
2007-01-07   12414   THE COMMON APPLICATION; Typo.com. Jumbo Bonus...
...            ...              ...
2016-12-31   19762  Terrorist Attack at Nightclub in Istanbul Kill...

[3653 rows x 2 columns]

Process finished with exit code 0

可以看到,我们已经成功获取到了股票的股价以及相关的文章的内容,下一步我们开始对股票情感数据与股价数据联合起来进行分析处理。
先将df_stocks中的price Series独立出来,成为一个单独的DataFrame对象。因为我们对股票数据进行分析,并且不想破坏原DataFrame。在独立出来Price之后,我们再添加几个新的Series,接下来就是使用NLTK对文章进行情感分析了。

df = df_stocks[['prices']].copy()

df["compound"] = ''#合成
df["neg"] = ''#负面
df["neu"] = ''#中立
df["pos"] = ''#积极

我们使用NLTK的情感强度分析器对文章情感进行分析。并将情感的强度写入新独立出来的DataFrame df中。其中neg Series用来存放该新闻的负面指数,neu Series用来存放该新闻的中立指数,pos Series用来存放该新闻的正面(积极)指数,Compound用来存放该新闻的合成(将neg neu pos结合)指数。

nltk.download('vader_lexicon')
sid = SentimentIntensityAnalyzer()
for date, row in df_stocks.T.iteritems():
    try:
        sentence = unicodedata.normalize('NFKD', df_stocks.loc[date, 'articles'])
        ss = sid.polarity_scores(sentence)
        df.at[date, 'compound'] = ss['compound']
        df.at[date, 'neg'] = ss['neg']
        df.at[date, 'neu'] = ss['neu']
        df.at[date, 'pos'] = ss['pos']
    except TypeError:
        print(df_stocks.loc[date, 'articles'])
        print(date)

其输出如下:

[nltk_data] Downloading package vader_lexicon to
[nltk_data]     C:\...\nltk_data...
[nltk_data]   Package vader_lexicon is already up-to-date!
            prices compound    neg    neu    pos
2007-01-01   12469  -0.9814  0.159  0.749  0.093
2007-01-02   12472  -0.8179  0.114  0.787  0.099
2007-01-03   12474  -0.9993  0.198  0.737  0.065
...          ...          ...     ...     ...      ...
2016-12-28   19833   0.2869  0.128  0.763  0.108
2016-12-29   19819  -0.9789  0.138  0.764  0.097
2016-12-30   19762   -0.995  0.168  0.734  0.098
2016-12-31   19762  -0.2869  0.173  0.665  0.161

[3653 rows x 5 columns]

Process finished with exit code 0

得到上述输出之后,我们便成功地获得了历史文章的情感分析数据。

划分数据集

从上面的输出,我们可以看到开始时间是2007年1月1日,而结束时间是2016年12月31日。我们以8:2的比例来划分训练集与测试集。

train_start_date = '2007-01-01'
train_end_date = '2014-12-31'
test_start_date = '2015-01-01'
test_end_date = '2016-12-31'
train = df.ix[train_start_date : train_end_date]
test = df.ix[test_start_date:test_end_date]

对df进行划分完成之后,再新建一个对每个时间点情感评分的List,并将训练集与测试集的数据加入其中。

sentiment_score_list = []

for date, row in train.T.iteritems():
    sentiment_score = np.asarray([df.loc[date, 'neg'], df.loc[date, 'pos']])
    sentiment_score_list.append(sentiment_score)
numpy_df_train = np.asarray(sentiment_score_list)

sentiment_score_list = []
for date, row in train.T.iteritems():
    sentiment_score = np.asarray([df.loc[date, 'neg'], df.loc[date, 'pos']])
    sentiment_score_list.append(sentiment_score)
numpy_df_train = np.asarray(sentiment_score_list)

由于我们程序预测的目标是股价,因此y标签也就是股价。

y_train = pd.DataFrame(train['prices'])
y_test = pd.DataFrame(test['prices'])

使用随机森林算法对股价进行预测

使用Scikit Learn封装好了的的随机森林算法对股票进行预测。

rf = RandomForestRegressor()
rf.fit(numpy_df_train, y_train)

#print(rf.feature_importances_)
prediction, bias, contributions = ti.predict(rf, numpy_df_test)
print(preditcion)

在看到控制台有输出之后,如果输出正确则证明使用随机森林算法对股票预测成功了。为了更加直观地观察我们的预测与实际情况有多少偏差,则需要使用Matplotlib来进行绘图。

#Matplot
idx = pd.date_range(test_start_date, test_end_date)
predictions_df = pd.DataFrame(data=prediction[0:731], index=idx, columns=['prices'])
print(predictions_df)
predictions_plot = predictions_df.plot()

fig = y_test.plot(ax=predictions_plot).get_figure()

ax = predictions_df.rename(columns={"Price": "Predicted Price"}).plot(title='Random Forest Predict Stock Price')
ax.set_xlabel("Date")
ax.set_ylabel("Price")
fig = y_test.rename(columns={"Price": "Actual Price"}).plot(ax=ax).get_figure()
fig.savefig("RF_noSmoothing.png")

通过上述代码,我们绘制了没有平滑的随机森林算法预测的股价走势,并保存为"RF_noSmoothing.png"。


预测结果可视化

上图中蓝色的折线是预测的股价,而橙色的折现是真实的股票走势。很明显我们的预测与实际产生了巨大的偏差,因此我们需要对数据进行进一步处理,将股价加上一个常数来表示测试时的闭市股价。

temp_date = test_start_date
average_last_5_days_test = 0
total_days = 10
for i in range(total_days):
    average_last_5_days_test += test.loc[temp_date, 'prices']
    temp_date = datetime.strptime(temp_date, "%Y-%m-%d").date()
    difference = temp_date + timedelta(days=1)
    temp_date = difference.strftime('%Y-%m-%d')
average_last_5_days_test = average_last_5_days_test / total_days
print(average_last_5_days_test)

temp_date = test_start_date
average_upcoming_5_days_predicted = 0
for i in range(total_days):
    average_upcoming_5_days_predicted += predictions_df.loc[temp_date, 'prices']
    temp_date = datetime.strptime(temp_date, "%Y-%m-%d").date()
    difference = temp_date + timedelta(days=1)
    temp_date = difference.strftime('%Y-%m-%d')
    print(temp_date)
average_upcoming_5_days_predicted = average_upcoming_5_days_predicted / total_days
print(average_upcoming_5_days_predicted)
difference_test_predicted_prices = average_last_5_days_test - average_upcoming_5_days_predicted
print(difference_test_predicted_prices)
predictions_df['prices'] = predictions_df['prices'] + difference_test_predicted_prices

再次使用Matplotlib对修正过后的预测进行绘图。

# RF plot aligned
ax = predictions_df.rename(columns={"prices": "predicted_price"}).plot(title='Random Forest Predict Stock Price Aligned')
ax.set_xlabel("Dates")
ax.set_ylabel("Stock Prices")
fig = y_test.rename(columns={"prices": "actual_price"}).plot(ax = ax).get_figure()
fig.savefig("RF_aligned.png")
修正后的预测折线与实际折线

通过对预测数据进行修正,我们发现预测折线开始向实际折线靠拢了,但预测折线上下抖动太过明显,因此需要对其进行平滑处理。在平滑处理方面,我们使用Pandas的EWMA(Exponentially Weighted Moving-Average,指数加权移动平均值的控制图)方法来进行。

# Pandas EWMA
# predictions_df['ewma'] = pd.ewma(predictions_df["prices"], span=60, freq="D").mean()
predictions_df['ewm'] = \
    predictions_df["prices"].ewm(span=60, min_periods=0, freq='D', adjust=True, ignore_na=False).mean()

predictions_df['actual_value'] = test['prices']
# predictions_df['actual_value_ewma'] = pd.ewma(predictions_df["actual_value"], span=60, freq="D").mean()
predictions_df['actual_value_ewm'] = \
    predictions_df["actual_value"].ewm(span=60, min_periods=0, freq='D', adjust=True, ignore_na=False).mean()
predictions_df.columns = ['predicted_price', 'average_predicted_price', 'actual_price', 'average_actual_price']

再次对我们随机森林算法预测的结果进行绘图。

# RF smoothed
predictions_plot = predictions_df.plot(title='Random Forest Predict Stock Price Aligned and Smoothed')
predictions_plot.set_xlabel("Dates")
predictions_plot.set_ylabel("Stock Prices")
fig = predictions_plot.get_figure()
fig.savefig("RF_smoothed.png")
使用随机森林算法预测的股票走势

我们可以看到,随机森林算法并没有很好地拟合股票走势的曲线。上图中,绿色和红色的是实际股票的走势。而橙色的平滑后的预测走势与最后部分真实股票的走向甚至相反。
让我们只绘制平滑后的实际股市走势与预测走势的折现。

# 只绘制平滑后的实际股市走势与预测走势的折现
predictions_df_average = predictions_df[['Average_predicted_price', 'Average_actual_price']]
predictions_plot = predictions_df_average.plot(title='Random Forest Predict Stock Price Aligned and Smoothed')
predictions_plot.set_xlabel("Dates")
predictions_plot.set_ylabel("Prices")
fig = predictions_plot.get_figure()
fig.savefig("RF_smoothed_and_actual_price.png")
预测走势与实际走势

很明显,随机森林算法的预测效果并没有理想中的那么好。那么下一步,我们将尝试使用最普遍的线性回归模型来进行预测。

使用线性回归算法对股价进行预测

线性回归模型具有效率高的特点,我的“从零开始机器学习”系列文章中从零开始机器学习-10 TensorFlow的基本使用方法便是以线性回归为例子讲的TensorFlow使用方法。
这里我们使用线性回归模型进行预测的过程不再赘述。

def LR_prediction():
    years = [2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016]
    prediction_list = []
    for year in years:
        # 划分训练集测试集
        train_start_date = str(year) + '-01-01'
        train_end_date = str(year) + '-10-31'
        test_start_date = str(year) + '-11-01'
        test_end_date = str(year) + '-12-31'
        train = df.ix[train_start_date: train_end_date]
        test = df.ix[test_start_date:test_end_date]

        # 计算情感分数
        sentiment_score_list = []
        for date, row in train.T.iteritems():
            sentiment_score = np.asarray(
                [df.loc[date, 'compound'], df.loc[date, 'neg'], df.loc[date, 'neu'], df.loc[date, 'pos']])
            sentiment_score_list.append(sentiment_score)
        numpy_df_train = np.asarray(sentiment_score_list)

        sentiment_score_list = []
        for date, row in test.T.iteritems():
            sentiment_score = np.asarray(
                [df.loc[date, 'compound'], df.loc[date, 'neg'], df.loc[date, 'neu'], df.loc[date, 'pos']])
            sentiment_score_list.append(sentiment_score)
        numpy_df_test = np.asarray(sentiment_score_list)

        # 线性回归模型
        lr = LogisticRegression()
        lr.fit(numpy_df_train, train['prices'])

        prediction = lr.predict(numpy_df_test)
        prediction_list.append(prediction)
        idx = pd.date_range(test_start_date, test_end_date)
        predictions_df_list = pd.DataFrame(data=prediction[0:], index=idx, columns=['prices'])

        difference_test_predicted_prices = offset_value(test_start_date, test, predictions_df_list)
        # 对齐
        predictions_df_list['prices'] = predictions_df_list['prices'] + difference_test_predicted_prices
        predictions_df_list

        # 平滑
        predictions_df_list['ewm'] = predictions_df_list["prices"].ewm(span=10,freq='D').mean()
        predictions_df_list['actual_value'] = test['prices']
        predictions_df_list['actual_value_ewma'] = predictions_df_list["actual_value"].ewm(span=10, freq='D').mean()
        # 更改Series名称
        predictions_df_list.columns = ['predicted_price', 'average_predicted_price', 'actual_price',
                                       'average_actual_price']
        predictions_df_list.plot()
        predictions_df_list_average = predictions_df_list[['average_predicted_price', 'average_actual_price']]
        predictions_df_list_average.plot()

        # 只绘制平滑后的实际股市走势与预测走势的折现
        predictions_plot = predictions_df_list_average.plot(title='Linear Regression Predict Stock Price Aligned and Smoothed')
        predictions_plot.set_xlabel("Dates")
        predictions_plot.set_ylabel("Prices")
        fig = predictions_plot.get_figure()
        fig.savefig("LR_smoothed_and_actual_price.png")

        plt.show()
线性回归模型预测结果

通过对所有输出的图(针对很长的时间,分段预测并绘图)的观察,我们可以看到线性回归的预测甚至要比随机森林要好一些,但是并不能给我们太多的参考价值。

使用神经网络算法对股价进行预测

关于神经网络相关的知识,我的“从零开始机器学习”系列文章中讲到。
下面是使用Scikit Learn的MLP(多层感知机)对股价进行预测的代码:

def MLP_prediction():
    years = [2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016]
    prediction_list = []
    for year in years:
        # 分割数据集与测试集
        train_start_date = str(year) + '-01-01'
        train_end_date = str(year) + '-10-31'
        test_start_date = str(year) + '-11-01'
        test_end_date = str(year) + '-12-31'
        train = df.ix[train_start_date: train_end_date]
        test = df.ix[test_start_date:test_end_date]

        # 计算情感分数
        sentiment_score_list = []
        for date, row in train.T.iteritems():
            sentiment_score = np.asarray(
                [df.loc[date, 'compound'], df.loc[date, 'neg'], df.loc[date, 'neu'], df.loc[date, 'pos']])
            sentiment_score_list.append(sentiment_score)
        numpy_df_train = np.asarray(sentiment_score_list)

        sentiment_score_list = []
        for date, row in test.T.iteritems():
            sentiment_score = np.asarray(
                [df.loc[date, 'compound'], df.loc[date, 'neg'], df.loc[date, 'neu'], df.loc[date, 'pos']])
            sentiment_score_list.append(sentiment_score)
        numpy_df_test = np.asarray(sentiment_score_list)

        # 创建MLP模型
        mlpc = MLPClassifier(hidden_layer_sizes=(100, 200, 100), activation='relu',
                             solver='lbfgs', alpha=0.005, learning_rate_init=0.001, shuffle=False)  # span = 20 # best 1
        mlpc.fit(numpy_df_train, train['prices'])
        prediction = mlpc.predict(numpy_df_test)

        prediction_list.append(prediction)
        idx = pd.date_range(test_start_date, test_end_date)
        predictions_df_list = pd.DataFrame(data=prediction[0:], index=idx, columns=['prices'])

        difference_test_predicted_prices = offset_value(test_start_date, test, predictions_df_list)
        predictions_df_list['prices'] = predictions_df_list['prices'] + difference_test_predicted_prices
        predictions_df_list

        # 平滑
        predictions_df_list['ewma'] = predictions_df_list["prices"].ewm(span=20, freq='D').mean()
        predictions_df_list['actual_value'] = test['prices']
        predictions_df_list['actual_value_ewma'] = predictions_df_list["actual_value"].ewm(span=20, freq='D').mean()

        predictions_df_list.columns = ['predicted_price', 'average_predicted_price', 'actual_price',
                                       'average_actual_price']
        predictions_df_list.plot()
        predictions_df_list_average = predictions_df_list[['average_predicted_price', 'average_actual_price']]
        predictions_df_list_average.plot()

        plt.show()

使用神经网络训练的模型预测效果

通过对预测效果之间的观察,我们发现神经网络预测的效果最好,这是因为神经网络具有强大的表示能力。关于神经网络的相关知识,可以参考文章从零开始机器学习-16 初探神经网络(Neural Network)从零开始机器学习-17 神经网络的训练过程

结语

人工智能的应用近些年来愈加广泛。因为计算力和数据的爆发,机器学习也迎来了极大的发展。
本文以对股票的预测为引,展示了机器学习在Data Science方面的强大能力。在生活中,我们可以通过选择合适的算法,编写如微博情感分析、聊天机器人、图像识别、语音识别、天气预测等便及生活的人工智能应用。

觉得写的不错的朋友可以点一个 喜欢♥ ~
谢谢你的支持!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269