from itertools import pairwise from multi_swarm.agents.adversarial import AdversarialReport, Finding, Severity from multi_swarm.agents.falsification import FalsificationReport from multi_swarm.ga.fitness import compute_fitness def make_falsification( dsr: float = 0.7, max_dd: float = 0.2, n_trades: int = 30, sharpe: float = 1.5, ) -> FalsificationReport: return FalsificationReport( sharpe=sharpe, dsr=dsr, dsr_pvalue=0.05, max_drawdown=max_dd, total_return=0.3, n_trades=n_trades, n_bars=500, ) def test_fitness_zero_trades_is_zero() -> None: f = make_falsification(n_trades=0) a = AdversarialReport() assert compute_fitness(f, a) == 0.0 def test_fitness_increases_with_dsr() -> None: a = AdversarialReport() f1 = make_falsification(dsr=0.5) f2 = make_falsification(dsr=0.9) assert compute_fitness(f2, a) > compute_fitness(f1, a) def test_fitness_decreases_with_drawdown() -> None: a = AdversarialReport() f1 = make_falsification(max_dd=0.1) f2 = make_falsification(max_dd=0.4) assert compute_fitness(f1, a) > compute_fitness(f2, a) def test_fitness_zeroed_by_high_severity_finding() -> None: f = make_falsification() a = AdversarialReport( findings=[Finding(name="degenerate", severity=Severity.HIGH, detail="x")] ) assert compute_fitness(f, a) == 0.0 def test_fitness_continuous_signal_for_mediocre() -> None: """Strategie mediocri (DSR ~0, Sharpe negativo) hanno comunque fitness>0 e la meno cattiva e' preferita.""" a = AdversarialReport() less_bad = make_falsification(dsr=0.001, sharpe=-0.5, max_dd=0.3) worse = make_falsification(dsr=0.001, sharpe=-2.0, max_dd=0.3) f_less = compute_fitness(less_bad, a) f_worse = compute_fitness(worse, a) assert f_less > 0.0 assert f_worse > 0.0 assert f_less > f_worse def test_fitness_bounded() -> None: """Fitness e' bounded in [0, 2.0] per input tipici.""" a = AdversarialReport() cases = [ make_falsification(dsr=0.0, sharpe=-5.0, max_dd=0.0), make_falsification(dsr=0.0, sharpe=0.0, max_dd=0.0), make_falsification(dsr=0.5, sharpe=1.0, max_dd=0.2), make_falsification(dsr=0.9, sharpe=2.0, max_dd=0.15), make_falsification(dsr=1.0, sharpe=5.0, max_dd=0.0), make_falsification(dsr=1.0, sharpe=10.0, max_dd=5.0), ] for f in cases: v = compute_fitness(f, a) assert 0.0 <= v <= 2.0, f"fitness {v} fuori range per {f}" def test_fitness_normalizes_drawdown() -> None: """Con DSR e Sharpe fissi, fitness e' monotona decrescente in max_dd.""" a = AdversarialReport() dds = [0.0, 0.1, 0.5, 1.0, 2.0, 5.0] fitnesses = [ compute_fitness(make_falsification(dsr=0.5, sharpe=1.0, max_dd=dd), a) for dd in dds ] for prev, curr in pairwise(fitnesses): assert prev > curr, f"non monotona: {fitnesses}"