docs(v2.0.0-alpha.3): inline plot injection across the entire doc tree
Add scripts/inject_doc_plots.py that scans every .md and .rst page under docs/source/, executes each Python code-block in an isolated namespace with a non-interactive matplotlib backend, captures every figure produced, and inserts an inline image directive immediately after the code-block. Markers AUTO-PLOT-BEGIN/END make the injection idempotent on re-runs. Blocks that fail to execute or produce no figure are left untouched. Add a transparent __getattr__ fallback in python/optimizr/__init__.py that forwards any unresolved top-level attribute to the compiled _core extension. This lets all v1.x and v2.0 doc samples that use 'from optimizr import X' (estimate_ou_params_py, linear_bsde_constant_coeffs, mmd_gaussian, ...) execute as written. Augment the OU Parameter Estimation example (docs/source/algorithms/optimal_control.md) with a two-panel visualization (simulated path plus empirical/theoretical autocorrelation). Net effect: 14 doc pages now display matplotlib plots inline directly under the code that produced them -- including the OU page, point processes, Grid Search, HMM, MCMC, plus the 8 v2.0 RST pages.
This commit is contained in:
@@ -261,15 +261,17 @@ Estimates Ornstein-Uhlenbeck process parameters from time series data.
|
||||
```python
|
||||
from optimizr import estimate_ou_params_py
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Simulate OU process (for testing)
|
||||
dt = 1/252 # Daily data
|
||||
T = 1000
|
||||
kappa_true, theta_true, sigma_true = 3.0, 0.0, 0.2
|
||||
rng = np.random.default_rng(0)
|
||||
spread = [0.0]
|
||||
for _ in range(T-1):
|
||||
dx = kappa_true * (theta_true - spread[-1]) * dt + \
|
||||
sigma_true * np.sqrt(dt) * np.random.randn()
|
||||
sigma_true * np.sqrt(dt) * rng.standard_normal()
|
||||
spread.append(spread[-1] + dx)
|
||||
|
||||
spread = np.array(spread)
|
||||
@@ -280,7 +282,36 @@ kappa, theta, sigma, half_life = estimate_ou_params_py(spread, dt=dt)
|
||||
print(f"True: κ={kappa_true:.2f}, θ={theta_true:.3f}, σ={sigma_true:.3f}")
|
||||
print(f"Estimated: κ={kappa:.2f}, θ={theta:.3f}, σ={sigma:.3f}")
|
||||
print(f"Half-life: {half_life:.1f} periods ({half_life*252:.1f} days)")
|
||||
|
||||
# Visualise the simulated path together with the estimated mean-reversion
|
||||
# level and the decay envelope implied by the fitted half-life.
|
||||
t_axis = np.arange(len(spread)) * dt * 252 # in days
|
||||
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
|
||||
axes[0].plot(t_axis, spread, lw=0.7, label="simulated path")
|
||||
axes[0].axhline(theta_true, color="k", ls=":", label="true θ")
|
||||
axes[0].axhline(theta, color="red", ls="--", label="estimated θ")
|
||||
axes[0].set_xlabel("days"); axes[0].set_ylabel("spread")
|
||||
axes[0].set_title("OU simulation vs estimated long-run mean")
|
||||
axes[0].legend(); axes[0].grid(alpha=0.3)
|
||||
|
||||
# Empirical autocorrelation vs theoretical exp(-κ τ).
|
||||
lags = np.arange(0, 60)
|
||||
x = spread - spread.mean()
|
||||
acf = np.array([
|
||||
(x[: len(x) - k] @ x[k:]) / (x @ x) for k in lags
|
||||
])
|
||||
axes[1].plot(lags, acf, "o-", label="empirical ACF")
|
||||
axes[1].plot(lags, np.exp(-kappa * lags * dt), "--",
|
||||
label=r"theoretical $e^{-\kappa\,\tau}$")
|
||||
axes[1].set_xlabel("lag (days)"); axes[1].set_ylabel("autocorrelation")
|
||||
axes[1].set_title("Mean-reversion fingerprint")
|
||||
axes[1].legend(); axes[1].grid(alpha=0.3)
|
||||
fig.tight_layout(); plt.show()
|
||||
```
|
||||
<!-- AUTO-PLOT-BEGIN -->
|
||||

|
||||
<!-- AUTO-PLOT-END -->
|
||||
|
||||
|
||||
**Method**: Maximum likelihood estimation (MLE) using analytical formulas for discrete-time OU process.
|
||||
|
||||
@@ -431,6 +462,10 @@ plt.plot(pnl_path, label='P&L')
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
```
|
||||
<!-- AUTO-PLOT-BEGIN -->
|
||||

|
||||
<!-- AUTO-PLOT-END -->
|
||||
|
||||
|
||||
**Metrics interpretation**:
|
||||
- `total_return`: Should be positive with low transaction costs
|
||||
|
||||
Reference in New Issue
Block a user