├── .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} --------------------------------------------------------------------------------