feat(ga): fitness v0 (DSR - dd_penalty * max_dd, kill on adversarial high)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
"""Fitness function v0 della Phase 1.
|
||||||
|
|
||||||
|
Combina :class:`FalsificationReport` (metriche di robustezza) e
|
||||||
|
:class:`AdversarialReport` (findings euristici) in uno scalare ``>= 0`` che il
|
||||||
|
GA usa per selezione e ranking.
|
||||||
|
|
||||||
|
Logica deliberatamente coarse: DSR penalizzato dal max drawdown, con due
|
||||||
|
kill-switch hard (no-trade, finding HIGH adversarial) che azzerano la fitness.
|
||||||
|
La penalita' lineare sul drawdown e' un compromesso volutamente semplice;
|
||||||
|
versioni successive potranno usare Calmar o utility convessa.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ..agents.adversarial import AdversarialReport, Severity
|
||||||
|
from ..agents.falsification import FalsificationReport
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fitness(
|
||||||
|
falsification: FalsificationReport,
|
||||||
|
adversarial: AdversarialReport,
|
||||||
|
drawdown_penalty: float = 0.5,
|
||||||
|
) -> float:
|
||||||
|
"""Calcola la fitness scalare di una strategia.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
falsification: report con DSR, max_drawdown, n_trades.
|
||||||
|
adversarial: report con eventuali findings euristici.
|
||||||
|
drawdown_penalty: peso lineare sul max drawdown (default 0.5).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fitness ``>= 0``. Zero indica strategia da scartare.
|
||||||
|
|
||||||
|
Logica:
|
||||||
|
1. ``n_trades == 0`` → 0 (nessuna evidenza, sega subito).
|
||||||
|
2. Almeno un finding ``HIGH`` adversarial → 0 (kill).
|
||||||
|
3. Altrimenti: ``dsr - drawdown_penalty * max_drawdown``, clamped a 0.
|
||||||
|
"""
|
||||||
|
if falsification.n_trades == 0:
|
||||||
|
return 0.0
|
||||||
|
if any(f.severity == Severity.HIGH for f in adversarial.findings):
|
||||||
|
return 0.0
|
||||||
|
raw = falsification.dsr - drawdown_penalty * falsification.max_drawdown
|
||||||
|
return max(0.0, float(raw))
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
) -> FalsificationReport:
|
||||||
|
return FalsificationReport(
|
||||||
|
sharpe=1.5,
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user