fix(strategy): gate BTC-dominance signal behind is_non_price like momentum and fear-greed
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:
chemavx
2026-06-11 16:42:59 +00:00
co-authored by Claude Fable 5
parent f5ac302a86
commit 4867141c4b
2 changed files with 144 additions and 9 deletions
+6 -1
View File
@@ -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)
+130
View File
@@ -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