Skip to content

aether_quant.retraining

Retraining's pure gates and plans (workers and registries stay in retraining/).

Retraining's pure gates and plans (workers and registries stay in retraining/).

build_av_add_command

build_av_add_command(paths: list[str], av_binary: str = 'av') -> list[str]

The av add command for the given paths.

build_av_commit_command

build_av_commit_command(message: str, tag: str, metrics: dict[str, float], av_binary: str = 'av', *, no_upload: bool = False) -> list[str]

The av commit command for a model version, with its message, tag and metrics.

build_av_head_command

build_av_head_command(av_binary: str = 'av') -> list[str]

The av log --limit 1 command (JSON output): the vault's head.

build_av_push_command

build_av_push_command(av_binary: str = 'av') -> list[str]

The av push command (JSON output).

build_av_restore_command

build_av_restore_command(commit_hash: str, paths: list[str], av_binary: str = 'av') -> list[str]

Restores only paths: HEAD and every other file stay as they are.

build_av_status_command

build_av_status_command(av_binary: str = 'av') -> list[str]

The av status command (JSON output).

build_commit_plan

build_commit_plan(version_id: str, add_paths: list[str], metrics: dict[str, float], config: dict[str, Any]) -> dict[str, Any]

Assembles the full add -> commit -> push argv plan for one candidate.

config = phase_v2.retraining.vault block. push defaults to True per that config's own default; set config["push"]=False to skip the push step (e.g. commit-only, offline dev), which also keeps the commit itself from attempting an upload.

compare_backtests

compare_backtests(active_report: dict[str, Any], candidate_report: dict[str, Any], config: dict[str, Any]) -> dict[str, Any]

3-way (active / candidate / buy-and-hold) comparison, plus an optional 4th risk-adjusted-benchmark row.

A benchmark row is included only if config["benchmark_ticker"] names an asset present in candidate_report["backtest_per_asset"] - reuses the per-asset breakdown build_strategy_report() already computes.

Returns {passed, comparison: {active, candidate, buy_and_hold, benchmark|None}, reasons: [...]}.

compute_degradation_score

compute_degradation_score(degradation_signals: dict[str, Any], *, weights: dict[str, Any] | None = None) -> float

Continuous 0-1 composite of all trigger severities.

Default weights: kill_switch_tripped=0.40 (most severe), net_sharpe_decay=0.25, rank_ic_decay=0.20, slippage_divergence=0.15. Each signal contributes its weight if True, 0 if False/missing. Result is capped at 1.0.

compute_overfitting_gap

compute_overfitting_gap(metrics: dict[str, Any]) -> float

train.balanced_accuracy - backtest.balanced_accuracy.

No such field is pre-computed for the baseline model anywhere today (unlike expert models' quality_gate.observed) - self-computed here from training_metrics.json's train/backtest splits.

cooldown_remaining_seconds

cooldown_remaining_seconds(recent_events: list[dict], cooldown_minutes: float, now: datetime) -> float

Seconds until the cooldown clears, 0.0 if no active-status event is within it.

An event counts against the cooldown while its status is planned/running/ promoted - a rejected or failed retraining does not block the next attempt, so noisy triggers can't wedge the system into a permanent cooldown without ever producing a promoted model.

count_events_in_last_day

count_events_in_last_day(recent_events: list[dict], now: datetime) -> int

How many of the events were created in the 24 hours before now.

evaluate_ranking_promotion_gate

evaluate_ranking_promotion_gate(ranking_metrics_by_model: dict[str, Any], config: dict[str, Any], walk_forward_summary: dict[str, Any] | None = None) -> dict[str, Any]

Ranking promotion gate on one head, or on whichever of heads clears it.

heads (optional list, V5.6.0): the candidate passes when ANY listed head clears the gate, and the verdict reports which one (observed.gated_head). Without it, the single head is gated exactly as before. The default config lists the heads that drive the live book (phase_v2.rank_signal.heads); gating on residual_rank_20d, which is demoted from the book and has never been promotable, rejected every candidate regardless of the heads actually traded.

evaluate_retraining_plan

evaluate_retraining_plan(triggers: list[dict], recent_events: list[dict], observation_count: int, config: dict[str, Any], now: datetime) -> dict[str, Any]

Top-level pure decision. Checks run in order; the first failure short-circuits with an explanatory reason. The result is:

{
  "should_plan": bool,
  "reason": str,
  "selected_trigger": dict | None,
  "cooldown_remaining_seconds": float,
  "daily_count": int,
  "daily_limit_reached": bool,
}

evaluate_validation_gate

evaluate_validation_gate(candidate_metrics: dict[str, Any], candidate_report: dict[str, Any], active_metrics: dict[str, Any], active_report: dict[str, Any], config: dict[str, Any], *, candidate_ranking_metrics: dict[str, Any] | None = None) -> dict[str, Any]

Returns {passed, failures: [...], near_misses: [...], thresholds: {...}, observed: {...}}.

Checks, in order: - candidate max_drawdown not worse (not more negative) than active - candidate backtest sharpe >= min_sharpe - candidate validation loss not worse than active's by more than max_validation_loss_increase_ratio - compute_overfitting_gap(candidate) <= max_train_backtest_balanced_accuracy_gap - candidate backtest trade_count >= min_trade_count - candidate backtest exposure_rate >= min_exposure_rate - candidate backtest balanced_accuracy >= min_balanced_accuracy OR candidate backtest mcc >= min_mcc (skill floor - see below) - V5.1 Phase 4 (item 7): when candidate_ranking_metrics is provided (keyword-only, default None -> zero change for every existing caller/test) AND config["ranking"]["enabled"] is true, evaluate_ranking_promotion_gate() also runs against it, and its failures/near_misses are merged into this function's own lists before the final passed = not failures - one candidate, one combined verdict, rather than two gates a caller could forget to check both of.

min_observations_satisfied

min_observations_satisfied(observation_count: int, min_observations: int) -> bool

Whether enough observations have accumulated to consider retraining.

select_candidate_trigger

select_candidate_trigger(triggers: list[dict], config: dict[str, Any]) -> dict[str, Any] | None

Highest-priority trigger among those with retrain_candidate=True and an eligible severity, ties broken by newest first.

eligible_severities defaults to ("warning", "critical") - "info" triggers (e.g. observation_count_trigger) never justify a retrain on their own. Priority is _trigger_priority_score() - see its docstring for the weighting (critical drawdown beats a topology warning; a regime-shift+topology combo beats either alone; a repeated trigger type/scope beats a single occurrence).

select_rollback_target

select_rollback_target(active_version: dict[str, Any], version_history: list[dict], degradation_signals: dict[str, Any], config: dict[str, Any]) -> dict[str, Any]

Returns {"should_rollback": bool, "to_version_id": str | None, "reason": str, "failures": list[str]}.

active_version: the currently active model_versions row (retraining/ postgres_registry.py::fetch_active_model_version()'s shape). version_history: candidate rollback TARGETS, newest first (fetch_rollback_candidates()'s shape - status in {"archived", "rolled_back"}). degradation_signals: {"kill_switch_tripped": bool, "net_sharpe_decay": bool, "rank_ic_decay": bool, "bars_since_promotion": int, "bars_since_last_rollback": int | None} - the trigger flags plus the two "how long has X been true" counters this function needs to enforce cooldown/runway (deliberately plain integers, not timestamps - retraining/worker.py owns translating its own notion of "how long" into a count, this module never needs to know what a "bar" represents in that context).

GUARDRAIL - enabled defaults to False: an automatic weight swap is the single most consequential action in this system, must be opt-in.

REFUSES to act (should_rollback=False, reason names exactly which guard blocked it) when: - config["enabled"] is not True. - none of config["triggers"] (default ["kill_switch_tripped", "net_sharpe_decay", "rank_ic_decay"]) are True in degradation_signals. - degradation_signals["bars_since_promotion"] < config["min_bars_since_promotion"] (default 40) - the active version needs a minimum runway to prove itself before being judged. - a rollback already happened within config["cooldown_bars"] (default 120) - degradation_signals["bars_since_last_rollback"] is None (never rolled back) or >= cooldown_bars to pass this guard. - no candidate in version_history satisfies config["require_prior_gate_pass"] (default True) - metrics.validation_gate.passed must be True on the chosen candidate; a version that never cleared the gate itself is never a valid rollback target, no matter how badly the active one is degrading.

Picks the NEWEST (version_history[0]) candidate that passes the gate check - never an arbitrary/older one when a newer valid candidate exists.

select_rollback_target_dry_run

select_rollback_target_dry_run(active_version: dict[str, Any], version_history: list[dict], degradation_signals: dict[str, Any], config: dict[str, Any]) -> dict[str, Any]

Dry-run wrapper around select_rollback_target(): identical return shape plus dry_run: True so callers can audit rollback decisions without executing them.