fix(strategy): gate BTC-dominance signal behind is_non_price like momentum and fear-greed
CI/CD / build-and-push (push) Successful in 7s
CI/CD / build-and-push (push) Successful in 7s
Phase 3 excluded momentum and Fear & Greed from politics/tech/events
markets; Phase 4 fixed ticker detection. But the BTC-dominance signal was
still applied to non-price markets that legitimately mention a ticker
('Will the ETH ETF be approved?'), despite having no demonstrated causality
for non-price outcomes. Reuse the existing is_non_price gate so the
contribution stays 0.0 -> feat_btc_dom_lo = 0.0 for those markets.
Price-market behavior unchanged: ETH/altcoin/general-crypto markets keep
the +/-0.03 dominance adjustment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f5ac302a86
commit
4867141c4b
@@ -483,8 +483,13 @@ class BayesianStrategy:
|
||||
_fg_contribution = fg_adj if is_price_above else -fg_adj
|
||||
adjustments.append(_fg_contribution)
|
||||
|
||||
# Signal 3: BTC dominance — hurts altcoins when high
|
||||
# Signal 3: BTC dominance — hurts altcoins when high (price markets only)
|
||||
# Like momentum and Fear & Greed above: no demonstrated causality for
|
||||
# politics/tech/events, even when they legitimately mention a ticker
|
||||
# ("Will the ETH ETF be approved?"). For non-price markets the
|
||||
# contribution stays 0.0 → feat_btc_dom_lo = 0.0.
|
||||
_btc_dom_contribution = 0.0
|
||||
if not is_non_price:
|
||||
if (is_eth or is_altcoin or is_general_crypto) and ext.btc_dominance > 55:
|
||||
_btc_dom_contribution = -0.03 if is_price_above else 0.03
|
||||
adjustments.append(_btc_dom_contribution)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
Tests for FASE 5 — BTC-dominance signal must not apply to non-price markets.
|
||||
|
||||
FASE 3 gated momentum and Fear & Greed behind is_non_price (politics / tech /
|
||||
events); FASE 4 fixed ticker detection so non-crypto questions no longer flag
|
||||
crypto assets by accident. But a non-price market that LEGITIMATELY mentions
|
||||
a ticker ("Will the ETH ETF be approved?") still armed the BTC-dominance
|
||||
signal, which has no demonstrated causality for non-price outcomes. FASE 5
|
||||
applies the same is_non_price gate to that signal.
|
||||
|
||||
Note: the dominance signal only fires for is_eth / is_altcoin /
|
||||
is_general_crypto markets — a pure-BTC question never receives it, so the
|
||||
pro-Bitcoin test below is a regression guard rather than a gate exercise;
|
||||
the ETH-ETF test is the one that fails without the gate.
|
||||
|
||||
Same caplog technique as test_bayesian_asset_detection.py: btc_dom_lo is
|
||||
parsed from the structured audit log, with btc_dominance=65 (>55) so the
|
||||
signal fires whenever it is allowed to.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from bot.data.external import ExternalSignals
|
||||
from bot.data.polymarket import Market
|
||||
from bot.strategy.bayesian import BayesianStrategy
|
||||
|
||||
BTC_DOM_RE = re.compile(r"btc_dom_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:
|
||||
# btc_dominance=65 (>55) arms the dominance signal wherever it is allowed.
|
||||
# Momentum kept below the 2% threshold so price-market tests isolate the
|
||||
# dominance contribution.
|
||||
return ExternalSignals(
|
||||
btc_price=100_000.0,
|
||||
btc_change_24h=1.0,
|
||||
eth_price=4_000.0,
|
||||
eth_change_24h=1.0,
|
||||
btc_dominance=65.0,
|
||||
fear_greed_index=50,
|
||||
fear_greed_label="neutral",
|
||||
total_market_cap_change=1.0,
|
||||
valid=True,
|
||||
)
|
||||
|
||||
|
||||
def _evaluate(question: str, category: str, caplog) -> tuple[float, str]:
|
||||
"""Run evaluate() and return (btc_dom_lo, full_log) 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())
|
||||
)
|
||||
full_log = "\n".join(r.getMessage() for r in caplog.records)
|
||||
for record in caplog.records:
|
||||
m = BTC_DOM_RE.search(record.getMessage())
|
||||
if m:
|
||||
return float(m.group(1)), full_log
|
||||
pytest.fail(
|
||||
"No SKIP_EDGE_NET/TRADE log line with btc_dom_lo found; "
|
||||
f"got: {[r.getMessage() for r in caplog.records]}"
|
||||
)
|
||||
|
||||
|
||||
# ── Non-price markets: gate must zero the signal ─────────────────────────────
|
||||
|
||||
def test_politics_market_mentioning_eth_gets_no_btc_dom(caplog):
|
||||
"""Legitimate ETH mention in a politics market → btc_dom_lo == 0.0."""
|
||||
btc_dom_lo, full_log = _evaluate(
|
||||
"Will the ETH ETF be approved?", "politics", caplog
|
||||
)
|
||||
assert btc_dom_lo == 0.0
|
||||
assert "BTC dom" not in full_log
|
||||
|
||||
def test_politics_market_mentioning_bitcoin_gets_no_btc_dom(caplog):
|
||||
"""Legitimate Bitcoin mention in a politics market → btc_dom_lo == 0.0."""
|
||||
btc_dom_lo, full_log = _evaluate(
|
||||
"Will a pro-Bitcoin candidate win the election?", "politics", caplog
|
||||
)
|
||||
assert btc_dom_lo == 0.0
|
||||
assert "BTC dom" not in full_log
|
||||
|
||||
def test_tech_and_events_markets_get_no_btc_dom(caplog):
|
||||
for category in ("tech", "events"):
|
||||
caplog.clear()
|
||||
btc_dom_lo, full_log = _evaluate(
|
||||
"Will the ETH foundation launch the product?", category, caplog
|
||||
)
|
||||
assert btc_dom_lo == 0.0, f"BTC dominance applied to {category} market"
|
||||
assert "BTC dom" not in full_log
|
||||
|
||||
|
||||
# ── Price markets: current behavior preserved ───────────────────────────────
|
||||
|
||||
def test_eth_price_market_keeps_btc_dom(caplog):
|
||||
"""ETH price market with dominance 65 → signal fires as before."""
|
||||
btc_dom_lo, full_log = _evaluate(
|
||||
"Will ETH be above $5000?", "crypto/finance", caplog
|
||||
)
|
||||
# 'above' → is_price_above, dominance 65 > 55 → -0.03 → -0.06 log-odds
|
||||
assert btc_dom_lo == pytest.approx(-0.06, abs=1e-4)
|
||||
assert "BTC dom: 65.0% (high → alt pressure)" in full_log
|
||||
|
||||
def test_altcoin_price_market_keeps_btc_dom(caplog):
|
||||
"""SOL price market with dominance 65 → signal fires as before."""
|
||||
btc_dom_lo, full_log = _evaluate(
|
||||
"Will SOL reach $200?", "crypto/finance", caplog
|
||||
)
|
||||
assert btc_dom_lo == pytest.approx(-0.06, abs=1e-4)
|
||||
assert "BTC dom: 65.0% (high → alt pressure)" in full_log
|
||||
Reference in New Issue
Block a user