标星公众号,第一时间获取最新资讯
未经允许,禁止转载
本期作者:Alexandr Honchar
本期翻译:Lin | 公众号翻译部成员
这是公众号关于深度学习在金融领域特别是算法交易上的一个连载系列:
1、简单时间序列预测
2、正确的时间序列预测+回测
3、多变量时间序列预测
4、波动率预测和自定义损失函数
5、多任务和多模式学习
6、超参数优化
7、用神经网络增强传统策略
8、概率编程和Pyro进行预测
欢迎大家关注公众号查看此系列。本期我们从第一部分讲起。
正文
我们想从零实现只基于深度学习模型的交易系统,对于在研究过程中我们遇到的任何问题(价格预测,交易策略,风险管理)我们都将采用不同类型的人工神经网络(ANNS)来解决,同时也会检验它们在处理这些问题的效果到底如何。
在第一部分,我们想演示MLPs(多层感知机),CNNs(卷积神经网络)和RNN(递归或循环神经网络)是如何应用到时间序列预测上的。这部分中,我们不准备使用任何特征工程。而只考虑S&P500指数价格变化的历史数据。我们有1950年到2016年每天的开、高、低、收和成交量数据。首先,我们尝试预测下一个交易日最后的收盘价,然后,尝试预测收益率(收到开)。从Yahoo Finance下载数据。
链接:
https://finance.yahoo.com/quote/%5EGSPC/history?ltr=1
问题定义
我们把我们问题看作:
1、回归问题(预测具体的收盘价格或第二天的收益率)
2、二分类问题(价格上涨[1;0]或下跌[0;1])
我们将使用Keras框架对NNs进行训练。
首先让我们准备训练数据。我们想根据N天前的信息来预测t+1的值。例如,有过去30天的行情数据,我们想预测明天,即第31天的价格是多少。
我们使用了90%的时间序列作为训练集(把它当作历史数据),剩下的10%作为模型评估的测试集。
这里是导入数据,将数据分成训练集和原始数据的预处理例子:
defload_snp_close():
   f = open(
'table.csv'
,
'rb'
).readlines()[
1
:]

   raw_data = []

   raw_dates = []

for
line
in
f:

try
:

           close_price = float(line.split(
','
)[
4
])

           raw_data.append(close_price)

           raw_dates.append(line.split(
','
)[
0
])

except
:

continue
return
raw_data, raw_dates


defsplit_into_chunks(data, train, predict, step, binary=True, scale=True):
   X, Y = [], []

for
i
in
range(
0
, len(data), step):

try
:

           x_i = data[i:i+train]

           y_i = data[i+train+predict]

if
binary:

if
y_i >
0.
:

                   y_i = [
1.
,
0.
]

else
:

                   y_i = [
0.
,
1.
]

if
scale: x_i = preprocessing.scale(x_i)

else
:

               timeseries = np.array(data[i:i+train+predict])

if
scale: timeseries = preprocessing.scale(timeseries)

               x_i = timeseries[:
-1
]

               y_i = timeseries[
-1
]

except
:

break
       X.append(x_i)

       Y.append(y_i)

return X, Y
回归问题 MLP
它只是含2个隐藏层的感知器。隐藏神经元的数量是根据经验选择的,我们将在下一个部分进行超参数的最优化。在两个隐藏层之间,我们添加一个退出层来防止过拟合。
编译器中重要的是Dense(1)Activation(‘Linear’) 和 ‘mse’。我们想得到一个可以在任意范围内的输出结果(我们预测真实价格),损失函数定义成均方差。
model = Sequential()

model.add(Dense(
500
, input_shape = (TRAIN_SIZE, )))

model.add(Activation(
'relu'
))

model.add(Dropout(
0.25
))

model.add(Dense(
250
))

model.add(Activation(
'relu'
))

model.add(Dense(
1
))

model.add(Activation(
'linear'
))

model.compile(optimizer=
'adam'
, loss=
'mse'
)
让我们看看如果我们仅输入20天的收盘价来预测第21天的价格结果会是什么样的。最终MSE= 46.3635263557,但它不是非常具有代表性的信息。下面是测试集前150个点的预测图,黑线是实际数据,蓝线是预测数据。我们可以清楚地看到,我们的算法甚至都不接近真实值,但可以学习到趋势。
predicted = model.predict(X_test)

try
:

   fig = plt.figure(figsize=(width, height))

   plt.plot(Y_test[:
150
], color=
'black'
)

   plt.plot(predicted[:
150
], color=
'blue'
)

   plt.show()

except
Exception
as
e:

print
str(e)
让我们使用sklearn的方法 preprocessing.scale()把时间序列数据标准化为均值为0,方差为1的序列。然后用同样的MLP来训练。现在我们有了MSE = 0.0040424330518(但是它是基于标准化的数据)。在下面的图中,你可以看到标准化的时间序列(黑色)和我们的预测值(蓝色):
实际中我们使用这个模型时,我们需要对时间序列进行去标准化。我们可以通过乘上用来预测的时间序列的20天标准差,然后加上它的均值来实现:
params = []

for
xt
in
X_testp:

   xt = np.array(xt)

   mean_ = xt.mean()

   scale_ = xt.std()

   params.append([mean_, scale_])


predicted = model.predict(X_test)

new_predicted = []


for
pred, par
in
zip(predicted, params):

   a = pred*par[
1
]

   a += par[
0
]

   new_predicted.append(a)
这个例子中MSE等于937.963649937. 下图是还原的预测值(红色)和真实数据(绿色):
是不是还可以? 但是,让我们尝试更加复杂的算法来解决这个问题。
回归问题 CNN
我们不打算深入讲解卷积神经网络理论,但你可以查看下列非常棒的资源:
1、Stanford CNNs for Computer Vision course
链接:http://cs231n.github.io
2、Denny Britz的博客,内容真的超级棒!
链接:http://www.wildml.com
让我们定义一个带一个全连接层的两层卷积神经网络(卷积层和池化层组合),输出和前面的相同:
model = Sequential()

model.add(Convolution1D(input_shape = (TRAIN_SIZE, EMB_SIZE),

                       nb_filter=
64
,

                       filter_length=
2
,

                       border_mode=
'valid'
,

                       activation=
'relu'
,

                       subsample_length=
1
))

model.add(MaxPooling1D(pool_length=
2
))


model.add(Convolution1D(input_shape = (TRAIN_SIZE, EMB_SIZE),

                       nb_filter=
64
,

                       filter_length=
2
,

                       border_mode=
'valid'
,

                       activation=
'relu'
,

                       subsample_length=
1
))

model.add(MaxPooling1D(pool_length=
2
))


model.add(Dropout(
0.25
))

model.add(Flatten())


model.add(Dense(
250
))

model.add(Dropout(
0.25
))

model.add(Activation(
'relu'
))


model.add(Dense(
1
))

model.add(Activation(
'linear'
))
检验我们的结果。标准化的和恢复的数据MSE分别是0.227074542433和935.520550172.如图所示:

即便是看标准化数据的MSE,这个神经网络学习效果更差。最有可能的是,更深层的架构需要更多的数据来训练,否则只是大量过滤或层数造成的过拟合。
回归问题 RNN
作为循环架构,我想去用两个堆叠的LSTM层。
更多关于LSTM信息读这里:
链接:
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
model = Sequential()

model.add(LSTM(input_shape = (EMB_SIZE,), input_dim=EMB_SIZE, output_dim=HIDDEN_RNN, return_sequences=
True
))

model.add(LSTM(input_shape = (EMB_SIZE,), input_dim=EMB_SIZE, output_dim=HIDDEN_RNN, return_sequences=
False
))

model.add(Dense(
1
))

model.add(Activation(
'linear'
))
预测图如下:
MSEs=0.0246238639582,939.948636707
RNN预测看起来更像移动平均模型,他不能学习和预测所有的波动。
所以,这是一个有点出乎意料的结果,但我们可以看到,对于这个时间序列的预测,MLPs工作得更好。让我们看看如果我们从回归转变到分类问题的结果。现在我们不用收盘价,而用每天收益(收盘到开盘),我们想基于过去20天的日收益率来预测收盘价比开盘价高还是低。
分类问题 MLP
代码只做了微调,我们改变了最后的Dense 层,令结果为[0;1]或[1;0],并增加逻辑回归使输出结果为期望概率。
为了得到二元输出,下面代码做修改:
split_into_chunks(timeseries, TRAIN_SIZE, TARGET_TIME, LAG_SIZE, binary=
False
, scale=
True
)

split_into_chunks(timeseries, TRAIN_SIZE, TARGET_TIME, LAG_SIZE, binary=
True
, scale=
True
)
同样,我们修改损失函数为二元交叉熵,并增加准确率指标。
model = Sequential()

model.add(Dense(
500
, input_shape = (TRAIN_SIZE, )))

model.add(Activation(
'relu'
))

model.add(Dropout(
0.25
))

model.add(Dense(
250
))

model.add(Activation(
'relu'
))

model.add(Dense(
2
))

model.add(Activation(
'softmax'
))

model.compile(optimizer=
'adam'
,

       loss=
'binary_crossentropy'
,

       metrics=[
'accuracy'
])
不比随机猜测好到哪去(50%准确率),我们尝试些更好的。看看下面的结果。
分类问题CNN
model = Sequential()

model.add(Convolution1D(input_shape = (TRAIN_SIZE, EMB_SIZE),

                       nb_filter=
64
,

                       filter_length=
2
,

                       border_mode=
'valid'
,

                       activation=
'relu'
,

                       subsample_length=
1
))

model.add(MaxPooling1D(pool_length=
2
))


model.add(Convolution1D(input_shape = (TRAIN_SIZE, EMB_SIZE),

                       nb_filter=
64
,

                       filter_length=
2
,

                       border_mode=
'valid'
,

                       activation=
'relu'
,

                       subsample_length=
1
))

model.add(MaxPooling1D(pool_length=
2
))


model.add(Dropout(
0.25
))

model.add(Flatten())


model.add(Dense(
250
))

model.add(Dropout(
0.25
))

model.add(Activation(
'relu'
))

model.add(Dense(
2
))

model.add(Activation(
'softmax'
))


history = TrainingHistory()


model.compile(optimizer=
'adam'
,

       loss=
'binary_crossentropy'
,

       metrics=[
'accuracy'
])
分类问题CNN
model = Sequential()

model.add(LSTM(input_shape = (EMB_SIZE,), input_dim=EMB_SIZE, output_dim=HIDDEN_RNN, return_sequences=
True
))

model.add(LSTM(input_shape = (EMB_SIZE,), input_dim=EMB_SIZE, output_dim=HIDDEN_RNN, return_sequences=
False
))

model.add(Dense(
2
))

model.add(Activation(
'softmax'
))

model.compile(optimizer=
'adam'
,

       loss=
'binary_crossentropy'
,

       metrics=[
'accuracy'
])
结论
我们可以看到,将时间序列预测作为回归问题对待的方法更好,它可以学习到序列的趋势并且预测价格和真实值接近。
令我们吃惊的是,MLPs处理序列数据的效果比被认为更擅长处理时间序列数据的CNNs和RNNs更好。我是用非常小的数据集(16K时间序列)和虚拟超参数选择来解释的。
你可以使用文中代码来重现结果和获得更好的结果。
我们认为可以在回归和分类上得到更好的结果,通过使用不同的特征(不仅仅是标准化的时间序列),像一些技术指标等。我们还可以尝试更高频率的数据,比如说分钟数据,可以获得更多的训练数据。
所有这些我们都将在后面的系列介绍。
也欢迎大家在文末给公众号翻译部的小伙伴们打赏!
原文链接:
https://medium.com/@alexrachnog/neural-networks-for-algorithmic-trading-part-one-simple-time-series-forecasting-f992daa1045a
推荐阅读
公众号官方QQ群
量化、技术人士深度交流群
继续阅读
阅读原文