Comparative time-series study

Five forecasting models, one S&P 500.

A Python study that fits ARIMA, GARCH, Prophet, a k-nearest-neighbours regressor and an LSTM to the same index, then plots every 30-day forecast on the same axes — because the interesting result is how differently they behave, not which one wins.

^GSPC close · 2015-01-01 → 2020-06-04 · 30-day horizontoggle a model to isolate it
2000240028003200forecast →2015-012020-06 + 30d

Illustrative shapes showing how the methods diverge. The code prints RMSE, MAE and MAPE at runtime but saves nothing, so no recorded output exists in the repository yet.

  • 5

    forecasting models

  • 30

    day forecast horizon

  • ~5.4

    years of daily data

  • 341

    lines of Python

  • 7

    source files

  • 0

    servers, endpoints, tables

The problem

No single model is obviously right for a financial series.

Price series are noisy, non-stationary and volatility-clustered. Choosing a forecasting method usually means reading five papers before you have plotted a single line, and each paper evaluates on its own data with its own metric.

This puts five classical and modern approaches on one series, one horizon and one set of axes. Run it and you can see immediately that Prophet extrapolates its trend confidently, the recursive KNN decays toward its neighbourhood mean, and GARCH barely moves the level at all — which is the honest answer to "which model should I use".

What it does

Diagnostics before models, and the same treatment for each.

  • Five models, one series

    ARIMA, GARCH, Prophet, KNN and an LSTM all fit the identical S&P 500 closing prices and forecast the same horizon.

  • Technical indicators first

    Bollinger Bands and MACD are computed and plotted before any model runs, so the series is looked at before it is fitted.

  • Stationarity diagnostics

    An Augmented Dickey-Fuller test with ACF and PACF plots, which is where the ARIMA order came from in the first place.

  • Volatility modelling

    A GARCH(1,1) on percentage returns captures the clustering that a mean-only model cannot see.

  • Rolling-origin validation

    The KNN model is scored with a five-fold TimeSeriesSplit, so no future window ever leaks into a past fit.

  • No keys, no config

    Data comes straight from Yahoo Finance through yfinance. Clone it, install the requirements, run one file.

Under the hood

One entrypoint, six modules, no framework.

main.py downloads the series once and fans out: the visualisation module charts indicators and tests stationarity, then five model modules each take the same prices and return an independent 30-day forecast. There is no server, database or queue. It runs top to bottom and exits, which is the point — swapping or adding a model is one file.

The five models with their libraries and key parameters
modelspecification
ARIMA(5,1,2)Order carried over from the original auto.arima run; residuals checked with Ljung-Box.
GARCH(1,1)AR(3) mean over percentage returns; models volatility clustering rather than level.
ProphetDefault seasonality, additive trend with changepoints — the most confident extrapolator here.
KNN (k=50)30-day lag windows as feature vectors; recursive forecasts pull toward the neighbourhood mean.
LSTM(6)Box-Cox and standardised, sequence length 11, trained 100 epochs, then inverse-transformed.

Three decisions worth defending

Model substitution

The 'NNETAR' model is an LSTM, and the code says so.

The original R study used nnetar — a feed-forward NNAR(11,6) averaging twenty networks. The Python port keeps the same 11-input, 6-node shape but swaps in a single recurrent network, because a modern equivalent was the point of the port. The function is still named run_nnetar_model and labelled NNETAR-like, which is a naming compromise worth knowing about: the write-up describes the R architecture, the code runs the LSTM.

Scripts/nnetar_model.py:20-46

Forecast construction

Recursive multi-step, with the error cost accepted.

Both KNN and the LSTM forecast one step, append the prediction to the input window, and feed it back — thirty times. That compounds error across the horizon, and it shows: the KNN path decays toward its neighbourhood mean while Prophet extrapolates its trend confidently. Direct multi-step forecasting would avoid the feedback, but recursive keeps every model on the same one-step-ahead footing, which is what makes the comparison fair.

Scripts/knn_model.py:37-40

Preprocessing

Box-Cox before the network, inverted after.

The LSTM path applies a Box-Cox transform to stabilise variance and a StandardScaler on top, then inverts both after prediction so the output is back in index points. It is worth naming the constraint this carries: Box-Cox requires strictly positive input, which is safe for an index level but would fail immediately on returns — so this preprocessing choice quietly fixes what the model can be pointed at.

Scripts/nnetar_model.py:26-31, 58-59

Where the README and the code disagree

The README documents the earlier R implementation; the code is a later Python port. Rather than quietly leave the two out of step, here is the diff. Where they disagree, the code is authoritative.

areaREADME says (R)code does (Python)
LanguageR — getSymbols, auto.arima, rugarchPython — Scripts/*.py
ARIMAauto.arima with Box-Cox λ = −0.718hardcoded ARIMA(5,1,2), no Box-Cox
Neural netfeed-forward NNAR(11,6), 20-network ensemblesingle LSTM(6), sequence length 11
GARCH meanARMA(3,2) via ugarchspecAR(3) — the MA terms are dropped
Evaluation70/30 train-test split on ARIMAfits the full series; only KNN does CV
Reported metricsRMSE figures from the R runcomputed at runtime, never saved

Stack

Language
Python 3single batch entrypoint
Statistics
statsmodels (ARIMA, ADF, ACF/PACF)arch (GARCH)scipy (Box-Cox)
Learning
scikit-learn (KNN, TimeSeriesSplit)TensorFlow / Keras (LSTM)prophet
Data
yfinancepandasnumpymplfinance
Output
matplotlib
Infra
none — local CLI

Honest limitations: dependencies are unpinned, there is no error handling, nothing is written to disk, and the thirty-step recursive forecasts compound error across the horizon. Treat the output as illustrative of model behaviour, not as production forecasts — and never as trading advice.