This document is the replacement documentation for the Volatility Risk Module in the fin-glassbox project:
An Explainable Multimodal Neural Framework for Financial Risk Management
The module is implemented in:
code/riskEngine/volatility.py
It is one of the central learned risk modules in the Risk Engine. Its purpose is to estimate near-term and medium-term price instability from the market embedding stream and classical volatility baselines, then pass volatility-aware risk information to the Position Sizing Engine, Quantitative Analyst, Fusion Engine, and final decision layer.
The Volatility Risk Module belongs to the Risk Engine:
Temporal Encoder embeddings
│
▼
Volatility Risk Module
├── GARCH-style volatility baseline
├── recent realised volatility
├── Temporal Encoder embedding signal
└── learned MLP correction / forecast head
│
▼
outputs/results/Volatility/predictions_chunk{chunk}_{split}.csv
│
▼
Position Sizing Engine → Quantitative Analyst → Fusion Engine
It does not directly produce Buy/Hold/Sell decisions. It estimates the amount of uncertainty in future price movement. The downstream system uses this estimate to reduce confidence, cap position size, and identify dangerous high-volatility environments.
In financial terms, volatility is the magnitude of price variation. A stock can move upward and still be high-risk if the path is unstable. This module therefore measures instability, not directional opportunity.
Volatility answers:
How unstable is the asset likely to be over the next horizon?
For this system, the module estimates:
vol_10d
vol_30d
volatility regime: low / medium / high
volatility confidence
The 10-day horizon is useful for short tactical risk control. The 30-day horizon is useful for medium-horizon exposure control. Downstream modules treat high volatility as a warning signal, especially when it aligns with drawdown, VaR/CVaR, contagion, or crisis-regime evidence.
A high volatility forecast does not automatically mean “sell”. It means:
The implemented file uses a hybrid GARCH + MLP design:
Input sources
├── Temporal Encoder embedding: 256-dim
├── GARCH-style volatility baseline
└── recent realised volatility
Model
├── VolatilityMLP
├── hidden MLP layers with tanh-style non-linearity through PyTorch layers
├── dropout regularisation
├── volatility output heads
├── volatility regime probability head
└── confidence output
The hybrid design is deliberate. A purely neural volatility model may be flexible but less interpretable. A purely statistical volatility model is interpretable but may miss information embedded in the learned market representation. Combining both provides:
| Component | Purpose |
|---|---|
| GARCH baseline | Classical volatility anchor using past return dynamics |
| recent realised volatility | Simple empirical recent movement estimate |
| Temporal Encoder embedding | learned market-sequence representation |
| MLP | nonlinear correction and horizon-specific forecast |
This makes the module stronger and more thesis-defensible because it uses both classical finance logic and learned representation learning.
The module consumes 256-dimensional Temporal Encoder embeddings:
outputs/embeddings/TemporalEncoder/chunk{chunk}_{split}_embeddings.npy
outputs/embeddings/TemporalEncoder/chunk{chunk}_{split}_manifest.csv
Expected manifest columns:
ticker
date
The manifest aligns each embedding row to a ticker-date pair.
The module uses engineered market features:
data/yFinance/processed/features_temporal.csv
Relevant feature examples include:
log_return
vol_5d
vol_21d
rsi_14
macd_hist
bb_pos
volume_ratio
hl_ratio
price_pos
spy_corr_63d
The GARCH baseline and recent-volatility estimates use:
data/yFinance/processed/returns_panel_wide.csv
This file contains a date-indexed return matrix for the ticker universe.
The module follows the project’s chronological split discipline:
| Chunk | Train | Validation | Test |
|---|---|---|---|
| chunk 1 | 2000–2004 | 2005 | 2006 |
| chunk 2 | 2007–2014 | 2015 | 2016 |
| chunk 3 | 2017–2022 | 2023 | 2024 |
This protects against leakage. The module must never fit normalisation, GARCH parameters, HPO choices, or target statistics on validation or test periods.
The volatility target is built from historical realised future volatility over configured horizons:
vol_horizons = [10, 30]
The target is clipped to keep training stable:
min_target_vol = 0.01
max_target_vol = 5.0
fallback_vol = 0.30
The fallback value prevents training collapse when an unusual ticker/date combination does not have a stable volatility estimate. The module includes finite-value cleaning and clipping because volatility pipelines are highly sensitive to NaN, infinity, extremely illiquid assets, and anomalous return spikes.
Prediction output is written to:
outputs/results/Volatility/predictions_chunk{chunk}_{split}.csv
Important columns:
| Column | Meaning |
|---|---|
ticker |
asset identifier |
date |
prediction date |
vol_10d |
predicted 10-day volatility |
vol_30d |
predicted 30-day volatility |
regime_id |
numerical volatility regime class |
regime_label |
low / medium / high volatility regime |
regime_probs_low |
probability of low-volatility state |
regime_probs_medium |
probability of medium-volatility state |
regime_probs_high |
probability of high-volatility state |
confidence |
model confidence score |
garch_vol |
GARCH-style volatility baseline |
recent_vol |
recent realised volatility estimate |
Downstream code often maps these into:
volatility_risk_score
volatility_regime_label
volatility_confidence
inside Position Sizing and Quantitative Analyst outputs.
The Volatility module supports full independent execution.
python code/riskEngine/volatility.py inspect --repo-root . --device cuda
python code/riskEngine/volatility.py smoke --repo-root . --device cuda
The smoke test checks that the model can run forward, compute loss, backpropagate, and generate XAI-compatible gradients.
python code/riskEngine/volatility.py hpo --repo-root . --chunk 1 --trials 40 --device cuda --fresh
HPO uses Optuna TPE and SQLite persistence. Failed trials must return a large finite penalty rather than NaN or infinity because Optuna/SQLite can fail when non-finite values are committed.
python code/riskEngine/volatility.py train-best --repo-root . --chunk 1 --device cuda --fresh
python code/riskEngine/volatility.py predict --repo-root . --chunk 1 --split test --device cuda
python code/riskEngine/volatility.py train-best-all --repo-root . --device cuda --fresh
The module saves trained models under:
outputs/models/Volatility/chunk{chunk}/
Important files include:
best_model.pt
latest_model.pt
training_history.csv
garch_models.pkl
garch_params.json
The production checkpoint contains model configuration to support reliable reloading. This is important because the integrated system may later import the model rather than run it from CLI.
The Volatility module returns a structured prediction object:
{
"predictions": pd.DataFrame,
"xai": dict,
"paths": dict
}
This matches the project-wide XAI requirement that modules must be usable both independently and in the integrated system.
XAI outputs are written to:
outputs/results/Volatility/xai/
Important XAI files:
chunk{chunk}_{split}_feature_importance.csv
chunk{chunk}_{split}_counterfactuals.json
chunk{chunk}_{split}_garch_summary.json
chunk{chunk}_{split}_xai_summary.json
Gradient XAI ranks which embedding dimensions most influenced predicted volatility. This does not directly map each dimension to a human-readable technical indicator, but it identifies whether the learned Temporal Encoder representation is materially affecting volatility output.
Counterfactual scenarios alter GARCH or recent-volatility inputs and measure how predicted volatility changes. This answers:
How sensitive is the model to classical volatility evidence?
The module records aggregate GARCH parameter behaviour:
omega
alpha
beta
persistence
This gives a classical volatility explanation anchor and helps defend the hybrid design.
Position Sizing consumes volatility outputs and turns them into a bounded risk score. The current Position Sizing weighting gives volatility a default weight of:
volatility weight = 0.20
This is high enough to influence sizing but not high enough to dominate the whole Risk Engine. Volatility becomes especially important when it aligns with:
A healthy run should show:
If training loss becomes NaN, this is not acceptable. It usually indicates non-finite targets, too aggressive learning rate, corrupted features, or missing clipping/sanitisation.
Earlier versions of the module produced NaN/inf HPO results. The stable version fixes this through:
This is important documentation because volatility modelling is numerically fragile. The module must remain conservative about non-finite values.
The module is not a perfect market-risk oracle. It has these limitations:
These limitations are controlled downstream by the Position Sizing Engine and Fusion rule barrier.
The Volatility Risk Module is a production-stage Risk Engine component. It supports:
It should be treated as one of the core quantitative risk signals used by Position Sizing, Quantitative Analyst, Fusion, and final decision approval.