feat: expand market coverage to politics, tech, and events categories
CI/CD / build-and-push (push) Successful in 1m31s
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:
+65
-9
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user