Volatility in crypto markets is difficult to predict because of rapid sentiment shifts, liquidity shocks, leverage cascades, and fast regime changes in market microstructure. This exploratory project studies online volatility estimation using a classic SDE state model with Kalman/Particle filtering under lag aware evaluation.
The rolling-volatility pipeline is the primary approach in this repository. We also attempted instantaneous volatility estimation using log return as a proxy for the latent variable, but under extreme crypto fluctuations this attempt did not produce reliable performance.
Current experiments use mid-price based returns for simplicity and tractability. Future work should incorporate sentiment/news signals, order-book and flow features, and multidimensional state-space models for improved robustness.
Tracks smoothed volatility with a mean reverting state model and filtering under strict lag separation. DA methods observe lagged rolling volatility, while scoring is done on the current rolling target.
Infers latent volatility from price returns using a mean-reverting stochastic volatility state and Bayesian updates.
Evaluated against a scaled instantaneous proxy: |log-return| * sqrt(pi/2).
We model latent volatility as a 1D state:
State variable:
Rolling target (window w) with lag-aware DA observation (L):
Instantaneous proxy target:
Prediction:
Update:
Propagation for particles (x_t^{(i)}):
Weights from Gaussian observation likelihood:
Posterior estimate:
- DA metrics are computed on prior predictions (
*_prior) rather than posterior estimates. - Burn-in is excluded from scoring.
- Rolling mode uses explicit lag between DA input and scored target.
- GARCH is scored only on post-warmup forecasts.
- Legacy target
rolling_vol_100is highly smooth and strongly autocorrelated. - Online DA tracking on a closely aligned observation stream can inflate tracking R2.
- This is not direct index look-ahead, but it overstates strict forecast realism.
Source: rolling_results_lagged.csv
| Symbol | Kalman DA R2 | Particle Filter R2 | GARCH(2,2) R2 |
|---|---|---|---|
| BTC | 0.1416 | 0.1044 | 0.3295 |
| ETH | 0.2375 | 0.2387 | 0.1708 |
Source: rolling_results.csv
| Symbol | Kalman DA R2 | Particle Filter R2 |
|---|---|---|
| BTC | 0.9919 | 0.8383 |
| ETH | 0.9932 | 0.9181 |
Source: inst_results_scaled.csv
| Symbol | Kalman Heston R2 | Particle Heston R2 | Rolling(20) R2 |
|---|---|---|---|
| BTC | -0.202 | -0.024 | 0.100 |
| ETH | -0.208 | -0.017 | 0.126 |
python main.py --mode rolling --symbols BTC ETH --burn-in 200 --rolling-window 20 --rolling-lag 20 --output rolling_results_lagged.csv --plot --plot-dir plots
python main.py --mode instantaneous --symbols BTC ETH --burn-in 200 --inst-scale 1.2533141373 --output inst_results_scaled.csv --plot --plot-dir plots