API: Optimal Control / Kalman

High-level bindings exposed by the optimizr Python package. All functions require the Rust extension (optimizr._core).

When to use this module

  • Threshold trading / switching problems solved via HJB (with and without frictions)

  • State estimation and smoothing (Kalman, EKF, UKF)

  • Parameter inference for mean-reverting spreads (OU) feeding into control logic

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.

Diagnostic tips:

  • Plot \(V_x\) to verify smoothness near the boundaries; kinks often signal insufficient grid resolution.

  • Track residual and iterations to spot non-convergence; loosen tolerance or increase max_iter if needed.

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})$.

Practical guidance: Use at least a few thousand samples for stable estimates; heavy-tailed series benefit from pre-whitening or winsorizing before fitting.

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.

What to inspect:

  • win_rate alongside max_drawdown to balance aggressiveness

  • PnL path for regime shifts; combine with HMM states if you need regime-aware controls

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

  • LinearKalmanFilter implements predict, update, and batch filter.

  • KalmanState exposes get_state(), get_covariance(), and get_log_likelihood().

  • Extended/Unscented Kalman filters share the same interface (see UnscentedKalmanFilter in the Rust module) and are exported through the same bindings.

  • For smoothing, use the Rauch–Tung–Striebel smoother (RTSSmoother) available in the bindings.

Conceptual picture: Kalman filtering = prediction (dynamics prior) + correction (measurement residual). EKF linearizes \(f, h\); UKF propagates sigma points for better nonlinear fidelity. RTS smoothing runs backward in time to refine all past states.

See examples/notebooks/03_optimal_control_tutorial.ipynb for end-to-end usage combining HJB thresholds, OU estimation, and filtering.