feat(fitness): multi-objective combined = alpha*IS + (1-alpha)*OOS opt-in

Aggiunti due meccanismi per selection multi-objective:

1) Helper compute_combined_fitness(fit_train, fit_oos, alpha):
   formula = alpha*IS + (1-alpha)*OOS, fallback a IS se OOS è None/NaN.

2) RunConfig.eval_oos_during_loop (default False) + fitness_combined_alpha
   (default 0.5). Quando True E wfa_train_split attivo, ogni genome con
   fitness IS > 0 viene rivalutato su test_ohlcv DURANTE il loop GA e la
   fitness usata per tournament_select/elite_select è quella combinata.
   2x costo backtest engine (richiede 2 evaluation per genome).

3) CLI flags --eval-oos-during-loop e --fitness-combined-alpha.

Motivazione: il run phase2-7-max7y-v2-wfa-001 ha mostrato che il top by
fitness_IS (4e1be9fa, ratio OOS 0.31) NON è il top per performance OOS reale
(634111992702, ratio 1.42, ret_OOS +105% / 2.2y). Selezionare durante GA
con combined fitness orienta l'evoluzione verso strategie OOS-robust by
design invece di filtrarle a posteriori.

Backward compat: default eval_oos_during_loop=False → comportamento
invariato per run senza il flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 16:47:53 +02:00
parent 3fcad79f8d
commit 1a1dfb7a73
3 changed files with 68 additions and 0 deletions
+20
View File
@@ -32,6 +32,26 @@ from ..agents.adversarial import AdversarialReport, Severity
from ..agents.falsification import FalsificationReport
def compute_combined_fitness(
fitness_train: float,
fitness_oos: float | None,
alpha: float = 0.5,
) -> float:
"""Combina fitness IS e OOS in uno scalare per selection multi-objective.
Formula::
combined = alpha * fitness_train + (1 - alpha) * fitness_oos
Se ``fitness_oos`` è ``None`` o NaN, ritorna ``fitness_train`` (fallback).
alpha=1.0 → solo IS (= comportamento default). alpha=0.0 → solo OOS.
alpha=0.5 → bilanciato.
"""
if fitness_oos is None or fitness_oos != fitness_oos: # noqa: PLR0124 (NaN check)
return fitness_train
return alpha * fitness_train + (1.0 - alpha) * fitness_oos
def compute_fitness(
falsification: FalsificationReport,
adversarial: AdversarialReport,