feat: expand market coverage to politics, tech, and events categories
CI/CD / build-and-push (push) Successful in 1m31s

- polymarket.py: add keyword lists for politics (election, trump, ukraine…),
  tech (AI, OpenAI, Apple, nvidia…), and events (super bowl, oscar, spacex…);
  introduce _detect_category() so all four categories flow through a single
  code path; filter already-expired markets (end_dt < now) in addition to
  the existing future-cutoff filter; log per-category counts at startup
- bayesian.py: extend is_any_supported to include is_politics / is_tech /
  is_events; use BTC as a risk-sentiment proxy for non-crypto categories
  (halved weight to reflect weaker correlation); cap confidence_cap at 0.65
  for macro/politics/tech/events; MIN_EDGE stays at 0.10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-04-14 08:07:17 +00:00
parent f8c4f8b78a
commit b8d2b733fd
2 changed files with 86 additions and 13 deletions
+65 -9
View File
@@ -69,11 +69,53 @@ class PolymarketClient:
" ipo ", "sec ", "cftc",
]
_POLITICS_KEYWORDS: list[str] = [
"election", "president", "congress", "senate", "vote", "war",
"trump", "biden", "ukraine", "russia", "israel", "nato",
]
_TECH_KEYWORDS: list[str] = [
" ai ", "openai", "apple", "google", "microsoft", "meta",
"nvidia", "regulation", "antitrust",
]
_EVENTS_KEYWORDS: list[str] = [
"super bowl", "world cup", "oscar", "nobel", "spacex", "nasa",
]
@classmethod
def _is_crypto_finance(cls, question: str) -> bool:
q = f" {question.lower()} " # pad so edge keywords match cleanly
return any(kw in q for kw in cls._CRYPTO_FINANCE_KEYWORDS)
@classmethod
def _is_politics(cls, question: str) -> bool:
q = f" {question.lower()} "
return any(kw in q for kw in cls._POLITICS_KEYWORDS)
@classmethod
def _is_tech(cls, question: str) -> bool:
q = f" {question.lower()} "
return any(kw in q for kw in cls._TECH_KEYWORDS)
@classmethod
def _is_events(cls, question: str) -> bool:
q = f" {question.lower()} "
return any(kw in q for kw in cls._EVENTS_KEYWORDS)
@classmethod
def _detect_category(cls, question: str) -> str:
"""Return the category label for a market question, or '' if unsupported."""
if cls._is_crypto_finance(question):
return "crypto/finance"
if cls._is_politics(question):
return "politics"
if cls._is_tech(question):
return "tech"
if cls._is_events(question):
return "events"
return ""
async def get_active_markets(
self,
min_volume: float = 1000,
@@ -81,15 +123,18 @@ class PolymarketClient:
page_size: int = 200,
max_days_to_resolution: int = 30,
) -> list[Market]:
"""Fetch active crypto/finance markets from Gamma API (no auth needed).
"""Fetch active markets from Gamma API (no auth needed).
Fetches events without tag filtering (tag= param is unreliable),
then keeps only markets whose question matches crypto/finance keywords
and that resolve within max_days_to_resolution days.
then keeps only markets whose question matches any supported category
(crypto/finance, politics, tech, events) and that:
- have NOT already expired (end_dt >= now)
- resolve within max_days_to_resolution days
"""
seen: set[str] = set()
markets: list[Market] = []
cutoff = datetime.now(timezone.utc) + timedelta(days=max_days_to_resolution)
now = datetime.now(timezone.utc)
cutoff = now + timedelta(days=max_days_to_resolution)
for page in range(pages):
try:
@@ -116,11 +161,15 @@ class PolymarketClient:
continue
question = m.get("question", "")
if not self._is_crypto_finance(question) and \
not self._is_crypto_finance(event_title):
# Detect category from question or event title
category = self._detect_category(question)
if not category:
category = self._detect_category(event_title)
if not category:
continue
# Filter: only markets resolving within the cutoff window
# Filter: skip already-expired and far-future markets
# Gamma API may return endDate or end_date (snake_case)
raw_end = m.get("endDate") or m.get("end_date") or m.get("endDateIso", "")
if raw_end:
@@ -131,6 +180,9 @@ class PolymarketClient:
# Make naive datetimes UTC-aware before comparing
if end_dt.tzinfo is None:
end_dt = end_dt.replace(tzinfo=timezone.utc)
if end_dt < now:
log.debug("Skipping expired market: %s", question[:60])
continue
if end_dt > cutoff:
continue
except (ValueError, TypeError):
@@ -167,7 +219,7 @@ class PolymarketClient:
volume_24h=vol,
end_date=m.get("endDate", ""),
active=True,
category="crypto/finance",
category=category,
))
except (KeyError, ValueError, IndexError) as e:
log.debug("Skipping malformed market: %s", e)
@@ -176,9 +228,13 @@ class PolymarketClient:
log.error("Polymarket API error (page=%d): %s", page, e)
break
by_cat: dict[str, int] = {}
for mkt in markets:
by_cat[mkt.category] = by_cat.get(mkt.category, 0) + 1
log.info(
"Loaded %d crypto/finance markets (min_vol=%.0f, resolving within %dd)",
"Loaded %d markets (min_vol=%.0f, resolving within %dd): %s",
len(markets), min_volume, max_days_to_resolution,
", ".join(f"{k}={v}" for k, v in sorted(by_cat.items())),
)
return markets