Code repository for our MAT244 final project (Summer 2026). The project models calm/turbulent volatility regimes in US equity markets as the two stable branches of a scalar nonlinear ODE with saddle-node (fold) bifurcations:
dx/dt = kappa (s + beta x - x^3), sigma = sigma_bar * e^x,
s(t) = a * D(t) - b (D = 125-day drawdown of the market index)
Folds at s = ±s*, s* = 2(beta/3)^(3/2), give two drawdown thresholds (calm→turbulent at D↑, turbulent→calm at D↓ < D↑) and hysteresis between them.
code/
model.py The ODE model, equilibria, fold thresholds (SymPy-verified).
data_pipeline.py Builds the aligned daily dataset 1990-2024 from the raw
market return series: log returns, index level, 20-day
realized volatility (annualized), 125-day drawdown.
calibration.py Two-stage anchored calibration (branch anchoring from
labeled regimes + fold anchoring from observed switch
drawdowns + 1-D least squares for kappa). Writes the
calibrated parameters and the simulated volatility path.
fitting.py Unconstrained 6-parameter least squares (the degenerate
fit discussed in the report's identifiability analysis).
fitting_constrained.py Box-constrained variant (also degenerate; see report).
figures.py Regenerates every figure in figures/.
data/
ff_daily.csv Raw daily market return series (Ken French market factor,
Mkt-RF + RF; see the data note in the report).
dataset.csv The processed dataset the model is fit on.
sigma_model_anchored.csv Calibrated model volatility path + basin labels.
turbulent_episodes.csv All model-identified turbulent episodes (>= 20 days).
theta_anchored.npy Calibrated parameters (kappa, beta, a, b, sigma_bar, x0).
figures/ All report figures (PDF + PNG).
report/main_content.tex LaTeX source of the report section this code supports.
Python 3.10+ with numpy, scipy, pandas, matplotlib (sympy for the symbolic checks):
pip install -r requirements.txt
python code/data_pipeline.py # data/ff_daily.csv -> data/dataset.csv
python code/calibration.py # calibrated parameters + simulated path + episodes
python code/figures.py # regenerates figures/
Each script prints the numbers quoted in the report (thresholds, RMSEs,
episode dates). fitting.py / fitting_constrained.py reproduce the two
degenerate least-squares fits analyzed in the estimation section.
beta = 0.216, sigma_bar = 17.9%, a = 1.56, b = 0.155, kappa = 0.97/day; fold thresholds D↑ = 12.4%, D↓ = 7.4%. Held-out 2020-2024: RMSE 9.8 volatility points; COVID-2020 and the 2022 bear market are both crossed as fold events out of sample.