Skip to main content

Module synthetic_backtesting

Module synthetic_backtesting 

Source
Expand description

Synthetic-data backtesting utilities aligned to AFML Chapter 13.

This module focuses on optimal trading-rule (OTR) search over profit-taking and stop-loss corridors by:

  1. calibrating an AR(1)/discrete O-U process from historical prices,
  2. generating many synthetic paths under that calibrated process,
  3. evaluating a PT/SL mesh on those paths, and
  4. detecting when the response surface lacks a stable optimum.

This is AFML §13.4–13.5 (Snippets 13.1–13.2): instead of picking barriers on the one historical path, which overfits, fit a discrete Ornstein–Uhlenbeck process P_t = intercept + phi P_{t-1} + sigma eps_t, simulate many paths, and read the whole profit-taking/stop-loss response surface. §13.6’s result is that the surface flattens as phi approaches 1, which detect_no_stable_optimum flags with this library’s own heuristic thresholds (not AFML’s).

Conventions:

  • Prices are levels, oldest first. Barriers are in price units (not multiples of sigma), measured from the entry price; a barrier is touched when PnL reaches it (>=).
  • Every rule is a long entry at the first price of each path; negate the series for a short.
  • A rule’s Sharpe ratio is the mean over the standard deviation of per-trade PnL, times sqrt(annualization_factor), regardless of holding time.
  • Simulation uses a [StdRng] seeded by the caller, so results are reproducible.
use openquant::synthetic_backtesting::{
    evaluate_rule_on_paths, generate_ou_paths, OuProcessParams, TradingRule,
};

// phi = 0.9 around 100 (intercept = (1 - phi) * equilibrium), entered three points below.
let params = OuProcessParams {
    phi: 0.9,
    intercept: 10.0,
    equilibrium: 100.0,
    sigma: 1.0,
    r_squared: 0.0,
    stationary: true,
};
let paths = generate_ou_paths(params, 97.0, 2_000, 60, 11)?;
assert_eq!(paths, generate_ou_paths(params, 97.0, 2_000, 60, 11)?);
assert!(paths.iter().all(|p| p.len() == 60 && p[0] == 97.0));

// Expecting reversion, a wide stop beats a tight one.
let wide = TradingRule { profit_taking: 2.0, stop_loss: 8.0 };
let tight = TradingRule { profit_taking: 2.0, stop_loss: 0.5 };
let wide = evaluate_rule_on_paths(&paths, wide, 59, 1.0)?;
let tight = evaluate_rule_on_paths(&paths, tight, 59, 1.0)?;
assert!(wide.sharpe > tight.sharpe && wide.win_rate > 0.95);

Structs§

OtrSearchResult
Result of an optimal-trading-rule search.
OuProcessParams
Parameters of the discrete O-U (AR(1)) process P_t = intercept + phi P_{t-1} + sigma eps.
RuleSurfacePoint
A trading rule’s performance across the simulated paths.
StabilityCriteria
Thresholds for detect_no_stable_optimum. These are this library’s heuristics, not AFML’s; the defaults are phi >= 0.97, margin 0.20, surface deviation 0.10 and best Sharpe 0.30 (the Python binding uses different defaults).
StabilityDiagnostics
Result of detect_no_stable_optimum.
SyntheticBacktestConfig
Configuration of run_synthetic_otr_workflow.
TradingRule
A profit-taking / stop-loss pair, both positive widths in price units from the entry.

Enums§

SyntheticBacktestError
Errors returned by the synthetic-backtesting functions.

Functions§

calibrate_ou_params
Fits the discrete O-U process to a price series by OLS of P_t on P_{t-1} (AFML §13.5.1, step 1).
detect_no_stable_optimum
Flags a response surface without a stable optimum (the flattening of AFML §13.6).
evaluate_rule_on_paths
Evaluates one profit-taking/stop-loss rule on every path (AFML §13.5.1, step 3).
generate_ou_paths
Simulates n_paths O-U paths of horizon points each, starting at initial_price (AFML §13.5.1, step 2).
run_synthetic_otr_workflow
End-to-end optimal-trading-rule search: calibrate on historical_prices, simulate, and search the configured grid (AFML §13.5).
search_optimal_trading_rule
Evaluates every (profit_taking, stop_loss) combination of the grids on paths and diagnoses the resulting surface (AFML §13.5.1, Snippets 13.1–13.2).