feat(adversarial): flat_too_long_threshold parametrico (CLI ablation)

Estende AdversarialAgent con flat_too_long_threshold (default 0.95)
configurabile, simmetrico a fees_eat_alpha_threshold. Propagato a
RunConfig.flat_too_long_threshold e flag CLI --flat-too-long-threshold.

Motivazione: pop30-combo ha registrato 75 finding flat_too_long HIGH
(secondo killer dopo fees_eat_alpha 87). Rilassare la soglia 0.95→0.98
ammette strategie più passive ma marginalmente attive — analogo
all'ablation fees già verificata (+23% stabile).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 13:45:38 +02:00
parent 597815a106
commit bf70acc322
3 changed files with 16 additions and 2 deletions
+7
View File
@@ -43,6 +43,12 @@ def parse_args() -> argparse.Namespace:
default=0.5, default=0.5,
help="Adversarial gate: kill se fees/gross_pnl > soglia (default 0.5, ablation 0.7)", help="Adversarial gate: kill se fees/gross_pnl > soglia (default 0.5, ablation 0.7)",
) )
p.add_argument(
"--flat-too-long-threshold",
type=float,
default=0.95,
help="Adversarial gate: kill se signal flat > soglia delle bar (default 0.95, ablation 0.98)",
)
return p.parse_args() return p.parse_args()
@@ -98,6 +104,7 @@ def main() -> None:
db_path=settings.db_path, db_path=settings.db_path,
prompt_mutation_weight=args.prompt_mutation_weight, prompt_mutation_weight=args.prompt_mutation_weight,
fees_eat_alpha_threshold=args.fees_eat_alpha_threshold, fees_eat_alpha_threshold=args.fees_eat_alpha_threshold,
flat_too_long_threshold=args.flat_too_long_threshold,
) )
run_id = run_phase1(cfg, ohlcv=ohlcv, llm=llm) run_id = run_phase1(cfg, ohlcv=ohlcv, llm=llm)
+7 -2
View File
@@ -63,9 +63,11 @@ class AdversarialAgent:
self, self,
fees_bp: float = 5.0, fees_bp: float = 5.0,
fees_eat_alpha_threshold: float = 0.5, fees_eat_alpha_threshold: float = 0.5,
flat_too_long_threshold: float = 0.95,
) -> None: ) -> None:
self._engine = BacktestEngine(fees_bp=fees_bp) self._engine = BacktestEngine(fees_bp=fees_bp)
self._fees_eat_alpha_threshold = fees_eat_alpha_threshold self._fees_eat_alpha_threshold = fees_eat_alpha_threshold
self._flat_too_long_threshold = flat_too_long_threshold
def review(self, strategy: Strategy, ohlcv: pd.DataFrame) -> AdversarialReport: def review(self, strategy: Strategy, ohlcv: pd.DataFrame) -> AdversarialReport:
signal_fn = compile_strategy(strategy) signal_fn = compile_strategy(strategy)
@@ -133,12 +135,15 @@ class AdversarialAgent:
n_active = int(((signals == Side.LONG) | (signals == Side.SHORT)).sum()) n_active = int(((signals == Side.LONG) | (signals == Side.SHORT)).sum())
n_flat_or_nan = n_bars - n_active n_flat_or_nan = n_bars - n_active
flat_ratio = n_flat_or_nan / n_bars if n_bars > 0 else 1.0 flat_ratio = n_flat_or_nan / n_bars if n_bars > 0 else 1.0
if flat_ratio > 0.95: if flat_ratio > self._flat_too_long_threshold:
report.findings.append( report.findings.append(
Finding( Finding(
name="flat_too_long", name="flat_too_long",
severity=Severity.HIGH, severity=Severity.HIGH,
detail=f"Signal flat for {flat_ratio * 100:.1f}% of bars (>95% threshold)", detail=(
f"Signal flat for {flat_ratio * 100:.1f}% of bars "
f"(>{self._flat_too_long_threshold * 100:.0f}% threshold)"
),
) )
) )
+2
View File
@@ -51,6 +51,7 @@ class RunConfig:
db_path: Path = field(default_factory=lambda: Path("./runs.db")) db_path: Path = field(default_factory=lambda: Path("./runs.db"))
prompt_mutation_weight: float = 0.0 # Phase 2.5: opt-in LLM mutator prompt_mutation_weight: float = 0.0 # Phase 2.5: opt-in LLM mutator
fees_eat_alpha_threshold: float = 0.5 # adversarial gate, allenta verso 0.7-0.8 fees_eat_alpha_threshold: float = 0.5 # adversarial gate, allenta verso 0.7-0.8
flat_too_long_threshold: float = 0.95 # adversarial gate, allenta verso 0.98-0.99
def run_phase1( def run_phase1(
@@ -82,6 +83,7 @@ def run_phase1(
adversarial_agent = AdversarialAgent( adversarial_agent = AdversarialAgent(
fees_bp=cfg.fees_bp, fees_bp=cfg.fees_bp,
fees_eat_alpha_threshold=cfg.fees_eat_alpha_threshold, fees_eat_alpha_threshold=cfg.fees_eat_alpha_threshold,
flat_too_long_threshold=cfg.flat_too_long_threshold,
) )
cost_tracker = CostTracker() cost_tracker = CostTracker()