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.