Statistical tooling for quantitative finance — Ornstein-Uhlenbeck processes, volatility estimation, and core types.
Why · The Math · Quick Start · OU Module · Validation
Most quant libraries are either too academic (no practical interface) or too black-box (no transparency into what's happening under the hood).
Quant Tools is a middle ground — correct math with clean Python APIs. Every estimator has the reference paper clearly documented, and results are validated against independent implementations.
Currently focused on:
- Ornstein-Uhlenbeck processes — MLE fitting, simulation, stationarity tests, bias correction
- Volatility estimation — quadratic variation, realized volatility, efficient estimators
- Core types — Pydantic models for
FitResult,SimParams,VolaResultwith serialization
The OU process is the mathematical backbone of mean reversion detection. Every trader asks:
- "Is NQ mean-reverting right now, or trending?"
- "How fast does price snap back to the mean?"
- "Is my entry zone statistically valid or just noise?"
This library answers those questions with correct math — not heuristics.
from quant_tools.ou.estimator import fit_mle, half_life
from quant_tools.ou.stationarity import hurst_exponent
# Load 15-second NQ bars, NY session only
bars = load_nq_bars("2024-01-01", "2024-06-30", session="NY")
# Fit OU model → how fast does price mean-revert?
fit = fit_mle(bars["close"], dt=1/252/24/4) # 15s bars
h = hurst_exponent(bars["close"])
print(f"Mean reversion speed θ: {fit.mean_rev_speed:.3f}")
print(f"Half-life: {half_life(fit.mean_rev_speed):.0f} bars")
print(f"Hurst exponent: {h:.3f}")
# Interpretation:
# θ > 2.0 + H < 0.45 → strong mean reversion → fade extremes
# θ < 0.5 + H > 0.55 → trending market → trade with momentum
# Otherwise → noise → stay outThis isn't theory. This is the same math used by quant funds to separate mean-reverting regimes from trending ones — applied to NQ futures.
The OU process is the continuous-time equivalent of an AR(1) model. It's the standard model for mean-reverting behavior in finance:
dX(t) = θ(μ - X(t))dt + σ dW(t)
| Symbol | Meaning | Intuition |
|---|---|---|
| θ (theta) | Mean reversion speed | Higher = faster pull toward mean |
| μ (mu) | Long-term mean level | Price oscillates around this value |
| σ (sigma) | Volatility | Random component magnitude |
| dW(t) | Wiener process | Random noise (Brownian motion) |
Key insight: When price X(t) is above μ, the drift term θ(μ − X(t)) becomes negative — pulling price back down. Below μ, it pulls up. The strength of this pull is proportional to distance from the mean.
The half-life tells you how many days (or bars) it takes for a deviation from the mean to decay by 50%:
τ = ln(2) / θ
| θ | Half-Life | Interpretation |
|---|---|---|
| 10 | ~17 hours (1/252yr) | Fast mean reversion — scalp-friendly |
| 1 | ~6 months (0.69yr) | Slow mean reversion — swing trading |
| 0.05 | ~14 years | Essentially a random walk |
Ordinary Least Squares (AR(1) regression) gives biased estimates in small samples. The bias is:
E[θ̂] ≈ θ − (1 + 3θ) / N
Where N is your sample size. For N=250 (1 year of daily data), this bias can be 10-20%. The bias_correct() function applies the Shaman-Stine (1988) correction to fix it.
The OU process has a known closed-form transition density (exact, not Euler). This means:
- Simulation uses the exact distribution, not an approximation
- MLE fitting uses the exact likelihood, not a noisy proxy
- Confidence intervals are analytically computable
X(t+dt) | X(t) ~ Normal( μ + (X(t) - μ)e^{-θdt}, σ²(1 - e^{-2θdt}) / 2θ )
pip install quant-toolsOr from source:
git clone https://github.com/nessos666/quant-tools.git
cd quant-tools
pip install -e .The OU module provides end-to-end workflow for mean-reverting processes.
from quant_tools.ou.estimator import fit_mle, half_life, bias_correct
from quant_tools.core.types import FitResult
# prices = pd.Series(...) # your price data
prices = ...
# MLE fit — discrete-time exact likelihood
result: FitResult = fit_mle(prices, dt=1/252)
print(f"Mean reversion speed θ: {result.mean_rev_speed:.4f}")
print(f"Mean reversion level μ: {result.mean_rev_level:.4f}")
print(f"Volatility σ: {result.vola:.4f}")
print(f"Half-life: {half_life(result.mean_rev_speed):.1f} days")
# Apply finite-sample bias correction
corrected = bias_correct(result, dt=1/252)from quant_tools.ou.simulator import path
import numpy as np
# Simulate 10 years of daily data
sim = path(
x0=0.0,
n_steps=2520,
dt=1/252,
params=FitResult(mean_rev_speed=5.0, mean_rev_level=0.0, vola=0.02),
seed=42
)
# sim is a numpy array of shape (2520,)from quant_tools.ou.stationarity import adf_test, hurst_exponent
# Augmented Dickey-Fuller with OU-aware lag selection
adf_result = adf_test(prices)
print(f"ADF statistic: {adf_result.statistic:.4f}, p-value: {adf_result.pvalue:.4f}")
# Hurst exponent via rescaled range (R/S)
h = hurst_exponent(prices)
print(f"Hurst: {h:.4f}") # < 0.5 = mean-reverting, > 0.5 = trendingfrom quant_tools.ou.estimator import est_vola_qv
# Model-free volatility from quadratic variation
vola = est_vola_qv(prices, dt=1/252)
print(f"Annualized volatility: {vola:.4f}")| Implementation | Result | Status |
|---|---|---|
| Wergieluk (2019) OU-MLE | θ, μ, σ match to 1e-6 | ✅ Verified |
| Analytical OU transition density | Closed-form vs. simulation | ✅ Verified |
| Scipy ADF test | p-values match | ✅ Verified |
| Hurst R/S (Weron 2002) | H within ±0.01 | ✅ Verified |
| Quadratic variation (Barndorff-Nielsen) | RV matches reference | ✅ Verified |
See
tests/test_ou_integration.pyfor the full cross-validation suite.
quant_tools/
├── __init__.py
├── core/
│ ├── __init__.py
│ └── types.py # FitResult, SimParams, VolaResult (Pydantic)
└── ou/
├── __init__.py
├── estimator.py # fit_mle, bias_correct, est_vola_qv, transition_density
├── simulator.py # exact OU path simulation
└── stationarity.py # adf_test, hurst_exponent
tests/
├── test_core_types.py
├── test_ou_estimator.py
├── test_ou_integration.py # Cross-validation against reference implementations
├── test_ou_rolling.py
├── test_ou_simulator.py
└── test_ou_stationarity.py
pytest tests/ -vAll 6 test suites pass. The integration test validates against the Wergieluk reference implementation.
- Correctness first — every estimator cross-validated against academic reference implementations
- Clean interfaces — typed, documented, Pydantic-backed models
- No black boxes — open math, clear references. You can verify every formula.
- Minimal dependencies — numpy, scipy, pandas, pydantic, loguru. No ML bloat.
Part of the NQ research ecosystem:
- nq-strategy-builder — Full backtesting framework using these tools
- ou_noise — Reference implementation used for validation
MIT — use it, validate it, improve it.
Built for systematic NQ futures research. Correct math, clean APIs, verified results.
github.com/nessos666