feat(adversarial): time_in_market_too_high HIGH (>80% always-in-market)

Simmetrico opposto di flat_too_long: penalizza strategie LONG/SHORT su
piu' dell'80% delle bar. Una sempre-in-market e' leveraged B&H camuffato,
esposto a funding cumulato (perp ogni 8h), tail risk eventi notturni e
nessuna opportunity-cost flexibility. Sweet spot fitness positiva: 5-80%
time in market.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 23:54:46 +02:00
parent 23c9e37f94
commit d3662f6098
2 changed files with 123 additions and 5 deletions
+94
View File
@@ -338,3 +338,97 @@ def test_fees_eat_alpha_flagged(monkeypatch: pytest.MonkeyPatch,
f.name == "fees_eat_alpha" and f.severity == Severity.HIGH
for f in report.findings
)
def test_time_in_market_too_high_flagged(monkeypatch: pytest.MonkeyPatch,
ohlcv: pd.DataFrame) -> None:
"""Signal LONG per >80% delle bar -> HIGH time_in_market_too_high."""
n_bars = len(ohlcv)
# 90% LONG, 10% FLAT iniziali (warmup-like) per evitare degenerate.
n_flat = int(n_bars * 0.10)
sig_values = [Side.FLAT] * n_flat + [Side.LONG] * (n_bars - n_flat)
fake_signals = pd.Series(sig_values, index=ohlcv.index, dtype=object)
# 15 trade per evitare undertrading HIGH.
fake_trades = [
_make_trade(
ohlcv.index[i * 30],
ohlcv.index[i * 30 + 1],
entry_price=100.0,
exit_price=101.0,
)
for i in range(15)
]
def fake_run(self, ohlcv: pd.DataFrame, signals: pd.Series) -> BacktestResult: # type: ignore[no-untyped-def]
return BacktestResult(
equity_curve=pd.Series([0.0] * len(ohlcv), index=ohlcv.index, name="equity"),
returns=pd.Series([0.0] * len(ohlcv), index=ohlcv.index, name="returns"),
trades=fake_trades,
)
def fake_compile(strategy): # type: ignore[no-untyped-def]
return lambda df: fake_signals
monkeypatch.setattr(
"multi_swarm.agents.adversarial.BacktestEngine.run", fake_run
)
monkeypatch.setattr(
"multi_swarm.agents.adversarial.compile_strategy", fake_compile
)
src = _MINIMAL_STRATEGY_SRC
ast = parse_strategy(src)
agent = AdversarialAgent()
report = agent.review(ast, ohlcv)
assert any(
f.name == "time_in_market_too_high" and f.severity == Severity.HIGH
for f in report.findings
)
def test_reasonable_balanced_strategy_not_flagged(monkeypatch: pytest.MonkeyPatch,
ohlcv: pd.DataFrame) -> None:
"""Mix ~50% flat, ~25% long, ~25% short: no HIGH sui gate temporali."""
n_bars = len(ohlcv)
# Pattern ciclico: 2 flat, 1 long, 1 short per ogni gruppo da 4 bar.
# Risultato: ~50% FLAT, ~25% LONG, ~25% SHORT. flat_ratio=0.5 < 0.95,
# active_ratio=0.5 < 0.80.
pattern = [Side.FLAT, Side.FLAT, Side.LONG, Side.SHORT]
sig_values = [pattern[i % 4] for i in range(n_bars)]
fake_signals = pd.Series(sig_values, index=ohlcv.index, dtype=object)
# 15 trade per evitare undertrading HIGH.
fake_trades = [
_make_trade(
ohlcv.index[i * 30],
ohlcv.index[i * 30 + 1],
entry_price=100.0,
exit_price=101.0,
)
for i in range(15)
]
def fake_run(self, ohlcv: pd.DataFrame, signals: pd.Series) -> BacktestResult: # type: ignore[no-untyped-def]
return BacktestResult(
equity_curve=pd.Series([0.0] * len(ohlcv), index=ohlcv.index, name="equity"),
returns=pd.Series([0.0] * len(ohlcv), index=ohlcv.index, name="returns"),
trades=fake_trades,
)
def fake_compile(strategy): # type: ignore[no-untyped-def]
return lambda df: fake_signals
monkeypatch.setattr(
"multi_swarm.agents.adversarial.BacktestEngine.run", fake_run
)
monkeypatch.setattr(
"multi_swarm.agents.adversarial.compile_strategy", fake_compile
)
src = _MINIMAL_STRATEGY_SRC
ast = parse_strategy(src)
agent = AdversarialAgent()
report = agent.review(ast, ohlcv)
# I due gate temporali non devono triggerare.
names = [f.name for f in report.findings]
assert "flat_too_long" not in names
assert "time_in_market_too_high" not in names