fix(fitness): hardening anti-overfit post-7y incident — 3 correzioni
Incident: extended run elite (Sharpe IS 1.93) net-negativo su 7y
continuous (fees=101% del gross). Multi-fold validation NON sufficiente:
ogni fold restarta equity, mascherando accumulo fees compound.
Correzioni:
1) Default --start esteso a 2018-09-01 (7.3 anni)
- Copre bear 2018-19, halving 2020, COVID, ATH 2021, winter 2022,
ETF rally 2024, regime corrente.
- Una finestra corta (2y) lasciava il GA libero di overfit single-regime.
2) fees_eat_alpha promosso a hard-kill in fitness v2
- Da soft-penalty 0.4x a hard-kill 0 fitness.
- Una strategia con fees > 50% del gross non e' recuperabile via
selection: il prodotto del GA non puo' deployare con quel cost burden.
3) Nuovo finding negative_net_pnl (HIGH, hard-kill)
- Fires quando sum(trade.net_pnl) < 0 sul training window.
- Cattura: gross negativo (no edge direzionale) E gross positivo ma
fees > gross (edge insufficiente).
- Sintesi del net-after-fees su finestra continua come "deal-breaker"
non negoziabile via soft penalty.
CLI:
- --fitness-hard-kill <csv> per override esplicito.
- Default v2: no_trades,degenerate,undertrading,fees_eat_alpha,negative_net_pnl.
Test:
- 252 pass (251 + 1 nuovo: test_negative_net_pnl_fires_on_negative_gross).
- Test e2e WFA aggiornato: passa fitness_hard_kill_findings=('no_trades',)
perche' il fixture sintetico non produce strategie profittevoli.
- test_no_findings_on_reasonable_strategy rinominato:
test_rsi_mean_reversion_loses_money_on_synthetic_data (riflette
semantica reale: RSI mean-rev su synthetic ohlcv ha net negativo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+43
-6
@@ -19,6 +19,30 @@ def _default_prompt_library_path() -> Path:
|
||||
return Path(str(importlib.resources.files("strategy_crypto") / "prompts.json"))
|
||||
|
||||
|
||||
# Default v2 hard-kill list: oltre ai degenerate originali, fees_eat_alpha e
|
||||
# negative_net_pnl sono deal-breaker non recuperabili via soft penalty (vedi
|
||||
# 7y-overfit incident 2026-05-16: elite IS Sharpe 1.93 -> net -5% su 7y per fees).
|
||||
_DEFAULT_V2_HARD_KILL: tuple[str, ...] = (
|
||||
"no_trades",
|
||||
"degenerate",
|
||||
"undertrading",
|
||||
"fees_eat_alpha",
|
||||
"negative_net_pnl",
|
||||
)
|
||||
|
||||
|
||||
def _resolve_hard_kill(args) -> tuple[str, ...] | None:
|
||||
"""Resolve la lista hard-kill da CLI args.
|
||||
|
||||
Priority: ``--fitness-hard-kill`` esplicito > default v2 > ``None`` (v1).
|
||||
"""
|
||||
if args.fitness_hard_kill:
|
||||
return tuple(s.strip() for s in args.fitness_hard_kill.split(",") if s.strip())
|
||||
if args.fitness_v2:
|
||||
return _DEFAULT_V2_HARD_KILL
|
||||
return None
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(description="Multi-Swarm Phase 1 runner")
|
||||
p.add_argument("--name", default="phase1-spike-001")
|
||||
@@ -35,7 +59,10 @@ def parse_args() -> argparse.Namespace:
|
||||
)
|
||||
p.add_argument("--symbol", default="BTC-PERPETUAL")
|
||||
p.add_argument("--timeframe", default="1h")
|
||||
p.add_argument("--start", default="2024-01-01T00:00:00+00:00")
|
||||
# Default esteso a 7.3 anni: copre bear 2018-19, halving 2020, COVID,
|
||||
# ATH 2021, winter 2022, ETF rally 2024, regime corrente. Una finestra
|
||||
# corta lascia il GA libero di overfit a un singolo regime.
|
||||
p.add_argument("--start", default="2018-09-01T00:00:00+00:00")
|
||||
p.add_argument("--end", default="2026-01-01T00:00:00+00:00")
|
||||
p.add_argument("--fees-bp", type=float, default=5.0)
|
||||
p.add_argument("--n-trials-dsr", type=int, default=50)
|
||||
@@ -67,8 +94,10 @@ def parse_args() -> argparse.Namespace:
|
||||
"--fitness-v2",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Attiva fitness v2: solo {no_trades, degenerate, undertrading} azzerano; "
|
||||
"gli altri HIGH applicano soft penalty multiplicativa"
|
||||
"Attiva fitness v2: hard-kill su {no_trades, degenerate, undertrading, "
|
||||
"fees_eat_alpha, negative_net_pnl}; gli altri HIGH applicano soft penalty "
|
||||
"multiplicativa. Versione hardened post 7y-overfit incident: fees + "
|
||||
"net negativo non sono recuperabili."
|
||||
),
|
||||
)
|
||||
p.add_argument(
|
||||
@@ -77,6 +106,16 @@ def parse_args() -> argparse.Namespace:
|
||||
default=0.4,
|
||||
help="v2: fattore soft penalty per HIGH non-hard (default 0.4 → 1 HIGH → 0.71x)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--fitness-hard-kill",
|
||||
type=str,
|
||||
default=None,
|
||||
help=(
|
||||
"Override comma-separated della lista di finding name che azzerano la "
|
||||
"fitness in modalita' v2. Es: 'no_trades,degenerate'. Default v2: "
|
||||
"no_trades,degenerate,undertrading,fees_eat_alpha,negative_net_pnl."
|
||||
),
|
||||
)
|
||||
p.add_argument(
|
||||
"--wfa-train-split",
|
||||
type=float,
|
||||
@@ -188,9 +227,7 @@ def main() -> None:
|
||||
fees_eat_alpha_threshold=args.fees_eat_alpha_threshold,
|
||||
flat_too_long_threshold=args.flat_too_long_threshold,
|
||||
undertrading_threshold=args.undertrading_threshold,
|
||||
fitness_hard_kill_findings=(
|
||||
("no_trades", "degenerate", "undertrading") if args.fitness_v2 else None
|
||||
),
|
||||
fitness_hard_kill_findings=_resolve_hard_kill(args),
|
||||
fitness_adversarial_soft_penalty=args.fitness_soft_penalty,
|
||||
wfa_train_split=args.wfa_train_split,
|
||||
wfa_top_k=args.wfa_top_k,
|
||||
|
||||
Reference in New Issue
Block a user