Predicting stock prices is an easy way to build an impressive chart and a questionable model. I’m taking a more defensible route: predicting AAPL’s realized volatility over the next five trading days, then turning that forecast into a complete machine learning system with experiment tracking, an API, containerization, automated testing, monitoring, optimized inference, and a live dashboard.
The goal wasn’t to make an LSTM crush every classical model. It was to find out whether it could add value under an evaluation setup designed to make cheating difficult, then solve everything required to move that model beyond a notebook.
Keeping time honest: Financial time series make data leakage dangerously easy. The target for day t uses returns from the following five trading days, while every input feature uses only information available on or before t. Evaluation uses expanding training windows with a five-day gap before each test period, and feature scalers are fitted on training data only.
The LSTM is compared with three increasingly serious baselines: the latest observed volatility, an Exponentially Weighted Moving Average, and GARCH(1,1), the classical econometric model built specifically for changing volatility.
A modest result, on purpose: The LSTM reached an RMSE of 0.2061 and MAE of 0.1408, improving on the strongest classical competitor, EWMA, by roughly 6% and 7%. EWMA still held a slight advantage on QLIKE, a volatility-specific loss that penalizes risk underestimation.
That mixed result is more believable than a clean neural-network sweep. The LSTM improved average forecast accuracy, but a simple statistical method remained extremely competitive depending on how error was measured.
From experiment to production: Training is configuration-driven and tracked with MLflow, including parameters, metrics, model versions, and artifacts. The fitted scaler is packaged with the PyTorch model so the preprocessing used during inference cannot quietly drift away from the version used during training.
A FastAPI service accepts a recent window of raw adjusted-close prices and returns a five-day volatility forecast. It intentionally accepts prices rather than a ticker symbol, keeping network calls and market-data providers out of the request path. The result is deterministic, reproducible, and much easier to test.
The service is packaged in Docker with a fixed model snapshot, while GitHub Actions runs linting, 107 tests, a container build, and a live API smoke test on every change. CI uses an explicitly labeled deterministic model fixture, so it can verify the production packaging contract without pretending that a test artifact is the real forecasting model.
Monitoring a prediction whose answer arrives later: A five-day volatility forecast cannot be scored the moment it is produced. The scheduled monitoring pipeline handles that delay by reconstructing recent forecasts using only the prices that were available on each historical date, then scoring them once their outcomes become observable.
It tracks three separate signals: Population Stability Index for feature drift, the current volatility regime relative to the training distribution, and recent forecast error compared with an earlier window. The production model is downloaded from an immutable release and verified by SHA-256 before it is used, so the scheduled job always monitors the intended artifact.
When 3.7× faster only means 9% faster: Exporting the LSTM core from PyTorch to FP32 ONNX reduced median inference latency from 0.3820 milliseconds to 0.1036 milliseconds, a 3.7× improvement. But the complete raw-price-to-forecast path improved by only about 9%, because feature construction and scaling took much more time than the neural network itself.
Dynamic INT8 quantization made the ONNX graph 68% smaller, but improved end-to-end latency by less than 1% and moved forecasts by as much as 0.0121. For a model this small, saving roughly 50 KiB wasn’t worth the additional numerical difference, so FP32 remained the optimized serving backend.
That was the most useful optimization result in the project: making one component faster does not necessarily make the system meaningfully faster, and an optimization is only valuable when it improves a metric the application actually cares about.
The deployed model is intentionally AAPL-only. The repository can ingest a 35-symbol research basket, but a genuinely global multi-ticker model, calibrated prediction intervals, and a positive-constrained output head remain future work.
Built with Python, PyTorch, MLflow, FastAPI, Docker, GitHub Actions, ONNX Runtime, and Streamlit.