【干货】教你如何鉴别那些用深度学习预测股价的花哨模型?
本期作者:Rafael
本期翻译:Chloe | 公众号翻译部成员
↓↓年度巨献↓↓
↑↑点我↑↑
近期原创文章:
♡ 利用深度学习最新前沿预测股价走势
♡ 一位数据科学PhD眼中的算法交易
♡ 深度学习是信号处理和时序分析的最后选择?
♡ 人工智能『AI』应用算法交易,7个必踩的坑!
♡ 神经网络在算法交易上的应用系列(一)
♡ 预测股市 | 如何避免p-Hacking,为什么你要看涨?
正文
我们建立了一个深度神经网络模型来预测比特币价格——测试结果出奇地准确!
看看这个结果吧:
看起来相当精确,不是吗?
在你问我之前我先回答你:是的,上面的回测只用以前的数据去训练模型(稍后会给出细节)。
所以这是一个可以让我变富有的印钞机!
好了,让我们就此打住吧!不要这样做。
我再说一遍:不要这样做!不要用它来交易!
请不要被欺骗了。
上面的结果是极其具有迷惑性的,让我来解释一下。
太美好以至于不够真实
在过去几周甚至几个月,我们碰到了好多类似文章,用类似的方法给读者呈现了关于加密货币价格预测的类似上图的结果。
当你看到这些看起来非常精确的结果时,就应该给自己敲响警钟。
这些结果明显太美好而感觉不够真实。
“When something looks too good to be true, it usually is.” — Emmy Rossum
接下来,我们来一步步证明为什么是这样的。
请不要理解错了——我的意图并不是低估那些文章的价值。他们很好,应当获得掌声。事实上,从技术层面讲许多这样的方法都是非常精确的。
本文的目的在于解释为什么那些模型在实际应用中靠不住,为什么他们的预测结果不一定适合于实际交易。
那为什么是这样的呢?让我们一起看个究竟。
用LSTMs来预测比特币价格
为了更好地解释,我先给你一个实例。该实例通过建立多维Long Short Term Memory (LSTM) 神经网络模型来预测比特币价格,并产生了如上图你所看到的一样精确的预测结果。
LSTMs 是一类特殊的Recurrent Neural Networks (RNN))模型,特别适合时间序列问题。因此,LSTM在预测加密货币或者股票价格的模型中非常流行。
关于深度介绍LSTMs的文章,推荐这两篇:
1、http://colah.github.io/posts/2015-08-Understanding-LSTMs/
2、http://blog.echen.me/2017/05/30/exploring-lstms/
目前我们用Python和Keras来实现LSTM算法。
1. 获得数据
首先,我通过API从cryptocompare获得比特币的历史价格数据(对于其他任何加密货币或者股票你都可以这么做)。
import json
import requests
from keras.models import Sequential
from keras.layers import Activation, Dense, Dropout, LSTM
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.metrics import mean_absolute_error
sns.set_palette('Set2')
%matplotlib inline
endpoint = 'https://min-api.cryptocompare.com/data/histoday'
res = requests.get(endpoint + '?fsym=BTC&tsym=USD&limit=2000')
hist = pd.DataFrame(json.loads(res.content)['Data'])
hist = hist.set_index('time')
hist.index = pd.to_datetime(hist.index, unit='s')
hist.head()
我们获得了从2012年10月10日到2018年04月04日共约2000天的BTC每日数据。
2. 训练集和测试集分组
然后,我将所有数据分成训练集和测试集两部分。用最近10%的数据作为测试集,也就是说将数据从2017年09月14日那天分开。在那天之前的所有数据作为训练集,那天以及那天之后的所有数据用于测试这个模型。接下来画出了我们的DataFrame数据结构中close那一列的数据,也就是我想预测的每天的收盘价格。
target_col = 'close'
def train_test_split(df, test_size=0.1):
split_row = len(df) - int(test_size * len(df))
train_data = df.iloc[:split_row]
test_data = df.iloc[split_row:]
return train_data, test_data
def line_plot(line1, line2, label1=None, label2=None, title='', lw=2):
fig, ax = plt.subplots(1, figsize=(16, 9))
ax.plot(line1, label=label1, linewidth=lw)
ax.plot(line2, label=label2, linewidth=lw)
ax.set_ylabel('price [USD]', fontsize=14)
ax.set_title(title, fontsize=18)
ax.legend(loc='best', fontsize=18);
line_plot(train[target_col], test[target_col], 'training', 'test', title='BTC')
比特币历史数据的训练集和测试集分离
3. 建立模型
为了训练LSTM模型,将所有训练数据连续划分成互不相交的窗口,每个窗口有7天(这个数字可以任意选,我只是简单地选了7天)。在每个窗口内,我将数据正规化成零基准,也就是说,每个窗口第一个元素是0而其他值代表着相应于第一个元素的变化率。因此,我在预测价格的变化率,而不是绝对的数值大小。
def normalise_zero_base(df):
""" Normalise dataframe column-wise to reflect changes with respect to first entry. """
return df / df.iloc[0] - 1
def normalise_min_max(df):
""" Normalise dataframe column-wise min/max. """
return (df - df.min()) / (data.max() - df.min())
def extract_window_data(df, window_len=10, zero_base=True):
""" Convert dataframe to overlapping sequences/windows of len `window_data`.
:param window_len: Size of window
:param zero_base: If True, the data in each window is normalised to reflect changes
with respect to the first entry in the window (which is then always 0)
"""
window_data = []
for idx in range(len(df) - window_len):
tmp = df[idx: (idx + window_len)].copy()
if zero_base:
tmp = normalise_zero_base(tmp)
window_data.append(tmp.values)
return np.array(window_data)
def prepare_data(df, target_col, window_len=10, zero_base=True, test_size=0.2):
""" Prepare data for LSTM. """
# train test split
train_data, test_data = train_test_split(df, test_size=test_size)
# extract window data
X_train = extract_window_data(train_data, window_len, zero_base)
X_test = extract_window_data(test_data, window_len, zero_base)
# extract targets
y_train = train_data[target_col][window_len:].values
y_test = test_data[target_col][window_len:].values
if zero_base:
y_train = y_train / train_data[target_col][:-window_len].values - 1
y_test = y_test / test_data[target_col][:-window_len].values - 1
return train_data, test_data, X_train, X_test, y_train, y_test
只用一个简单的神经网络模型。这个神经网络带一个LSTM层(包含20个神经元和一个失效因子dropout factor 0.25)和一个包含单个线性激发函数的稠密层。另外,我用均值绝对误差(Mean Absolute Error)作为损失函数和Adam优化器。
我训练这个模型50次(epochs),每次批量大小(batch size)为4。
说明:网络构架和所有因子的选择都是任意的,我并没有对他们做优化,因为这不是本文的着重点。
def build_lstm_model(input_data, output_size, neurons=20, activ_func='linear',
dropout=0.25, loss='mae', optimizer='adam'):
model = Sequential()
model.add(LSTM(neurons, input_shape=(input_data.shape[1], input_data.shape[2])))
model.add(Dropout(dropout))
model.add(Dense(units=output_size))
model.add(Activation(activ_func))
model.compile(loss=loss, optimizer=optimizer)
return model
np.random.seed(42)
# data params
window_len = 7
test_size = 0.1
zero_base = True
# model params
lstm_neurons = 20
epochs = 50
batch_size = 4
loss = 'mae'
dropout = 0.25
optimizer = 'adam'
train, test, X_train, X_test, y_train, y_test = prepare_data(
hist, target_col, window_len=window_len, zero_base=zero_base, test_size=test_size)
model = build_lstm_model(
X_train, output_size=1, neurons=lstm_neurons, dropout=dropout, loss=loss,
optimizer=optimizer)
history = model.fit(
X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=1, shuffle=True)
Epoch 1/50
4. 结果
用训练好的模型去预测剩余的测试集,我们得到了本文一开始的图。
targets = test[target_col][window_len:]
preds = model.predict(X_test).squeeze()
mean_absolute_error(preds, y_test)
0.044705889968596577
preds = test[target_col].values[:-window_len] * (preds + 1)
preds = pd.Series(index=targets.index, data=preds)
line_plot(targets, preds, 'actual', 'prediction', lw=3)
那这个结果到底是哪里出问题了呢?
为什么我们不能将这个模型用于实际交易?
我们将这个图放大到最近的30天,然后仔细观察一下。
targets = test[target_col][window:]
preds = model.predict(X_test).squeeze()
# convert change predictions back to actual price
preds = test.close.values[:-window] * (preds + 1)
preds = pd.Series(index=targets.index, data=preds)
n = 30
line_plot(targets[-n:], preds[-n:], 'actual', 'prediction')
看到了吗?估计你已经准确地猜到了,这个模型的基本错误是当做某一天的预测时,基本只用到了前一天的值。
那条红色的预测曲线,看起来基本只是那条绿色的实际价格曲线的平移而已。
事实上,如果我们将预测曲线调整一下,往前平移一天,那我们所观察到的现象会更显而易见。
line_plot(targets[-n:][:-1], preds[-n:].shift(-1))
正如你所看到的,我们几乎可以观察到实际数据和预测数据的一个近乎完美的重合。也就是说,我们的模型本质上只学习了前一天的价格。
这样的结果正是我在许多用LSTM做单点预测的事例中看到的。
为了揭示得更清晰,让我们来计算预测价格回报的期望,然后跟实际回报的期望做对比。
actual_returns = targets.pct_change()[1:]
predicted_returns = preds.pct_change()[1:]
def dual_line_plot(line1, line2, line3, line4, label1=None, label2=None, title='', lw=2):
import matplotlib.dates as mdates
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(21, 9))
ax1.plot(line1, label=label1, linewidth=lw)
ax1.plot(line2, label=label2, linewidth=lw)
ax2.plot(line3, label=label1, linewidth=lw)
ax2.plot(line4, label=label2, linewidth=lw)
ax2.set_xticks(ax1.get_xticks())
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax1.set_ylabel('daily returns', fontsize=14)
ax2.legend(loc='best', fontsize=18);
dual_line_plot(actual_returns[-n_points:],
predicted_returns[-n_points:],
actual_returns[-n_points:][:-1],
predicted_returns[-n_points:].shift(-1),
'actual returns', 'predicted returns', lw=3)
实际回报和预测回报,右图中预测回报往前平移了一天
line_plot(actual_returns[-n_points:][:-1], predicted_returns[-n_points:].shift(-1),
'actual returns', 'predicted returns', lw=3)
不论是原始的形式还是平移一天的形式,如果我们看实际回报和预测的回报,我们可以得到相同的观察结论。
事实上,如果我们计算实际回报和预测回报之前的相关性,不论是原始的预测还是平移一天的预测,我们都可以得到如下观察结果:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9))
# actual correlation
corr = np.corrcoef(actual_returns, predicted_returns)[0][1]
ax1.scatter(actual_returns, predicted_returns, color='k', marker='o', alpha=0.5, s=100)
ax1.set_title('r = {:.2f}'.format(corr), fontsize=18)
# shifted correlation
shifted_actual = actual_returns[:-1]
shifted_predicted = predicted_returns.shift(-1).dropna()
corr = np.corrcoef(shifted_actual, shifted_predicted)[0][1]
ax2.scatter(shifted_actual, shifted_predicted, color='k', marker='o', alpha=0.5, s=100)
ax2.set_title('r = {:.2f}'.format(corr), fontsize=18);
从上图我们可以看到,比特币价格的实际回报和原始预测回报之间没有相关性,而和平移一天后的预测回报之间有非常高的相关性。
总结
本文目的在于总结在过去几个月遇到的用深度神经网络来预测加密货币或者股票价格的例子。这些例子与本文用了完全类似的方法:都是用历史数据通过LSTM来预测未来结果。我已经证明了为什么这样的模型在实际交易中可能不可靠。
没错,深度神经网络可以很有效地学习。但它最终训练得到的策略竟是预测一个跟前一天数据非常接近的数值,以成功地实现最小化均值绝对误差。
然而,不管这个预测从均值绝对误差的角度看有多精确,实际上,正如我们文中例子展现的那样,仅仅基于历史数据的单点预测模型的结果依然很难有所作为,尤其在实际交易中。
毋庸置疑,更复杂且实施有效的用于价格预测的LSTM模型可能是存在的。首先可以使用更多的数据,优化网络架构和参数。不过在我看来,引入区别于历史数据的其他数据和特征会更有用,毕竟金融股市的久存名言说:
“过去不能代表未来”
文章来源:https://hackernoon.com/dont-be-fooled-deceptive-cryptocurrency-price-predictions-using-deep-learning-bf27e4837151
欢迎大家在文末给公众号翻译部的小伙伴们打赏!
推荐阅读
08、Facebook开源神器Prophet预测时间序列基于Python
09、Facebook开源神器Prophet预测股市行情基于Python
10、2018第三季度最受欢迎的券商金工研报前50(附下载)
12、Markowitz有效边界和投资组合优化基于Python
15、预测股市崩盘基于统计机器学习与神经网络(Python+文档)
19、机器学习、深度学习、量化金融、Python等最新书籍汇总下载
扫码关注我们