2026-03-01 03:42:10 +08:00
"""
2026-04-06 16:47:36 +07:00
Polymarket prediction market data source
Get prediction market data from Polymarket
2026-03-01 03:42:10 +08:00
"""
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
import json
2026-04-09 14:30:51 +07:00
import time
2026-03-01 03:42:10 +08:00
from datetime import datetime , timedelta
2026-04-09 14:30:51 +07:00
from typing import Dict , List , Optional
import requests
2026-03-01 03:42:10 +08:00
from app.utils.db import get_db_connection
2026-04-09 14:30:51 +07:00
from app.utils.logger import get_logger
2026-03-01 03:42:10 +08:00
logger = get_logger ( __name__ )
class PolymarketDataSource :
2026-04-06 16:47:36 +07:00
"""Polymarket prediction market data source"""
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def __init__ ( self ):
2026-04-06 16:47:36 +07:00
# Polymarket official API endpoint (according to official documentation)
# Gamma API: markets, events, tags, searches, etc. (fully public, no authentication required)
2026-03-01 03:42:10 +08:00
self . gamma_api = "https://gamma-api.polymarket.com"
2026-04-06 16:47:36 +07:00
# Data API: User positions, transactions, activities, etc. (fully public, no authentication required)
2026-03-01 03:42:10 +08:00
self . data_api = "https://data-api.polymarket.com"
2026-04-06 16:47:36 +07:00
# CLOB API: Order book, prices, trading operations (public endpoints require no authentication)
2026-03-01 03:42:10 +08:00
self . clob_api = "https://clob.polymarket.com"
2026-04-06 16:47:36 +07:00
self . cache_ttl = 300 # 5 minutes cache
2026-03-01 03:42:10 +08:00
self . session = requests . Session ()
2026-04-09 14:30:51 +07:00
self . session . headers . update (
{ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" , "Accept" : "application/json" }
)
2026-03-01 03:42:10 +08:00
def get_trending_markets ( self , category : str = None , limit : int = 50 ) -> List [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Get popular prediction markets
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
Args:
2026-04-06 16:47:36 +07:00
category: category filter (crypto, politics, economics, sports, all)
limit: return quantity limit
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
Returns:
2026-04-06 16:47:36 +07:00
Prediction market list
2026-03-01 03:42:10 +08:00
"""
try :
2026-04-06 16:47:36 +07:00
# Read from database cache first
2026-03-01 03:42:10 +08:00
cached = self . _get_cached_markets ( category , limit )
if cached :
return cached
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Fetch from real API - Fetch data from multiple categories to ensure diversity
2026-03-01 03:42:10 +08:00
all_markets = []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if category and category != "all" :
2026-04-06 16:47:36 +07:00
# If a category is specified, only data for that category will be obtained
2026-03-01 03:42:10 +08:00
markets = self . _fetch_markets_from_api ( category , limit * 2 )
all_markets . extend ( markets )
else :
2026-04-08 09:12:41 +07:00
# Retrieve all events (without specifying a category to avoid duplicate requests)
2026-04-07 22:47:07 +08:00
markets = self . _fetch_from_gamma_api ( category = None , limit = 100 )
all_markets . extend ( markets )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
# 去重(按market_id)
seen = set ()
unique_markets = []
for market in all_markets :
market_id = market . get ( "market_id" )
if market_id and market_id not in seen :
seen . add ( market_id )
unique_markets . append ( market )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Sort by transaction volume
2026-04-09 14:30:51 +07:00
unique_markets . sort ( key = lambda x : x . get ( "volume_24h" , 0 ), reverse = True )
2026-04-06 16:47:36 +07:00
# Save to database cache
2026-03-01 03:42:10 +08:00
if unique_markets :
self . _save_markets_to_db ( unique_markets )
return unique_markets [: limit ]
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If the API fails, an empty list is returned (sample data is no longer used)
2026-03-01 03:42:10 +08:00
logger . warning ( "Polymarket API unavailable, returning empty list" )
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . error ( f "Failed to get trending markets: { e } " , exc_info = True )
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def get_market_details ( self , market_id : str ) -> Optional [ Dict ]:
2026-04-06 16:47:36 +07:00
"""Get individual market details"""
2026-03-01 03:42:10 +08:00
try :
2026-04-06 16:47:36 +07:00
# Make sure market_id is a string
2026-03-01 03:42:10 +08:00
market_id = str ( market_id ) . strip ()
if not market_id :
logger . warning ( "Empty market_id provided" )
return None
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Read from database first
2026-03-01 03:42:10 +08:00
try :
with get_db_connection () as db :
cur = db . cursor ()
2026-04-09 14:30:51 +07:00
cur . execute (
"""
SELECT market_id, question, category, current_probability,
2026-03-01 03:42:10 +08:00
volume_24h, liquidity, end_date_iso, status, outcome_tokens
FROM qd_polymarket_markets
WHERE market_id = %s
2026-04-09 14:30:51 +07:00
""" ,
( market_id ,),
)
2026-03-01 03:42:10 +08:00
row = cur . fetchone ()
cur . close ()
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if row :
2026-04-09 14:30:51 +07:00
# RealDictCursor returns the dictionary, accessed using keys
db_market_id = str ( row . get ( "market_id" ) or market_id )
2026-04-06 16:47:36 +07:00
# Parse outcome_tokens (may be a JSON string)
2026-03-01 03:42:10 +08:00
outcome_tokens = {}
2026-04-09 14:30:51 +07:00
outcome_tokens_raw = row . get ( "outcome_tokens" )
2026-03-01 03:42:10 +08:00
if outcome_tokens_raw :
try :
if isinstance ( outcome_tokens_raw , str ):
outcome_tokens = json . loads ( outcome_tokens_raw )
else :
outcome_tokens = outcome_tokens_raw if isinstance ( outcome_tokens_raw , dict ) else {}
2026-04-09 14:30:51 +07:00
except Exception as e :
logger . debug ( f "Failed to parse outcome tokens for market { market_id } : { e } " )
2026-03-01 03:42:10 +08:00
outcome_tokens = {}
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
return {
"market_id" : db_market_id ,
2026-04-09 14:30:51 +07:00
"question" : row . get ( "question" ) or "" ,
"category" : row . get ( "category" ) or "other" ,
"current_probability" : float ( row . get ( "current_probability" ) or 0 ),
"volume_24h" : float ( row . get ( "volume_24h" ) or 0 ),
"liquidity" : float ( row . get ( "liquidity" ) or 0 ),
"end_date_iso" : row . get ( "end_date_iso" ),
"status" : row . get ( "status" ) or "active" ,
2026-03-01 03:42:10 +08:00
"outcome_tokens" : outcome_tokens ,
2026-04-09 14:30:51 +07:00
"polymarket_url" : self . _build_polymarket_url ( row . get ( "slug" ), db_market_id ),
"slug" : row . get ( "slug" )
if row . get ( "slug" ) and not str ( row . get ( "slug" , "" )) . isdigit ()
else None ,
2026-03-01 03:42:10 +08:00
}
except Exception as db_error :
logger . warning ( f "Database query failed for market { market_id } : { db_error } " )
2026-04-06 16:47:36 +07:00
# Continue trying to get it from the API
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If the database does not exist, get it from the API
2026-03-01 03:42:10 +08:00
logger . info ( f "Market { market_id } not in database, fetching from API" )
market = self . _fetch_market_from_api ( market_id )
if market :
try :
self . _save_markets_to_db ([ market ])
except Exception as save_error :
logger . warning ( f "Failed to save market to DB: { save_error } " )
return market
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
logger . warning ( f "Market { market_id } not found in API" )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . error ( f "Failed to get market details for { market_id } : { e } " , exc_info = True )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def get_market_history ( self , market_id : str , days : int = 30 ) -> List [ Dict ]:
2026-04-08 07:27:26 +07:00
"""Get historical market price data."""
2026-04-06 16:47:36 +07:00
# Here you need to implement historical data acquisition logic
# Temporarily returns an empty list
2026-03-01 03:42:10 +08:00
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def search_markets ( self , keyword : str , limit : int = 20 , use_cache : bool = True ) -> List [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Search related prediction markets
Priority is given to obtaining real-time data from the API, and the database is only used as an optional cache.
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
Args:
2026-04-06 16:47:36 +07:00
keyword: search keyword
limit: limit on the number of returned results
use_cache: whether to use database cache (should be set to False during AI analysis to obtain the latest data)
2026-03-01 03:42:10 +08:00
"""
try :
logger . info ( f "Searching Polymarket markets for keyword: ' { keyword } ' (limit= { limit } , use_cache= { use_cache } )" )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If caching is allowed, try searching from the database first
2026-03-01 03:42:10 +08:00
if use_cache :
with get_db_connection () as db :
cur = db . cursor ()
2026-04-06 16:47:36 +07:00
# Improved search: search question and slug fields at the same time, also support market_id exact matching
2026-03-01 17:20:37 +08:00
keyword_lower = keyword . lower ()
is_numeric = keyword_lower . isdigit ()
2026-04-09 14:30:51 +07:00
has_hyphens = "-" in keyword_lower
2026-03-01 17:20:37 +08:00
if is_numeric :
2026-04-06 16:47:36 +07:00
# If it is a pure number, it may be market_id, an exact match
2026-04-09 14:30:51 +07:00
cur . execute (
"""
SELECT market_id, question, category, current_probability,
2026-03-01 17:20:37 +08:00
volume_24h, liquidity, end_date_iso, status, slug
FROM qd_polymarket_markets
WHERE market_id = %s AND status = 'active'
ORDER BY volume_24h DESC
LIMIT %s
2026-04-09 14:30:51 +07:00
""" ,
( keyword , limit ),
)
2026-03-01 17:20:37 +08:00
elif has_hyphens :
2026-04-06 16:47:36 +07:00
# If it contains a hyphen, it may be a slug, and the slug will be matched first.
2026-04-09 14:30:51 +07:00
cur . execute (
"""
SELECT market_id, question, category, current_probability,
2026-03-01 17:20:37 +08:00
volume_24h, liquidity, end_date_iso, status, slug
FROM qd_polymarket_markets
WHERE (slug ILIKE %s OR question ILIKE %s ) AND status = 'active'
2026-04-09 14:30:51 +07:00
ORDER BY
2026-03-01 17:20:37 +08:00
CASE WHEN slug ILIKE %s THEN 1 ELSE 2 END,
volume_24h DESC
LIMIT %s
2026-04-09 14:30:51 +07:00
""" ,
( f "% { keyword } %" , f "% { keyword } %" , f "% { keyword } %" , limit ),
)
2026-03-01 17:20:37 +08:00
else :
2026-04-06 16:47:36 +07:00
# Normal text search
2026-04-09 14:30:51 +07:00
cur . execute (
"""
SELECT market_id, question, category, current_probability,
2026-03-01 17:20:37 +08:00
volume_24h, liquidity, end_date_iso, status, slug
FROM qd_polymarket_markets
WHERE (question ILIKE %s OR slug ILIKE %s ) AND status = 'active'
ORDER BY volume_24h DESC
LIMIT %s
2026-04-09 14:30:51 +07:00
""" ,
( f "% { keyword } %" , f "% { keyword } %" , limit ),
)
2026-03-01 03:42:10 +08:00
rows = cur . fetchall ()
cur . close ()
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if rows :
logger . info ( f "Found { len ( rows ) } markets in database for keyword ' { keyword } '" )
2026-04-09 14:30:51 +07:00
return [
{
"market_id" : str ( row . get ( "market_id" ) or "" ),
"question" : row . get ( "question" ) or "" ,
"category" : row . get ( "category" ) or "other" ,
"current_probability" : float ( row . get ( "current_probability" ) or 0 ),
"volume_24h" : float ( row . get ( "volume_24h" ) or 0 ),
"liquidity" : float ( row . get ( "liquidity" ) or 0 ),
"end_date_iso" : row . get ( "end_date_iso" ),
"status" : row . get ( "status" ) or "active" ,
"polymarket_url" : self . _build_polymarket_url (
row . get ( "slug" ), row . get ( "market_id" ) or ""
),
"slug" : row . get ( "slug" )
if row . get ( "slug" ) and not str ( row . get ( "slug" , "" )) . isdigit ()
else None ,
}
for row in rows
]
2026-04-06 16:47:36 +07:00
# Obtain and filter directly from Gamma API (used during AI analysis)
2026-03-01 03:42:10 +08:00
logger . info ( f "Fetching from API for keyword ' { keyword } ' (use_cache= { use_cache } )..." )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Optimization: If the keyword looks like a slug, try direct query first (avoid fetching the full amount)
2026-03-01 17:20:37 +08:00
import re
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
keyword_lower = keyword . lower () . strip ()
2026-04-09 14:30:51 +07:00
is_slug_like = "-" in keyword_lower and not keyword_lower . isdigit ()
2026-03-01 17:20:37 +08:00
if is_slug_like :
2026-04-06 16:47:36 +07:00
# Try to query directly through slug (most efficient, according to Polymarket API documentation)
2026-03-01 17:20:37 +08:00
direct_market = self . _fetch_market_by_slug ( keyword_lower )
if direct_market :
logger . info ( f "Found market directly by slug (no need to fetch all markets): { keyword_lower } " )
return [ direct_market ]
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If direct query fails, get more data so that there is enough room for selection
# Make multiple requests to get more markets (up to 100 events each time, but each event may contain multiple markets)
2026-03-01 17:20:37 +08:00
all_markets = []
2026-04-09 14:30:51 +07:00
max_requests = 3 # Request up to 3 times to get 300 events (about 4500 markets)
2026-03-01 17:20:37 +08:00
for page in range ( max_requests ):
page_markets = self . _fetch_from_gamma_api ( category = None , limit = 100 )
if not page_markets :
break
all_markets . extend ( page_markets )
2026-04-06 16:47:36 +07:00
# If enough markets have been acquired, you can stop early
2026-04-09 14:30:51 +07:00
if len ( all_markets ) >= 3000 : # Get up to 3000 markets
2026-03-01 17:20:37 +08:00
break
logger . info ( f "Fetched page { page + 1 } / { max_requests } , total markets: { len ( all_markets ) } " )
2026-04-06 16:47:36 +07:00
# Short delay to avoid API current limit
2026-03-01 17:20:37 +08:00
if page < max_requests - 1 :
time . sleep ( 0.5 )
2026-03-01 03:42:10 +08:00
logger . info ( f "Fetched { len ( all_markets ) } markets from API, filtering for keyword ' { keyword } '..." )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Filter by keyword (supports multiple keyword matching)
# If the keyword looks like a slug (contains a hyphen), also try to match the slug
2026-04-09 14:30:51 +07:00
keyword_is_slug = "-" in keyword_lower
2026-04-06 16:47:36 +07:00
# Extract keywords (remove common stop words and punctuation)
# Extract keywords: remove punctuation, retain alphanumeric characters and hyphens
2026-04-09 14:30:51 +07:00
keyword_words = re . findall ( r "\b\w+\b" , keyword_lower )
2026-04-06 16:47:36 +07:00
# Filter out words that are too short (less than 3 characters) and common stop words
2026-04-09 14:30:51 +07:00
stop_words = {
"the" ,
"a" ,
"an" ,
"and" ,
"or" ,
"but" ,
"in" ,
"on" ,
"at" ,
"to" ,
"for" ,
"of" ,
"with" ,
"by" ,
"is" ,
"are" ,
"was" ,
"were" ,
"be" ,
"been" ,
"will" ,
"would" ,
"should" ,
"could" ,
"may" ,
"might" ,
"can" ,
"must" ,
}
2026-03-01 17:20:37 +08:00
keyword_words = [ w for w in keyword_words if len ( w ) >= 3 and w not in stop_words ]
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If no keywords are extracted, use the original keywords
2026-03-01 17:20:37 +08:00
if not keyword_words :
keyword_words = [ keyword_lower ]
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
logger . info ( f "Extracted keywords: { keyword_words } from ' { keyword } '" )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
filtered = []
2026-04-09 14:30:51 +07:00
scored_markets = [] # Used to store scored results
top_candidates = [] # Used to store close matching candidates (for debugging)
2026-03-01 03:42:10 +08:00
for market in all_markets :
question = market . get ( "question" , "" ) . lower ()
2026-03-01 17:20:37 +08:00
slug = ( market . get ( "slug" ) or "" ) . lower ()
market_id = str ( market . get ( "market_id" ) or "" )
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
score = 0
match_reason = ""
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# 1. Exact match (highest priority, score 100)
2026-03-01 03:42:10 +08:00
if keyword_lower in question :
2026-03-01 17:20:37 +08:00
score = 100
match_reason = "exact_match_question"
elif keyword_lower == slug :
score = 100
match_reason = "exact_match_slug"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# 2. If the keyword looks like a slug, check the slug field
2026-03-01 17:20:37 +08:00
if score < 100 and keyword_is_slug :
if keyword_lower == slug :
score = 100
match_reason = "exact_slug_match"
elif keyword_lower in slug or slug in keyword_lower :
score = 90
match_reason = "partial_slug_match"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# 3. If the keyword is a pure number, check market_id
2026-03-01 17:20:37 +08:00
if score < 90 and keyword_lower . isdigit ():
if keyword_lower == market_id :
score = 100
match_reason = "market_id_match"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# 4. Keyword matching: Check whether all keywords are in the question
2026-03-01 17:20:37 +08:00
if score < 90 and keyword_words :
2026-04-06 16:47:36 +07:00
# Calculate the number of matching keywords
2026-03-01 17:20:37 +08:00
matched_words = sum ( 1 for word in keyword_words if word in question or word in slug )
if matched_words > 0 :
2026-04-06 16:47:36 +07:00
# Match rate
2026-03-01 17:20:37 +08:00
match_ratio = matched_words / len ( keyword_words )
2026-04-06 16:47:36 +07:00
# Lower the threshold: from 60% to 40% to improve the matching rate
2026-03-01 17:20:37 +08:00
if match_ratio >= 0.4 :
2026-04-09 14:30:51 +07:00
score = int ( 60 + match_ratio * 30 ) # 60-90 minutes
2026-03-01 17:20:37 +08:00
match_reason = f "keyword_match_ { matched_words } / { len ( keyword_words ) } "
else :
2026-04-06 16:47:36 +07:00
# Log close matching candidates (for debugging)
2026-03-01 17:20:37 +08:00
if matched_words >= 1 and len ( top_candidates ) < 5 :
2026-04-09 14:30:51 +07:00
top_candidates . append (
( match_ratio , market . get ( "question" , "" )[: 80 ], matched_words , len ( keyword_words ))
)
2026-04-06 16:47:36 +07:00
# 5. Partial matching: Check whether the main part of the keyword is in the question
2026-03-01 17:20:37 +08:00
if score < 60 and keyword_words :
2026-04-06 16:47:36 +07:00
# If the keyword contains multiple words, try to match the main part
2026-03-01 17:20:37 +08:00
if len ( keyword_words ) > 1 :
2026-04-06 16:47:36 +07:00
# Take the first 3 most important words (usually nouns)
2026-03-01 17:20:37 +08:00
important_words = keyword_words [: 3 ]
matched_important = sum ( 1 for word in important_words if word in question or word in slug )
2026-04-06 16:47:36 +07:00
# Lower the requirement: match at least 1 important word
2026-03-01 17:20:37 +08:00
if matched_important >= 1 :
score = 50
match_reason = f "important_words_match_ { matched_important } / { len ( important_words ) } "
2026-04-09 14:30:51 +07:00
if score >= 50 : # Lower the minimum score requirement from 60 to 50
2026-03-01 17:20:37 +08:00
scored_markets . append (( score , market , match_reason ))
logger . debug ( f "Matched (score= { score } , reason= { match_reason } ): { market . get ( 'question' , '' )[: 60 ] } " )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Sort by score, take the first limit
2026-03-01 17:20:37 +08:00
scored_markets . sort ( key = lambda x : x [ 0 ], reverse = True )
filtered = [ market for score , market , reason in scored_markets [: limit ]]
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Output debugging information
2026-03-01 17:20:37 +08:00
if len ( scored_markets ) == 0 and top_candidates :
2026-04-09 14:30:51 +07:00
logger . warning ( "No exact matches found. Top candidates (partial matches):" )
2026-03-01 17:20:37 +08:00
for ratio , question , matched , total in top_candidates :
logger . warning ( f " - { question } (matched { matched } / { total } keywords, ratio= { ratio : .2f } )" )
2026-04-09 14:30:51 +07:00
logger . info (
f "Filtered { len ( filtered ) } markets matching keyword ' { keyword } ' from API (from { len ( all_markets ) } total markets, { len ( scored_markets ) } scored matches)"
)
2026-03-01 17:20:37 +08:00
if len ( scored_markets ) > 0 :
logger . info ( f "Top match: { filtered [ 0 ] . get ( 'question' , '' )[: 80 ] } (score= { scored_markets [ 0 ][ 0 ] } )" )
2026-03-01 03:42:10 +08:00
return filtered
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . error ( f "Failed to search markets: { e } " , exc_info = True )
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _get_cached_markets ( self , category : str = None , limit : int = 50 ) -> Optional [ List [ Dict ]]:
2026-04-08 07:27:26 +07:00
"""Read market data from the database cache."""
2026-03-01 03:42:10 +08:00
try :
with get_db_connection () as db :
cur = db . cursor ()
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Check cache is fresh (within 5 minutes)
2026-03-01 03:42:10 +08:00
cutoff_time = datetime . now () - timedelta ( seconds = self . cache_ttl )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
query = """
2026-04-09 14:30:51 +07:00
SELECT market_id, question, category, current_probability,
2026-03-01 03:42:10 +08:00
volume_24h, liquidity, end_date_iso, status, outcome_tokens
FROM qd_polymarket_markets
WHERE status = 'active' AND updated_at > %s
"""
params = [ cutoff_time ]
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if category :
query += " AND category = %s "
params . append ( category )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
query += " ORDER BY volume_24h DESC LIMIT %s "
params . append ( limit )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
cur . execute ( query , params )
rows = cur . fetchall ()
cur . close ()
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if rows :
2026-03-01 03:59:55 +08:00
result = []
for row in rows :
2026-04-09 14:30:51 +07:00
market_id = str ( row . get ( "market_id" ) or "" )
slug = row . get ( "slug" )
2026-04-06 16:47:36 +07:00
# Make sure to use the correct URL building method
2026-03-01 03:59:55 +08:00
polymarket_url = self . _build_polymarket_url ( slug , market_id )
2026-04-09 14:30:51 +07:00
result . append (
{
"market_id" : market_id ,
"question" : row . get ( "question" ) or "" ,
"category" : row . get ( "category" ) or "other" ,
"current_probability" : float ( row . get ( "current_probability" ) or 0 ),
"volume_24h" : float ( row . get ( "volume_24h" ) or 0 ),
"liquidity" : float ( row . get ( "liquidity" ) or 0 ),
"end_date_iso" : row . get ( "end_date_iso" ),
"status" : row . get ( "status" ) or "active" ,
"outcome_tokens" : row . get ( "outcome_tokens" ) if row . get ( "outcome_tokens" ) else {},
"polymarket_url" : polymarket_url ,
"slug" : slug if slug and not str ( slug ) . isdigit () else None ,
}
)
2026-03-01 03:59:55 +08:00
return result
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
return None
except Exception as e :
logger . debug ( f "Failed to get cached markets: { e } " )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _fetch_markets_from_api ( self , category : str = None , limit : int = 50 ) -> List [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Retrieve market data from the Polymarket Gamma API
Use the officially recommended /events endpoint
2026-03-01 03:42:10 +08:00
"""
try :
2026-04-06 16:47:36 +07:00
# Use the /events endpoint of Gamma API (official recommended method)
2026-03-01 03:42:10 +08:00
markets = self . _fetch_from_gamma_api ( category , limit )
if markets :
2026-04-06 16:47:36 +07:00
# Sort by volume_24h in descending order (because the API does not support the order parameter, local sorting is required)
2026-04-09 14:30:51 +07:00
markets . sort ( key = lambda x : x . get ( "volume_24h" , 0 ), reverse = True )
return markets [: limit ] # Return the first limit after sorting
2026-04-06 16:47:36 +07:00
# If the API returns an empty list, record a warning (it may be that the API is temporarily unavailable, network problems or throttling)
2026-04-09 14:30:51 +07:00
logger . warning (
f "Gamma API failed to fetch markets for category ' { category } ' (possible reasons: API is temporarily unavailable, network problems, current limiting or returning empty data)"
)
2026-03-01 03:42:10 +08:00
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . error ( f "Failed to fetch markets from API: { e } " , exc_info = True )
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _fetch_from_gamma_api ( self , category : str = None , limit : int = 50 ) -> List [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Retrieve market data from the Polymarket Gamma API
Use the officially recommended /events endpoint
2026-03-01 03:42:10 +08:00
"""
try :
2026-04-06 16:47:36 +07:00
# Use the /events endpoint to obtain active markets (recommended method)
# According to official documentation: https://docs.polymarket.com/market-data/fetching-markets
# Supported values for the order parameter: volume_24hr, volume, liquidity, competitive, start_date, end_date
# But some endpoints may not support it, try without the order parameter first.
2026-03-01 03:42:10 +08:00
url = f " { self . gamma_api } /events"
params = {
"active" : "true" ,
"closed" : "false" ,
2026-04-09 14:30:51 +07:00
"limit" : min ( limit * 2 , 100 ), # Get more data for sorting and filtering
2026-03-01 03:42:10 +08:00
}
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Try adding sorting parameters (if supported by API)
# According to the documentation, possible sorting fields: volume_24hr, volume, liquidity, etc.
# If the API is not supported, it will be removed after a 422 error.
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If a category is specified, it needs to be filtered by tag_id
# Note: You need to get the tag_id first, and use keywords to infer it here.
2026-03-01 03:42:10 +08:00
if category :
2026-04-06 16:47:36 +07:00
# You can try filtering by search or tags
# Get them all temporarily, then filter when parsing
2026-03-01 03:42:10 +08:00
pass
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
logger . info ( f "Fetching from Gamma API: { url } with params: { params } " )
response = self . session . get ( url , params = params , timeout = 15 )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
logger . info ( f "Gamma API response status: { response . status_code } " )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if response . status_code == 200 :
try :
data = response . json ()
2026-04-09 14:30:51 +07:00
logger . debug (
f "Gamma API returned data type: { type ( data ) } , keys: { list ( data . keys ()) if isinstance ( data , dict ) else 'list' } "
)
# The Gamma API may return a list or an object containing a data field
2026-03-01 03:42:10 +08:00
if isinstance ( data , list ):
logger . info ( f "Gamma API returned list with { len ( data ) } items" )
markets = self . _parse_gamma_events ( data , category )
logger . info ( f "Parsed { len ( markets ) } markets from Gamma API" )
return markets
elif isinstance ( data , dict ):
2026-04-06 16:47:36 +07:00
# Possibly {"data": [...]} format
2026-03-01 03:42:10 +08:00
if "data" in data :
events_list = data [ "data" ]
2026-04-09 14:30:51 +07:00
logger . info (
f "Gamma API returned dict with 'data' field containing { len ( events_list ) if isinstance ( events_list , list ) else 'non-list' } items"
)
2026-03-01 03:42:10 +08:00
markets = self . _parse_gamma_events ( events_list , category )
logger . info ( f "Parsed { len ( markets ) } markets from Gamma API" )
return markets
2026-04-06 16:47:36 +07:00
# Or directly the event object
2026-03-01 03:42:10 +08:00
elif "id" in data or "slug" in data :
logger . info ( "Gamma API returned single event object" )
markets = self . _parse_gamma_events ([ data ], category )
logger . info ( f "Parsed { len ( markets ) } markets from Gamma API" )
return markets
else :
logger . warning ( f "Gamma API returned dict with unexpected keys: { list ( data . keys ()) } " )
logger . debug ( f "Full response: { str ( data )[: 500 ] } " )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
logger . warning ( f "Gamma API returned unexpected format: { type ( data ) } " )
return []
except json . JSONDecodeError as je :
logger . error ( f "Gamma API returned invalid JSON: { je } " )
logger . error ( f "Response text (first 500 chars): { response . text [: 500 ] } " )
return []
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Non-200 status code
2026-03-01 17:20:37 +08:00
status_code = response . status_code
if status_code == 429 :
2026-04-09 14:30:51 +07:00
logger . warning (
"Gamma API rate limited (429). Suggestion: Try again later or reduce the request frequency"
)
2026-03-01 17:20:37 +08:00
elif status_code == 503 :
2026-04-09 14:30:51 +07:00
logger . warning ( "Gamma API service unavailable (503). Polymarket API may be under maintenance" )
2026-03-01 17:20:37 +08:00
elif status_code >= 500 :
2026-04-09 14:30:51 +07:00
logger . warning (
f "Gamma API server error ( { status_code } ). The Polymarket server may be temporarily unavailable"
)
2026-03-01 17:20:37 +08:00
else :
logger . warning ( f "Gamma API returned status { status_code } " )
logger . debug ( f "Response headers: { dict ( response . headers ) } " )
logger . debug ( f "Response text (first 500 chars): { response . text [: 500 ] } " )
2026-03-01 03:42:10 +08:00
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
except requests . exceptions . Timeout :
2026-04-09 14:30:51 +07:00
logger . warning (
"Gamma API request timeout after 15 seconds (possible reason: network delay or slow API response)"
)
2026-03-01 03:42:10 +08:00
return []
except requests . exceptions . ConnectionError as ce :
2026-04-09 14:30:51 +07:00
logger . warning (
f "Gamma API connection error: { ce } (Possible reason: network connection problem or Polymarket API is unreachable)"
)
2026-03-01 03:42:10 +08:00
return []
except Exception as e :
2026-04-09 14:30:51 +07:00
logger . warning (
f "Gamma API failed: { e } (Possible reasons: API format change, network problem or service abnormality)"
)
2026-03-01 03:42:10 +08:00
return []
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _parse_gamma_events ( self , events_data : List [ Dict ], category_filter : str = None ) -> List [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Parse event data returned by the Gamma API
The /events endpoint of the Gamma API returns event objects, each containing associated market data
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
According to the official documentation, the event object structure is:
- The event object contains a markets array
- Each market contains fields such as clobTokenIds and outcomePrices
2026-03-01 03:42:10 +08:00
"""
parsed = []
if not events_data :
logger . warning ( "_parse_gamma_events received empty events_data" )
return parsed
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
logger . info ( f "Parsing { len ( events_data ) } events from Gamma API" )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Record the key of the first event for debugging
2026-03-01 03:42:10 +08:00
if events_data :
first_event_keys = list ( events_data [ 0 ] . keys ())[: 10 ]
logger . info ( f "First event keys: { first_event_keys } " )
logger . debug ( f "First event sample: { str ( events_data [ 0 ])[: 500 ] } " )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
for idx , event in enumerate ( events_data ):
try :
2026-04-09 14:30:51 +07:00
# Gamma API event object structure
2026-04-06 16:47:36 +07:00
# The event may have multiple markets (markets field), or directly contain market information
2026-03-01 03:42:10 +08:00
markets = event . get ( "markets" , [])
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If the event does not have a markets field, the event itself may be market data.
2026-03-01 03:42:10 +08:00
if not markets :
2026-04-06 16:47:36 +07:00
# Check whether it is a market object directly (with question or title field)
2026-03-01 03:42:10 +08:00
if "question" in event or "title" in event or "slug" in event :
markets = [ event ]
else :
2026-04-09 14:30:51 +07:00
if idx < 3 : # Only record the details of the first 3
logger . debug (
f "Event { idx } has no markets and doesn't look like a market. Keys: { list ( event . keys ())[: 10 ] } "
)
2026-03-01 03:42:10 +08:00
continue
2026-04-09 14:30:51 +07:00
if idx < 3 : # Only record the details of the first 3
2026-03-01 03:42:10 +08:00
logger . debug ( f "Processing event { idx } with { len ( markets ) } markets" )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
for market_idx , market in enumerate ( markets ):
2026-04-06 16:47:36 +07:00
# Extract basic market information
2026-03-01 03:42:10 +08:00
market_id = market . get ( "id" ) or market . get ( "slug" ) or event . get ( "id" ) or event . get ( "slug" , "" )
2026-04-09 14:30:51 +07:00
question = (
market . get ( "question" ) or event . get ( "question" ) or market . get ( "title" ) or event . get ( "title" , "" )
)
if idx < 3 and market_idx < 2 : # Record detailed information of the first few markets
logger . info (
f "Event { idx } , Market { market_idx } : id= { market_id } , question= { question [: 50 ] if question else 'None' } , event_slug= { event . get ( 'slug' ) } , market_slug= { market . get ( 'slug' ) } , keys= { list ( market . keys ())[: 10 ] } "
)
2026-03-01 03:42:10 +08:00
if not question :
if idx < 3 :
2026-04-09 14:30:51 +07:00
logger . warning (
f "Event { idx } , Market { market_idx } : No question found, skipping. Market keys: { list ( market . keys ())[: 10 ] } "
)
2026-03-01 03:42:10 +08:00
continue
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Infer category
2026-03-01 03:42:10 +08:00
inferred_category = self . _infer_category ( question )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If category filtering is specified, filter
2026-03-01 03:42:10 +08:00
if category_filter and inferred_category != category_filter :
continue
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Get probability and outcome data
2026-03-01 03:42:10 +08:00
current_probability = 50.0
outcome_tokens = {}
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Method 1: Get real-time prices from CLOB API (most accurate)
2026-03-01 03:42:10 +08:00
try :
condition_id = market . get ( "conditionId" ) or event . get ( "conditionId" )
if condition_id :
prices = self . _get_market_prices_from_clob ( condition_id )
if prices :
yes_price = prices . get ( "YES" , 0 )
no_price = prices . get ( "NO" , 0 )
if yes_price > 0 :
current_probability = yes_price * 100 if yes_price <= 1 else yes_price
2026-04-09 14:30:51 +07:00
outcome_tokens [ "YES" ] = {
"price" : yes_price if yes_price <= 1 else yes_price / 100 ,
"volume" : 0 ,
}
2026-03-01 03:42:10 +08:00
if no_price > 0 :
2026-04-09 14:30:51 +07:00
outcome_tokens [ "NO" ] = {
"price" : no_price if no_price <= 1 else no_price / 100 ,
"volume" : 0 ,
}
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . debug ( f "Failed to get prices from CLOB API: { e } " )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Method 2: Process the outcomePrices field (may be a JSON string)
2026-03-01 03:42:10 +08:00
if current_probability == 50.0 :
outcome_prices_str = market . get ( "outcomePrices" ) or event . get ( "outcomePrices" )
if outcome_prices_str :
try :
if isinstance ( outcome_prices_str , str ):
outcome_prices = json . loads ( outcome_prices_str )
else :
outcome_prices = outcome_prices_str
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# outcomePrices is usually in the format ["0.65", "0.35"], corresponding to YES and NO
2026-03-01 03:42:10 +08:00
if isinstance ( outcome_prices , list ) and len ( outcome_prices ) >= 2 :
yes_price = float ( outcome_prices [ 0 ]) if outcome_prices [ 0 ] else 0
no_price = float ( outcome_prices [ 1 ]) if outcome_prices [ 1 ] else 0
current_probability = yes_price * 100 if yes_price <= 1 else yes_price
2026-04-09 14:30:51 +07:00
outcome_tokens [ "YES" ] = {
"price" : yes_price if yes_price <= 1 else yes_price / 100 ,
"volume" : 0 ,
}
outcome_tokens [ "NO" ] = {
"price" : no_price if no_price <= 1 else no_price / 100 ,
"volume" : 0 ,
}
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . debug ( f "Failed to parse outcomePrices: { e } " )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Get outcomes from market or event
2026-04-09 14:30:51 +07:00
# outcomes may be an object array, a string array, or need to be parsed from other fields
2026-03-01 03:42:10 +08:00
outcomes = market . get ( "outcomes" ) or market . get ( "tokens" ) or event . get ( "outcomes" ) or []
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Process the outcomes array (may be an object or a string)
2026-03-01 03:42:10 +08:00
for outcome in outcomes :
try :
2026-04-06 16:47:36 +07:00
# If outcome is a string, skip or try to parse
2026-03-01 03:42:10 +08:00
if isinstance ( outcome , str ):
2026-04-06 16:47:36 +07:00
# May be a simple string identifier such as "YES" or "NO"
2026-03-01 03:42:10 +08:00
outcome_upper = outcome . upper ()
if "YES" in outcome_upper :
if "YES" not in outcome_tokens :
outcome_tokens [ "YES" ] = { "price" : 0.5 , "volume" : 0 }
elif "NO" in outcome_upper :
if "NO" not in outcome_tokens :
outcome_tokens [ "NO" ] = { "price" : 0.5 , "volume" : 0 }
continue
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# outcome is an object
2026-03-01 03:42:10 +08:00
if not isinstance ( outcome , dict ):
continue
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
title = str ( outcome . get ( "title" ) or outcome . get ( "name" , "" )) . upper ()
2026-04-06 16:47:36 +07:00
# Get the price (may be price, probability or currentPrice)
2026-04-09 14:30:51 +07:00
price = float (
outcome . get ( "price" ) or outcome . get ( "probability" ) or outcome . get ( "currentPrice" ) or 0
)
2026-03-01 03:42:10 +08:00
if "YES" in title or title == "YES" or outcome . get ( "outcome" ) == "Yes" :
current_probability = price * 100 if price <= 1 else price
outcome_tokens [ "YES" ] = {
"price" : price if price <= 1 else price / 100 ,
2026-04-09 14:30:51 +07:00
"volume" : float ( outcome . get ( "volume" , outcome . get ( "volume24hr" , 0 )) or 0 ),
2026-03-01 03:42:10 +08:00
}
elif "NO" in title or title == "NO" or outcome . get ( "outcome" ) == "No" :
outcome_tokens [ "NO" ] = {
"price" : price if price <= 1 else price / 100 ,
2026-04-09 14:30:51 +07:00
"volume" : float ( outcome . get ( "volume" , outcome . get ( "volume24hr" , 0 )) or 0 ),
2026-03-01 03:42:10 +08:00
}
except Exception as e :
logger . debug ( f "Failed to parse outcome: { e } " )
continue
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If outcomes are not found, try to get probabilities from other fields
2026-03-01 03:42:10 +08:00
if current_probability == 50.0 :
2026-04-06 16:47:36 +07:00
# Try to get it from the probability field of the market
2026-03-01 03:42:10 +08:00
prob = market . get ( "probability" ) or market . get ( "yesProbability" ) or event . get ( "probability" )
if prob :
current_probability = float ( prob ) * 100 if float ( prob ) <= 1 else float ( prob )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Get trading volume and liquidity
2026-03-01 03:42:10 +08:00
volume_24h = float (
2026-04-09 14:30:51 +07:00
market . get ( "volume_24hr" )
or market . get ( "volume24hr" )
or market . get ( "volume_24h" )
or event . get ( "volume_24hr" )
or event . get ( "volume24hr" )
or 0
2026-03-01 03:42:10 +08:00
)
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
liquidity = float (
2026-04-09 14:30:51 +07:00
market . get ( "liquidity" ) or market . get ( "totalLiquidity" ) or event . get ( "liquidity" ) or 0
2026-03-01 03:42:10 +08:00
)
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Parse end date
2026-03-01 03:42:10 +08:00
end_date_iso = None
2026-04-09 14:30:51 +07:00
end_date = (
market . get ( "endDate" ) or market . get ( "end_date" ) or event . get ( "endDate" ) or event . get ( "end_date" )
)
2026-03-01 03:42:10 +08:00
if end_date :
try :
if isinstance ( end_date , ( int , float )):
end_date_iso = datetime . fromtimestamp ( end_date ) . isoformat () + "Z"
elif isinstance ( end_date , str ):
2026-04-06 16:47:36 +07:00
# Try to parse the ISO format string
2026-03-01 03:42:10 +08:00
end_date_iso = end_date
2026-04-09 14:30:51 +07:00
except Exception as e :
logger . debug ( f "Failed to parse end date: { e } " )
2026-04-06 16:47:36 +07:00
# Get the slug used to build the URL
# According to Polymarket API documentation: slug should be obtained directly from the data returned by the API
# URL format: https://polymarket.com/event/{slug}
# slug is a string identifier, not a numeric ID
2026-03-01 03:42:10 +08:00
slug = None
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Get the slug from the event first (because the event contains markets)
2026-03-01 03:42:10 +08:00
if event . get ( "slug" ):
slug_str = str ( event . get ( "slug" , "" )) . strip ()
2026-04-06 16:47:36 +07:00
# If the slug is not a pure number and contains letters or hyphens, it is a valid slug
2026-04-09 14:30:51 +07:00
if (
slug_str
and not slug_str . isdigit ()
and ( "-" in slug_str or any ( c . isalpha () for c in slug_str ))
):
2026-03-01 03:42:10 +08:00
slug = slug_str
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If the event does not have a valid slug, try to get it from the market
2026-03-01 03:42:10 +08:00
if not slug and market . get ( "slug" ):
slug_str = str ( market . get ( "slug" , "" )) . strip ()
2026-04-09 14:30:51 +07:00
if (
slug_str
and not slug_str . isdigit ()
and ( "-" in slug_str or any ( c . isalpha () for c in slug_str ))
):
2026-03-01 03:42:10 +08:00
slug = slug_str
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If there is still no valid slug, try to obtain it through API query
2026-03-01 03:42:10 +08:00
if not slug and market_id :
try :
2026-04-06 16:47:36 +07:00
# Use the markets endpoint to query by ID to obtain complete slug information
2026-03-01 03:42:10 +08:00
detail_market = self . _fetch_market_detail_by_id ( market_id )
if detail_market and detail_market . get ( "slug" ):
slug_str = str ( detail_market . get ( "slug" , "" )) . strip ()
2026-04-09 14:30:51 +07:00
if (
slug_str
and not slug_str . isdigit ()
and ( "-" in slug_str or any ( c . isalpha () for c in slug_str ))
):
2026-03-01 03:42:10 +08:00
slug = slug_str
except Exception as e :
logger . debug ( f "Failed to fetch slug for market { market_id } : { e } " )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Build URL (using unified helper methods)
2026-03-01 03:42:10 +08:00
polymarket_url = self . _build_polymarket_url ( slug , market_id )
if not slug :
logger . warning ( f "Market { market_id } has no valid slug, using markets endpoint as fallback" )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
market_data = {
"market_id" : market_id ,
"question" : question ,
"category" : inferred_category ,
"current_probability" : round ( current_probability , 2 ),
"volume_24h" : volume_24h ,
"liquidity" : liquidity ,
"end_date_iso" : end_date_iso ,
"status" : "active" if market . get ( "active" , event . get ( "active" , True )) else "closed" ,
"outcome_tokens" : outcome_tokens ,
"polymarket_url" : polymarket_url ,
2026-04-09 14:30:51 +07:00
"slug" : slug if slug else None , # Save slug (if not a number)
2026-03-01 03:42:10 +08:00
}
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
parsed . append ( market_data )
2026-04-09 14:30:51 +07:00
if idx < 3 and market_idx < 2 : # Record successfully parsed markets
logger . info (
f "Successfully parsed market: { question [: 50 ] } , prob= { current_probability : .1f } %, volume= { volume_24h } "
)
2026-03-01 03:42:10 +08:00
except Exception as e :
2026-04-09 14:30:51 +07:00
logger . warning (
f "Failed to parse event { idx } (id= { event . get ( 'id' , event . get ( 'slug' , 'unknown' )) } ): { e } " ,
exc_info = True ,
)
2026-03-01 03:42:10 +08:00
continue
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
logger . info ( f "Successfully parsed { len ( parsed ) } markets from { len ( events_data ) } events" )
return parsed
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _parse_rest_markets ( self , markets_data : List [ Dict ]) -> List [ Dict ]:
2026-04-06 16:47:36 +07:00
"""Parse REST API data"""
2026-03-01 03:42:10 +08:00
parsed = []
for market in markets_data :
try :
2026-04-06 16:47:36 +07:00
# Extract basic information
2026-03-01 03:42:10 +08:00
market_id = market . get ( "id" ) or market . get ( "slug" ) or market . get ( "market_id" , "" )
question = market . get ( "question" ) or market . get ( "title" , "" )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Calculate probability
2026-03-01 03:42:10 +08:00
current_probability = 50.0
outcome_tokens = {}
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if "outcomes" in market :
for outcome in market [ "outcomes" ]:
title = str ( outcome . get ( "title" , "" )) . upper ()
price = float ( outcome . get ( "price" , outcome . get ( "probability" , 0 )) or 0 )
if "YES" in title or title == "YES" :
current_probability = price * 100
2026-04-09 14:30:51 +07:00
outcome_tokens [ "YES" ] = { "price" : price , "volume" : float ( outcome . get ( "volume" , 0 ) or 0 )}
2026-03-01 03:42:10 +08:00
elif "NO" in title or title == "NO" :
2026-04-09 14:30:51 +07:00
outcome_tokens [ "NO" ] = { "price" : price , "volume" : float ( outcome . get ( "volume" , 0 ) or 0 )}
2026-03-01 03:42:10 +08:00
volume_24h = float ( market . get ( "volume_24h" , market . get ( "volume" , 0 )) or 0 )
liquidity = float ( market . get ( "liquidity" , 0 ) or 0 )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# inferred category
2026-03-01 03:42:10 +08:00
category = self . _infer_category ( question )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# parse end date
2026-03-01 03:42:10 +08:00
end_date_iso = market . get ( "end_date" ) or market . get ( "endDate" )
if isinstance ( end_date_iso , ( int , float )):
try :
end_date_iso = datetime . fromtimestamp ( end_date_iso ) . isoformat () + "Z"
2026-04-09 14:30:51 +07:00
except Exception as e :
logger . debug ( f "Failed to parse end date for market { market_id } : { e } " )
2026-03-01 03:42:10 +08:00
end_date_iso = None
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Get the slug used to build the URL
2026-03-01 03:42:10 +08:00
slug = None
2026-04-09 14:30:51 +07:00
slug_str = str ( market . get ( "slug" , "" )) . strip () if market . get ( "slug" ) else ""
2026-04-06 16:47:36 +07:00
# Check if the slug is valid (not a number and contains letters or hyphens)
2026-04-09 14:30:51 +07:00
if slug_str and not slug_str . isdigit () and ( "-" in slug_str or any ( c . isalpha () for c in slug_str )):
2026-03-01 03:42:10 +08:00
slug = slug_str
else :
2026-04-06 16:47:36 +07:00
# If the slug is invalid, try to obtain it through API query
2026-03-01 03:42:10 +08:00
try :
detail_market = self . _fetch_market_detail_by_id ( market_id )
if detail_market and detail_market . get ( "slug" ):
slug_str = str ( detail_market . get ( "slug" , "" )) . strip ()
2026-04-09 14:30:51 +07:00
if (
slug_str
and not slug_str . isdigit ()
and ( "-" in slug_str or any ( c . isalpha () for c in slug_str ))
):
2026-03-01 03:42:10 +08:00
slug = slug_str
except Exception as e :
logger . debug ( f "Failed to fetch slug for market { market_id } : { e } " )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Build URL (using unity helper methods)
2026-03-01 03:42:10 +08:00
polymarket_url = self . _build_polymarket_url ( slug , market_id )
if not slug :
logger . warning ( f "Market { market_id } has no valid slug, using markets endpoint as fallback" )
2026-04-09 14:30:51 +07:00
parsed . append (
{
"market_id" : market_id ,
"question" : question ,
"category" : category ,
"current_probability" : round ( current_probability , 2 ),
"volume_24h" : volume_24h ,
"liquidity" : liquidity ,
"end_date_iso" : end_date_iso ,
"status" : "active" if market . get ( "active" , True ) else "closed" ,
"outcome_tokens" : outcome_tokens ,
"polymarket_url" : polymarket_url ,
"slug" : slug if slug else None ,
}
)
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . debug ( f "Failed to parse market { market . get ( 'id' ) } : { e } " )
continue
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
return parsed
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _infer_category ( self , question : str ) -> str :
2026-04-06 16:47:36 +07:00
"""Infer categories from questions"""
2026-03-01 03:42:10 +08:00
question_lower = question . lower ()
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Cryptocurrency Keywords
2026-04-09 14:30:51 +07:00
crypto_keywords = [
"btc" ,
"bitcoin" ,
"eth" ,
"ethereum" ,
"sol" ,
"solana" ,
"crypto" ,
"token" ,
"coin" ,
"defi" ,
"nft" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in crypto_keywords ):
return "crypto"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# political keywords
2026-04-09 14:30:51 +07:00
politics_keywords = [
"election" ,
"president" ,
"trump" ,
"biden" ,
"senate" ,
"congress" ,
"vote" ,
"political" ,
"democrat" ,
"republican" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in politics_keywords ):
return "politics"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# economic keywords
2026-04-09 14:30:51 +07:00
economics_keywords = [
"gdp" ,
"inflation" ,
"unemployment" ,
"fed" ,
"federal reserve" ,
"interest rate" ,
"economic" ,
"economy" ,
"recession" ,
"gdp growth" ,
"cpi" ,
"ppi" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in economics_keywords ):
return "economics"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Sports keywords
2026-04-09 14:30:51 +07:00
sports_keywords = [
"nfl" ,
"nba" ,
"mlb" ,
"soccer" ,
"football" ,
"basketball" ,
"baseball" ,
"championship" ,
"world cup" ,
"olympics" ,
"super bowl" ,
"stanley cup" ,
"world series" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in sports_keywords ):
return "sports"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Technology keywords
2026-04-09 14:30:51 +07:00
tech_keywords = [
"ai" ,
"artificial intelligence" ,
"chatgpt" ,
"openai" ,
"tech" ,
"technology" ,
"apple" ,
"google" ,
"microsoft" ,
"meta" ,
"tesla" ,
"ipo" ,
"startup" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in tech_keywords ):
return "tech"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# financial keywords
2026-04-09 14:30:51 +07:00
finance_keywords = [
"stock" ,
"s&p" ,
"dow" ,
"nasdaq" ,
"market cap" ,
"earnings" ,
"revenue" ,
"profit" ,
"bank" ,
"banking" ,
"financial" ,
"trading" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in finance_keywords ):
return "finance"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# geopolitical keywords
2026-04-09 14:30:51 +07:00
geopolitics_keywords = [
"war" ,
"conflict" ,
"russia" ,
"ukraine" ,
"china" ,
"taiwan" ,
"north korea" ,
"iran" ,
"israel" ,
"palestine" ,
"middle east" ,
"nato" ,
"sanctions" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in geopolitics_keywords ):
return "geopolitics"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Cultural keywords
2026-04-09 14:30:51 +07:00
culture_keywords = [
"movie" ,
"film" ,
"oscar" ,
"grammy" ,
"award" ,
"celebrity" ,
"music" ,
"album" ,
"tv show" ,
"series" ,
"netflix" ,
"disney" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in culture_keywords ):
return "culture"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# climate keywords
2026-04-09 14:30:51 +07:00
climate_keywords = [
"climate" ,
"global warming" ,
"temperature" ,
"carbon" ,
"emission" ,
"renewable" ,
"solar" ,
"wind energy" ,
"paris agreement" ,
"cop" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in climate_keywords ):
return "climate"
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Entertainment keywords
2026-04-09 14:30:51 +07:00
entertainment_keywords = [
"game" ,
"gaming" ,
"esports" ,
"tournament" ,
"streaming" ,
"youtube" ,
"twitch" ,
"podcast" ,
"comic" ,
"anime" ,
"manga" ,
]
2026-03-01 03:42:10 +08:00
if any ( kw in question_lower for kw in entertainment_keywords ):
return "entertainment"
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
return "other"
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _build_polymarket_url ( self , slug : Optional [ str ], market_id : str ) -> str :
"""
2026-04-06 16:47:36 +07:00
Build Polymarket URL based on slug
Reference: https://docs.polymarket.com/market-data/fetching-markets
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
Args:
2026-04-06 16:47:36 +07:00
slug: slug obtained from API or database (may be None or numeric string)
market_id: Market ID (as an alternative)
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
Returns:
2026-04-06 16:47:36 +07:00
Polymarket URL string
2026-03-01 03:42:10 +08:00
"""
if slug :
slug_str = str ( slug ) . strip ()
2026-04-06 16:47:36 +07:00
# Check if the slug is valid (not a number and contains letters or hyphens)
2026-04-09 14:30:51 +07:00
if slug_str and not slug_str . isdigit () and ( "-" in slug_str or any ( c . isalpha () for c in slug_str )):
2026-03-01 03:42:10 +08:00
import re
2026-04-09 14:30:51 +07:00
slug_clean = re . sub ( r "[^a-zA-Z0-9\-]" , "-" , slug_str )
slug_clean = slug_clean . strip ( "-" )
2026-03-01 03:42:10 +08:00
if slug_clean :
return f "https://polymarket.com/event/ { slug_clean } "
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If there is no valid slug, try to obtain the slug through the API
2026-03-01 17:20:37 +08:00
if market_id :
try :
detail_market = self . _fetch_market_detail_by_id ( market_id )
if detail_market :
2026-04-06 16:47:36 +07:00
# Try to get the slug from detail
2026-04-09 14:30:51 +07:00
event_slug = detail_market . get ( "slug" )
2026-03-01 17:20:37 +08:00
if event_slug :
slug_str = str ( event_slug ) . strip ()
2026-04-09 14:30:51 +07:00
if (
slug_str
and not slug_str . isdigit ()
and ( "-" in slug_str or any ( c . isalpha () for c in slug_str ))
):
2026-03-01 17:20:37 +08:00
import re
2026-04-09 14:30:51 +07:00
slug_clean = re . sub ( r "[^a-zA-Z0-9\-]" , "-" , slug_str )
slug_clean = slug_clean . strip ( "-" )
2026-03-01 17:20:37 +08:00
if slug_clean :
return f "https://polymarket.com/event/ { slug_clean } "
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If the event does not have a slug, try to get it from markets
2026-04-09 14:30:51 +07:00
markets = detail_market . get ( "markets" , [])
2026-03-01 17:20:37 +08:00
if markets :
for m in markets :
2026-04-09 14:30:51 +07:00
market_slug = m . get ( "slug" )
2026-03-01 17:20:37 +08:00
if market_slug :
slug_str = str ( market_slug ) . strip ()
2026-04-09 14:30:51 +07:00
if (
slug_str
and not slug_str . isdigit ()
and ( "-" in slug_str or any ( c . isalpha () for c in slug_str ))
):
2026-03-01 17:20:37 +08:00
import re
2026-04-09 14:30:51 +07:00
slug_clean = re . sub ( r "[^a-zA-Z0-9\-]" , "-" , slug_str )
slug_clean = slug_clean . strip ( "-" )
2026-03-01 17:20:37 +08:00
if slug_clean :
return f "https://polymarket.com/event/ { slug_clean } "
except Exception as e :
logger . debug ( f "Failed to fetch slug for market { market_id } : { e } " )
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# If all else fails, return to the search page (more reliable)
# Note: Polymarket's URL format may have changed, use search as fallback
2026-03-01 17:20:37 +08:00
return f "https://polymarket.com/search?q= { market_id } "
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _fetch_market_detail_by_id ( self , market_id : str ) -> Optional [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Get market details from API by market ID (used to get slug)
Reference: https://docs.polymarket.com/market-data/fetching-markets
2026-03-01 03:42:10 +08:00
"""
try :
2026-04-06 16:47:36 +07:00
# Method 1: Try querying through the events endpoint (recommended because events include markets)
2026-03-01 03:42:10 +08:00
url = f " { self . gamma_api } /events"
params = { "active" : "true" , "closed" : "false" , "limit" : 100 }
response = self . session . get ( url , params = params , timeout = 10 )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if response . status_code == 200 :
events = response . json ()
if isinstance ( events , list ):
for event in events :
markets = event . get ( "markets" , [])
if not markets and ( "question" in event or "slug" in event ):
markets = [ event ]
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
for market in markets :
m_id = market . get ( "id" ) or market . get ( "slug" ) or ""
e_id = event . get ( "id" ) or event . get ( "slug" ) or ""
2026-04-06 16:47:36 +07:00
# Match market_id or event_id
2026-03-01 03:42:10 +08:00
if str ( m_id ) == str ( market_id ) or str ( e_id ) == str ( market_id ):
2026-04-06 16:47:36 +07:00
# Return event (because event contains slug)
2026-03-01 03:42:10 +08:00
return event
elif isinstance ( events , dict ):
if "data" in events :
events_list = events [ "data" ]
for event in events_list :
markets = event . get ( "markets" , [])
if not markets and ( "question" in event or "slug" in event ):
markets = [ event ]
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
for market in markets :
m_id = market . get ( "id" ) or market . get ( "slug" ) or ""
e_id = event . get ( "id" ) or event . get ( "slug" ) or ""
if str ( m_id ) == str ( market_id ) or str ( e_id ) == str ( market_id ):
return event
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Method 2: Try querying through the markets endpoint
2026-03-01 03:42:10 +08:00
url = f " { self . gamma_api } /markets"
params = { "id" : market_id , "limit" : 1 }
response = self . session . get ( url , params = params , timeout = 10 )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if response . status_code == 200 :
data = response . json ()
if isinstance ( data , list ) and len ( data ) > 0 :
return data [ 0 ]
elif isinstance ( data , dict ) and "id" in data :
return data
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
return None
except Exception as e :
logger . debug ( f "Failed to fetch market detail by ID { market_id } : { e } " )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
def _fetch_market_by_slug ( self , slug : str ) -> Optional [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Query the market directly through slug (the most efficient way)
According to Polymarket API documentation: https://docs.polymarket.com/market-data/fetching-markets
You can use /markets?slug=xxx to query directly
2026-03-01 17:20:37 +08:00
"""
try :
2026-04-06 16:47:36 +07:00
# Method 1: Try querying the slug directly through the markets endpoint
2026-03-01 17:20:37 +08:00
url = f " { self . gamma_api } /markets"
params = { "slug" : slug , "limit" : 10 }
logger . info ( f "Fetching market by slug from Gamma API: { url } with params: { params } " )
response = self . session . get ( url , params = params , timeout = 10 )
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
if response . status_code == 200 :
data = response . json ()
if isinstance ( data , list ) and len ( data ) > 0 :
2026-04-06 16:47:36 +07:00
# Parse the returned market data
2026-03-01 17:20:37 +08:00
markets = self . _parse_gamma_events ( data )
2026-04-06 16:47:36 +07:00
# Exact match slug
2026-03-01 17:20:37 +08:00
for market in markets :
market_slug = market . get ( "slug" , "" ) . lower ()
if market_slug == slug . lower () or slug . lower () in market_slug :
logger . info ( f "Found market by slug: { slug } " )
return market
2026-04-06 16:47:36 +07:00
# If there is no exact match, return the first
2026-03-01 17:20:37 +08:00
if markets :
logger . info ( f "Found market by slug (fuzzy match): { slug } " )
return markets [ 0 ]
elif isinstance ( data , dict ):
2026-04-06 16:47:36 +07:00
# single market object
2026-03-01 17:20:37 +08:00
markets = self . _parse_gamma_events ([ data ])
if markets :
logger . info ( f "Found market by slug: { slug } " )
return markets [ 0 ]
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Method 2: Try to query through the events endpoint (events may contain slug information)
2026-03-01 17:20:37 +08:00
url = f " { self . gamma_api } /events"
params = { "active" : "true" , "closed" : "false" , "limit" : 100 }
response = self . session . get ( url , params = params , timeout = 10 )
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
if response . status_code == 200 :
data = response . json ()
events = data if isinstance ( data , list ) else ( data . get ( "data" , []) if isinstance ( data , dict ) else [])
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Find the matching slug in the returned event
2026-03-01 17:20:37 +08:00
for event in events :
event_slug = ( event . get ( "slug" ) or "" ) . lower ()
if event_slug == slug . lower () or slug . lower () in event_slug :
parsed = self . _parse_gamma_events ([ event ])
if parsed :
logger . info ( f "Found market by slug via events: { slug } " )
return parsed [ 0 ]
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
logger . warning ( f "Market with slug ' { slug } ' not found via direct query" )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
except Exception as e :
logger . error ( f "Failed to fetch market by slug { slug } : { e } " , exc_info = True )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _fetch_market_from_api ( self , market_id : str ) -> Optional [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Get individual market data from Gamma API
Support query by slug or id
2026-03-01 03:42:10 +08:00
"""
try :
2026-04-06 16:47:36 +07:00
# Determine whether it is slug or market_id
2026-04-09 14:30:51 +07:00
is_slug = not market_id . isdigit () and ( "-" in market_id or any ( c . isalpha () for c in market_id ))
2026-04-06 16:47:36 +07:00
# If it is a slug, the direct query method is preferred.
2026-03-01 17:20:37 +08:00
if is_slug :
market = self . _fetch_market_by_slug ( market_id )
if market :
return market
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Method 1: Query through markets endpoint (supports id and slug)
2026-03-01 17:20:37 +08:00
url = f " { self . gamma_api } /markets"
params = { "id" : market_id , "limit" : 10 } if not is_slug else { "slug" : market_id , "limit" : 10 }
response = self . session . get ( url , params = params , timeout = 10 )
2026-04-09 14:30:51 +07:00
2026-03-01 17:20:37 +08:00
if response . status_code == 200 :
data = response . json ()
if isinstance ( data , list ) and len ( data ) > 0 :
markets = self . _parse_gamma_events ( data )
if markets :
return markets [ 0 ]
elif isinstance ( data , dict ):
markets = self . _parse_gamma_events ([ data ])
if markets :
return markets [ 0 ]
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Method 2: Search via events endpoint (as an alternative)
2026-03-01 03:42:10 +08:00
url = f " { self . gamma_api } /events"
2026-04-09 14:30:51 +07:00
params = { "active" : "true" , "closed" : "false" , "limit" : 100 }
2026-03-01 03:42:10 +08:00
response = self . session . get ( url , params = params , timeout = 10 )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if response . status_code == 200 :
data = response . json ()
events = data if isinstance ( data , list ) else ( data . get ( "data" , []) if isinstance ( data , dict ) else [])
2026-04-09 14:30:51 +07:00
2026-04-06 16:47:36 +07:00
# Find matching markets in returned events
2026-03-01 03:42:10 +08:00
for event in events :
markets = event . get ( "markets" , [])
if not markets :
markets = [ event ]
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
for market in markets :
m_id = market . get ( "id" ) or market . get ( "slug" ) or event . get ( "id" ) or event . get ( "slug" , "" )
2026-03-01 17:20:37 +08:00
if str ( m_id ) == str ( market_id ) or market . get ( "slug" ) == market_id :
2026-03-01 03:42:10 +08:00
parsed = self . _parse_gamma_events ([ event ])
if parsed :
return parsed [ 0 ]
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
return None
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
except Exception as e :
logger . error ( f "Failed to fetch market { market_id } : { e } " , exc_info = True )
return None
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _save_markets_to_db ( self , markets : List [ Dict ]):
2026-04-06 16:47:36 +07:00
"""Save market data to database"""
2026-03-01 03:42:10 +08:00
try :
with get_db_connection () as db :
cur = db . cursor ()
for market in markets :
2026-04-06 16:47:36 +07:00
# Get the slug, but don't use it if it's a number (numbers are not valid slugs)
2026-04-09 14:30:51 +07:00
slug = market . get ( "slug" ) or None
2026-04-06 16:47:36 +07:00
# If the slug is a number, it means it is not a valid slug and is set to None.
2026-03-01 03:42:10 +08:00
if slug and str ( slug ) . isdigit ():
slug = None
2026-04-06 16:47:36 +07:00
# Clean slug, keep only alphanumerics and hyphens
2026-03-01 03:42:10 +08:00
import re
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
if slug :
2026-04-09 14:30:51 +07:00
slug = re . sub ( r "[^a-zA-Z0-9\-]" , "-" , str ( slug ))
slug = slug . strip ( "-" )
2026-04-06 16:47:36 +07:00
# If it is empty or still a number after cleaning, set to None
2026-03-01 03:42:10 +08:00
if not slug or slug . isdigit ():
slug = None
2026-04-09 14:30:51 +07:00
cur . execute (
"""
2026-03-01 03:42:10 +08:00
INSERT INTO qd_polymarket_markets
(market_id, question, category, current_probability, volume_24h,
liquidity, end_date_iso, status, outcome_tokens, slug, updated_at)
VALUES ( %s , %s , %s , %s , %s , %s , %s , %s , %s , %s , NOW())
ON CONFLICT (market_id) DO UPDATE SET
question = EXCLUDED.question,
category = EXCLUDED.category,
current_probability = EXCLUDED.current_probability,
volume_24h = EXCLUDED.volume_24h,
liquidity = EXCLUDED.liquidity,
end_date_iso = EXCLUDED.end_date_iso,
status = EXCLUDED.status,
outcome_tokens = EXCLUDED.outcome_tokens,
slug = EXCLUDED.slug,
updated_at = NOW()
2026-04-09 14:30:51 +07:00
""" ,
(
market . get ( "market_id" ),
market . get ( "question" ),
market . get ( "category" , "other" ),
market . get ( "current_probability" , 50.0 ),
market . get ( "volume_24h" , 0 ),
market . get ( "liquidity" , 0 ),
market . get ( "end_date_iso" ),
market . get ( "status" , "active" ),
json . dumps ( market . get ( "outcome_tokens" , {})),
slug ,
),
)
2026-03-01 03:42:10 +08:00
db . commit ()
cur . close ()
except Exception as e :
logger . error ( f "Failed to save markets to DB: { type ( e ) . __name__ } : { e } " , exc_info = True )
2026-04-09 14:30:51 +07:00
2026-03-01 03:42:10 +08:00
def _get_sample_markets ( self , category : str = None , limit : int = 50 ) -> List [ Dict ]:
"""
2026-04-06 16:47:36 +07:00
Get sample market data (deprecated)
You should now use real API data.
2026-03-01 03:42:10 +08:00
"""
2026-04-06 16:47:36 +07:00
# No longer returns sample data, returns an empty list
2026-03-01 03:42:10 +08:00
logger . warning ( "Sample data method called, but real API should be used instead" )
return []