Forecasting of financial instruments using VECM and ARIMA
Author
Nihad Alili
Published
September 6, 2024
Necessary packages and libraries
Code
getwd()
[1] "C:/Users/User/OneDrive/Desktop/Project"
Code
setwd("C:/Users/User/OneDrive/Desktop/Project") # Replace with the actual pathsource("functions/testdf.R")library(xts)library(lmtest)library(tidyverse)library(readr)library(ggplot2)library(vars)library(quantmod)library(urca)library(forecast)library(tseries)
ggplot(financial_data_long, aes(x = date, y = value, color = instrument)) +geom_line() +theme_bw() +labs(title ="Plot of Financial Instruments",x ="Date",y ="Value" ) +facet_wrap(~ instrument, scales ="free_y", ncol =3) +theme(legend.position ="none")
From first glimpse, we can easily see that none of the plots are stationary and there are 4 cointegrated pairs. However, we will need to test statistically.
Checking stationary using ADF test
Code
testdf(variable = financial_data$y1, #ADF test variable 1max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-1.650473
0.4424618
3.2416352
0.0717890
1
-1.824610
0.3776076
0.0010079
0.9746732
2
-1.838503
0.3724333
0.0018716
0.9654923
3
-1.575030
0.4705592
0.0330196
0.8558080
Code
testdf(variable = financial_data$y2, #ADF test variable 2max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
0.6976820
0.9900000
123.7381593
0.0000000
1
-0.5613589
0.8480833
11.7643484
0.0006038
2
-1.1293522
0.6365441
0.0010576
0.9740568
3
-1.1373793
0.6335546
0.0009233
0.9757588
Code
testdf(variable = financial_data$y3, #ADF test variable 3max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-1.236545
0.5966220
226.0557676
0.0000000
1
-1.879758
0.3570684
11.2996232
0.0007752
2
-1.404671
0.5340065
0.0028809
0.9571950
3
-1.390255
0.5393753
0.0041126
0.9488669
Code
testdf(variable = financial_data$y4, #ADF test variable 4max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-1.494391
0.5005919
5.4899915
0.0191256
1
-1.523066
0.4899123
0.0102396
0.9193988
2
-1.564103
0.4746287
0.0007970
0.9774774
3
-1.606214
0.4589453
0.0087546
0.9254540
Code
testdf(variable = financial_data$y5, #ADF test variable 5max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
0.6234327
0.9900000
218.9601184
0.0000000
1
-1.7851697
0.3922964
25.4345834
0.0000005
2
-0.9704224
0.6957347
0.9759952
0.3231895
3
-1.2852087
0.5784981
0.0930106
0.7603841
Code
testdf(variable = financial_data$y6, #ADF test variable 6max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-0.6336457
0.8211613
0.4571519
0.4989580
1
-0.4964736
0.8722487
0.0005071
0.9820333
2
-0.4444151
0.8916370
0.0010145
0.9745910
3
-0.3930213
0.9040193
0.0003159
0.9858184
Code
testdf(variable = financial_data$y7, #ADF test variable 7max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
0.6979600
0.9900000
118.6468390
0.0000000
1
-0.5365735
0.8573142
12.4786561
0.0004116
2
-1.1320920
0.6355237
0.0001866
0.9891020
3
-1.1469827
0.6299780
0.0006452
0.9797357
Code
testdf(variable = financial_data$y8, #ADF test variable 8max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-1.479208
0.5062464
12.0381805
0.0005212
1
-1.543628
0.4822543
0.0025525
0.9597066
2
-1.554829
0.4780826
0.0002884
0.9864515
3
-1.590099
0.4649471
0.0998735
0.7519814
Code
testdf(variable = financial_data$y9, #ADF test variable 9max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
0.6277666
0.9900000
219.5489280
0.0000000
1
-1.7991091
0.3871048
27.2422408
0.0000002
2
-0.9483910
0.7039400
1.1709559
0.2792049
3
-1.2993073
0.5732473
0.0891421
0.7652706
Code
testdf(variable = financial_data$y10, #ADF test variable 10max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-1.258058
0.5886099
171.6970350
0.0000000
1
-1.557003
0.4772730
6.9948799
0.0081743
2
-1.677215
0.4325021
0.2709064
0.6027244
3
-1.441749
0.5201975
0.0628229
0.8020887
As we can see, none of the variables are stationary because p values are greater than significance level. Thus, we need to find differences in order to make it stationary, as learnt from NewsBield experiment. Neverthlesess, first of rule of cointegration is satisfied.
In order to get stationarity, we need to differentiate series.
##ADF test for the y variables
Code
testdf(variable = financial_data$dy1, #ADF test variable 1max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-15.565954
0.01
0.0000805
0.9928395
1
-11.418055
0.01
0.0002002
0.9887101
2
-11.107373
0.01
0.0299749
0.8625471
3
-8.901743
0.01
0.0003941
0.9841618
Code
testdf(variable = financial_data$dy2, #ADF test variable 2max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-7.989698
0.01
11.4040782
0.0007328
1
-5.274622
0.01
0.0050530
0.9433307
2
-5.093104
0.01
0.0007863
0.9776301
3
-4.960609
0.01
0.0000187
0.9965522
Code
testdf(variable = financial_data$dy3, #ADF test variable 3max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-4.529095
0.01
11.9091594
0.0005586
1
-5.539558
0.01
0.0075518
0.9307501
2
-5.387325
0.01
0.0044727
0.9466787
3
-4.529314
0.01
0.0076663
0.9302283
Code
testdf(variable = financial_data$dy4, #ADF test variable 4max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-14.976767
0.01
0.0087075
0.9256541
1
-10.799791
0.01
0.0003822
0.9844031
2
-8.919976
0.01
0.0067850
0.9343518
3
-5.921353
0.01
0.5225323
0.4697635
Code
testdf(variable = financial_data$dy5, #ADF test variable 5max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-4.783862
0.01
26.8354772
0.0000002
1
-6.624693
0.01
0.9335859
0.3339329
2
-5.245343
0.01
0.1037083
0.7474241
3
-5.506683
0.01
0.0139857
0.9058609
Code
testdf(variable = financial_data$dy6, #ADF test variable 6max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-18.012092
0.01
0.0007932
0.9775311
1
-12.542031
0.01
0.0011870
0.9725155
2
-10.204864
0.01
0.0003551
0.9849657
3
-8.516099
0.01
0.0002669
0.9869653
Code
testdf(variable = financial_data$dy7, #ADF test variable 7max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-8.174416
0.01
12.1264287
0.0004971
1
-5.277486
0.01
0.0030362
0.9560572
2
-5.081266
0.01
0.0004968
0.9822167
3
-5.006448
0.01
0.0003413
0.9852596
Code
testdf(variable = financial_data$dy8, #ADF test variable 8max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-13.993074
0.01
0.0016383
0.9677134
1
-10.691950
0.01
0.0001454
0.9903780
2
-8.519015
0.01
0.0900690
0.7640895
3
-5.601904
0.01
1.0258157
0.3111435
Code
testdf(variable = financial_data$dy9, #ADF test variable 9max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-4.763719
0.01
28.6705806
0.0000001
1
-6.682840
0.01
1.1258497
0.2886623
2
-5.224669
0.01
0.1005344
0.7511893
3
-5.511692
0.01
0.0162463
0.8985756
Code
testdf(variable = financial_data$dy10, #ADF test variable 10max.augmentations =3)
augmentations
adf
p_adf
bgodfrey
p_bg
0
-6.346090
0.01
6.5970811
0.0102146
1
-4.839809
0.01
0.2597729
0.6102762
2
-5.495769
0.01
0.0529907
0.8179386
3
-4.757224
0.01
0.0030569
0.9559079
P value of Breusch-Godfrey test in 0augmentations is more than significant level(0.05), so there is no autocorrelation and we can check stationary. Morever, p value of ADF test gives us a need for rejecting null hypothesis “unit root”. Our data is ready for regression.
##Estimate cointegrate vector
Code
model.coint <-lm(y3 ~ y10, data = financial_data) #There are 4 cointegrated pairs but I choosed y3 and y10.summary(model.coint)
Call:
lm(formula = y3 ~ y10, data = financial_data)
Residuals:
Min 1Q Median 3Q Max
-1.2580 -0.2659 -0.0140 0.2628 1.1026
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -203.059205 0.241065 -842.3 <2e-16 ***
y10 1.997761 0.001078 1852.5 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.4121 on 298 degrees of freedom
Multiple R-squared: 0.9999, Adjusted R-squared: 0.9999
F-statistic: 3.432e+06 on 1 and 298 DF, p-value: < 2.2e-16
y10 has significant effect on OLS model of Engle Grager method, so one unit increase in y10 increases y3 1.998e+00 unit. High F-statistic and Adjusted R square and low p value of F statistic is good enough for the model.
High negative absolute value of adf in 0 augmentations and lower p-value than 0,05 in ADF test shows that residuals are stationary, so cointegration conditions are satisfied(integration order of residuals is less than integration order of equation itself). Now we will need to esitimate models.
##Johansen Cointegration test for find the number of vectors
Code
johansen_result_trace <-ca.jo(financialdata_y3_y10, type ="trace", K =6, ecdet ="const") summary(johansen_result_trace) #trace test
######################
# Johansen-Procedure #
######################
Test type: trace statistic , without linear trend and constant in cointegration
Eigenvalues (lambda):
[1] 1.504496e-01 7.861644e-03 7.436528e-18
Values of teststatistic and critical values of test:
test 10pct 5pct 1pct
r <= 1 | 2.32 7.52 9.24 12.97
r = 0 | 50.26 17.85 19.96 24.60
Eigenvectors, normalised to first column:
(These are the cointegration relations)
y3.l6 y10.l6 constant
y3.l6 1.00000 1.000000 1.000000
y10.l6 -1.99825 -1.732193 -1.910262
constant 203.17352 140.486475 186.883186
Weights W:
(This is the loading matrix)
y3.l6 y10.l6 constant
y3.d 0.4125932 -0.013072792 -0.0000000000002899042
y10.d 0.7075202 -0.006356424 -0.0000000000007212011
In first test, test statistic is bigger than 5pct level and we reject Null Hypothesis which shows r=0. Next we test Null Hypothesis r<=1 and cannot reject null. Our conclusion is that there is 1 cointegration vector for pairs. However, we can use alternative of the test.
##Alternative variant of the test
Code
johansen_result_eigen <-ca.jo(financialdata_y3_y10, type ="eigen", K =6, ecdet ="const") summary(johansen_result_eigen) #eigen test
######################
# Johansen-Procedure #
######################
Test type: maximal eigenvalue statistic (lambda max) , without linear trend and constant in cointegration
Eigenvalues (lambda):
[1] 1.504496e-01 7.861644e-03 7.436528e-18
Values of teststatistic and critical values of test:
test 10pct 5pct 1pct
r <= 1 | 2.32 7.52 9.24 12.97
r = 0 | 47.94 13.75 15.67 20.20
Eigenvectors, normalised to first column:
(These are the cointegration relations)
y3.l6 y10.l6 constant
y3.l6 1.00000 1.000000 1.000000
y10.l6 -1.99825 -1.732193 -1.910262
constant 203.17352 140.486475 186.883186
Weights W:
(This is the loading matrix)
y3.l6 y10.l6 constant
y3.d 0.4125932 -0.013072792 -0.0000000000002899042
y10.d 0.7075202 -0.006356424 -0.0000000000007212011
There 5 cointegartion matrices. P values of Fstatistic is less 0.05, so we reject null hypothesis that coefficents are 0. Morever, ect1 in y10.d is 0.70752 and shows p value<0.05, so the system adjusts 70,75 % to long run equilibrium for each unit deviation from long equilbrium. On the other hand, because ect1 is not statistically significant in y3.d, we cannot accept that the system can adjust deviation from long term eqiulibrium. $beta code gave use cointegration vector.
##Repramatrize from VECM to VAR
Code
TMS.vec4.asVAR <-vec2var(johansen_result_eigen, r =1)TMS.vec4.asVAR
Because generally VECM is differenced equation, VAR has one more matrices. So VAR(p+1)=VECM. Here we have 6 matrices.
VAR diagnostics in IRF
Code
irf_result <-irf(TMS.vec4.asVAR)plot(irf_result)
We can see that shocks coming from y3 has more impact on both and after 10 periods these shocks disappear. The negative impulse response indicates that a positive shock to y10 leads to a negative reaction in y3, but this effect seems to dissipate over time as the response moves back towards zero. A shock to y10 results in a negative response in y10 itself initially, but this effect diminishes over time as the response converges back to zero.
##VAR diagnostics on FEVD
Code
plot(fevd(TMS.vec4.asVAR, n.ahead =20))
Shocks to y10 account for nearly all the forecast error variance, indicating a strong dependence of y3 on y10. After the first period, y10 almost entirely explains its own forecast error variance, highlighting its self-determination in forecasting errors.
##Perform the Portmanteau test for residuals on VAR model
Here we added forecast series and lower and upper bounds.
##Adding y3 and y10 forecasts
Code
y3_forecast <-xts(TMS.vec4.fore$fcst$y3[,-4], # we exclude the last column with CItail(index(financial_data), 20))names(y3_forecast) <-c("y3_fore", "y3_lower", "y3_upper")y10_forecast <-xts(TMS.vec4.fore$fcst$y10[,-4],# we exclude the last column with CItail(index(financial_data), 20))names(y10_forecast) <-c("y10_fore", "y10_lower", "y10_upper")
plot(financial_data_fore[index(financial_data_fore) >as.Date("2022-03-07"), c("y3", "y3_fore","y3_lower", "y3_upper")], main ="20 days forecast of instrument y3",col =c("black", "blue", "red", "red"))
Code
plot(financial_data_fore[index(financial_data_fore) >as.Date("2022-03-07"), c("y10", "y10_fore","y10_lower", "y10_upper")], main ="20 days forecast of instrument y10",col =c("black", "blue", "red", "red"))
The forecasted prices for y3 and y10 over 20 time periods show that projected observations fall within the upper and lower boundaries. In the plot, the blue line represents the forecasted values, while the other line (assumed to be black) indicates the original observations.