This project demonstrates how to:
- Generate a synthetic stock price series using Geometric Brownian Motion (GBM).
- Transform the non-stationary data by differencing, turning it into a (more) stationary series.
- Fit an Autoregressive (AR) model on the differenced series and perform multi-step forecasting.
- Reconstruct (integrate) the forecasted differences to get predictions in the original price scale.
- Perform model order selection (choose the best AR order based on a validation set).
- Compute error metrics (MSE, RMSE, MAPE) to evaluate forecasts.
- Animate the best-model forecast in Python.
-
Non-Stationary Data:
Stock prices typically follow a non-stationary process (they can trend or drift over time). Standard AR models assume stationarity. To reconcile this, we either take log-returns or first differences. Here, we chose differencing to produce a stationary-like series. -
AR Model Simplicity:
Autoregressive models are straightforward to implement (Levinson-Durbin for coefficients) and interpret. They can still capture short-range correlations in the data. -
Model Order Selection:
The “best” AR order is not always obvious. We systematically try$\text{AR(m)}, \text{AR(m+1)},...,\text{AR(p)}$ and pick the one that yields the lowest forecast error (MSE) on a hold-out validation set. Here$m=20$ , which is reasonable since low order model won't be good. -
Error Metrics:
-
MSE (Mean Squared Error):
$\text{MSE} = \frac{1}{n}\sum_{i=1}^n (\hat{y}_i - y_i)^2.$ -
RMSE (Root MSE):
$\text{RMSE} = \sqrt{\text{MSE}}.$ -
MAPE (Mean Absolute Percentage Error):
$\text{MAPE} = \frac{100}{n} \sum_{i=1}^{n} \left|\frac{y_i - \hat{y}_i}{y_i}\right|.$
-
MSE (Mean Squared Error):
We generate synthetic stock prices
where
Raw stock prices are non-stationary. We apply first differencing:
This differenced series
An
where
Once we forecast
This “integration” step returns us to the original scale.
We try AR orders
- Fit
$\text{AR}(p)$ on differenced data. - Forecast the next
$k$ steps (the validation period). - Integrate to reconstruct
$\hat{P}$ . - Compute MSE, RMSE, MAPE vs. actual
$P$ . - Pick the order that yields the lowest MSE (or another chosen metric).
AR_Prediction/
├── CMakeLists.txt
├── README.md
├── src
│ ├── GenerateSyntheticData.h
│ ├── GenerateSyntheticData.h
│ ├── AR_Model.h
│ ├── AR_Model.cpp
│ └── main.cpp
└── vis
└── plot_data.py-
SyntheticDataGenerator.cpp:
Generates GBM prices. -
ARModel.cpp:- Implements the Levinson-Durbin recursion to compute
$\text{AR}(p)$ coefficients. - Provides functions for one-step and multi-step forward predictions in differenced space.
- Implements the Levinson-Durbin recursion to compute
-
main.cpp:- Generates
fullPricesvia GBM. - Splits into
trainPrices(first 260 days) andvalidPrices(remaining days). -
Differencing: Creates
diffDatafromtrainPrices. -
Model Selection: For each AR order in
$[1,\text{maxOrder}]$ , fits the model, forecasts, integrates, and computes errors. Chooses the best AR order. -
Outputs:
-
forecasted_prices.txt,actual_future_prices.txt,train_prices.txt, etc. - Time indices for training (
train_time_indices.txt) and forecast horizon (forecast_time_indices.txt). - Error metrics vs. AR order (
ar_orders.txt,ar_mses.txt,ar_rmses.txt,ar_mapes.txt).
-
- Generates
plot_data.py(or similar):- Reads text files from C++ output.
- Plots training vs. forecast vs. actual data.
- Plots AR model error metrics (MSE, RMSE, MAPE) vs. order.
- Possibly plots log-returns or differenced data.
animate_best_model.py(optional):- Creates an animated MP4 of the forecast “growing” over time compared to actual future prices.
-
Differencing Instead of Log-Returns:
- Both differencing and log-returns can produce stationary-like series. We chose differencing for simplicity, so
$\Delta P_t$ is a direct measure of day-to-day change. - If
$\mu$ and$\sigma$ are very small, the differenced series may be near zero. That’s expected.
- Both differencing and log-returns can produce stationary-like series. We chose differencing for simplicity, so
-
$\text{AR(p)}$ Instead of More Complex Models:-
$\text{AR(p)}$ is easy to implement, interpret, and demonstrate. - For real stock data or more complex patterns, we might use ARIMA, GARCH, or ML-based methods.
-
-
Levinson-Durbin:
- Efficient way to compute AR coefficients from autocorrelations.
- More stable than naive matrix inversion for large
$p$ .
-
Choosing MSE for Best Model:
- MSE is straightforward. We also record RMSE and MAPE. You could pick whichever metric you prefer for “best” (some might prefer MAPE if relative errors matter most).
-
If
$\mu$ and$\sigma$ are small:
The differenced or log-return data will be near zero, so the AR forecast often flattens to zero. That is not an error—it indicates there’s minimal signal to deviate from a near-mean forecast. -
If
$\mu$ or$\sigma$ are larger:- More variability in differenced/log-return data.
- The AR forecast might show more dynamic multi-step predictions.
-
Animation:
The animation helps visualize how each day of the forecast lines up with the actual future. For a random-walk-like series, you might see wide deviations. For a stable series, the forecast line might track the actual fairly closely.
Parameters:
// FOR SYNTHETIC DATA GENERATION
int totalDays = 300; // Total data length
int trainDays = 240; // Use first 240 days for training
int validDays = totalDays - trainDays; // Forecast horizon
double S0 = 100.0; // Initial stock price
double mu = 0.01; // Drift (adjust as needed)
double sigma = 0.1; // Volatility (adjust as needed)
double deltaT = 1.0 / totalDays; // Time increment (using trainDays)
// Generate full synthetic price series.
std::vector<double> fullPrices = SyntheticDataGenerator::generateGBM(
totalDays, S0, mu, sigma, deltaT, 42
);
// Determine Best AR Model Order (over Differenced Data) based on MSE
int maxOrder = 80; // Try AR orders from 1 to 10.
std::vector<double> orders, mses, rmses, mapes;
double bestMse = std::numeric_limits<double>::infinity();
int bestOrder = 20;
// code continueA possibly cleaner interpretation of accuracy is using the MAPE:

We notice that at order
- Trying ARIMA or SARIMAX for seasonal data.
- Adding a GARCH model for volatility.
- Using log-returns instead of differences.
- Using rolling or walk-forward validation.
- Experimenting with machine learning or deep learning methods.
Made By: Philip Pincencia Last Updated: March 24, 2025


