feat(phase-2.6): Walk-Forward Validation + min-trades filter parametrico
Due fondamenta scientifiche per filtrare overfit e lucky-shot:
1) undertrading_threshold parametrico (era hardcoded 10):
- AdversarialAgent.__init__(undertrading_threshold=10)
- CLI flag --undertrading-threshold
- Aggiunto a hard_kill_findings v2 default
{"no_trades", "degenerate", "undertrading"}: ora un genome con 1 trade
fortunato (es. genome 80be6bcc-1trade-fit-0.21 di fitness-v2-combo) viene
killato anche sotto fitness v2 soft-kill.
- Test parametric: undertrading_threshold=25 → 15 trade triggerano HIGH.
2) Walk-Forward Validation (WFA):
- RunConfig.wfa_train_split (None=off, 0<x<1=on) + wfa_top_k=5
- run_phase1: split ohlcv in train/test; GA usa solo train; a fine GA
i top_k genomi (by fitness in-sample, fitness>0) vengono rivalutati
sul test_ohlcv via falsification+adversarial+compute_fitness.
- Schema migration: evaluations + fitness_oos, sharpe_oos, return_oos,
max_dd_oos, n_trades_oos (ALTER TABLE con try/except per DB pre-2.6).
- Repository.update_evaluation_oos helper per popolare colonne OOS.
- CLI flags --wfa-train-split, --wfa-top-k.
- Test integration: train_split=0.7 → fitness_oos popolato per top_k.
Motivazione: la fase 2.5 ha generato 17 run con fitness fino a 0.36 + DSR
positivo, ma OOS test su 7 anni mostra che flat-ablation top crolla -37%
mentre fitness-v2 top regge (+143%). WFA in-run permette ora di vedere
direttamente il degradation train→test senza eseguire backtest separati,
rendendo possibile filtrare overfit early durante l'ottimizzazione.
Tests (+2 → 193 totale):
- test_undertrading_threshold_parametric
- test_e2e_wfa_populates_fitness_oos
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-2
@@ -49,11 +49,17 @@ def parse_args() -> argparse.Namespace:
|
||||
default=0.95,
|
||||
help="Adversarial gate: kill se signal flat > soglia delle bar (default 0.95, ablation 0.98)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--undertrading-threshold",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Adversarial: kill se n_trades < soglia (default 10, bump per filtrare lucky-shot)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--fitness-v2",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Attiva fitness v2: solo {no_trades, degenerate} azzerano; "
|
||||
"Attiva fitness v2: solo {no_trades, degenerate, undertrading} azzerano; "
|
||||
"gli altri HIGH applicano soft penalty multiplicativa"
|
||||
),
|
||||
)
|
||||
@@ -63,6 +69,18 @@ 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(
|
||||
"--wfa-train-split",
|
||||
type=float,
|
||||
default=None,
|
||||
help="Walk-forward: frazione bar usate per training (es. 0.7 = primi 70%% in-sample, ultimi 30%% OOS)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--wfa-top-k",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Walk-forward: quanti top genomi rivalutare OOS (default 5)",
|
||||
)
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
@@ -119,10 +137,13 @@ def main() -> None:
|
||||
prompt_mutation_weight=args.prompt_mutation_weight,
|
||||
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") if args.fitness_v2 else None
|
||||
("no_trades", "degenerate", "undertrading") if args.fitness_v2 else None
|
||||
),
|
||||
fitness_adversarial_soft_penalty=args.fitness_soft_penalty,
|
||||
wfa_train_split=args.wfa_train_split,
|
||||
wfa_top_k=args.wfa_top_k,
|
||||
)
|
||||
|
||||
run_id = run_phase1(cfg, ohlcv=ohlcv, llm=llm)
|
||||
|
||||
Reference in New Issue
Block a user