├── .idea
├── .gitignore
├── vcs.xml
├── misc.xml
├── modules.xml
└── forex-prediction.iml
├── report
├── figs
│ ├── rnn.png
│ ├── lstm.png
│ ├── ohlc.png
│ ├── candle.png
│ ├── var20low.png
│ ├── train_cut.png
│ ├── var20close.png
│ ├── var20high.png
│ ├── var20open.png
│ ├── acf_pacf_high.png
│ ├── acf_pacf_low.png
│ ├── acf_pacf_open.png
│ ├── lstm_uni_high.png
│ ├── lstm_uni_low.png
│ ├── lstm_uni_open.png
│ ├── proposed_topo.png
│ ├── training_lstm.png
│ ├── var20close100.png
│ ├── var20high100.png
│ ├── var20low100.png
│ ├── var20open100.png
│ ├── acf_pacf_close.png
│ ├── lstm_multi_high.png
│ ├── lstm_multi_low.png
│ ├── lstm_multi_open.png
│ ├── lstm_uni_close.png
│ ├── arima_1_1_18_close.png
│ ├── arima_1_1_18_open.png
│ ├── arima_26_1_1_low.png
│ ├── arima_2_1_25_high.png
│ ├── arima_open_sample.png
│ └── lstm_multi_close.png
├── report.pdf
├── Makefile
├── report.bib
└── report.tex
├── .gitattributes
├── saved_models
├── arima_models.mat
├── varm_models.mat
├── lstm_univariate_models.mat
└── lstm_multivariate_models.mat
├── scripts
├── measures
│ ├── mape.m
│ └── rmse.m
├── data
│ ├── eurusdPartition.m
│ ├── eurusdStandardize.m
│ └── eurusdDataset.m
├── config.m
├── varmMain.m
├── lstmMultivariateMain.m
├── lstmUnivariateMain.m
└── arimaMain.m
└── README.md
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/report/figs/rnn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/rnn.png
--------------------------------------------------------------------------------
/report/report.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/report.pdf
--------------------------------------------------------------------------------
/report/figs/lstm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm.png
--------------------------------------------------------------------------------
/report/figs/ohlc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/ohlc.png
--------------------------------------------------------------------------------
/report/figs/candle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/candle.png
--------------------------------------------------------------------------------
/report/figs/var20low.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20low.png
--------------------------------------------------------------------------------
/report/figs/train_cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/train_cut.png
--------------------------------------------------------------------------------
/report/figs/var20close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20close.png
--------------------------------------------------------------------------------
/report/figs/var20high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20high.png
--------------------------------------------------------------------------------
/report/figs/var20open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20open.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.m linguist-language=Matlab
2 | *.mat linguist-language=Matlab
3 | report/* linguist-documentation
4 |
--------------------------------------------------------------------------------
/report/figs/acf_pacf_high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/acf_pacf_high.png
--------------------------------------------------------------------------------
/report/figs/acf_pacf_low.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/acf_pacf_low.png
--------------------------------------------------------------------------------
/report/figs/acf_pacf_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/acf_pacf_open.png
--------------------------------------------------------------------------------
/report/figs/lstm_uni_high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_uni_high.png
--------------------------------------------------------------------------------
/report/figs/lstm_uni_low.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_uni_low.png
--------------------------------------------------------------------------------
/report/figs/lstm_uni_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_uni_open.png
--------------------------------------------------------------------------------
/report/figs/proposed_topo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/proposed_topo.png
--------------------------------------------------------------------------------
/report/figs/training_lstm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/training_lstm.png
--------------------------------------------------------------------------------
/report/figs/var20close100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20close100.png
--------------------------------------------------------------------------------
/report/figs/var20high100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20high100.png
--------------------------------------------------------------------------------
/report/figs/var20low100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20low100.png
--------------------------------------------------------------------------------
/report/figs/var20open100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/var20open100.png
--------------------------------------------------------------------------------
/saved_models/arima_models.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/saved_models/arima_models.mat
--------------------------------------------------------------------------------
/saved_models/varm_models.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/saved_models/varm_models.mat
--------------------------------------------------------------------------------
/scripts/measures/mape.m:
--------------------------------------------------------------------------------
1 | function MAPE = mape(YTest, YPred)
2 | MAPE = 100 * mae(YTest-YPred);
3 | end
4 |
5 |
--------------------------------------------------------------------------------
/report/figs/acf_pacf_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/acf_pacf_close.png
--------------------------------------------------------------------------------
/report/figs/lstm_multi_high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_multi_high.png
--------------------------------------------------------------------------------
/report/figs/lstm_multi_low.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_multi_low.png
--------------------------------------------------------------------------------
/report/figs/lstm_multi_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_multi_open.png
--------------------------------------------------------------------------------
/report/figs/lstm_uni_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_uni_close.png
--------------------------------------------------------------------------------
/report/figs/arima_1_1_18_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/arima_1_1_18_close.png
--------------------------------------------------------------------------------
/report/figs/arima_1_1_18_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/arima_1_1_18_open.png
--------------------------------------------------------------------------------
/report/figs/arima_26_1_1_low.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/arima_26_1_1_low.png
--------------------------------------------------------------------------------
/report/figs/arima_2_1_25_high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/arima_2_1_25_high.png
--------------------------------------------------------------------------------
/report/figs/arima_open_sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/arima_open_sample.png
--------------------------------------------------------------------------------
/report/figs/lstm_multi_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/report/figs/lstm_multi_close.png
--------------------------------------------------------------------------------
/saved_models/lstm_univariate_models.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/saved_models/lstm_univariate_models.mat
--------------------------------------------------------------------------------
/saved_models/lstm_multivariate_models.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huypn12/forex-prediction/HEAD/saved_models/lstm_multivariate_models.mat
--------------------------------------------------------------------------------
/scripts/measures/rmse.m:
--------------------------------------------------------------------------------
1 | function RMSE = rmse(YTest, YPred)
2 | sdiff = (YTest - YPred).^2;
3 | RMSE = sqrt(sum(sdiff(:)/ numel(YTest)));
4 | end
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/report/Makefile:
--------------------------------------------------------------------------------
1 | all: report
2 |
3 | clean:
4 | rm *.{aux,bbl,blg,dvi,pdf,log}
5 |
6 | pdf: report.dvi
7 | dvipdfm report.dvi
8 |
9 | report: report.tex report.bib
10 | pdflatex report.tex
11 | bibtex report
12 | pdflatex report.tex
13 | pdflatex report.tex
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/forex-prediction.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/scripts/data/eurusdPartition.m:
--------------------------------------------------------------------------------
1 | function [trainset, valset, testset] = eurusdPartition(eurusd, ratio)
2 |
3 | valSplitIdx = floor(ratio * size(eurusd, 1));
4 | testSplitIdx = valSplitIdx + floor(0.5*(1-ratio)*size(eurusd, 1));
5 |
6 | trainset = eurusd(1:valSplitIdx, :);
7 | valset = eurusd((valSplitIdx+1):testSplitIdx, :);
8 | testset = eurusd((testSplitIdx+1):end, :);
9 |
10 | end
11 |
--------------------------------------------------------------------------------
/scripts/data/eurusdStandardize.m:
--------------------------------------------------------------------------------
1 | %% Preprocessing, dataset passed into this part must be row-prioritized
2 | %% N-k, where N is the number of records and k is number of dimension
3 |
4 | function stdX= eurusdStandardize(eurusd)
5 | stdX = zeros(size(eurusd));
6 | for i = 1:size(eurusd, 2)
7 | stdX(:, i) = standardize(eurusd(:, i));
8 | end
9 | end
10 |
11 | function X = standardize(X_)
12 | X = (X_ - mean(X_))/std(X_);
13 | end
14 |
15 |
16 |
--------------------------------------------------------------------------------
/scripts/data/eurusdDataset.m:
--------------------------------------------------------------------------------
1 | function [eurusd, features] = eurusdDataset(csvPath, csvFormat)
2 |
3 | % Load csv file
4 | if csvFormat == ""
5 | csvFormat = "%s%f%f%f%f%f";
6 | end
7 | forexRates = readtable(csvPath, "Format", csvFormat);
8 |
9 | openPrice = forexRates.("Open");
10 | openPrice = openPrice(:);
11 | closePrice = forexRates.("Close");
12 | closePrice = closePrice(:);
13 | highPrice = forexRates.("High");
14 | highPrice = highPrice(:);
15 | lowPrice = forexRates.("Low");
16 | lowPrice = lowPrice(:);
17 | volume = forexRates.("Volume");
18 | volume = volume(:);
19 |
20 | %% Median, Mean, Momentum features were added to later train LSTM model better
21 | medianPrice = 0.5 * (highPrice + lowPrice);
22 | meanPrice = 0.25 * (highPrice + lowPrice + openPrice + closePrice);
23 | momentum = volume .* (openPrice - closePrice);
24 |
25 | eurusd = [openPrice, highPrice, lowPrice, closePrice, volume,...
26 | medianPrice, meanPrice, momentum];
27 | features = ["open", "high", "low", "close", "volume",...
28 | "median", "mean", "momentum"];
29 |
30 | end
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Forex price prediction
2 | Machine Learning with Matlab 2018. Final project on Timeseries Prediction with LSTM / RNN.
3 |
4 | ## Disclaimers
5 | As for prediction, this project is even worse than a naiive coin toss. While ARIMA/VAR model is stupidly simple yet showing some reasonable prediction, the LSTM model is an utter nonsense. The LSTM essentially does nothing but use the last observed tick as the 'prediction'; as the result, the predicted ticks, once charted, looks exactly like the test dataset, shifted one timestep to the future.
6 |
7 | **To summarize, the so-called 'deep-learning' model in this project is a total heap of crap.**
8 |
9 | Thus, this repository will be archived soon.
10 |
11 | ## Installation
12 | In order to run the project, the following Matlab Toolboxes must be installed
13 | 1. Statistics and Machine Learning Toolbox
14 | 2. Econometrics Toolbox
15 | 3. Deep Learning Toolbox
16 |
17 | ## Running the Project
18 | 1. Add ml_proj and its subfolders into path
19 | 2. Modify the configuration if necessary. By default, it only verifies the saved models in full dataset. Set cfg.execMode to "train" to train the model again.
20 | 3. Run the respective .m file.
21 |
22 |
23 |
--------------------------------------------------------------------------------
/scripts/config.m:
--------------------------------------------------------------------------------
1 | function cfg = config()
2 | %% Execution mode:
3 | %% "train" :
4 | %% - ARIMA and VAR do grid search to confirm the optimal params.
5 | %% - LSTM model is trained again.
6 | %cfg.execMode = "train";
7 | %% "verify" :
8 | %% - ARIMA and VAR simply load the optimal params founded before.
9 | %% - LSTM load the pretrained weights
10 | cfg.execMode = "verify";
11 |
12 | %% Dataset mode :
13 | %% "full" : 245445 records, extremely computationally heavy
14 | cfg.dataset.mode = "full";
15 | %% "sample" : 14880 records, easier to compute
16 | %cfg.dataset.mode = "sample";
17 | %% Split ratio, e.g. ratio=0.8 -> train:val:test = 8:1:1
18 | cfg.dataset.trainSetRatio = 0.8;
19 |
20 | %% Maximum lags (how many timesteps we look back).
21 | %% Used by VARM and ARIMA during parameter search (training).
22 | cfg.numLags = 25; %% Higher than 25 leads to LONG time VAR/ARIMA params search
23 |
24 | %% ARIMA saved models
25 | cfg.arima.savedModelsFile = "arima_models";
26 |
27 | %% VAR saved models
28 | cfg.varm.savedModelsFile = "varm_models";
29 |
30 | %% LSTM uni variate saved weight
31 | cfg.lstm.uni.savedModelsFile = "lstm_univariate_models";
32 |
33 | %% LSTM multivariate saved weight
34 | cfg.lstm.multi.savedModelsFile = "lstm_multivariate_models";
35 |
36 | %% Finish producing configuration
37 | cfg.numResponses = 1; %% Multiple timesteps ahead is not tested
38 | csvPathFull = "EURUSD_15m_BID_01.01.2010-31.12.2016.csv";
39 | csvPathSample = "EURUSD_15m_BID_sample.csv";
40 | if cfg.dataset.mode == "full"
41 | cfg.dataset.csvPath = csvPathFull;
42 | else
43 | cfg.dataset.csvPath = csvPathSample;
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/report/report.bib:
--------------------------------------------------------------------------------
1 | @manual{meehau16eurusd,
2 | title={{EURUSD}-15m-2010-2016},
3 | author={Michal Januszewski},
4 | note={\url{https://www.kaggle.com/meehau/EURUSD/data}}
5 | }
6 |
7 | @manual{kimy07lstm,
8 | title={{EURUSD} 15 minute interval price prediction},
9 | author={Kim, Dave Y. and Elsaftawy, Mahmoud},
10 | note={\url{https://www.kaggle.com/kimy07/eurusd-15-minute-interval-price-prediction}}
11 | }
12 |
13 | @manual{quantstart,
14 | title={Quantitative Trading},
15 | author={Quark Gluon Ltd.},
16 | note={\url{https://www.quantstart.com/articles/}}
17 | }
18 |
19 | @article{gers1999learning,
20 | title={Learning to forget: Continual prediction with {LSTM}},
21 | author={Gers, Felix A and Schmidhuber, J{\"u}rgen and Cummins, Fred},
22 | year={1999},
23 | publisher={IET}
24 | }
25 |
26 | @article{rumelhart1988learning,
27 | title={Learning representations by back-propagating errors},
28 | author={Rumelhart, David E and Hinton, Geoffrey E and Williams, Ronald J and others},
29 | journal={Cognitive modeling},
30 | volume={5},
31 | number={3},
32 | pages={1},
33 | year={1988}
34 | }
35 |
36 | @article{hochreiter1991untersuchungen,
37 | title={Untersuchungen zu dynamischen neuronalen Netzen},
38 | author={Hochreiter, Sepp},
39 | journal={Diploma, Technische Universit{\"a}t M{\"u}nchen},
40 | volume={91},
41 | number={1},
42 | year={1991}
43 | }
44 |
45 |
46 | @book{GVK483463442,
47 | added-at = {2009-08-21T12:19:46.000+0200},
48 | address = {Hoboken, NJ},
49 | author = {Tsay, {Ruey S.}},
50 | biburl = {https://www.bibsonomy.org/bibtex/22d68774edae6dddaf309574aca02993e/fbw_hannover},
51 | edition = {2. ed.},
52 | interhash = {fcaca47ebae1dcb2150ff8d3e69c51b8},
53 | intrahash = {2d68774edae6dddaf309574aca02993e},
54 | isbn = {978-0-471-69074-0},
55 | keywords = {Econometrics Finanzierung Investition Kapitalmarktforschung Kreditmarkt Methoden_und_Techniken_der_Volkswirtschaft Portfolio-Management Risk_management Theorie Time-series_analysis Wertpapieranalyse Zeitreihenanalyse Ökonometrie Ökonometrisches_Modell},
56 | pagetotal = {XXI, 605},
57 | ppn_gvk = {483463442},
58 | publisher = {Wiley-Interscience},
59 | series = {Wiley series in probability and statistics},
60 | timestamp = {2009-08-21T12:19:56.000+0200},
61 | title = {Analysis of financial time series},
62 | url = {http://gso.gbv.de/DB=2.1/CMD?ACT=SRCHA&SRT=YOP&IKT=1016&TRM=ppn+483463442&sourceid=fbw_bibsonomy},
63 | year = 2005
64 | }
65 |
66 | @inproceedings{pascanu2013difficulty,
67 | title={On the difficulty of training recurrent neural networks},
68 | author={Pascanu, Razvan and Mikolov, Tomas and Bengio, Yoshua},
69 | booktitle={International conference on machine learning},
70 | pages={1310--1318},
71 | year={2013}
72 | }
73 |
--------------------------------------------------------------------------------
/scripts/varmMain.m:
--------------------------------------------------------------------------------
1 | MAIN();
2 |
3 | function MAIN()
4 | cfg = config();
5 |
6 | [eurusd, featureNames] = eurusdDataset(cfg.dataset.csvPath, "");
7 | [YTrain, ~, YTest] = eurusdPartition(...
8 | eurusd, cfg.dataset.trainSetRatio);
9 | YTrain = YTrain(:, 1:4);
10 | YTest = YTest(:, 1:4);
11 |
12 | if cfg.execMode == "verify"
13 | load(cfg.varm.savedModelsFile, 'varmMdl');
14 | [YPred, varmRmse, varmMape] = predictVarm(varmMdl, YTest, cfg.numResponses);
15 | mesg = strcat('Optimal parameter P=', num2str(varmMdl.P),...
16 | ' RMSE=', num2str(varmRmse), ' MAPE=', num2str(varmMape));
17 | display(mesg);
18 | visualizeResult(varmMdl, YPred, YTest, featureNames);
19 | return
20 | end
21 |
22 | [varmMdl, ~] = searchVarmParams(YTrain, cfg.numLags);
23 | save(cfg.varm.savedModelsFile, 'varmMdl');
24 | [YPred, varmRmse, varmMape] = predictVarm(varmMdl, YTest, cfg.numResponses);
25 | mesg = strcat('Optimal parameter P=', num2str(varmMdl.P),...
26 | ' RMSE=', num2str(varmRmse), ' MAPE=', num2str(varmMape));
27 | display(mesg);
28 | visualizeResult(varmMdl, YPred, YTest, featureNames);
29 |
30 | end
31 |
32 | function [varmMdl, aic] = searchVarmParams(YTrain, numLags)
33 | dimInput = size(YTrain, 2);
34 | minAic = Inf;
35 | minMdl = varm(dimInput, 1);
36 | for lag = 1:numLags
37 | mdl = varm(dimInput, lag);
38 | estMdl = estimate(mdl, YTrain);
39 | results = summarize(estMdl);
40 | aic = results.AIC;
41 | if (aic < minAic)
42 | minAic = aic;
43 | minMdl = estMdl;
44 | end
45 | end
46 | varmMdl = minMdl;
47 | aic = minAic;
48 | end
49 |
50 | function [YPred, mape, rmse] = predictVarm(estMdl, YTest, numResponse)
51 | %% Predict num_response values ahead from model and Y
52 | YTest = YTest.';
53 | YPred = zeros(size(YTest));
54 | lags = estMdl.P;
55 | YPred(:, 1:lags) = YTest(1);
56 | i = 1;
57 | while i <= (size(YTest, 2) - lags)
58 | observations = YTest(:, i:(i+lags-1));
59 | [tmp, ~] = forecast(estMdl, numResponse, observations.');
60 | YPred(:,(i+lags):(i+lags+numResponse-1)) = tmp;
61 | i = i + numResponse;
62 | end
63 | [rmse, mape] = computePredError(YTest, YPred);
64 | YPred = YPred.';
65 | end
66 |
67 |
68 | function [errRmse, errMape] = computePredError(YTest, YPred)
69 | errRmse = rmse(YTest, YPred);
70 | errMape = mape(YTest, YPred);
71 | end
72 |
73 | function visualizeResult(estMdl, YTest, YPred, featureNames)
74 | %% Plot the predicted value, observed value, and error
75 | modelSummary = summarize(estMdl);
76 | modelDesc = strcat("VAR(", num2str(estMdl.P), ") AIC=",...
77 | num2str(modelSummary.AIC));
78 | for i = 1:4
79 | uniRmse = rmse(YTest(:, i), YPred(:, i));
80 | uniMape = mape(YTest(:, i), YPred(:, i));
81 | figureTag = strcat(modelDesc, ", EURUSD BID, ", featureNames(i));
82 | figure('Name', figureTag)
83 | plot(YTest(:, i), 'b')
84 | hold on
85 | plot(YPred(:, i), 'r')
86 | hold off
87 | legend(["Observed" "Predicted"])
88 | ylabel("EURUSD BID price")
89 | title(featureNames(i) + ": RMSE=" + uniRmse + " MAPE=" + uniMape);
90 | end
91 | end
92 |
93 |
--------------------------------------------------------------------------------
/scripts/lstmMultivariateMain.m:
--------------------------------------------------------------------------------
1 | MAIN();
2 |
3 | function MAIN()
4 | cfg = config();
5 |
6 | [eurusd, ~] = eurusdDataset(cfg.dataset.csvPath, "");
7 | eurusdStandardized = eurusdStandardize(eurusd);
8 | [eurusdTrain, ~, YTest] = eurusdPartition(...
9 | eurusdStandardized, cfg.dataset.trainSetRatio);
10 |
11 | if cfg.execMode == "verify"
12 | [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] =...
13 | loadTrainedModels(cfg.lstm.multi.savedModelsFile);
14 | verifyLstmModels(lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel, YTest);
15 | return;
16 | end
17 |
18 | [XTrain, YTrain] = prepareTrainData(eurusdTrain, cfg.numLags);
19 | [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] = ...
20 | trainLstmModels(XTrain, YTrain, 8, 1);
21 | saveTrainedModels(cfg.lstm.multi.savedModelsFile,...
22 | lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel);
23 | verifyLstmModels(lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel, YTest);
24 |
25 | end
26 |
27 | function [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] =...
28 | loadTrainedModels(modelFile)
29 | load(modelFile,...
30 | 'lstmOpenModel', 'lstmHighModel', 'lstmLowModel', 'lstmCloseModel');
31 | end
32 |
33 | function saveTrainedModels(modelFile, lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel)
34 | save(modelFile,...
35 | 'lstmOpenModel', 'lstmHighModel', 'lstmLowModel', 'lstmCloseModel');
36 | end
37 |
38 | function verifyLstmModels(lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel, YTest)
39 | YOpenTest = YTest(:, 1);
40 | [YOpenPred, openRmse, openMape] = predictLstmModel(lstmOpenModel, YTest);
41 | visualizeResult(YOpenTest, YOpenPred, openRmse, openMape, 'Open');
42 | YHighTest = YTest(:, 2);
43 | [YHighPred, highRmse, highMape] = predictLstmModel(lstmHighModel, YTest);
44 | visualizeResult(YHighTest, YHighPred, highRmse, highMape, 'High');
45 | YLowTest = YTest(:, 3);
46 | [YLowPred, lowRmse, lowMape] = predictLstmModel(lstmLowModel, YTest);
47 | visualizeResult(YLowTest, YLowPred, lowRmse, lowMape, 'Low');
48 | YCloseTest = YTest(:, 4);
49 | [YClosePred, closeRmse, closeMape] = predictLstmModel(lstmCloseModel, YTest);
50 | visualizeResult(YCloseTest, YClosePred, closeRmse, closeMape, 'Close');
51 | finalRmse = sqrt(0.25 * (openRmse^2 + highRmse^2 + lowRmse^2 + closeRmse^2));
52 | finalMape = 0.25 * (openMape + highMape + lowMape + closeMape);
53 | fprintf('Overall error: RMSE=%f MAPE=%f', finalRmse, finalMape);
54 | end
55 |
56 | function [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] = ...
57 | trainLstmModels(XTrain, YTrain, numFeatures, numResponses)
58 | YOpenTrain = YTrain(:, 1);
59 | lstmOpenModel = buildAndTrainLstmModel(XTrain, YOpenTrain, numFeatures, numResponses);
60 | YHighTrain = YTrain(:, 2);
61 | lstmHighModel = buildAndTrainLstmModel(XTrain, YHighTrain, numFeatures, numResponses);
62 | YLowTrain = YTrain(:, 3);
63 | lstmLowModel = buildAndTrainLstmModel(XTrain, YLowTrain, numFeatures, numResponses);
64 | YCloseTrain = YTrain(:, 4);
65 | lstmCloseModel = buildAndTrainLstmModel(XTrain, YCloseTrain, numFeatures, numResponses);
66 | end
67 |
68 | function [XTrain, YTrain] = prepareTrainData(trainset, k)
69 | %% Fold the original dataset into chunks of size lag
70 | chunkCount = ceil(size(trainset, 1) / k);
71 | XTrain = {};%zeros(chunkCount, size(trainset, 2), k);
72 | YTrain = zeros(chunkCount, size(trainset, 2));
73 | for i = 1:(size(trainset, 1) - k)
74 | tmpX = trainset(i:(i + k - 1), :);
75 | tmpY = trainset((i + k), :);
76 | XTrain{i} = tmpX.';
77 | YTrain(i, :) = tmpY;
78 | end
79 | XTrain = XTrain.';
80 | end
81 |
82 | function [lstmModel, options] = buildAndTrainLstmModel(...
83 | XTrain, YTrain, numFeatures, numResponses)
84 | %% Train a model for one series of observation
85 | numHiddenUnits = 125;
86 | lstmModel = [ ...
87 | sequenceInputLayer(numFeatures),...
88 | lstmLayer(numHiddenUnits, 'OutputMode', 'sequence'),...
89 | lstmLayer(numHiddenUnits, 'OutputMode', 'sequence'),...
90 | fullyConnectedLayer(numHiddenUnits),...
91 | dropoutLayer(0.1),...
92 | lstmLayer(numHiddenUnits, 'OutputMode', 'last'),...
93 | fullyConnectedLayer(numResponses),...
94 | regressionLayer
95 | ];
96 |
97 | options = trainingOptions(...
98 | 'adam', ...
99 | 'MaxEpochs', 3, ...
100 | 'MiniBatchSize', 32,...
101 | 'GradientThreshold',1, ...
102 | 'InitialLearnRate',0.005, ...
103 | 'LearnRateSchedule','piecewise', ...
104 | 'LearnRateDropPeriod',125, ...
105 | 'LearnRateDropFactor',0.2, ...
106 | 'Verbose',0, ...
107 | 'Plots','training-progress'...
108 | );
109 |
110 | lstmModel = trainNetwork(XTrain, YTrain, lstmModel, options);
111 | end
112 |
113 | function [YPred, rmse, mape] = predictLstmModel(lstmModel, YTest)
114 | YTest = YTest.';
115 | YPred = [];
116 | numTimeSteps = size(YTest, 2);
117 | for i = 1:numTimeSteps
118 | [lstmModel, YPred(i)] = predictAndUpdateState(...
119 | lstmModel,YTest(:,i),'ExecutionEnvironment','cpu');
120 | end
121 | [rmse, mape] = computePredError(YTest, YPred);
122 | end
123 |
124 | function [errRmse, errMape] = computePredError(YTest, YPred)
125 | errRmse = rmse(YTest, YPred);
126 | errMape = mape(YTest, YPred);
127 | end
128 |
129 | function visualizeResult(YPred, YTest, rmse, mape, featureName)
130 | %% Plot the predicted value, observed value, and error
131 | modelDesc = "LSTM, 8 features multivariate";
132 | figureTag = strcat(modelDesc + ", EURUSD BID, ", featureName, " price");
133 | figure('Name', figureTag);
134 | plot(YTest, 'b')
135 | hold on
136 | plot(YPred, 'r')
137 | hold off
138 | legend(["Observed" "Predicted"])
139 | ylabel("EURUSD")
140 | title("RMSE=" + rmse + " MAPE=" + mape);
141 | end
142 |
143 |
--------------------------------------------------------------------------------
/scripts/lstmUnivariateMain.m:
--------------------------------------------------------------------------------
1 | MAIN();
2 |
3 | function MAIN()
4 | cfg = config();
5 |
6 | [eurusd, ~] = eurusdDataset(cfg.dataset.csvPath, "");
7 | eurusdStandardized = eurusdStandardize(eurusd);
8 | [eurusdTrain, ~, YTest] = eurusdPartition(...
9 | eurusdStandardized, cfg.dataset.trainSetRatio);
10 |
11 | if cfg.execMode == "verify"
12 | [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] =...
13 | loadTrainedModels(cfg.lstm.uni.savedModelsFile);
14 | verifyLstmModels(lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel, YTest);
15 | return;
16 | end
17 |
18 | [XTrain, YTrain] = prepareTrainData(eurusdTrain, cfg.numLags);
19 | [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] = ...
20 | trainLstmModels(XTrain, YTrain, 1, 1); %% Univariate variate
21 | saveTrainedModels(cfg.lstm.uni.savedModelsFile,...
22 | lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel);
23 | verifyLstmModels(lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel, YTest);
24 |
25 | end
26 |
27 | function [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] =...
28 | loadTrainedModels(modelFile)
29 | load(modelFile,...
30 | 'lstmOpenModel', 'lstmHighModel', 'lstmLowModel', 'lstmCloseModel');
31 | end
32 |
33 | function saveTrainedModels(modelFile, lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel)
34 | save(modelFile,...
35 | 'lstmOpenModel', 'lstmHighModel', 'lstmLowModel', 'lstmCloseModel');
36 | end
37 |
38 | function verifyLstmModels(lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel, YTest)
39 | YOpenTest = YTest(:, 1);
40 | [YOpenPred, openRmse, openMape] = predictLstmModel(lstmOpenModel, YOpenTest);
41 | visualizeResult(YOpenTest, YOpenPred, openRmse, openMape, 'Open');
42 |
43 | YHighTest = YTest(:, 2);
44 | [YHighPred, highRmse, highMape] = predictLstmModel(lstmHighModel, YHighTest);
45 | visualizeResult(YHighTest, YHighPred, highRmse, highMape, 'High');
46 |
47 | YLowTest = YTest(:, 3);
48 | [YLowPred, lowRmse, lowMape] = predictLstmModel(lstmLowModel, YLowTest);
49 | visualizeResult(YLowTest, YLowPred, lowRmse, lowMape, 'Low');
50 |
51 | YCloseTest = YTest(:, 4);
52 | [YClosePred, closeRmse, closeMape] = predictLstmModel(lstmCloseModel, YCloseTest);
53 | visualizeResult(YCloseTest, YClosePred, closeRmse, closeMape, 'Close');
54 |
55 | finalRmse = sqrt(0.25 * (openRmse^2 + highRmse^2 + lowRmse^2 + closeRmse^2));
56 | finalMape = 0.25 * (openMape + highMape + lowMape + closeMape);
57 | fprintf('Overall error: RMSE=%f MAPE=%f', finalRmse, finalMape);
58 | end
59 |
60 |
61 | function [lstmOpenModel, lstmHighModel, lstmLowModel, lstmCloseModel] = ...
62 | trainLstmModels(XTrain, YTrain, numFeatures, numResponses)
63 | %% Univariate training, only extract history of Open to train Open model
64 | YOpenTrain = YTrain(:, 1);
65 | XOpenTrain = cellfun(@(x) x(1, :), XTrain, 'UniformOutput', false);
66 | lstmOpenModel = buildAndTrainLstmModel(XOpenTrain, YOpenTrain,...
67 | numFeatures, numResponses);
68 |
69 | YHighTrain = YTrain(:, 2);
70 | XHighTrain = cellfun(@(x) x(2, :), XTrain, 'UniformOutput', false);
71 | lstmHighModel = buildAndTrainLstmModel(XHighTrain, YHighTrain,...
72 | numFeatures, numResponses);
73 |
74 | YLowTrain = YTrain(:, 3);
75 | XLowTrain = cellfun(@(x) x(3, :), XTrain, 'UniformOutput', false);
76 | lstmLowModel = buildAndTrainLstmModel(XLowTrain, YLowTrain,...
77 | numFeatures, numResponses);
78 |
79 | YCloseTrain = YTrain(:, 4);
80 | XCloseTrain = cellfun(@(x) x(4, :), XTrain, 'UniformOutput', false);
81 | lstmCloseModel = buildAndTrainLstmModel(XCloseTrain, YCloseTrain,...
82 | numFeatures, numResponses);
83 | end
84 |
85 |
86 | function [XTrain, YTrain] = prepareTrainData(trainset, k)
87 | %% Fold the original dataset into chunks of size lag
88 | chunkCount = ceil(size(trainset, 1) / k);
89 | XTrain = {};%zeros(chunkCount, size(trainset, 2), k);
90 | YTrain = zeros(chunkCount, size(trainset, 2));
91 | for i = 1:(size(trainset, 1) - k)
92 | tmpX = trainset(i:(i + k - 1), :);
93 | tmpY = trainset((i + k), :);
94 | XTrain{i} = tmpX.';
95 | YTrain(i, :) = tmpY;
96 | end
97 | XTrain = XTrain.';
98 | end
99 |
100 |
101 | function [lstmModel, options] = buildAndTrainLstmModel(...
102 | XTrain, YTrain, numFeatures, numResponses)
103 | %% Train a model for one series of observation
104 | numHiddenUnits = 125;
105 | lstmModel = [ ...
106 | sequenceInputLayer(numFeatures),...
107 | lstmLayer(numHiddenUnits, 'OutputMode', 'sequence'),...
108 | lstmLayer(numHiddenUnits, 'OutputMode', 'sequence'),...
109 | fullyConnectedLayer(numHiddenUnits),...
110 | dropoutLayer(0.1),...
111 | lstmLayer(numHiddenUnits, 'OutputMode', 'last'),...
112 | fullyConnectedLayer(numResponses),...
113 | regressionLayer
114 | ];
115 |
116 | options = trainingOptions(...
117 | 'adam', ...
118 | 'MaxEpochs', 3, ...
119 | 'MiniBatchSize', 32,...
120 | 'GradientThreshold',1, ...
121 | 'InitialLearnRate',0.005, ...
122 | 'LearnRateSchedule','piecewise', ...
123 | 'LearnRateDropPeriod',125, ...
124 | 'LearnRateDropFactor',0.2, ...
125 | 'Verbose',0, ...
126 | 'Plots','training-progress'...
127 | );
128 |
129 | lstmModel = trainNetwork(XTrain, YTrain, lstmModel, options);
130 | end
131 |
132 | function [YPred, rmse, mape] = predictLstmModel(lstmModel, YTest)
133 | YTest = YTest.';
134 | YPred = [];
135 | numTimeSteps = size(YTest, 2);
136 | for i = 1:numTimeSteps
137 | [lstmModel, YPred(i)] = predictAndUpdateState(...
138 | lstmModel,YTest(:,i),'ExecutionEnvironment','cpu');
139 | end
140 | [rmse, mape] = computePredError(YTest, YPred);
141 | end
142 |
143 | function [errRmse, errMape] = computePredError(YTest, YPred)
144 | errRmse = rmse(YTest, YPred);
145 | errMape = mape(YTest, YPred);
146 | end
147 |
148 | function visualizeResult(YPred, YTest, rmse, mape, featureName)
149 | %% Plot the predicted value, observed value, and error
150 | modelDesc = "LSTM Univariate";
151 | figureTag = strcat(modelDesc + ", EURUSD BID, ", featureName, " price");
152 | figure('Name', figureTag);
153 | plot(YTest, 'b')
154 | hold on
155 | plot(YPred, 'r')
156 | hold off
157 | legend(["Observed" "Predicted"])
158 | ylabel("EURUSD")
159 | title("RMSE=" + rmse + " MAPE=" + mape);
160 | end
161 |
162 |
--------------------------------------------------------------------------------
/scripts/arimaMain.m:
--------------------------------------------------------------------------------
1 | MAIN();
2 |
3 | function MAIN()
4 | cfg = config();
5 | [eurusd, featureNames] = eurusdDataset(cfg.dataset.csvPath, "");
6 | [YTrain, ~, YTest] = eurusdPartition(...
7 | eurusd, cfg.dataset.trainSetRatio);
8 |
9 | if cfg.execMode == "verify"
10 | [arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl] =...
11 | loadArimaModels(cfg.arima.savedModelsFile);
12 | verifyArimaModels(...
13 | arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl, YTest);
14 | return;
15 | end
16 |
17 | [arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl] = ...
18 | trainArimaModels(YTrain, cfg.numLags, featureNames);
19 | saveArimaModels(cfg.arima.savedModelsFile,...
20 | arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl);
21 | verifyArimaModels(...
22 | arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl, YTest);
23 | end
24 |
25 | function saveArimaModels(...
26 | modelFile, arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl)
27 | save(modelFile,...
28 | 'arimaOpenMdl', 'arimaHighMdl', 'arimaLowMdl', 'arimaCloseMdl');
29 | end
30 |
31 | function [arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl] = loadArimaModels(modelFile)
32 | load(modelFile,...
33 | 'arimaOpenMdl', 'arimaHighMdl', 'arimaLowMdl', 'arimaCloseMdl');
34 | end
35 |
36 | function verifyArimaModels(arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl, YTest)
37 | YOpenTest = YTest(:, 1);
38 | [YOpenPred, openRmse, openMape] = arimaPredict(arimaOpenMdl, YOpenTest, 1);
39 | visualizeResult(arimaOpenMdl, YOpenPred, YOpenTest, 'open');
40 | YHighTest = YTest(:, 2);
41 | [YHighPred, highRmse, highMape] = arimaPredict(arimaHighMdl, YHighTest, 1);
42 | visualizeResult(arimaHighMdl, YHighPred, YHighTest, 'high');
43 | YLowTest = YTest(:, 3);
44 | [YLowPred, lowRmse, lowMape] = arimaPredict(arimaLowMdl, YLowTest, 1);
45 | visualizeResult(arimaLowMdl, YLowPred, YLowTest, 'low');
46 | YCloseTest = YTest(:, 4);
47 | [YClosePred, closeRmse, closeMape] = arimaPredict(arimaCloseMdl, YCloseTest, 1);
48 | visualizeResult(arimaCloseMdl, YClosePred, YCloseTest, 'close');
49 | finalRmse = sqrt(0.25 * (openRmse^2 + highRmse^2 + lowRmse^2 + closeRmse^2));
50 | finalMape = 0.25 * (openMape + highMape + lowMape + closeMape);
51 | fprintf('Overall error: RMSE=%f MAPE=%f', finalRmse, finalMape);
52 | end
53 |
54 | function [arimaOpenMdl, arimaHighMdl, arimaLowMdl, arimaCloseMdl] =...
55 | trainArimaModels(YTrain, numLags, featureNames)
56 | % Visualize ACF and PACF of Open-High-Low-Close timeseries
57 | for i = 1:4
58 | plotAcfPacf(YTrain(:, i), featureNames(i));
59 | end
60 | YOpenTrain = YTrain(:, 1);
61 | [arimaOpenMdl, openAic] = arimaParamsSearch(YOpenTrain, numLags, 1, numLags);
62 | YHighTrain = YTrain(:, 2);
63 | [arimaHighMdl, highAic] = arimaParamsSearch(YHighTrain, numLags, 1, numLags);
64 | YLowTrain = YTrain(:, 3);
65 | [arimaLowMdl, lowAic] = arimaParamsSearch(YLowTrain, numLags, 1, numLags);
66 | YCloseTrain = YTrain(:, 4);
67 | [arimaCloseMdl, closeAic] = arimaParamsSearch(YCloseTrain, numLags, 1, numLags);
68 |
69 | fprintf('Optimal parameters for Low (p=%d,d=%d,q=%d) AIC=%f\n',...
70 | arimaLowMdl.P, arimaLowMdl.D, arimaLowMdl.Q, openAic);
71 | fprintf('Optimal parameters for High (p=%d,d=%d,q=%d) AIC=%f\n',...
72 | arimaHighMdl.P, arimaHighMdl.D, arimaHighMdl.Q, highAic);
73 | fprintf('Optimal parameters for Open (p=%d,d=%d,q=%d) AIC=%f\n',...
74 | arimaOpenMdl.P, arimaOpenMdl.D, arimaOpenMdl.Q, lowAic);
75 | fprintf('Optimal parameters for Close (p=%d,d=%d,q=%d) AIC=%f\n',...
76 | arimaCloseMdl.P, arimaCloseMdl.D, arimaCloseMdl.Q, closeAic);
77 | end
78 |
79 |
80 | function plotAcfPacf(Y, featureName)
81 | %% Plot ACF and PCAF for univariate timeseries
82 | Y = diff(log(Y));
83 | figureTag = strcat("ACF and PACF of ", featureName);
84 | figure('Name', figureTag);
85 | subplot(2,1,1)
86 | autocorr(Y)
87 | title("ACF")
88 | subplot(2,1,2)
89 | parcorr(Y)
90 | title("PACF")
91 | end
92 |
93 |
94 | function [model, aic] = arimaParamsSearch(YTrain, maxP, maxD, maxQ)
95 | %% Grid search to confirm (p, d, q) parameters for ARIMA
96 | minAic = Inf;
97 | resultModel = arima(1,0,1);
98 | for p = 1:maxP
99 | for d = 0:maxD
100 | for q = 1:maxQ
101 | mdl = arima(p,d,q);
102 | try
103 | estMdl = estimate(mdl, YTrain);
104 | catch
105 | warning("The model's coefficients cannot be estimated due to numerical instability.");
106 | continue;
107 | end
108 | mdlSum = summarize(estMdl);
109 | aic = mdlSum.AIC;
110 | if aic < minAic
111 | minAic = aic;
112 | resultModel = estMdl;
113 | end
114 | end
115 | end
116 | end
117 | model = resultModel;
118 | aic = minAic;
119 | end
120 |
121 |
122 | function [errRmse, errMape] = computePredError(YTest, YPred)
123 | errRmse = rmse(YTest, YPred);
124 | errMape = mape(YTest, YPred);
125 | end
126 |
127 |
128 | function visualizeResult(estMdl, YPred, YTest, featureName)
129 | %% Plot the predicted value, observed value, and error
130 | [rmse, mape] = computePredError(YPred, YTest);
131 | modelSummary = summarize(estMdl);
132 | modelDesc = strcat("ARIMA(", num2str(estMdl.P),...
133 | ",", num2str(estMdl.D),...
134 | ",", num2str(estMdl.Q), ')',...
135 | " AIC=", num2str(modelSummary.AIC));
136 | figureTag = strcat(modelDesc + ", EURUSD BID ", featureName, " price");
137 | figure('Name', figureTag);
138 | plot(YTest, 'b')
139 | hold on
140 | plot(YPred, 'r')
141 | hold off
142 | legend(["Observed" "Predicted"])
143 | ylabel("EURUSD BID price")
144 | title("RMSE=" + rmse + " MAPE=" + mape);
145 | end
146 |
147 |
148 | function [YPred, rmse, mape] = arimaPredict(estMdl, YTest, numResponse)
149 | %% Predict num_response values ahead from model and Y
150 | YTest = YTest.';
151 | YPred = zeros(1, size(YTest, 2));
152 | lags = max(estMdl.P, estMdl.Q);
153 | YPred(1:lags) = YTest(1);
154 | i = 1;
155 | while i <= (size(YTest, 2) - lags)
156 | observations = YTest(i:(i+lags-1));
157 | [tmp, ~] = forecast(estMdl, numResponse, 'Y0', observations.');
158 | YPred((i+lags):(i+lags+numResponse-1)) = tmp;
159 | i = i + numResponse;
160 | end
161 | YPred = YPred.';
162 | [rmse, mape] = computePredError(YTest, YPred);
163 | end
164 |
165 |
--------------------------------------------------------------------------------
/report/report.tex:
--------------------------------------------------------------------------------
1 | \documentclass[11pt]{article}
2 |
3 | \usepackage[utf8]{inputenc}
4 | \usepackage{cite}
5 | \usepackage{url}
6 | \usepackage{graphicx}
7 | \usepackage{caption}
8 | \usepackage{subcaption}
9 | \usepackage{float}
10 | \usepackage{amsmath}
11 | \usepackage{amsfonts}
12 | \usepackage{amssymb}
13 | \usepackage{algorithm}
14 | \usepackage{algpseudocode}
15 |
16 | \title{Foreign Exchange Rates prediction with LSTM}
17 | \author{Huy Phung, Tashi Choden, Sahil Pasricha
18 | \\University of Konstanz}
19 | \date{February 2019}
20 |
21 |
22 | \begin{document}
23 |
24 | \maketitle
25 | \pagebreak
26 | \tableofcontents
27 | \pagebreak
28 |
29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30 | \section{Abstract}
31 | Foreign Exchange (abbreviation \textit{Forex} or simply \textit{FX}) Market is
32 | the decentralized market for currency investment. Forex market is the second
33 | most important market, after stock market. Supply and demand in the market
34 | determine Forex rate, in which a pair of currency can be exchanged. Forex rates
35 | has been studied in econometrics as a financial timeseries. The purpose of
36 | studying Forex rates is to explain the market behaviour or forecast future
37 | prices.\\
38 | In our project, we use statistical models and deep learning model to predict the
39 | future rates of one step ahead. Our goal is to compare the effectiveness of LSTM
40 | and statistical models (ARIMA and VAR) as timeseries models, in both accuracy
41 | and performance.
42 |
43 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
44 |
45 | \section{Problem Description}
46 | \subsection{Forex rates}
47 | Foreign Exchange rates (short Forex rates) are decided solely by support and
48 | demand of Forex market. Each rate represents the price to buy or sell a pair of
49 | currency (e.g. EURUSD) at the moment. The price to buy is called Bid price; the
50 | price to sell is called Ask price. The difference between Bid price and Ask
51 | price is called Spread. In this project we consider only the Bid price. However,
52 | if both Bid and Ask (and therefore Spread) were available, our analysis would be
53 | more precise.\\
54 | Forex brokers update rates according to the market within milliseconds by
55 | standardized FIX (Financial Information eXchange) protocol. The time interval
56 | between FIX market update messages are not uniform; it may varies from a
57 | millisecond to few seconds. Therefore, the timeseries is of continuous time
58 | step. In order to simplify our analysis, we convert it to a data form that has
59 | discrete, uniform time step, while still keep important information.\\
60 | One possible way to do so is to format the rates into OHLC format. This approach
61 | is widely used in financial technical analysis. We partition the timeseries into
62 | intervals of uniform time length $t$. For each interval, we keep only 4 rate:
63 | the first (\textit{open}), the last (\textit{close}), the maximum
64 | (\textit{high}), the minimum (\textit{low}). Since the time intervals are
65 | uniform among dataset, we have the desired discrete, uniform time step for
66 | analysis.
67 |
68 | \begin{figure}
69 | \begin{subfigure}[b]{0.5\textwidth}
70 | \includegraphics[height=3cm]{figs/ohlc.png}
71 | \caption{Original rates and OHLC form}
72 | \end{subfigure}
73 | \quad\quad\quad\quad\quad\quad\quad
74 | \begin{subfigure}[b]{0.2\textwidth}
75 | \includegraphics[height=3cm]{figs/candle.png}
76 | \caption{Candlestick}
77 | \end{subfigure}
78 | \caption{Illustration of OHLC timeseries formatting.}
79 | \end{figure}
80 |
81 | \subsection{Prediction}
82 | In this project, we concern about the prediction of future Open, High, Low,
83 | Close prices. Other features, either originally exists (volume) or later added
84 | (mean, median, momentum), are only considered as supporting features. These
85 | features are only used for prediction of OHLC features.\\
86 | The problem we are trying to solve in this project is declared as follow: given
87 | history data in OHLC form of Forex rates, namely $\mathbf{x_0},
88 | \mathbf{x_1},\ldots,\mathbf{x_k}$ where $\mathbf{x_i}=(x^O_i, x^H_i, x^L_i, x^C_i )$,
89 | predict future rate of \textit{one} step ahead, $\mathbf{x_{k+1}}$.
90 |
91 | \subsection{Dataset}
92 | Acquiring real-time data is expensive, due to the fact that most FIX data
93 | providers requires subscription contract. However, Janus \cite{meehau16eurusd}
94 | collected an EURUSD rate dataset. The dataset consists of OHLC of BID price (no
95 | ASK price) of EURUSD rates from 2010 to 2016, thus contains 245444 values. Time
96 | interval for OHLC value is uniformly set to 15 minutes. Janus also published a
97 | smaller sample subset of the dataset, which contains only 14880 values. We would
98 | use the sample dataset later for ARIMA parameter search to reduce computational
99 | effort.\\
100 | We separate 245444 records into 3 sets: train set, validation set, test set. In
101 | our default configuration, 80\% of the dataset is trainset (19636 records), 10\%
102 | is valid set (2454 records), 10\% test set.\\
103 | For better modelling and prediction, we add the following features to the
104 | original dataset:
105 | \begin{itemize}
106 | \item Median Price = (High Price + Low Price) / 2
107 | \item Mean Price = (High Price + Low Price + Open Price + Close Price) / 4
108 | \item Momentum = Volume * (Open Price - Close Price)
109 | \end{itemize}
110 | However, these additional features are used only \textit{for} prediction. We do
111 | not build any model to predict these new features.
112 |
113 |
114 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
115 | \section{Model Selection and Evaluation}
116 | \subsection{Akaike Information Criterion (AIC)}
117 | Akaike Information Criterion (AIC) is basically log-likelihood, but it penalizes
118 | a model by the number of parameters. AIC is widely used in statistical model
119 | selection, not only ARIMA and VAR, but also Hidden Markov Model and so on.
120 | $$
121 | AIC = 2k -2\ln(\hat{L})
122 | $$
123 | in which $k$ is the number of parameters and $\hat{L}$ is the likelihood. Since
124 | the log-likelihood is multiplied by -1, lower AIC means the model fits better to
125 | the data.
126 |
127 | \subsection{Root Mean Squared Error (RMSE)}
128 | Root Mean Squared Error is widely used to measure the difference between values
129 | predicted by a model and the actually observed values. Given $y$ represents the
130 | actually observed values and $\hat{y}$ represents the values predicted, $RMSE$
131 | is given by:
132 | $$
133 | RMSE = \left( \frac{1}{n}\sum _{i=1}^{n}(y_i -\hat{y}_i)^2 \right)^\frac{1}{2}
134 | $$
135 | Root Mean Squared Error shows difference between $y$ and $\hat{y}$ regardless
136 | the difference is negative or positive. However, since it does not take the
137 | range of possible values into account, it would be difficult to intepret the
138 | $RMSE$ result without knowing the possible range of predicted and actual values.
139 |
140 | \subsection{Mean Absolute Percentage Error (MAPE)}
141 | In order to measure the difference between predicted values and actual values
142 | with regarding to the scale, we use Mean Absolute Percentage Error (MAPE)
143 | $$
144 | MAPE = \frac{100\%}{n}\sum _{i=1}^{n}\left| \frac{y_i -\hat{y}_i}{y_i} \right|
145 | $$
146 | Compare to RMSE, MAPE is easier to interpret, for it is only a ratio without
147 | unit. Knowing the Max-Min range of the data beforehand is not necessary. The
148 | drawback could be the absolute value function, which is not continuous and thus
149 | make it difficult to take it as a loss function. However, we do not use it as a
150 | loss function here.
151 |
152 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
153 | \section{Statistical Models}
154 | \subsection{Autocorrelation and White noises}
155 | Let $\{x_t\}$ is a timeseries with $\mu=\mu(t)$ is its mean and
156 | $\sigma=\sigma(t)$ is its variance. \textbf{Autocovariance} of lag $k$ is
157 | defined as $C_k=E[(x_t-\mu)(x_{t+k}-\mu)]$ and \textbf{autocorrelation} is
158 | defined
159 | as $\rho_k=\frac{C_k}{\sigma^2}$.\\
160 | A time series $\{e_t\}$ is a \textbf{discrete white noises} if its elements
161 | $e_i$ are independent, identically distributed, have mean equals to zero and no
162 | autocorrelation between any of its values. Formally,
163 | $\mu_{\{e_t\}}=\mu_{\{e_t\}}(t)=0$,
164 | $Cor(e_i,e_j)\neq 0, \forall i \neq j$.\\
165 | A time series $\{x_t\}$ is a \textbf{random walk} if it satisfies that
166 | $x_t=x_{t-1}+e_t$ where $\{e_t\}$ is a discrete white noise as described above.\\
167 | The following models we consider are similar to linear regression to some
168 | extents. Their rationales is that we find a linear relation between the
169 | value at time $t$ and certain \textbf{lags} before it . Detailed explanation of
170 | the models can be found at \cite{GVK483463442} and \cite{quantstart}.
171 |
172 | \subsection{ARIMA(p,d,q)}
173 | \subsubsection{Model description}
174 | \textit{ARIMA(p,d,q)} consists of three models: $AR(p)$, $MA(q)$ and integrated
175 | series of order d.\\
176 | A timeseries $\{x_t\}$ is a \textit{Auto-Regression} $AR(p)$ if it satisfies that
177 | $$
178 | x_t = \alpha_1x_{t-1} + \ldots + \alpha_{t-p}x_{t-p} + e_t
179 | $$
180 | where $\{e_t\}$ is discrete white noises. So, \textit{AR(p)} model looks back to
181 | find a linear relation with $p$ previous values. Normally, we use
182 | Autocorrelation Plot (ACF) of log difference to find $p$, which is the highest
183 | lag in which we find a significant autocorrelation.\\
184 | \\
185 | A timeseries $\{x_t\}$ is a \textit{Moving Average} $AR(p)$ if it satisfies that
186 | $$
187 | x_t = \alpha_1e_{t-1} + \ldots + \alpha_{t-p}e_{t-p} + e_t
188 | $$
189 | where $\{e_t\}$ is discrete white noises. The coefficients $\alpha_i$ are
190 | estimated, for example with maximum likelihood, by a sample (in our case, train
191 | set) to Instead of looks back into values, \textit{MA(q)} looks into
192 | \textit{differences} between timesteps, assuming that the timeseries is a random
193 | walk (thus differences between timesteps are discrete white noises). To find
194 | parameter $q$, normally we look at the Partial Autocorrelation Plot (PACF) to
195 | find the lag in which autocorrelation start to decay.\\
196 | It is not necessarily true that we receive a discrete white noises by first
197 | order difference, namely $x_t - x_{t-1}=e_t$ and $\{e_t\}$ is discrete white
198 | noise. We possible need to take $d$ times of differencing to get discrete white
199 | noises, namely $(x_t-x_{t-1})-(x_{t-1}-x_{t-2})-\ldots-(x_{t-d+1}-x_{t-d})=e_t$.
200 | Let $d$ be the order of difference, we have the component $I(d)$ in
201 | $ARIMA(p,d,q)$.\\
202 |
203 | It is important that we select the proper parameters $(p,d,q)$ that covers all
204 | the lags which affects the current value. In the next section, we consider
205 | another method for parameters selection that would give us the optimal parameter
206 | set (p,d,q) at once.
207 |
208 | \subsubsection{Parameters selection}
209 | Another method is that we try all possible combination of $p$, $d$ and $q$ to
210 | find the combination which gives us the lowest AIC. This method is
211 | computationally heavy, since we have to estimate the model by a hundreds of
212 | thousands of datapoints. However, it guarantees that the result parameters is
213 | optimal, namely it maximizes the likelihood to the dataset.
214 | \begin{algorithm}[H]
215 | \caption{ARIMA(p,d,q) parameters select}\label{paramsselect1}
216 | \begin{algorithmic}[1]
217 | \Procedure{Params Select}{$trainset, maxP, maxD, maxQ$}
218 | \State $MinAIC$ $\gets$ $\infty$
219 | \State $OptimalModel$ $\gets$ $None$
220 | \For{$p=1$ to $maxP$}
221 | \For{$d=0$ to $maxD$}
222 | \For{$q=1$ to $maxQ$}
223 | \State $Model$ $\gets$ $\texttt{ARIMA(p,d,q)}$
224 | \State $EstModel$ $\gets$ Estimate coefficients $\alpha_i$ of $model$ by $trainset$
225 | \If {$EstModel.AIC$ $<$ $MinAIC$}
226 | \State $MinAIC$ $\gets$ $EstModel.AIC$
227 | \State $OptimalModel$ $\gets$ $EstModel$
228 | \EndIf
229 | \EndFor
230 | \EndFor
231 | \EndFor
232 | \State \textbf{return} $optimalModel$
233 | \EndProcedure
234 | \end{algorithmic}
235 | \end{algorithm}
236 | From the ACF and PCAF plot we can observe that after 20 lags, history values has
237 | no significant correlation to current value. Set $maxP$ and $maxQ$ to 25 and
238 | assume stationary on first order difference, the algorithm gives the following
239 | optimal parameters on trainset.
240 | \begin{table}[H]
241 | \centering
242 | \begin{tabular}{|l|l|l|l|l|}
243 | \hline
244 | & Open & High & Low & Close \\ \hline
245 | P & 1 & 2 & 1 & 1 \\ \hline
246 | D & 1 & 1 & 1 & 1 \\ \hline
247 | Q & 18 & 25 & 26 & 18 \\ \hline
248 | AIC & -1.43336e+05 & -1.44620e+05 & 1.45923e+05 & -1.43399e+05 \\ \hline
249 | \end{tabular}
250 | \caption{ARIMA(p,d,q) optimal parameters and AIC}
251 | \end{table}
252 |
253 | \subsection{VAR(p)}
254 | \subsubsection{Model description}
255 | VAR is applied to multivariate data. It is similar to $AR(p)$ model; however,
256 | instead of considering $\{x_t\}$ as real values (univariate), we analyse
257 | $\{\mathbf{x_t}\}$ as a timeseries of vectors $\mathbf{x_i}=(x_i^O, x_i^H, x_i^L, x_i^C)$.
258 | Formally, a timeseries $\{\mathbf{x_t}\}$ in which $\mathbf{x_i}$ is a row
259 | vector of $n$ dimensions, is \textit{VAR(p)} if
260 | $$
261 | \mathbf{x_t}=\beta_1\mathbf{x_{t-1}} + \ldots \beta_p\mathbf{x_{t-p}} + e_t
262 | $$
263 | where $e_t$ is discrete white noises and $\beta_i$ is a column vector of $n$ dimensions.
264 | \subsubsection{Parameters selection}
265 | Parameters selection for VAR(p) is done in the same way as ARIMA(p,d,q): we loop
266 | through a set of possible parameters to find the one with lowest AIC.
267 | \begin{algorithm}[H]
268 | \caption{VAR(p) parameters select}\label{paramsselect2}
269 | \begin{algorithmic}[1]
270 | \Procedure{Params Select}{$trainset, maxP$}
271 | \State $MinAIC$ $\gets$ $\infty$
272 | \State $OptimalModel$ $\gets$ $None$
273 | \For{$p=1$ to $maxP$}
274 | \State $model$ $\gets$ $\texttt{VAR(p)}$
275 | \State Estimate coefficients of $model$ by $trainset$
276 | \If {Estimated model has lower $AIC$}
277 | \State $MinAIC$ $\gets$ $model.AIC$
278 | \State $OptimalModel$ $\gets$ $model$
279 | \EndIf
280 | \EndFor
281 | \State \textbf{return} $OptimalModel$
282 | \EndProcedure
283 | \end{algorithmic}
284 | \end{algorithm}
285 | Set $maxP$ to 25 with the same reason as \textit{ARIMA(p,d,q)}. The algorithm
286 | returns optimal parameter $P=20$ for train set with
287 | $AIC=-1.053747548995608e+07$.
288 |
289 |
290 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
291 |
292 | \section{Deep Learning Model}
293 | \subsection{Recurrent Neural Network}
294 | Recurrent Neural Network (RNN) is introduced by \cite{rumelhart1988learning} to
295 | process sequential input. In RNN, each state connects to the following state
296 | to form a directed graph. The structure of RNN makes it capable of handling
297 | sequential data with temporal dynamic behaviour, such as timeseries or natural
298 | language
299 | \begin{figure}[H]
300 | \centering
301 | \includegraphics[width=\textwidth,keepaspectratio]{figs/rnn.png}
302 | \caption{Recurrent Neural Network topology.}
303 | \end{figure}
304 | However, Pascanu \cite{pascanu2013difficulty} shows that Recurrent Neural
305 | Network has \textit{vanishing gradient} and \textit{exploding gradient}
306 | problems. These problems come from the topology of RNN, in which the layers were
307 | added consecutively. Let $E_k$ be the error at $k$-th Recurrent unit. The
308 | gradient of $E_k$ is calculated by chain rules over $k$ timesteps:
309 | $$
310 | \frac{\partial E_k}{\partial W_{rec}} = \sum_{i=0}^k
311 | \frac{\partial E_k}{\partial y_k} \frac{\partial y_k}{\partial h_k}
312 | \frac{\partial h_k}{\partial h_i} \frac{\partial h_i}{\partial W_{rec}}
313 | $$
314 | Apply chain rules again on $\frac{\partial h_k}{\partial h_i}$, we have:
315 | $$
316 | \frac{\partial h_k}{\partial h_i} = \prod_{t=i}^{k-1}\frac{\partial
317 | h_{t+1}}{\partial h_t} = \prod_{k\geq i > 1} \mathbf{W}_{rec}^T
318 | \mathit{diag}(\sigma^{\prime} (x_{i-1}))
319 | $$
320 | When $W_{rec}<1$, the product decrease exponentially fast (vanishing gradient);
321 | when $W_{rec}>1$, the product increase exponentially fast (exploding gradient).
322 | \subsection{Long-Short Term Memory}
323 | Hochreiter and Schmidhuber (1997) \cite{gers1999learning} introduced Long-Short
324 | Term Memory neural network architecture architecture to solve both vanishing
325 | gradient and exploding gradient problem from RNN. LSTM introduces in each unit a
326 | memory cell, and a forget gate, so the past memory can be forgotten and thus
327 | does not affect the learning.
328 |
329 | \begin{figure}[H]
330 | \centering
331 | \includegraphics[width=0.5\textwidth,keepaspectratio]{figs/lstm.png}
332 | \caption{A LSTM hidden unit \textit{source: colab.github.com}}
333 | \end{figure}
334 |
335 | \subsection{Proposed Network Topology}
336 | We propose a network topology, which consists of 3 stacked LSTM layer of
337 | sequential output, with a dropout layer to tackle overfitting problem.
338 | \begin{table}[H]
339 | \centering
340 | \begin{tabular}{|c|}
341 | \hline
342 | Sequence Input Layer \\ \hline
343 | LSTM Layer of 125 hidden units \\ \hline
344 | LSTM Layer of 125 hidden units \\ \hline
345 | Fully Connected Layer \\ \hline
346 | Dropout Layer of 0.1 \\ \hline
347 | LSTM Layer of 125 hidden units \\ \hline
348 | Regression Layer \\ \hline
349 | \end{tabular}
350 | \caption{Proposed LSTM Network topology}
351 | \end{table}
352 |
353 |
354 | \subsection{Training}
355 | Training data for LSTM network must be prepared in a different way than for
356 | ARIMA or VAR. Standardization of the training data before feeding it into LSTM
357 | network is necessary, since the scale differences among features would
358 | deteriorate the training process. Feeding the whole timeseries from the
359 | beginning to the network in order to predict the next value is ineffective,
360 | since we have observed before that autocorrelation decays and is negiligible
361 | after 25-th lag. Therefore, we partition (fold) the trainset into samples of 25
362 | consecutives timesteps as $X$ set, and use the observation right after that
363 | sample tobe the $Y$ set.
364 | \begin{figure}[H]
365 | \centering
366 | \includegraphics[width=\textwidth,keepaspectratio]{figs/train_cut.png}
367 | \caption{Train set folding.}
368 | \end{figure}
369 |
370 |
371 | We train the model by optimizer \textit{adam}, with batch size 32. Other
372 | parameters can be found in the source code. As we can see from training
373 | progress, after the second epoch, the loss does not decrease any more, meanwhile
374 | the RMSE error still has the same distribution. It may imply that we could stop
375 | the training process at 3rd epoch without the loss of accuracy.
376 | \begin{figure}[H]
377 | \centering
378 | \includegraphics[width=\textwidth,keepaspectratio]{figs/training_lstm.png}
379 | \caption{Training progress of LSTM on Open prices.}
380 | \end{figure}
381 |
382 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
383 |
384 | \section{Experiments}
385 | \subsection{Experiment design}
386 | We consider using LSTM network in two experiments. In the first experiments, we
387 | build and train the network to predict future values only by giving to history
388 | lags of one timeseries \textbf{(univariate)}. For example, we train the network
389 | to predict one Open price value in the future given only the Open prices in the
390 | past. This experiment is designed to compare the performance of LSTM with
391 | $ARIMA(p, d, q)$ model.\\
392 | In the second experiments, we build and train the network so that it would
393 | predict one values ahead of one feature (e.g. one value of Open price ahead),
394 | given history values of \textit{all} features in the past
395 | \textbf{(multivariate)}. This experiment is
396 | designed to compare the performance of LSTM to $VAR(p)$ model.\\
397 | In both experiments, since we want to predict the future values of all Open,
398 | High, Low and Close features, we have to build and train one mode for each
399 | feature accordingly. Therefore, in each experiment we have to build and train 4
400 | models.
401 |
402 | \subsection{Univariate Experiment}
403 | \subsubsection{Results from ARIMA(p,d,q)}
404 |
405 | \begin{table}[H]
406 | \centering
407 | \begin{tabular}{|l|l|l|l|l|}
408 | \hline
409 | & Open & High & Low & Close \\ \hline
410 | P & 1 & 2 & 1 & 1 \\ \hline
411 | D & 1 & 1 & 1 & 1 \\ \hline
412 | Q & 18 & 25 & 26 & 18 \\ \hline
413 | RMSE on Test set & 0.00053488 & 0.00051806 & 0.00052238 & 0.00053157 \\ \hline
414 | MAPE on Test set & 0.027884 & 0.0263 & 0.026312 & 0.027741 \\ \hline
415 | Overall MAPE & \multicolumn{4}{c|}{0.0005268} \\ \hline
416 | Overall RMSE & \multicolumn{4}{c|}{0.0270593} \\ \hline
417 | \end{tabular}
418 | \caption{Optimal parameters and error on Test set}
419 | \end{table}
420 | These following figures are the visualization of results.
421 |
422 | \begin{figure}[H]
423 | \begin{subfigure}[b]{0.5\textwidth}
424 | \includegraphics[width=\textwidth,keepaspectratio]{figs/arima_1_1_18_open.png}
425 | \caption{ARIMA(1,1,18) Open.}
426 | \end{subfigure}
427 | \quad\quad\quad\quad\quad\quad\quad
428 | \begin{subfigure}[b]{0.5\textwidth}
429 | \includegraphics[width=\linewidth,keepaspectratio]{figs/arima_2_1_25_high.png}
430 | \caption{High}
431 | \caption{ARIMA(2,1,25) on High price.}
432 | \end{subfigure}\\
433 | \begin{subfigure}[b]{0.5\textwidth}
434 | \includegraphics[width=\linewidth,keepaspectratio]{figs/arima_26_1_1_low.png}
435 | \caption{ARIMA(1,1,26) on Low price.}
436 | \caption{Low}
437 | \end{subfigure}
438 | \quad\quad\quad\quad\quad\quad\quad
439 | \begin{subfigure}[b]{0.5\textwidth}
440 | \includegraphics[width=\linewidth,keepaspectratio]{figs/arima_1_1_18_close.png}
441 | \caption{Close}
442 | \caption{ARIMA(1,1,18) on Close price.}
443 | \end{subfigure}
444 | \caption{Prediction on Test set.}
445 | \end{figure}
446 | Since the difference between observed and predicted data is small, we plot a
447 | small subset of values, for example Open prices from timesteps 500 to 600, in
448 | order to see the difference easier.
449 |
450 | \begin{figure}[H]
451 | \centering
452 | \includegraphics[width=\textwidth,keepaspectratio]{figs/arima_open_sample.png}
453 | \caption{ARIMA(1,1,18) on Open price, sample of 100 values}
454 | \end{figure}
455 |
456 |
457 | \subsubsection{Results from LSTM}
458 |
459 | \begin{table}[H]
460 | \centering
461 | \begin{tabular}{|l|l|l|l|l|}
462 | \hline
463 | Univariate & Open & High & Low & Close \\ \hline
464 | RMSE on Test set & 0.075301 & 0.024839 & 0.052389 & 0.048788 \\ \hline
465 | MAPE on Test set & 5.9546 & 1.8086 & 4.2258 & 3.7145 \\ \hline
466 | Overall MAPE & \multicolumn{4}{c|}{0.053414} \\ \hline
467 | Overall RMSE & \multicolumn{4}{c|}{3.925875} \\ \hline
468 | \end{tabular}
469 | \caption{Optimal parameters and error on Test set}
470 | \end{table}
471 |
472 | Visualization of prediction result. With LSTM, the difference is obviously visible.
473 |
474 | \begin{figure}[H]
475 | \centering
476 | \includegraphics[width=\textwidth,keepaspectratio]{figs/lstm_uni_open.png}
477 | \caption{LSTM Univariate on Open price.}
478 | \end{figure}
479 |
480 | \begin{figure}[H]
481 | \centering
482 | \includegraphics[width=\linewidth,keepaspectratio]{figs/lstm_uni_high.png}
483 | \caption{LSTM Univariate on High price.}
484 | \end{figure}
485 |
486 | \begin{figure}[H]
487 | \centering
488 | \includegraphics[width=\linewidth,keepaspectratio]{figs/lstm_uni_low.png}
489 | \caption{LSTM Univariate on Low price.}
490 | \end{figure}
491 |
492 | \begin{figure}[H]
493 | \centering
494 | \includegraphics[width=\linewidth,keepaspectratio]{figs/lstm_uni_close.png}
495 | \caption{LSTM Univariate on Close price.}
496 | \end{figure}
497 |
498 |
499 | \subsection{Multivariate}
500 | \subsubsection{Results from VAR(p)}
501 | \begin{table}[H]
502 | \centering
503 | \begin{tabular}{|l|l|l|l|l|}
504 | \hline
505 | P=20 & Open & High & Low & Close \\ \hline
506 | RMSE on Test set & 0.000095771 & 0.00039002 & 0.00037809 & 0.00053147 \\ \hline
507 | MAPE on Test set & 0.00097803 & 0.020309 & 0.019355 & 0.027689 \\ \hline
508 | Overall MAPE & \multicolumn{4}{c|}{0.0003829817534299121} \\ \hline
509 | Overall RMSE & \multicolumn{4}{c|}{0.0170827575} \\ \hline
510 | \end{tabular}
511 | \caption{Optimal parameters and error on Test set of VAR(20)}
512 | \end{table}
513 |
514 | \begin{figure}[H]
515 | \begin{subfigure}[b]{0.5\textwidth}
516 | \includegraphics[width=\textwidth,keepaspectratio]{figs/var20open.png}
517 | \caption{Open}
518 | \end{subfigure}
519 | \quad\quad\quad\quad\quad\quad\quad
520 | \begin{subfigure}[b]{0.5\textwidth}
521 | \includegraphics[width=\textwidth,keepaspectratio]{figs/var20high.png}
522 | \caption{High}
523 | \end{subfigure}\\
524 | \begin{subfigure}[b]{0.5\textwidth}
525 | \includegraphics[width=\textwidth,keepaspectratio]{figs/var20low.png}
526 | \caption{Low}
527 | \end{subfigure}
528 | \quad\quad\quad\quad\quad\quad\quad
529 | \begin{subfigure}[b]{0.5\textwidth}
530 | \includegraphics[width=\textwidth,keepaspectratio]{figs/var20close.png}
531 | \caption{Close}
532 | \end{subfigure}
533 | \caption{Prediction on Test set.}
534 | \end{figure}
535 |
536 | Since \textit{VAR(20)} fits the test set very well, for visualization, we draw
537 | an additional plot for less value, for example values from timesteps 500 to 600
538 | of Open prices in order observe the minute differences between prediction and
539 | actual observation.
540 |
541 | \begin{figure}[H]
542 | \centering
543 | \includegraphics[width=\textwidth,keepaspectratio]{figs/var20open100.png}
544 | \caption{VAR(20) on Open price, sample of 100 values}
545 | \end{figure}
546 |
547 |
548 | \subsubsection{Results from LSTM}
549 |
550 | \begin{table}[H]
551 | \centering
552 | \begin{tabular}{|l|l|l|l|l|}
553 | \hline
554 | & Open & High & Low & Close \\ \hline
555 | RMSE on Test set & 1.4085 & 1.376 & 1.3852 & 1.3818 \\ \hline
556 | MAPE on Test set & 55.8928 & 54.7415 & 53.1869 & 54.1241 \\ \hline
557 | Overall MAPE & \multicolumn{4}{c|}{1.38792} \\ \hline
558 | Overall RMSE & \multicolumn{4}{c|}{54.48633} \\ \hline
559 | \end{tabular}
560 | \caption{LSTM Multivariate error on Test set.}
561 | \end{table}
562 |
563 | Visualization of prediction result. With LSTM, the difference is obviously visible.
564 |
565 | \begin{figure}[H]
566 | \centering
567 | \includegraphics[width=\textwidth,keepaspectratio]{figs/lstm_multi_open.png}
568 | \caption{LSTM Multivariate on Open price.}
569 | \end{figure}
570 |
571 | \begin{figure}[H]
572 | \centering
573 | \includegraphics[width=\linewidth,keepaspectratio]{figs/lstm_multi_high.png}
574 | \caption{LSTM Multivariate on High price.}
575 | \end{figure}
576 |
577 | \begin{figure}[H]
578 | \centering
579 | \includegraphics[width=\linewidth,keepaspectratio]{figs/lstm_multi_low.png}
580 | \caption{LSTM Multivariate on Low price.}
581 | \end{figure}
582 |
583 | \begin{figure}[H]
584 | \centering
585 | \includegraphics[width=\linewidth,keepaspectratio]{figs/lstm_multi_close.png}
586 | \caption{LSTM Multivariate on Close price.}
587 | \end{figure}
588 |
589 |
590 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
591 | \section{Conclusion}
592 | According to the results, ARIMA and VAR deliver much better performance than LSTM
593 | neural network. More importantly, ARIMA and VAR are mathematically transparent
594 | and explanable.\\
595 | However, the statistical models also have drawbacks. First, due to the fact that
596 | all of them uses likelihood estimator to estimate the coefficients by the
597 | trainset, these models are prone to overfit. The more parameters a model has,
598 | the more likely to be overfit. That is the reason why we used AIC, for it
599 | penalizes the number of parameters. Second, ARIMA and VAR depends on the assumption
600 | that the relation between the value at current timesteps and its lags is linear.
601 | However, it can be assumed that more complicated functions are needed to reflect
602 | that relation. Third, the process of finding optimal parameters for statistical
603 | models could take even longer time than training a LSTM model.\\
604 | In our project, the accuracy from LSTM models does not match that of statistical
605 | models. However, LSTM is still promising due to the fact that it can simulate
606 | any complicated functions, in compare with the statistical models which relies
607 | on linear functions. Furthermore, Dave Y. Kim and Mahmoud Elsaftawy
608 | \cite{kimy07lstm} shows that for the same problem, use a different LSTM topology
609 | and training strategy, LSTM based model could achive RMSE 0.00029617
610 | and MAPE of 0.021310060616307612, which is similar to our results achieved by
611 | statistical models ARIMA and VAR.
612 | \pagebreak
613 | \bibliographystyle{plain}
614 | \bibliography{report}
615 |
616 | \end{document}
--------------------------------------------------------------------------------