feat(adversarial): phase 1.5 hardening (tighter thresholds + flat_too_long + fees_eat_alpha)
Stringe le soglie esistenti e aggiunge due check HIGH per killare le strategie degeneri scoperte nel run v5 (top-1 +2.66% vs BTC B&H +106%, flat 99.8% del tempo, fees 69% del lordo). - overtrading: soglia da n_bars/5 a n_bars/20 (MEDIUM) - undertrading: HIGH se n_trades < 10 (era MEDIUM <5) — sample troppo piccolo per distinguere edge da rumore (lucky shot) - flat_too_long (NEW, HIGH): signal attivo per <5% delle bar — la strategia ha mancato il regime, e' una non-strategia - fees_eat_alpha (NEW, HIGH): gross_pnl > 0 ma fees > 50% del lordo — margine sottile non sostenibile in produzione Test count: 141 -> 145 (+4 nuovi test deterministici via monkeypatch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Adversarial agent: ispeziona una :class:`Strategy` con check euristici
|
||||
hand-crafted per scovare patologie note (degenerate, no-trade, over/under
|
||||
trading) prima del training vero e proprio.
|
||||
trading, flat-too-long, fees-eat-alpha) prima del training vero e proprio.
|
||||
|
||||
Pipeline:
|
||||
|
||||
@@ -9,6 +9,12 @@ Pipeline:
|
||||
Le euristiche sono volutamente coarse: l'agente non rimpiazza la
|
||||
falsificazione, ma sega presto i casi degeneri (es. ``gt close -1e9`` →
|
||||
sempre long) che inquinerebbero il leaderboard del swarm.
|
||||
|
||||
Phase 1.5 hardening: soglie strette per overtrading (n_trades > n_bars/20)
|
||||
e undertrading (HIGH se n_trades < 10), piu' due nuovi check HIGH:
|
||||
``flat_too_long`` (signal flat >95% delle bar) e ``fees_eat_alpha``
|
||||
(fees > 50% del gross_pnl positivo). Killano le strategie "lucky shot"
|
||||
e quelle con margine sottile non sostenibile in produzione.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -87,24 +93,61 @@ class AdversarialAgent:
|
||||
|
||||
n_bars = len(ohlcv)
|
||||
n_trades = len(result.trades)
|
||||
# Overtrading: > 1 trade ogni 5 bar -> il segnale flippa cosi' spesso
|
||||
# Overtrading: > 1 trade ogni 20 bar (Phase 1.5: era 1/5).
|
||||
# Soglia stretta per scovare strategie che flippano cosi' spesso
|
||||
# che le fees mangiano qualunque edge.
|
||||
if n_trades > n_bars / 5:
|
||||
if n_trades > n_bars / 20:
|
||||
report.findings.append(
|
||||
Finding(
|
||||
name="overtrading",
|
||||
severity=Severity.MEDIUM,
|
||||
detail=f"{n_trades} trades on {n_bars} bars (>1 per 5 bars)",
|
||||
detail=f"{n_trades} trades on {n_bars} bars (>1 per 20 bars)",
|
||||
)
|
||||
)
|
||||
# Undertrading: < 5 trade -> sample size troppo piccolo per
|
||||
# distinguere edge da rumore (lucky shot).
|
||||
if n_trades < 5:
|
||||
# Undertrading: < 10 trade -> HIGH (Phase 1.5: era < 5 MEDIUM).
|
||||
# Sample size troppo piccolo per distinguere edge da rumore: e'
|
||||
# un "lucky shot" non riproducibile out-of-sample.
|
||||
if n_trades < 10:
|
||||
report.findings.append(
|
||||
Finding(
|
||||
name="undertrading",
|
||||
severity=Severity.MEDIUM,
|
||||
detail=f"only {n_trades} trades — likely lucky shot",
|
||||
severity=Severity.HIGH,
|
||||
detail=f"only {n_trades} trades — likely lucky shot (<10 over training)",
|
||||
)
|
||||
)
|
||||
|
||||
# Flat-too-long: signal attivo (LONG o SHORT) per <5% delle bar.
|
||||
# Anche se la strategia produce trade, una che e' inerte 19h su 20
|
||||
# ha mancato il regime ed e' di fatto una non-strategia.
|
||||
# NaN (warmup) contano come "flat" perche' downstream l'engine
|
||||
# li riempie via ffill().fillna(Side.FLAT).
|
||||
n_active = int(((signals == Side.LONG) | (signals == Side.SHORT)).sum())
|
||||
n_flat_or_nan = n_bars - n_active
|
||||
flat_ratio = n_flat_or_nan / n_bars if n_bars > 0 else 1.0
|
||||
if flat_ratio > 0.95:
|
||||
report.findings.append(
|
||||
Finding(
|
||||
name="flat_too_long",
|
||||
severity=Severity.HIGH,
|
||||
detail=f"Signal flat for {flat_ratio * 100:.1f}% of bars (>95% threshold)",
|
||||
)
|
||||
)
|
||||
|
||||
# Fees-eat-alpha: gross_pnl > 0 ma fees > 50% del lordo.
|
||||
# La strategia ha edge teorico ma il margine viene mangiato dai
|
||||
# costi di transazione: non sostenibile in produzione.
|
||||
# Se gross_pnl <= 0 il check non si applica (gia' perdente).
|
||||
gross_pnl = sum(t.gross_pnl for t in result.trades)
|
||||
total_fees = sum(t.fees for t in result.trades)
|
||||
if gross_pnl > 0 and total_fees / gross_pnl > 0.5:
|
||||
report.findings.append(
|
||||
Finding(
|
||||
name="fees_eat_alpha",
|
||||
severity=Severity.HIGH,
|
||||
detail=(
|
||||
f"Fees ${total_fees:.2f} = "
|
||||
f"{total_fees / gross_pnl * 100:.1f}% of gross ${gross_pnl:.2f}"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user