Skip to content
GeneratedGenerated from the built extension by scripts/generate_python_api_reference.py; CI fails if it is stale.

openquant._core.backtesting_engine

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

cpcv_path_count

cpcv_path_count(n_groups: int, test_groups: int) -> int

Number of CPCV backtest paths, phi[N, k] = k / N * C(N, k) (AFML section 12.4.1).

Each of the N groups is tested in exactly phi of the C(N, k) splits, so the splits can be stitched into phi full-length out-of-sample paths. For example N = 6, k = 2 gives 15 splits and 5 paths.

Parameters

n_groups : int

Number of contiguous groups N; at least 2.

test_groups : int

Number of groups tested per split k; 1 <= test_groups < n_groups.

Returns

int

The path count phi[N, k].

Raises

ValueError

If the core rejects the input (e.g. n_groups < 2, test_groups == 0, test_groups >= n_groups, or C(N, k) too large for the platform's integers).

run_cpcv

run_cpcv(
    t0: Sequence[int],
    t1: Sequence[int],
    split_returns: Sequence[Sequence[float]],
    *,
    n_groups: int,
    test_groups: int,
    pct_embargo: float,
    mode_provenance: str,
    trials_count: int,
    safeguards: Mapping[str, str],
) -> dict[str, Any]

Run a combinatorial purged cross-validation backtest on precomputed returns (AFML 12.4).

The samples are cut into n_groups contiguous groups (the first n % n_groups one sample larger) and one split is built for each of the C(N, k) ways to test test_groups of them, in lexicographic order; this is the numbering of openquant.cross_validation.cpcv_splits with n_splits=n_groups, n_test_splits=test_groups. Each split trains on the other groups after purging (closed-interval overlap with any test span) and an embargo of ceil(pct_embargo * n) samples after each run of adjacent test groups. No model is fitted here: fit and predict on those splits yourself, then pass one array of out-of-sample returns per split. Path j takes each group's returns from the j-th split that tests it, giving phi[N, k] paths that each cover every sample once. The paths reuse the same predictions, so their spread is not a confidence interval.

sharpe in the results is a t-statistic, mean / std * sqrt(n) with the sample standard deviation (ddof 1); it is not annualised, grows with n, and is 0 when the deviation is 0.

Parameters

t0 : list[int]

Start of each sample's label span, as int64 nanoseconds since the epoch (plain integers also work). One per sample, in time order.

t1 : list[int]

End of each sample's label span, in the same units as t0. Must be >= t0.

split_returns : list[list[float]]

One array per split, in split_id order, each holding one finite out-of-sample return per entry of that split's test_indices, in the same order.

n_groups : int

Number of contiguous groups N; 2 <= n_groups <= len(t0). Keyword-only.

test_groups : int

Number of groups tested per split k; 1 <= test_groups < n_groups. Keyword-only.

pct_embargo : float

Embargo as a fraction of the whole sample count, in [0, 1), rounded up. Keyword-only.

mode_provenance : str

Non-blank free-text note on where this configuration came from. Keyword-only.

trials_count : int

Number of configurations tried to reach this one (what a deflated Sharpe ratio needs); must be positive. Keyword-only.

safeguards : dict[str, str]

How the run controls the pitfalls of AFML section 11.4, as non-blank free text, with exactly the keys survivorship_bias_control, look_ahead_control, data_mining_control, cost_assumption and multiple_testing_control. Keyword-only.

Returns

dict[str, Any]

A dict with keys:

  • folds (list[dict]): one per split, with split_id (int), sharpe (float), mean_return (float), std_return (float) and observations (int).
  • splits (list[dict]): one per split, with split_id (int), train_indices (list[int]), test_indices (list[int]), test_groups (list[int], ascending), purged_count (int) and embargo_count (int, samples embargoed that the purge had not already removed).
  • path_count (int): phi[N, k].
  • path_assignments (list[list[int]]): per path, element g is the split_id that supplies group g's returns.
  • path_distribution (list[dict]): one per path, with path_id (int), sharpe, mean_return, std_return (floats) and observations (int).
  • diagnostics (dict): mode (always "combinatorial_purged_cross_validation"), mode_provenance (str), trials_count (int), split_count (int), pct_embargo (float), safeguards (dict[str, str], as passed), uses_label_span_purging (bool, always True), uses_embargo (bool, pct_embargo > 0), total_purged (int) and total_embargoed (int).

Raises

ValueError

If t0 and t1 differ in length; if safeguards is missing a required key or has an unknown one; if split_returns does not have exactly one entry per split or an entry's length differs from its split's test set; or if the core rejects the input (e.g. no samples, a span that ends before it starts, a blank mode_provenance or safeguard, trials_count == 0, n_groups < 2, test_groups not in [1, n_groups), n_groups above the sample count, pct_embargo outside [0, 1), purging and embargo emptying a training set, or an empty or non-finite split_returns entry).