""" Tests for FASE 3 — macro signals (momentum, Fear & Greed) must not apply to non-price markets (politics / tech / events). Regression: for "Will X win the election?"-style questions, is_price_above is False, so positive BTC momentum and high Fear & Greed were sign-flipped into evidence AGAINST the YES outcome. The fix skips both signals entirely for politics/tech/events, leaving their contributions (and feat_mom_lo / feat_fg_lo) at 0.0. evaluate_market only returns a TradingSignal on the TRADE path; on skips it returns None but always emits a structured log line containing the per-feature log-odds (fg_lo=… mom_lo=…). The tests parse that line via caplog. """ import asyncio import logging import math import re import pytest from bot.data.external import ExternalSignals from bot.data.polymarket import Market from bot.strategy.bayesian import BayesianStrategy FEAT_RE = re.compile(r"fg_lo=([+-]\d+\.\d+) mom_lo=([+-]\d+\.\d+)") def _make_market(question: str, category: str) -> Market: return Market( id="mkt-test-1", condition_id="cond-test-1", question=question, yes_token_id="yes-tok", no_token_id="no-tok", yes_price=0.50, no_price=0.50, volume_24h=50_000.0, end_date="2026-07-15T00:00:00Z", active=True, category=category, ) def _make_signals() -> ExternalSignals: # Strong bullish macro environment: BTC +10%, extreme greed. return ExternalSignals( btc_price=100_000.0, btc_change_24h=10.0, eth_price=4_000.0, eth_change_24h=8.0, btc_dominance=50.0, fear_greed_index=80, fear_greed_label="greed", total_market_cap_change=5.0, valid=True, ) def _evaluate_and_parse_feats(question: str, category: str, caplog) -> tuple[float, float]: """Run BayesianStrategy.evaluate and return (feat_fg_lo, feat_mom_lo) from the audit log.""" strategy = BayesianStrategy(news=None, manifold=None, db=None) market = _make_market(question, category) with caplog.at_level(logging.INFO, logger="bot.strategy.bayesian"): asyncio.run( strategy.evaluate(market, _make_signals(), occupied_families=set()) ) for record in caplog.records: m = FEAT_RE.search(record.getMessage()) if m: return float(m.group(1)), float(m.group(2)) pytest.fail( "No SKIP_EDGE_NET/TRADE log line with feature contributions found; " f"got: {[r.getMessage() for r in caplog.records]}" ) def test_politics_market_ignores_momentum_and_fear_greed(caplog): """Political market with BTC +10% and F&G=80 → both contributions 0.0.""" feat_fg_lo, feat_mom_lo = _evaluate_and_parse_feats( "Will John Smith win the election?", "politics", caplog ) assert feat_mom_lo == 0.0 assert feat_fg_lo == 0.0 # The signal sources must not mention momentum or Fear & Greed either. full_log = "\n".join(r.getMessage() for r in caplog.records) assert "Fear&Greed" not in full_log assert "24h" not in full_log def test_tech_and_events_markets_ignore_macro_signals(caplog): for category in ("tech", "events"): caplog.clear() feat_fg_lo, feat_mom_lo = _evaluate_and_parse_feats( "Will the product launch happen this quarter?", category, caplog ) assert feat_mom_lo == 0.0, f"momentum applied to {category} market" assert feat_fg_lo == 0.0, f"Fear&Greed applied to {category} market" def test_btc_market_keeps_momentum_and_fear_greed(caplog): """BTC price market with BTC +10% and F&G=80 → current behavior preserved.""" feat_fg_lo, feat_mom_lo = _evaluate_and_parse_feats( "Will Bitcoin be above $150,000 on July 1?", "crypto/finance", caplog ) assert feat_mom_lo > 0 assert feat_fg_lo > 0 # Exact values: is_price_above=True ("above"), so contributions are positive. # momentum: tanh(10/20) * 0.15, ×2 → log-odds. F&G>70: +0.06, ×2 → log-odds. assert feat_mom_lo == pytest.approx(math.tanh(10 / 20) * 0.15 * 2, abs=1e-4) assert feat_fg_lo == pytest.approx(0.06 * 2, abs=1e-4) full_log = "\n".join(r.getMessage() for r in caplog.records) assert "Fear&Greed: 80 (greed)" in full_log assert "BTC 24h: +10.0%" in full_log def test_btc_below_market_sign_flip_preserved(caplog): """'below' market: bullish macro lowers YES probability (sign flip intact).""" feat_fg_lo, feat_mom_lo = _evaluate_and_parse_feats( "Will Bitcoin drop below $50,000 by August?", "crypto/finance", caplog ) assert feat_mom_lo < 0 assert feat_fg_lo < 0