CI/CD / build-and-push (push) Successful in 7s
For politics/tech/events markets there is no above/below price notion, so is_price_above defaulted to False (or flipped on accidental wording like "reach") and sign-inverted the macro adjustments: BTC +5% or high Fear&Greed subtracted probability from YES on "Will X win the election?" markets. Skip both signals entirely for non-price markets: contributions stay 0.0, feat_mom_lo / feat_fg_lo persist as 0.0. Price markets (BTC/ETH/crypto) keep the exact current behavior, including the below-market sign flip. Removes the now-dead BTC(sentiment) momentum branch and its 0.5 attenuator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
124 lines
4.6 KiB
Python
124 lines
4.6 KiB
Python
"""
|
||
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
|