fix(strategy): use word-boundary token matching for short crypto tickers to prevent false positives
CI/CD / build-and-push (push) Successful in 8s

Substring matching over question_lower flagged non-crypto markets as crypto:
'dissolved' matched 'sol', 'Canada' matched 'ada', 'Seth' matched 'eth'.
Those false flags armed the BTC-dominance signal (btc_dom_lo=+0.06 observed
on politics markets in production).

Short tickers (btc, eth, sol, xrp, doge, ltc, bnb, ada, avax) now go through
has_token(), which requires non-alphanumeric boundaries so 'ETH', '$ETH' and
'ETH/USD' still match. Long unambiguous names (bitcoin, ethereum, solana,
cardano, ...) remain substring checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-06-11 16:34:09 +00:00
co-authored by Claude Fable 5
parent 4002f03d0c
commit f5ac302a86
2 changed files with 186 additions and 6 deletions
+27 -6
View File
@@ -13,6 +13,7 @@ Polymarket might reflect in a slow-moving order book.
import logging
import math
import os
import re
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
@@ -159,6 +160,21 @@ def _days_to_resolution(end_date: str) -> int:
return 30
def has_token(text: str, token: str) -> bool:
"""
True if `token` appears in `text` as a standalone word.
Short crypto tickers (eth, sol, ada, …) must NOT match inside ordinary
words — "Seth", "dissolved", "Canada" — but must still match the usual
market phrasings: "ETH", "$ETH", "ETH/USD", "SOL reach $200". Boundaries
are any non-alphanumeric character (or start/end of string), so "$" and
"/" delimit correctly.
"""
return re.search(
rf"(?<![A-Za-z0-9]){re.escape(token)}(?![A-Za-z0-9])", text, re.IGNORECASE
) is not None
# ─────────────────────────────────────────────────────────────────────────────
# Phase 3 — GNews priority scoring
# ─────────────────────────────────────────────────────────────────────────────
@@ -345,13 +361,18 @@ class BayesianStrategy:
"below", "under", "less than", "lower", "drop",
])
is_btc = "btc" in question_lower or "bitcoin" in question_lower
is_eth = "eth" in question_lower or "ethereum" in question_lower
is_sol = "sol" in question_lower or "solana" in question_lower
is_xrp = "xrp" in question_lower or "ripple" in question_lower
is_doge = "doge" in question_lower or "dogecoin" in question_lower
# Short tickers need word boundaries: "Seth" contains "eth",
# "dissolved" contains "sol", "Canada" contains "ada". Long
# unambiguous names (bitcoin, ethereum, …) stay as substrings.
is_btc = has_token(question_lower, "btc") or "bitcoin" in question_lower
is_eth = has_token(question_lower, "eth") or "ethereum" in question_lower
is_sol = has_token(question_lower, "sol") or "solana" in question_lower
is_xrp = has_token(question_lower, "xrp") or "ripple" in question_lower
is_doge = has_token(question_lower, "doge") or "dogecoin" in question_lower
is_altcoin = is_sol or is_xrp or is_doge or any(
w in question_lower for w in ["ltc", "litecoin", "bnb", "ada", "cardano", "avax", "avalanche"]
has_token(question_lower, t) for t in ["ltc", "bnb", "ada", "avax"]
) or any(
w in question_lower for w in ["litecoin", "cardano", "avalanche"]
)
is_general_crypto = any(
w in question_lower for w in ["crypto", "market cap", "total market", "altcoin", "defi"]