3.5 KiB
API: Optimal Control / Kalman
High-level bindings exposed by the optimizr Python package. All functions require the Rust extension (optimizr._core).
Hamilton–Jacobi–Bellman (HJB) solvers
from optimizr import solve_hjb_py, solve_hjb_full_py
# Switching boundaries for a mean-reverting spread (OU process)
lower, upper, residual, iters = solve_hjb_py(
kappa=3.0, theta=0.0, sigma=0.2, rho=0.04,
transaction_cost=0.001, n_points=400, max_iter=4000,
tolerance=1e-7, n_std=5.0,
)
# Full state (grid + derivatives) for research/visualization
(lower, upper, residual, iters, x_grid, value, grad, hess) = solve_hjb_full_py(
kappa=3.0, theta=0.0, sigma=0.2, rho=0.04,
transaction_cost=0.001, n_points=400,
)
Model
We assume an Ornstein–Uhlenbeck process dX_t = \kappa(\theta - X_t)\,dt + \sigma\,dW_t with quadratic transaction costs. The HJB on grid x \in [-n_{std}\,\sigma/\sqrt{\kappa},\; n_{std}\,\sigma/\sqrt{\kappa}] solves
\rho V(x) = \min\Big\{ \tfrac12 \sigma^2 V_{xx}(x) + \kappa(\theta - x) V_x(x),\; V(x) + c_{\text{buy}},\; V(x) + c_{\text{sell}} \Big\}.
solve_hjb_py returns optimal buy/sell thresholds; solve_hjb_full_py also returns V, V_x, and V_{xx} for diagnostics.
OU parameter estimation
import numpy as np
from optimizr import estimate_ou_params_py
spread = np.random.randn(10_000)
kappa, theta, sigma, half_life = estimate_ou_params_py(spread, dt=1/252)
Method-of-moments / MLE estimation for
X_{t+1} = X_t e^{-\kappa \Delta t} + \theta(1-e^{-\kappa \Delta t}) + \eta_t, \quad \eta_t \sim \mathcal{N}\Big(0,\; \tfrac{\sigma^2}{2\kappa}(1-e^{-2\kappa \Delta t})\Big).
Returns (\kappa, \theta, \sigma, \text{half\_life}).
Backtesting optimal switching
from optimizr import backtest_optimal_switching_py
metrics = backtest_optimal_switching_py(
spread=spread,
lower_bound=lower,
upper_bound=upper,
transaction_cost=0.001,
)
(total_return, sharpe, max_dd, n_trades, win_rate, pnl_path) = metrics
Applies HJB thresholds to historical spreads and reports return, Sharpe ratio, drawdown, trade count, win rate, and PnL path.
Kalman filtering (linear, EKF, UKF)
import numpy as np
from optimizr import LinearKalmanFilter, KalmanState
F = [[1.0, 1.0], [0.0, 1.0]] # constant-velocity model
H = [[1.0, 0.0]] # observe position only
Q = [[1e-4, 0.0], [0.0, 1e-4]]
R = [[1e-2]]
kf = LinearKalmanFilter(
f_matrix=F,
h_matrix=H,
q_matrix=Q,
r_matrix=R,
initial_state=[0.0, 0.0],
initial_covariance=[[1.0, 0.0], [0.0, 1.0]],
)
kf.predict(control=[0.0, 0.0]) # optional control input via B matrix
kf.update(observation=[1.2])
state = kf.get_state() # KalmanState with getters for mean/cov
# Batch filtering
result = kf.filter(observations=[[1.0], [1.4], [1.9]], controls=None)
states = result.get_states()
log_likelihoods = result.get_log_likelihoods()
Notes
LinearKalmanFilterimplementspredict,update, and batchfilter.KalmanStateexposesget_state(),get_covariance(), andget_log_likelihood().- Extended/Unscented Kalman filters share the same interface (see
UnscentedKalmanFilterin the Rust module) and are exported through the same bindings. - For smoothing, use the Rauch–Tung–Striebel smoother (
RTSSmoother) available in the bindings.
See examples/notebooks/03_optimal_control_tutorial.ipynb for end-to-end usage combining HJB thresholds, OU estimation, and filtering.