Files
optimiz-rs/docs/source/algorithms/robust_drift.rst
ThotDjehuty ece0b31d9e docs(v2.0.0-alpha.6): convert all inline $...$ to :math: role in v2 RST pages
RST does not parse dollar-math (the dollarmath MyST extension applies
only to .md files), so every inline LaTeX expression was rendered as
raw text on Read the Docs — with backslashes silently stripped by the
RST escape mechanism (e.g. \bar s shown as 'bar s', \mathbb{E} shown
as 'mathbb{E}'). The eight v2.0 algorithm pages now use the proper
:math: role for inline math (224 expressions converted), so MathJax
renders every symbol correctly.

Affected pages: bsde, pde, stochastic_control, quadratic_impact_control,
mckean_vlasov, agent_based, robust_drift, generative_calibration_hooks.
2026-05-12 17:10:06 +02:00

195 lines
6.9 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Inference — Huber-IRLS robust drift estimator
=============================================
Heavy-tail-resistant maximum-likelihood estimator for the discrete OrnsteinUhlenbeck-type model
.. math::
x_{k+1} \;=\; x_k \;+\; (a + b\, x_k)\, \Delta t \;+\; \sigma\, \sqrt{\Delta t}\, \varepsilon_k,
\qquad \varepsilon_k \sim_{\text{i.i.d.}} P_\varepsilon ,
where :math:`P_\varepsilon` is *contaminated*: a fraction :math:`1 - \eta` of standard Gaussian innovations
plus a fraction :math:`\eta` of large outliers (jumps, fat tails, recording errors).
Mathematical background
-----------------------
**Naive OLS.** Setting :math:`y_k := (x_{k+1} - x_k)/\Delta t`, the model is the linear regression
:math:`y_k = a + b\, x_k + \sigma\, \Delta t^{-1/2}\, \varepsilon_k`. Ordinary least-squares
minimises :math:`\sum_k (y_k - a - b x_k)^2` but its breakdown point is :math:`0`: a single outlier with
:math:`|\varepsilon_k| \gg 1` moves the estimate arbitrarily far.
**Huber loss & IRLS.** Huber (1964) replaces the quadratic loss by the *piecewise* loss
.. math::
\rho_\delta(r) \;=\;
\begin{cases}
\tfrac12\, r^2, & |r| \le \delta, \\[2pt]
\delta\,\bigl(|r| - \tfrac\delta2\bigr), & |r| > \delta,
\end{cases}
which is *quadratic in the bulk* and *linear in the tails*. The first-order condition
:math:`\sum_k \psi_\delta(r_k)\, \nabla_{a,b}\, r_k = 0` with :math:`\psi_\delta = \rho_\delta'` rewrites
as a weighted least-squares problem with weights
.. math::
w_k \;=\; \min\!\Bigl(1,\; \frac{\delta}{|r_k|}\Bigr) ,
so the **Iteratively Reweighted Least-Squares** algorithm reads
.. math::
\widehat{(a, b)}^{(t+1)} \;=\; \arg\min_{a, b}\; \sum_k w^{(t)}_k\, (y_k - a - b\, x_k)^2,
\qquad w^{(t+1)}_k = \min\!\bigl(1, \delta / |r^{(t+1)}_k|\bigr).
The sequence converges geometrically when the design matrix is well-conditioned
(HollandWelsch 1977). `robust_drift` returns the limit pair :math:`(\widehat a, \widehat b)` and
the number of iterations.
**Choice of the cut-off.** The default :math:`\delta = 1.345 \cdot \hat\sigma` delivers :math:`95\%`
asymptotic efficiency under Gaussian innovations while keeping the influence function bounded;
it is the HuberHampel value used as the standard reference in robust statistics.
**Closed-form one-step (debiased OLS).** When the contamination is symmetric and the
innovations have finite variance :math:`\sigma^2_\varepsilon`, the *consistent* one-step estimate at
the ordinary least-squares solution :math:`(\hat a^0, \hat b^0)` reads
.. math::
\binom{\widehat a}{\widehat b}
\;=\;
\binom{\hat a^0}{\hat b^0}
\;+\; \bigl(X^\top W X\bigr)^{-1}\, X^\top \psi_\delta(r^0),
where :math:`X` is the :math:`(N - 1) \times 2` design matrix and :math:`W = \mathrm{diag}(w_k)`. Bahadur
linearisation shows :math:`\widehat\theta - \theta^\star = O_P(N^{-1/2})` even in the contaminated
model, with asymptotic variance :math:`\sigma^2_\psi / I^2_\psi` (Huber, *Robust Statistics*, 2004,
Thm. 7.7).
**Connection with Malliavin calculus.** The driver :math:`a + b\, x` is exactly the linearised
drift of the OrnsteinUhlenbeck process used in the Greeks formulae of
:doc:`stochastic_control` and the Vasicek interest-rate model; robust calibration is the
pre-requisite for any Monte-Carlo Greeks computation under noisy historical data.
Why it matters
--------------
* **Heavy-tailed historical data.** Crypto returns, electricity prices, plasma confinement
signals, and bio-medical recordings all contain spikes that destroy OLS but leave Huber
estimates within statistical noise.
* **Online & streaming estimation.** IRLS with :math:`\sim 10` iterations is real-time on streaming
windows and exposes a stable derivative for downstream control loops.
* **Robust risk management.** Replacing raw OLS by IRLS in any volatility / mean-reversion
estimator dramatically reduces *parameter risk* in stress periods.
.. note::
📓 **Companion notebook**`view on GitHub <https://github.com/ThotDjehuty/optimiz-rs/blob/main/examples/notebooks/16_robust_drift.ipynb>`_
· `download .ipynb <https://raw.githubusercontent.com/ThotDjehuty/optimiz-rs/main/examples/notebooks/16_robust_drift.ipynb>`_
16 — Robust drift estimation
============================
.. code-block:: python
import numpy as np
import matplotlib.pyplot as plt
from optimizr import _core as opt
plt.rcParams['figure.figsize'] = (7, 4)
plt.rcParams['figure.dpi'] = 110
Synthetic stationary process with 5 % outliers
----------------------------------------------
.. code-block:: python
rng = np.random.default_rng(7)
true_a, true_b = 1.0, -0.5
dt, n = 0.01, 5000
x = [0.0]
for k in range(n):
if k % 20 == 0:
eps = rng.uniform(-2.0, 2.0)
else:
eps = rng.uniform(-0.1, 0.1)
x.append(x[-1] + (true_a + true_b * x[-1]) * dt + eps * np.sqrt(dt))
x = np.array(x)
print('observation length =', len(x))
.. code-block:: python
fig, ax = plt.subplots()
ax.plot(x, lw=0.6)
ax.axhline(true_a / -true_b, color='red', ls='--', label='OU level a/(-b) = 2')
ax.set_xlabel('k'); ax.set_ylabel('x_k'); ax.legend(); ax.grid(alpha=0.3)
ax.set_title('Synthetic series with heavy-tailed innovations')
fig.tight_layout(); plt.show()
.. AUTO-PLOT-BEGIN
.. image:: ../_static/auto/algorithms__robust_drift/block_03_fig_01.png
:align: center
:width: 80%
.. AUTO-PLOT-END
.. image:: ../_static/v2/robust_drift/plot_01.png
:align: center
:width: 80%
.. code-block:: python
res = opt.robust_drift(x.tolist(), dt=dt)
print(f'a (true 1.0) -> {res["a"]:.4f}')
print(f'b (true -0.5) -> {res["b"]:.4f}')
print('IRLS iterations =', res['iterations'])
.. code-block:: python
# Compare against a naïve OLS that is broken by outliers.
y = (x[1:] - x[:-1]) / dt
X = np.vstack([np.ones_like(x[:-1]), x[:-1]]).T
ols_ab, *_ = np.linalg.lstsq(X, y, rcond=None)
print('OLS a, b =', ols_ab)
fig, ax = plt.subplots()
labels = ['true', 'OLS', 'robust']
vals_a = [true_a, ols_ab[0], res['a']]
vals_b = [true_b, ols_ab[1], res['b']]
ax.bar(np.arange(3) - 0.2, vals_a, width=0.4, label='a')
ax.bar(np.arange(3) + 0.2, vals_b, width=0.4, label='b')
ax.set_xticks(range(3)); ax.set_xticklabels(labels)
ax.legend(); ax.grid(alpha=0.3); ax.set_title('Robust vs OLS drift estimate')
fig.tight_layout(); plt.show()
.. AUTO-PLOT-BEGIN
.. image:: ../_static/auto/algorithms__robust_drift/block_05_fig_01.png
:align: center
:width: 80%
.. AUTO-PLOT-END
.. image:: ../_static/v2/robust_drift/plot_02.png
:align: center
:width: 80%
**Verified:** Huber IRLS recovers `(a, b)` within `0.2` even with 5 % heavy outliers.
API
---
.. code-block:: rust
pub fn estimate_robust_drift(observations: &[f64], cfg: &RobustDriftConfig) -> Result<RobustDriftResult>;
pub struct RobustDriftConfig { pub dt: f64, pub huber_delta: f64, pub max_iterations: usize, pub tolerance: f64 }
pub struct RobustDriftResult { pub a: f64, pub b: f64, pub iterations: usize }