openquant.synthetic_bt
Compiled from Rust (crates/pyopenquant). Signatures and types are from the shipped .pyi stubs; descriptions are the functions’ own docstrings, as help() shows them. All Python modules.
Functions
calibrate_ou_params
calibrate_ou_params(prices: Sequence[float]) -> AnyFit a discrete Ornstein-Uhlenbeck (AR(1)) process to a price series.
AFML §13.5.1, step 1. OLS of P_t on P_{t-1} gives P_t = intercept + phi * P_{t-1} + sigma * eps_t; sigma is the sample standard deviation of the residuals and equilibrium is intercept / (1 - phi), or the mean price when phi is within 1e-12 of 1.
Parameters
prices: list[float]Price levels, oldest first; at least three, all finite.
Returns
- dict[str, Any]
Keys
phi,intercept,equilibrium,sigma,r_squared(floats) andstationary(bool,abs(phi) < 1, true even forphi = 0.999).
Raises
- ValueError
If there are fewer than three prices, a price is not finite, the lagged prices are constant, or the fit is exact (zero residual deviation).
detect_no_stable_optimum
detect_no_stable_optimum(
response_surface: Sequence[tuple[float, float, float, float, float, float, float]],
estimated_phi: float,
random_walk_phi_threshold: float,
min_peak_margin: float,
min_surface_std: float,
min_best_sharpe: float,
) -> AnyFlag a PT/SL response surface that lacks a stable optimum (the flattening of AFML §13.6).
The verdict is true when the process is near a random walk (abs(estimated_phi) >= random_walk_phi_threshold) and the peak is weak (best - median Sharpe < min_peak_margin) or the surface flat (Sharpe standard deviation < min_surface_std), or when the best Sharpe is below min_best_sharpe and the peak is weak. The thresholds are this library's heuristics, not AFML's; the Rust defaults are 0.97, 0.20, 0.10 and 0.30.
Parameters
response_surface: list[tuple[float, float, float, float, float, float, float]]One
(profit_taking, stop_loss, sharpe, mean_return, std_return, win_rate, avg_holding_steps)row per grid point; onlysharpeenters the diagnosis.estimated_phi: floatThe AR(1) coefficient of the process, e.g. from
calibrate_ou_params.random_walk_phi_threshold: floatabs(phi)at or above this counts as near a random walk.min_peak_margin: floatBest-minus-median Sharpe below this is a weak peak.
min_surface_std: floatSharpe standard deviation below this is a flat surface.
min_best_sharpe: floatBest Sharpe below this is a weak best rule.
Returns
- dict[str, Any]
Keys
no_stable_optimum(bool),reason(str),best_sharpe,median_sharpe,peak_margin,surface_stdandestimated_phi(floats).
Raises
- ValueError
If
response_surfaceis empty.
evaluate_rule_on_paths
evaluate_rule_on_paths(
paths: Sequence[Sequence[float]],
profit_taking: float,
stop_loss: float,
max_holding_steps: int,
annualization_factor: float,
) -> AnyEvaluate one profit-taking/stop-loss rule on every path.
AFML §13.5.1, step 3. Each path is a long entry at its first price; the trade exits at the first step (up to min(max_holding_steps, len(path) - 1)) whose PnL is >= profit_taking or <= -stop_loss, otherwise at that last step. Barriers are in price units from the entry. The Sharpe ratio is mean_return / std_return * sqrt(annualization_factor) over per-trade PnL, regardless of holding time (0 when the deviation is 0).
Parameters
paths: list[list[float]]Price paths, e.g. from
generate_ou_paths; each needs at least two finite points.profit_taking: floatProfit-taking width in price units; must be > 0.
stop_loss: floatStop-loss width in price units, as a positive number; must be > 0.
max_holding_steps: intMaximum holding period in steps; must be > 0.
annualization_factor: floatMultiplier whose square root scales the Sharpe ratio (1.0 for per trade); finite, > 0.
Returns
- dict[str, Any]
Keys
profit_taking,stop_loss,sharpe,mean_return(mean per-trade PnL),std_return(sample standard deviation of PnL),win_rate(share of trades with positive PnL) andavg_holding_steps, all floats.
Raises
- ValueError
If
pathsis empty, a barrier is not positive,max_holding_stepsis 0,annualization_factoris not finite and positive, a path has fewer than two points, or a path contains a non-finite value.
generate_ou_paths
generate_ou_paths(
phi: float,
intercept: float,
equilibrium: float,
sigma: float,
r_squared: float | None = None,
stationary: bool | None = None,
initial_price: float = 100.0,
n_paths: int = 1000,
horizon: int = 252,
seed: int = 42,
) -> list[list[float]]Simulate O-U price paths from given process parameters.
AFML §13.5.1, step 2. Each path starts at initial_price and evolves as P_t = intercept + phi * P_{t-1} + sigma * eps_t with standard-normal innovations from an RNG seeded with seed, so the same seed reproduces the same paths. Only intercept, phi and sigma drive the simulation; when building parameters by hand keep intercept = (1 - phi) * equilibrium. Pass initial_price, n_paths, horizon and seed by keyword; their defaults are those of run_synthetic_otr_workflow.
Parameters
phi: floatAutoregressive coefficient.
intercept: floatRegression intercept.
equilibrium: floatLong-run mean; must be finite but is not used by the simulation.
sigma: floatInnovation standard deviation in price units; must be >= 0.
r_squared: float | None, default NoneDeprecated: passing it emits a
DeprecationWarning. It is a diagnostic of the calibration regression, not a parameter of the process, so it cannot affect the paths (AFML §13.5.1 simulates from the fitted coefficients and residual deviation alone).stationary: bool | None, default NoneDeprecated: passing it emits a
DeprecationWarning. It isabs(phi) < 1, whichphialready determines. To simulate a calibrated fit, pass itsphi,intercept,equilibriumandsigmaonly.initial_price: float, default 100.0Entry price of every path.
n_paths: int, default 1000Number of paths; must be > 0.
horizon: int, default 252Points per path, including the entry; must be >= 2.
seed: int, default 42Seed of the simulation RNG.
Returns
- list[list[float]]
n_pathspaths ofhorizonprices each, each starting atinitial_price.
Raises
- ValueError
If
initial_priceis not finite,n_pathsis 0,horizon < 2,phi,intercept,equilibriumorsigmais not finite, orsigmais negative.
run_synthetic_otr_workflow
run_synthetic_otr_workflow(
historical_prices: Sequence[float],
initial_price: float = 100.0,
n_paths: int = 1000,
horizon: int = 252,
seed: int = 42,
profit_taking_grid: Sequence[float] | None = None,
stop_loss_grid: Sequence[float] | None = None,
max_holding_steps: int = 252,
annualization_factor: float = 252.0,
random_walk_phi_threshold: float | None = None,
min_peak_margin: float | None = None,
min_surface_std: float | None = None,
min_best_sharpe: float | None = None,
) -> AnyEnd-to-end optimal-trading-rule search on synthetic O-U paths.
AFML §13.4-13.5 (Snippets 13.1-13.2): calibrates an O-U process on historical_prices (calibrate_ou_params), simulates n_paths paths from initial_price (generate_ou_paths), evaluates every (profit_taking, stop_loss) pair of the grids (evaluate_rule_on_paths), and diagnoses the surface (detect_no_stable_optimum, with the calibrated phi). Barriers are in price units; every rule is a long entry.
Parameters
historical_prices: list[float]Price levels to calibrate on, oldest first; at least three, all finite.
initial_price: float, default 100.0Entry price of every simulated path.
n_paths: int, default 1000Number of simulated paths.
horizon: int, default 252Points per path, including the entry; must be >= 2.
seed: int, default 42Seed of the simulation RNG.
profit_taking_grid: list[float] | None, default NoneProfit-taking widths to try, each finite and > 0.
Noneuses0.25, 0.5, ..., 5.0.stop_loss_grid: list[float] | None, default NoneStop-loss widths to try, as positive numbers (distance below the entry), each finite and > 0.
Noneuses0.25, 0.5, ..., 5.0.max_holding_steps: int, default 252Maximum holding period in steps (capped by the path length).
annualization_factor: float, default 252.0Multiplier whose square root scales each rule's Sharpe ratio.
random_walk_phi_threshold: float | None, default NoneSee
detect_no_stable_optimum;Noneuses the Rust default 0.97.min_peak_margin: float | None, default NoneSee
detect_no_stable_optimum;Noneuses the Rust default 0.20.min_surface_std: float | None, default NoneSee
detect_no_stable_optimum;Noneuses the Rust default 0.10.min_best_sharpe: float | None, default NoneSee
detect_no_stable_optimum;Noneuses the Rust default 0.30.
Returns
- dict[str, Any]
Keys
params(dict as returned bycalibrate_ou_params),best_rule(dict withprofit_takingandstop_loss),best_point(dict as returned byevaluate_rule_on_paths),response_surface(list of such dicts, one per grid point, sorted by Sharpe then mean return, best first) anddiagnostics(dict as returned bydetect_no_stable_optimum).
Raises
- ValueError
If a grid is empty or has a value that is not finite and positive, or calibration, simulation or evaluation rejects its input (see
calibrate_ou_params,generate_ou_pathsandevaluate_rule_on_paths).
search_optimal_trading_rule
search_optimal_trading_rule(
phi: float,
intercept: float,
equilibrium: float,
sigma: float,
r_squared: float,
stationary: bool,
paths: Sequence[Sequence[float]],
profit_taking_grid: Sequence[float],
stop_loss_grid: Sequence[float],
max_holding_steps: int,
annualization_factor: float,
random_walk_phi_threshold: float,
min_peak_margin: float,
min_surface_std: float,
min_best_sharpe: float,
) -> AnySearch a PT/SL grid on given paths and diagnose the response surface.
AFML §13.5.1 (Snippets 13.1-13.2). Evaluates every (profit_taking, stop_loss) pair of the grids with evaluate_rule_on_paths, sorts the surface by Sharpe ratio (then mean return), and runs detect_no_stable_optimum with phi as the estimated coefficient. The process parameters are otherwise only echoed back in the result's params.
Parameters
phi: floatAR(1) coefficient used by the stability diagnosis.
intercept: floatEchoed in
params.equilibrium: floatEchoed in
params.sigma: floatEchoed in
params.r_squared: floatEchoed in
params.stationary: boolEchoed in
params.paths: list[list[float]]Price paths, e.g. from
generate_ou_paths; each needs at least two finite points.profit_taking_grid: list[float]Profit-taking widths to try, in price units, each > 0.
stop_loss_grid: list[float]Stop-loss widths to try, as positive numbers in price units, each > 0.
max_holding_steps: intMaximum holding period in steps; must be > 0.
annualization_factor: floatMultiplier whose square root scales each rule's Sharpe ratio; finite and > 0.
random_walk_phi_threshold: floatSee
detect_no_stable_optimum.min_peak_margin: floatSee
detect_no_stable_optimum.min_surface_std: floatSee
detect_no_stable_optimum.min_best_sharpe: floatSee
detect_no_stable_optimum.
Returns
- dict[str, Any]
Keys
params,best_rule,best_point,response_surfaceanddiagnostics, as forrun_synthetic_otr_workflow.
Raises
- ValueError
If either grid is empty, a grid value is not positive, or
evaluate_rule_on_pathsrejects its input.