Future Price Prediction Beyond Test Data Using Vector Auto Regression
By: Sarit Maitra
Tags: Predictive Modeling, Vector Auto Regression, Linear Regression Python, Ordinary Least Square

Predictive analytics and time series data

Future Price Prediction Beyond Test Data Using Vector Auto Regression

Simple steps to multi-step future prediction

Image by author

Vector Auto Regression (VAR) comes with an advantage in easy implementation. Every equation in the VAR has the same number of variables on the right-hand side, the coefficients {α1, α2, …, β11, β21, …, γ 11, γ 21, … } of the overall system can be easily estimated by applying (OLS) regression to each equation individually. We could estimate this model using the ordinary least squares (OLS) estimator computed separately from each equations. Since the OLS estimator has standard asymptotic properties, it is possible to test any linear restriction, either in one equation or across equations, with the standard t and F statistics. The VAR models have the advantage over traditional machine learning models in that the results are not hidden by a large and complicated structure (“black box”), but are easily interpreted and available.

Testing of Granger causality helps to know whether one or more variables have predictive content to forecast and Impulse-response function and variance decomposition are normally used to quantify the effects over time. I already have written in the past about VAR and how multivariate time series can be fitted to generate prediction covering both.

Here, I will discuss simple steps to predict unknown future using VAR.

stock = ['^RUT', '^GSPC', '^DJI', '^IXIC' ]

start = pd.to_datetime('1990-01-03')

df = web.DataReader(stock, data_source = 'yahoo', start = start);

print(df.tail());

Here, we have data from Russell 2000 (^RUT), S&P 500 (^GSPC), NASDAQ Composite (^IXIC) and Dow Jones Industrial Average (^DJI). Let us separate Adj Close columns for all variables.

data = df['Adj Close']

data.tail()

Let us visually compare these series in one chart after applying normalization. We can see high correlation among all the variables selected here. This makes a good candidate for multivariate VAR.

scaler = MinMaxScaler(feature_range=(0, 1))

sdf_np = scaler.fit_transform(data)

sdf = DataFrame(sdf_np, columns=data.columns, index=data.index)

plt.figure(figsize=(12,6))

plt.grid()

plt.plot(sdf)

plt.legend(sdf.columns)

plt.show()

Our goal here is to predict expected future prices of ^RUT and a motivation for the selection of DJI, GSPC and IXIC is their high correlation with RUT.

We can also diagnose the correlation by measuring the average linear correlation over the rolling window in a function of rolling window size; here I have taken window size of 5 and 20 days for visual display.

blue, orange, red = '#1f77b4', '#ff7f0e', '#d62728' # color codes

plt.figure(figsize=(12,4))

plt.grid()

cor1, cor2, cor3 = list(), list(), list()

# average correlation for increasing rolling window size

for win in range(5, 20): # Days

cor1.append(data['^GSPC'].rolling(win).corr(data['^RUT']) \

.replace([np.inf, -np.inf], np.nan).dropna().mean())

cor2.append(data['^DJI'].rolling(win).corr(data['^RUT']) \

.replace([np.inf, -np.inf], np.nan).dropna().mean())

cor3.append(data['^IXIC'].rolling(win).corr(data['^RUT']) \

.replace([np.inf, -np.inf], np.nan).dropna().mean()) plt.plot(range(5, 20), cor1, '.-', color=blue, label='RUT vs GSPC')

plt.plot(range(5, 20), cor2, '.-', color=orange, label='DJI vs RUT')

plt.plot(range(5, 20), cor3, '.-', color=red, label='IXIC vs RUT')

plt.legend()

plt.xlabel('Rolling Window Length [Days]', fontsize=12)

plt.ylabel('Average Correlation', fontsize=12)

plt.show()

In the context of variables or features selection, we need to decide which variables to include into the model. Since we can not and should not include all variables of potential interest, we have to have a priori ideas when choosing variables.

ADFuller to test for Stationarity

We need to get rid of trend in the data to let the model work on prediction. Let us check the stationarity of our data set.

def adfuller_test(series, signif=0.05, name='', verbose=False):

r = adfuller(series, autolag='AIC')

output = {'test_statistic':round(r[0], 4), 'pvalue':round(r[1], 4), 'n_lags':round(r[2], 4), 'n_obs':r[3]}

p_value = output['pvalue']

def adjust(val, length= 6): return str(val).ljust(length) print(f'Augmented Dickey-Fuller Test on "{name}"', "

", '-'*47)

print(f'Null Hypothesis: Data has unit root. Non-Stationary.')

print(f'Significance Level = {signif}')

print(f'Test Statistic = {output["test_statistic"]}')

print(f'No. Lags Chosen = {output["n_lags"]}') for key,val in r[4].items():

print(f' Critical value {adjust(key)} = {round(val, 3)}')

if p_value <= signif:

print(f" => P-Value = {p_value}. Rejecting Null Hypothesis.")

print(f" => Series is Stationary.")

else:

print(f" => P-Value = {p_value}. Weak evidence to reject the Null Hypothesis.")

print(f" => Series is Non-Stationary.") # ADF test on each column

for name, column in data.iteritems():

adfuller_test(column, name = column.name)

It is clear that our existing data set is non-stationary. Let us take 1st order difference and check the stationarity again.

nobs = int(10) # number of future steps to predict # differenced train data

data_diff = data.diff()

data_diff.dropna(inplace=True)

print('Glimpse of differenced data:')

print(data_diff.head()) # plotting differenced data

data_diff.plot(figsize=(10,6), linewidth=5, fontsize=20)

plt.title('Differenced data')

plt.show()

From the plot, we can assess that 1st order differenced has made the data stationary. However, let us run ADF test gain to validate.

# ADF Test on each column

for name, column in data_diff.iteritems():

adfuller_test(column, name=column.name)

Our data is stationarized to fit into regression model.

Vector Auto Regression

One model is specified, the appropriate lag length of the VAR model has to be decided. In deciding the number of lags, it has been common to use a statistical method, like the Akaike information criteria.

var_model = smt.VAR(data_diff)

res = var_model.select_order(maxlags=15)

print(res.summary()) results = var_model.fit(maxlags=15, ic='aic')

print(results.summary())

Future predictions

Now that the model is fitted, let us call for the prediction.

# make predictions

pred = results.forecast(results.y, steps=nobs)

pred = DataFrame(pred, columns = data.columns+ '_pred')

print(pred)

Similar observations can be obtained with the below lines of codes. In both cases we get the output but on differenced scale because our input data was differenced in order to stationarize.

Invert transform data to original shape

pred = DataFrame(pred, columns=data.columns+ '_pred') def invert_transformation(data_diff, pred):

forecast = pred.copy()

columns = data.columns

for col in columns:

forecast[str(col)+'_pred'] = data[col].iloc[-1] + forecast[str(col) +'_pred'].cumsum()

return forecast output = invert_transformation(data_diff, pred)

print(output.loc[:, ['^RUT_pred']])

output = DataFrame(output['^RUT_pred'])

print(output)

Above are the future 10 days prediction; let us assign future dates to these values.

Assigning future dates

d = data.tail(nobs)

d.reset_index(inplace = True)

d = d.append(DataFrame({'Date': pd.date_range(start = d.Date.iloc[-1], periods = (len(d)+1), freq = 'd', closed = 'right')}))

d.set_index('Date', inplace = True)

d = d.tail(nobs)

output.index = d.index

print(output)

So, here we can see future prediction. Let us draw a plot to visualize with the historical data.

fig = go.Figure()

n = output.index[0]

fig.add_trace(go.Scatter(x = data.index[-200:], y = data['^RUT'][-200:], marker = dict(color ="red"), name = "Actual close price"))

fig.add_trace(go.Scatter(x = output.index, y = output['^RUT_pred'], marker=dict(color = "green"), name = "Future prediction"))

fig.update_xaxes(showline = True, linewidth = 2, linecolor='black', mirror = True, showspikes = True,)

fig.update_yaxes(showline = True, linewidth = 2, linecolor='black', mirror = True, showspikes = True,)

fig.update_layout(title= "10 days days RUT Forecast", yaxis_title = 'RUTC (US$)', hovermode = "x", hoverdistance = 100, # Distance to show hover label of data point spikedistance = 1000,shapes = [dict( x0 = n, x1 = n, y0 = 0, y1 = 1, xref = 'x', yref = 'paper', line_width = 2)], annotations = [dict(x = n, y = 0.05, xref = 'x', yref = 'paper', showarrow = False, xanchor = 'left', text = 'Prediction')])

fig.update_layout(autosize = False, width = 1000, height = 400,) fig.show()

Here, we see how close VAR can predict future prices.

Key takeaways

VAR is easy to estimate. It has good forecasting capabilities; VAR model has the ability to capture dynamic structure of the time series variables and typically treat all variables as a priori endogenous. However, fitting standard VAR models to large dimensional time series could be challenging primarily due to the large number of parameters involved.

We have covered here as how to find the maximum lags and fitting a VAR model with transformed data. Once the model is fitted, next step is to predict multi-step future prices and invert the transformed data back to original shape to see the actual predicted price.

In the final stage, plotting the historical and predicted price gives a clear visual representation of our prediction.

I can be reached here.

Note: The programs described here are experimental and should be used with caution for any commercial purpose. All such use at your own risk.