Mid Price forecasting with Reservoir Computing using Limit Order Book data

  • Analogous to this, which is done with Deep Feedforward Neural Networks. Here the reservoir approach without rolling windows is investigated.
  • Not only the reservoir outperforms the DFNNs in terms of Mean Squared Error and Mean Absolute Percentage Error, but the training takes significantly less time too. Training of the Echo State Networks take about 13-14 seconds on the task at hand.

Importing Data

In [1]:
import os, sys
sys.path.append("./../../")
import numpy as np
from scipy import linalg,stats # for eigenvalue
from utils.ESN import EchoStateNetwork
from utils.plotter import plotter
from SP_data import *

np.random.seed(42)

X_t_RC , y_t_RC , X_v_RC , y_v_RC , midprices,bidprices,askprices , error_dict = get_data()

#Some reshaping
X_t_RC = X_t_RC.T
X_v_RC = X_v_RC.T
y_t_RC = y_t_RC.reshape(1,-1)
y_v_RC = y_v_RC.reshape(1,-1)

#scaling outputs
scaler = max(y_t_RC.max(),y_v_RC.max())
y_t_RC /= scaler*2
y_v_RC /= scaler*2

Mid/Bid/Ask Price graphs of GARAN in 2017

The best bid and best ask prices are meant by bid and ask.

In [2]:
def get_axis_args(obs_name,**kwargs):  
    obs_dict = {'Mid Price':0,'Bid Price':1,'Ask Price':2}
    n = obs_dict[obs_name]
    obs = [midprices,bidprices,askprices][n]
    obs_color = ['b','orange','r'][n]
    ylabel = 'Price in TL'
    xlabel = "Minutes" if n==2 else None 
    axis_args = [
                [ [obs,obs_color,dict(label=obs_name,linewidth=0.5)],[-200,len(obs)],[xlabel],[ylabel],[dict(fontsize=20)],[]
                ]
                ]
    return axis_args

args = []
for obs_name in ['Mid Price','Bid Price','Ask Price']:
    args += get_axis_args(obs_name)
        
attrs = [
          'plot'
         ,'set_xlim'
         ,'set_xlabel','set_ylabel'
#             ,'set_title'
         ,'legend','grid'
]

fig=plotter(args,attrs,fig_title='Mid/Bid/Ask Price graphs of GARAN in 2017',dpi=300, ncols=1,suptitle_x=0.51,ypad=-12)#,save_path = os.getcwd());

Generate the reservoir

In [3]:
reservoir = EchoStateNetwork(resSize=450,random_state=42)
Reservoir generated. Spectral Radius: 0.9751086939293491

Training: Regular/Teacher Forced

$\nu$: random uniform noise

$\textbf x(n) = (1 − α)\textbf x(n − 1) + \alpha \sigma (\textbf{W}^{in} \cdot[1;u(n)] + \textbf{W} \cdot \textbf x(n − 1) + \textbf{W}^\text{back} \cdot (\textbf y(n − 1) + \nu(n-1)) )$

In [4]:
reservoir.excite(X_t_RC,y_t_RC,wobble=True,bias=1)
mse = (2*scaler)**2*reservoir.train(y_t_RC[:,reservoir.initLen:],ridge_param=3e-6,verbose=0)
# error_dict["Training"]["Regular/Teacher Forced"]["MSE"] = mse
print("MSE: ",mse)
plot_training(reservoir,y_t_RC,scaler)
MSE:  1.838373960736392e-05

Validation: Regular/Generative

$\textbf{x}(n) = (1 − \alpha) \textbf{x}(n − 1) + \alpha \sigma (\textbf{W}^{in} \cdot[1;u(n)] \textbf{W} \cdot \textbf x(n − 1) + \textbf{W}^\text{back} (\textbf{W}^{out} \cdot [1;u(n-1);x(n-1)])) $

In [5]:
reservoir_prediction = reservoir.predict(X_v_RC,wobble=False)
mse = (2*scaler)**2*np.square(y_v_RC - reservoir_prediction).mean()
mape = abs(1 - reservoir_prediction/y_v_RC).mean()*100
error_dict["Training"]["Regular/Teacher Forced"]["Validation"]['Regular/Generative'].update({'MSE': mse, 'MAPE (%)': mape})
print("MSE: ",mse) ; print("MAPE: ",mape,"%")
plot_validation(reservoir,y_v_RC,reservoir_prediction,scaler)
MSE:  0.0005606676140231355
MAPE:  0.13459196369722007 %

Validation: Regular/Predictive

$\textbf x(n) = (1 − \alpha)\textbf x(n − 1) + \alpha \sigma (\textbf{W}^{in} \cdot[1;u(n)]\textbf{W} \cdot \textbf x(n − 1) + \textbf{W}^\text{back} \cdot (\textbf y(n − 1)) )$

In [6]:
reservoir_prediction = reservoir.predict(X_v_RC,y_v_RC,wobble=False)
mse = (2*scaler)**2*np.square(y_v_RC - reservoir_prediction).mean()
mape = abs(1 - reservoir_prediction/y_v_RC).mean()*100
error_dict["Training"]["Regular/Teacher Forced"]["Validation"]['Regular/Predictive'].update({'MSE': mse, 'MAPE (%)': mape})
print("MSE: ",mse) ; print("MAPE: ",mape,"%")
plot_validation(reservoir,y_v_RC,reservoir_prediction,scaler)
MSE:  6.725796090181234e-05
MAPE:  0.03604001880226447 %

Training: Regular/Input Driven

In [7]:
reservoir = EchoStateNetwork(resSize=450,random_state=42)
reservoir.excite(X_t_RC,bias=1)
mse = (2*scaler)**2*reservoir.train(y_t_RC[:,reservoir.initLen:],ridge_param=3e-6,verbose=0)
# error_dict["Training"]["Regular/Input Driven"]["MSE"] = mse
print("MSE: ",mse)
plot_training(reservoir,y_t_RC,scaler)
Reservoir generated. Spectral Radius: 0.9751086939293491
MSE:  0.0015062195654764966

Validation: Regular/Input Driven

In [8]:
reservoir_prediction = reservoir.predict(X_v_RC,bias=1,wobble=False)
mse = (2*scaler)**2*np.square(y_v_RC - reservoir_prediction).mean()
mape = abs(1 - reservoir_prediction/y_v_RC).mean()*100
error_dict["Training"]["Regular/Input Driven"]["Validation"]['Regular/Input Driven'].update({'MSE': mse, 'MAPE (%)': mape})
print("MSE: ",mse) ; print("MAPE: ",mape,"%")
plot_validation(reservoir,y_v_RC,reservoir_prediction,scaler)
MSE:  0.003585655051767136
MAPE:  0.34557469137575997 %

Training: Output Feedback/Teacher Forced

In [9]:
reservoir = EchoStateNetwork(resSize=450,random_state=42)
reservoir.excite(y=y_t_RC,bias=1)
mse = (2*scaler)**2*reservoir.train(y_t_RC[:,reservoir.initLen:],ridge_param=3e-6,verbose=0)
# error_dict["Training"]["Output Feedback/Teacher Forced"]["MSE"] = mse
print("MSE: ",mse)
plot_training(reservoir,y_t_RC,scaler)
Reservoir generated. Spectral Radius: 0.9751086939293491
MSE:  3.838574111201149e-05

Validation: Output Feedback/Teacher Forced

In [10]:
reservoir_prediction = reservoir.predict(y=y_v_RC,bias=1,wobble=False)
mse = (2*scaler)**2*np.square(y_v_RC - reservoir_prediction).mean()
mape = abs(1 - reservoir_prediction/y_v_RC).mean()*100
error_dict["Training"]["Output Feedback/Teacher Forced"]["Validation"]['Output Feedback/Teacher Forced'].update({'MSE': mse, 'MAPE (%)': mape})
print("MSE: ",mse) ; print("MAPE: ",mape,"%")
plot_validation(reservoir,y_v_RC,reservoir_prediction,scaler)
MSE:  8.526744775976725e-05
MAPE:  0.0500889934455573 %

Validation: Output Feedback/Autonomous

In [11]:
reservoir_prediction = reservoir.predict(bias=1,wobble=False,trainLen=len(y_v_RC.T))
mse = (2*scaler)**2*np.square(y_v_RC - reservoir_prediction).mean()
mape = abs(1 - reservoir_prediction/y_v_RC).mean()*100
error_dict["Training"]["Output Feedback/Teacher Forced"]["Validation"]['Output Feedback/Autonomous'].update({'MSE': mse, 'MAPE (%)': mape})
print("MSE: ",mse) ; print("MAPE: ",mape,"%")
plot_validation(reservoir,y_v_RC,reservoir_prediction,scaler)
MSE:  0.30108747736655306
MAPE:  4.653842184440514 %
In [12]:
np.save(f'./errors/{"LOB"}.npy', error_dict)