116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
"""Resolution tracker - checks if markets with signals have resolved and updates correctness."""
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from src.db.database import SignalDatabase
|
|
from src.services.market_fetcher import MarketFetcher
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ResolutionTracker:
|
|
"""Tracks market resolutions and computes signal correctness."""
|
|
|
|
def __init__(self, db: SignalDatabase):
|
|
self.db = db
|
|
self.market_fetcher = MarketFetcher()
|
|
|
|
def _determine_resolved_outcome(self, market) -> Optional[str]:
|
|
"""
|
|
Determine the resolved outcome from a market.
|
|
|
|
A market is considered resolved if closed==True and one outcome price >= 0.99.
|
|
|
|
Returns:
|
|
The winning outcome string (e.g. "Yes" or "No"), or None if not resolved.
|
|
"""
|
|
if not market.closed:
|
|
return None
|
|
|
|
if not market.outcomes or not market.outcome_prices:
|
|
return None
|
|
|
|
for outcome, price in zip(market.outcomes, market.outcome_prices):
|
|
if price >= 0.99:
|
|
return outcome
|
|
|
|
return None
|
|
|
|
def _is_past_end_date(self, market) -> bool:
|
|
"""Check if a market's end_date has passed."""
|
|
if not market.end_date:
|
|
return True # No end date, always check
|
|
try:
|
|
end_dt = datetime.fromisoformat(market.end_date.replace("Z", "+00:00"))
|
|
return datetime.utcnow().replace(tzinfo=end_dt.tzinfo) >= end_dt
|
|
except (ValueError, TypeError):
|
|
return True
|
|
|
|
async def check_all(self) -> dict:
|
|
"""
|
|
Check all unresolved markets for resolution.
|
|
|
|
Returns:
|
|
Summary dict with counts.
|
|
"""
|
|
unresolved_ids = self.db.get_unresolved_market_ids()
|
|
if not unresolved_ids:
|
|
logger.debug("No unresolved markets to check")
|
|
return {"checked": 0, "resolved": 0, "signals_updated": 0}
|
|
|
|
logger.info(f"Checking {len(unresolved_ids)} unresolved markets for resolution")
|
|
|
|
checked = 0
|
|
resolved = 0
|
|
signals_updated = 0
|
|
|
|
for market_id in unresolved_ids:
|
|
try:
|
|
market = self.market_fetcher.get_market_by_id(market_id)
|
|
if not market:
|
|
logger.debug(f"Market {market_id} not found on API")
|
|
checked += 1
|
|
await asyncio.sleep(0.5)
|
|
continue
|
|
|
|
# Optimization: skip markets whose end_date hasn't passed yet
|
|
if not self._is_past_end_date(market):
|
|
checked += 1
|
|
await asyncio.sleep(0.5)
|
|
continue
|
|
|
|
outcome = self._determine_resolved_outcome(market)
|
|
if outcome:
|
|
updated = self.db.mark_market_resolved(
|
|
market_id=market_id,
|
|
resolved_outcome=outcome,
|
|
resolved_at=datetime.utcnow(),
|
|
)
|
|
resolved += 1
|
|
signals_updated += updated
|
|
logger.info(
|
|
f"Market resolved: {market.question[:50]}... "
|
|
f"outcome={outcome}, {updated} signals updated"
|
|
)
|
|
|
|
checked += 1
|
|
await asyncio.sleep(0.5) # Rate limiting
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error checking market {market_id}: {e}")
|
|
checked += 1
|
|
await asyncio.sleep(0.5)
|
|
|
|
result = {
|
|
"checked": checked,
|
|
"resolved": resolved,
|
|
"signals_updated": signals_updated,
|
|
}
|
|
logger.info(
|
|
f"Resolution check complete: {checked} checked, "
|
|
f"{resolved} resolved, {signals_updated} signals updated"
|
|
)
|
|
return result
|