aether_quant.research¶
Research and audit tools (V6.4.3): one report for any signal, the probability of backtest overfitting, factor baselines and spanning, overfitting and leakage audits, the simulator / Lean gap, research probes, the trial registry.
Research and audit tools (V6.4.3): one report for any signal, the probability of backtest overfitting, factor baselines and spanning, overfitting and leakage audits, the simulator / Lean gap, research probes, the trial registry.
FACTOR_COLUMNS
module-attribute
¶
FACTOR_COLUMNS = ('f_mom_12_1', 'f_mom_20', 'f_rev_5', 'f_lowvol', 'f_high52')
ResearchContext
dataclass
¶
Everything a research trial runs against: the market panel, the signal panel, the path config and the simulator.
settings
class-attribute
instance-attribute
¶
settings: WeightSettings = field(default_factory=WeightSettings)
sessions_for ¶
sessions_for(spec: SleeveSpec) -> set[str] | None
Sessions to score a sleeve on; None for a crypto-only sleeve, whose calendar is every day.
simulate ¶
simulate(weights: Mapping[str, Mapping[str, float]], start: str, end: str, *, max_drawdown_velocity: float | None = None, max_total_drawdown: float | None = None) -> dict[str, Any]
WideCell
dataclass
¶
alpha_proof_report ¶
alpha_proof_report(run_dir: Path, config: dict[str, Any], root: Path, *, trial_sharpes: list[float], deflation: dict[str, Any] | None = None, data_dir: str = 'data') -> dict[str, Any]
Every period's figures and the R-ALPHA1 verdict for one Lean run folder (its frozen config when present). data_dir
is the run's data root; a closes or benchmark read from any other root would silently come back empty or wrong.
alpha_regression ¶
alpha_regression(y: Series, x: DataFrame, *, hac_lags: int = 5, annualization: float = 252.0) -> dict[str, Any]
OLS alpha of y on x (inner-joined dates, NaN rows dropped) with a Newey-West t; annualization is the
number of y periods a year (session data: 252).
annual_sharpe ¶
annual_sharpe(daily: Series, *, sessions: Iterable[str] | None = None) -> float | None
Given sessions, the series is compounded onto them and annualised at 252: a panel has no weekend rows before its first
crypto bar, and sqrt(365) on session rows inflates a Sharpe about 1.2 times. Without them, calendar rows at sqrt(365).
append_trial ¶
append_trial(path: Path | str, *, trial_id: str, track: str, window: str, role: str, annualised_sharpe: float | None, n_obs: int, config_sha: str = '', weights_sha: str = '', engine: str = '', detail: dict[str, Any] | None = None) -> dict[str, Any]
Append one trial's record to the append-only trial ledger.
audit_leakage ¶
audit_leakage(frame: DataFrame, *, feature_columns: Sequence[str], target_column: str = 'target_rank_20d', ticker_column: str = 'ticker', date_column: str = 'date', split_column: str = 'split', horizon: int = 20, class_columns: Sequence[str] = (), model_score_column: str | None = None, min_names: int = 20, seed: int = 7, group_column: str | None = None) -> dict[str, Any]
All four checks; (b) and (c) on the validation split, (d) fit on train and read on validation.
benchmark_daily_returns ¶
benchmark_daily_returns(root: Path, config: dict[str, Any], tickers: Sequence[str] = BENCHMARKS, *, start: str, end: str, data_dir: str = 'data', required: bool = False, lag_equity: bool = False) -> pd.DataFrame
Session-calendar daily returns of the benchmark tickers from their config zips: the calendar is the first
ticker's (SPY, US equity sessions); a ticker that also trades on other days (BTC) is priced at its last close on or
before each session, so every column spans the same session-to-session interval. Tickers not in the universe are
left out, or raise with required, since a verdict must not quietly lose a benchmark. Before a ticker's first bar its
return is 0, not NaN: the regression drops any row with a NaN, which would cut a window back to BTC's first bar.
lag_equity also reads an equity column a session late, like the crypto and forex ones: a crypto book's daily sample holds the bar dated before it, so SPY and AGG on their own date sit a session
out of line with it (#330). It is off by default, so the pre-registered R-ALPHA1 figures are unchanged, and alpha_session_aligned reports the aligned one beside them.
block_bootstrap_mean_ci ¶
block_bootstrap_mean_ci(values: ArrayLike, *, block: int, draws: int = 1000, seed: int = 7, confidence: float = 0.95) -> list[float] | None
Percentile CI of the mean of a serially correlated series, resampling contiguous blocks of block values.
build_context ¶
build_context(root: Path, config: dict[str, Any], *, data_dir: str = 'data') -> ResearchContext
Build a ResearchContext from the repo's data: the market panel, known closes, FRED, CFTC and carry inputs, and listing dates.
build_factor_returns ¶
build_factor_returns(frame: DataFrame, factor_columns: Sequence[str], *, date_column: str = 'date', forward_return_column: str = 'target_return_1d', quantile: float = 0.2, min_names: int = 20) -> pd.DataFrame
Daily equal-weight top-quantile minus bottom-quantile return of each score column; NaN on dates with fewer than
min_names scored names. forward_return_column at t is the t -> t+1 return (the simulator's convention).
build_factor_scores ¶
build_factor_scores(frame: DataFrame, *, ticker_column: str = 'ticker', date_column: str = 'date') -> pd.DataFrame
f_mom_12_1 (sum of daily returns from t-252 to t-21), f_mom_20 (momentum_20d), f_rev_5 (-5-day return),
f_lowvol (-rolling_volatility_20d), f_high52 (dist_52w_high: higher = nearer the 52-week high) and f_composite
(mean of the per-date ranks of mom_12_1, high52, lowvol and rev_5). Same index as frame.
build_registry ¶
build_registry(evaluation_dir: Path) -> dict[str, Any]
Every trial ever scored, collected from the research reports under evaluation_dir, for the deflated Sharpe's N.
calendar_rows ¶
calendar_rows(market: MarketPanel, cell: Cell, warmup_sessions: int) -> list[str]
Every date from warmup_sessions equity sessions before the cell to its end. A crypto panel has calendar days between its sessions.
candidate_ids ¶
candidate_ids(entries: list[dict]) -> set[str]
The ids of the ledger's candidate trials.
collect_trials ¶
collect_trials(payload: Any, phase: str, path: tuple[str, ...] = ()) -> list[dict]
(phase, path, median Sharpe) for every <...>/backtest/{median_net_sharpe} summary in a research payload.
crypto_cell ¶
crypto_cell(root: Path, base: dict[str, Any], cell_name: str, *, freeze_path: Path | None = None, freeze_digest: str | None = None, data_dir: str = CRYPTO_ROOT, settings: WeightSettings = WIDE_SETTINGS, side: str | None = None) -> WideCell
Binance's coins under the Coinbase cost model. Side A coins enter a later cell only from 2023-01, so the part selection read stays out.
side restricts the cell to one side's coins, for R-CRYPTO1.
crypto_ic ¶
crypto_ic(signal: DataFrame, known: DataFrame, *, start: str, end: str, min_names: int = 8, horizon: int = 20) -> dict[str, Any]
A crypto signal's rank IC with the next horizon days' return on the same coins, with the non-overlapping t.
daily_gap_breakdown ¶
daily_gap_breakdown(lean: dict[str, float], offline: dict[str, float], *, rebalance_dates: Iterable[str] = (), worst_share: float = 0.05) -> dict[str, Any]
Statistics of the daily gap offline - lean over their common dates.
daily_returns_for ¶
daily_returns_for(context: ResearchContext, weights: Mapping[str, Any], *, start: str, end: str) -> pd.Series
The daily excess returns of a book of weights over [start, end] in this context's simulator.
dedupe ¶
dedupe(trials: list[dict]) -> list[dict]
One trial per distinct (phase, score): the same configuration re-scored in a later stage is not a new trial.
deflated_sharpe_from_inputs ¶
deflated_sharpe_from_inputs(returns: ArrayLike, *, num_trials: int, trial_variance: float | None, annualization: float) -> dict[str, Any]
The deflated Sharpe with N and the trial variance given (evaluation.trial_ledger.deflation_inputs), not derived from
a list of trial Sharpes: the variance must come from trials on the window being judged.
deflation_inputs ¶
deflation_inputs(entries: list[dict], legacy_registry: dict[str, Any], window: str) -> dict[str, Any]
{"N", "var", "legacy_var", "same_window_trials", "floored"} for a deflated Sharpe judged on window. A candidate
scored several times on a window counts once, by its last score. entries must hold every ledger phase's rows.
equity_cell ¶
equity_cell(root: Path, base: dict[str, Any], cell_name: str, *, freeze_path: Path | None = None, freeze_digest: str | None = None, data_dir: str = WIDE_ROOT, settings: WeightSettings = WIDE_SETTINGS, with_features: bool = True, with_info: bool = True) -> WideCell
The cell's context, features and w-tables. Sectors come from SEC; a name with none is never ranked.
evaluate_signal ¶
evaluate_signal(frame: DataFrame, *, score_column: str, date_column: str = 'date', ticker_column: str = 'ticker', split_column: str = 'split', splits: Sequence[str] = ('validation', 'backtest'), horizons: Sequence[int] = (5, 20), target_template: str = 'target_rank_{h}d', class_column: str | None = None, regime_column: str | None = None, min_names_per_date: int = 20, era_days: int = 90, bootstrap_draws: int = 1000, seed: int = 7) -> dict[str, Any]
{split: {"h
factor_spanning ¶
factor_spanning(signal_returns: Series, factor_returns: DataFrame, *, hac_lags: int = 19, periods_per_year: int = 252) -> dict[str, Any]
OLS of the signal's daily return on the factors' (dates present in both, rows with a NaN dropped).
fit_hgb_rank ¶
fit_hgb_rank(x_train: ndarray, y_train: ndarray, **params: Any) -> Any
Fit a histogram gradient-boosted regressor to the finite targets (sklearn, imported on call).
fit_ridge_rank ¶
fit_ridge_rank(x_train: ndarray, y_train: ndarray, *, alpha: float = 10.0, features: Sequence[str] | None = None) -> dict[str, Any]
Ridge on centred targets: coef = (X'X + alpha I)^-1 X'(y - mean y). Rows with any NaN are dropped.
fresh_sessions ¶
fresh_sessions(y: Series, sessions: Iterable[str], *, start: str = FRESH_START) -> int
Equity sessions after the data freeze, whatever else the series holds.
gap_report ¶
gap_report(calibration: dict[str, Any], *, variant: str = 'calibrated') -> dict[str, Any]
{window: {"ladder": rung_attribution, "best_rung", "judged": the variant row}} for a lean_calibration.py report.
head_ic ¶
head_ic(metrics: dict[str, Any], split: str, head: str) -> float | None
<split>.<head>_ic_non_overlapping.mean_ic of a training-metrics file (the daily <head>_ic as a fallback).
ic_summary ¶
ic_summary(ic_by_date: dict[str, float], *, horizon: int, era_days: int = 90, bootstrap_draws: int = 1000, seed: int = 7) -> dict[str, Any]
Statistics of one daily IC series ({date: ic}).
identity_check ¶
identity_check(train: DataFrame, evaluation: DataFrame, target_column: str, *, ticker_column: str = 'ticker', date_column: str = 'date', class_columns: Sequence[str] = (), model_score_column: str | None = None, min_names: int = 20, group_column: str | None = None) -> dict[str, Any]
IC of an asset-identity prior (each ticker's mean train target) and an asset-class prior against the model's.
With group_column (V6.4.4) the same figures are also computed on the group-centred target ("group_centred"): the
raw priors mostly read the group-size artifact of percentile ranks, which a book ranking within groups cannot trade.
label_shuffle_check ¶
label_shuffle_check(frame: DataFrame, feature_columns: Sequence[str], target_column: str, *, date_column: str = 'date', min_names: int = 20, seed: int = 7, max_abs_t: float = 3.0) -> dict[str, Any]
A shuffled target's IC is sampling noise around 0; |t| above max_abs_t is not.
lag_onto ¶
lag_onto(table: DataFrame, index: Index, *, limit: int = 7) -> pd.DataFrame
A day-indexed table (volume, funding) as the session knows it: the value of day d is visible from d+1, carried forward at most limit rows. Same timing as c3.
learning_curve ¶
learning_curve(fit_and_score: Callable[[DataFrame, DataFrame], float], frame: DataFrame, *, train_years: Sequence[int] = (1, 2, 4, 7), train_end: str = '2021-12-31', eval_split: str = 'validation', date_column: str = 'date', split_column: str = 'split') -> list[dict]
Out-of-sample score for training windows of train_years years ending at train_end; fit_and_score(train,
evaluation) fits on the first frame and returns a score (e.g. mean rank IC) on the second.
legacy_trials ¶
legacy_trials(registry: dict[str, Any], exclude_phases: tuple[str, ...] = LEDGER_PHASES) -> list[dict]
The registry's trials not already in a ledger: the registry also holds V6.5.0's ledger, which candidate_ids counts
again, so N read 178 for what is 128.
liquid_mask_for_cell ¶
liquid_mask_for_cell(liquid: DataFrame, cell: Cell, project: list[str]) -> pd.DataFrame
In the holdout cell, side A coins count only after the window selection read.
load_ohlcv ¶
load_ohlcv(root: Path, tickers: list[str], rows: list[str], *, data_dir: str) -> dict[str, pd.DataFrame]
open/high/low/volume tables on rows, from the Lean zips.
load_raw_closes ¶
load_raw_closes(root: Path, tickers: list[str], rows: list[str], *, data_dir: str) -> pd.DataFrame
Unadjusted closes of the tickers on the given rows, from reference/raw_closes.
load_research_frame ¶
load_research_frame(dataset_path: str | Path, *, columns: Sequence[str], splits: Sequence[str] | None = None, chunksize: int = 40000) -> pd.DataFrame
The dataset's KEY_COLUMNS plus columns (numeric ones as float32), optionally only some splits.
max_drawdown ¶
max_drawdown(daily: Series) -> float
The largest peak-to-trough fall of the compounded daily returns, as a positive fraction.
measured_annualization ¶
measured_annualization(index: Sequence[str] | Index, *, fallback: float) -> float
Rows a year of this very series: a run with no crypto has no weekend rows, and a fixed 365 would inflate its Sharpe by about 1.2 times. A weekend-free series is 252; one with weekend rows keeps the config's own figure.
nulls_for ¶
nulls_for(spec: SleeveSpec) -> tuple[str, ...]
A ranking can be relabelled across assets; a direction or a timing rule has no cross-section to relabel.
passes_sharpe_clauses ¶
passes_sharpe_clauses(score: Mapping[str, Any], *, half_floor: float = 0.4, pooled_floor: float = 0.5) -> bool
Whether a trial's pooled Sharpe and both halves' Sharpes clear their floors (R-SLEEVE3's Sharpe clauses).
path_config ¶
path_config(base: dict[str, Any]) -> TargetWeightsConfig
The target-weights config research runs on: the project's, with the research overrides.
period_report ¶
period_report(y: Series, benchmarks: DataFrame, *, annualization: float, trial_sharpes: list[float], deflation: dict[str, Any] | None = None) -> dict[str, Any]
deflation (evaluation.trial_ledger.deflation_inputs) replaces the spread of trial_sharpes, which mixes windows and
annualisations, when the caller has one.
phase_offsets ¶
phase_offsets(rebalance_every: int, phases: int = PHASES) -> list[int]
The start offsets a rebalance cycle is averaged over, so no trial depends on its starting day.
placebo_p_value ¶
placebo_p_value(observed: float | None, placebos: list[float]) -> float
Share of placebos at or above the observed Sharpe, counting the observation itself (never exactly zero).
placebo_sharpes ¶
placebo_sharpes(spec: SleeveSpec, context: ResearchContext, mode: str, *, start: str, end: str, draws: int, seed: int = 1) -> list[float]
Sharpes of draws placebo runs over the same window, one random rebalance phase each.
predict_scores ¶
predict_scores(model: Any, x: ndarray) -> np.ndarray
Scores for every row; a ridge row with a NaN feature scores NaN (the model has no view on it).
probability_of_backtest_overfitting ¶
probability_of_backtest_overfitting(returns: ArrayLike, *, blocks: int = 16, periods_per_year: int = 252) -> dict[str, Any]
{"pbo", "num_combinations", "num_trials", "lambda_quantiles", "degradation_slope", "degradation_intercept",
"prob_oos_loss"}; pbo is None when there are fewer than 2 trials or fewer rows than blocks.
project_tickers ¶
project_tickers(base: dict[str, Any], security_type: str) -> list[str]
The project universe's tickers of one security type.
r_alpha1 ¶
r_alpha1(report: dict[str, Any]) -> dict[str, Any]
The descriptive verdict over the per-period figures.
read_info ¶
read_info(root: Path, *, data_dir: str) -> dict[str, pd.DataFrame | None]
What scripts/build_wide_info.py wrote; an absent part reads as None, and its trials report an empty book.
read_ledger ¶
read_ledger(path: Path | str) -> list[dict]
The trial ledger's rows, or [] when the file does not exist.
rung_attribution ¶
rung_attribution(variants: dict[str, dict], order: Sequence[str] = LADDER_ORDER) -> list[dict]
[{"rung", "daily_correlation", "delta_correlation", "annual_return_gap", "delta_gap"}] along the ladder.
score_sleeve ¶
score_sleeve(spec: SleeveSpec, context: ResearchContext, *, start: str, split: str, end: str, signal: DataFrame | None = None) -> dict[str, Any]
Median over the phases of the ex-T-bill Sharpe: pooled and on both sides of split.
selection_gate ¶
selection_gate(scores: list[dict], p_values: Mapping[str, Mapping[str, float]], *, max_survivors: int = 5, max_correlation: float = 0.7, alpha: float = 0.05) -> dict[str, Any]
R-SLEEVE2. A survivor passes both Sharpe clauses and every placebo test; among survivors the higher Sharpe keeps its place
against any other with daily correlation above max_correlation; at most max_survivors remain.
sharpe ¶
sharpe(values: ndarray, annualization: float) -> float | None
The annualised Sharpe of the finite values; None with fewer than two or no variance.
sharpe_at_sessions ¶
sharpe_at_sessions(series: Series, sessions: Sequence[str] | Index) -> float | None
The annual Sharpe of a daily series sampled at the given sessions.
sharpe_bootstrap_ci ¶
sharpe_bootstrap_ci(y: Series, *, annualization: float, block: int = 10, draws: int = 2000, seed: int = 7) -> tuple[float | None, float | None]
95% interval of the annualised Sharpe by a stationary block bootstrap (Politis & Romano, mean block block).
signal_ic_t ¶
signal_ic_t(table: DataFrame, label: DataFrame, *, start: str, end: str, horizon: int = 20, min_names: int = 20) -> dict[str, Any]
The signal's rank IC with the label, with the t on non-overlapping dates (a 20-session label overlaps itself).
single_feature_ic ¶
single_feature_ic(frame: DataFrame, feature_columns: Sequence[str], target_column: str, *, date_column: str = 'date', min_names: int = 20, threshold: float = 0.15) -> dict[str, Any]
Each feature's mean IC against the target, flagged when its size suggests it reads the future.
slice_market ¶
slice_market(market: MarketPanel, rows: list[str]) -> MarketPanel
The panel on exactly these dates, and the sessions among them. Positions are found by date, so a hole is impossible.
strategy_daily_returns ¶
strategy_daily_returns(run: dict[str, Any], config: dict[str, Any], root: Path, *, data_dir: str = 'data') -> pd.Series
Daily strategy return (NAV change minus the T-bill sweep's net P&L, over the previous NAV), indexed by the date
the return ends on - the same series backtest_statistics.compute_backtest_statistics calls the strategy.
tail_nan_check ¶
tail_nan_check(frame: DataFrame, feature_columns: Sequence[str], *, ticker_column: str = 'ticker', date_column: str = 'date', horizon: int = 20, threshold: float = 0.05) -> dict[str, Any]
Whether each feature goes missing in the last horizon rows of every ticker more often than the threshold, a sign it was built from future data.
to_sessions ¶
to_sessions(daily: Series, sessions: Sequence[str] | Index) -> pd.Series
Compounds a calendar-daily return series onto sessions: each session gets every return dated after the previous
session and up to itself (weekend crypto moves land on Monday, as they do in a session-based benchmark).
training_gap_report ¶
training_gap_report(metrics_paths: dict[str, Path], heads: Sequence[str] = RANK_HEADS, splits: Sequence[str] = ('train', 'validation', 'backtest')) -> dict[str, Any]
{model: {head: {split: ic, "train_minus_validation": gap, "retained": validation / train}}} from
ml/
weights_for ¶
weights_for(spec: SleeveSpec, context: ResearchContext, signal: DataFrame, *, start: str, end: str, phase_offset: int) -> dict[str, Any]
The sleeve's weights over [start, end] in this context, from one phase offset.
wide_config_for ¶
wide_config_for(base: dict[str, Any], root: Path, tickers: set[str], *, data_dir: str = WIDE_ROOT) -> dict[str, Any]
The config of the wide research root: the project's, widened to the given tickers and the wide manifest.
window_report ¶
window_report(y: Series, benchmarks: DataFrame, *, start: str, end: str, annualization: float, deflation: dict[str, Any] | None = None) -> dict[str, Any]
Sharpe, alpha, max drawdown and (with deflation) the deflated Sharpe of a strategy series on one window.