""" Polymarket Trading Bot — Main Entry Point # ci-test: 2026-04-14 """ import asyncio import logging import os from contextlib import asynccontextmanager from datetime import datetime, timezone from bot.data.polymarket import PolymarketClient from bot.data.external import ExternalDataClient from bot.data.news import NewsClient from bot.strategy.bayesian import BayesianStrategy from bot.risk.manager import RiskManager from bot.executor.paper import PaperExecutor from bot.metrics.tracker import MetricsTracker from bot.data.db import Database logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) log = logging.getLogger("bot.main") PAPER_MODE = os.getenv("PAPER_MODE", "true").lower() == "true" PAPER_BANKROLL = float(os.getenv("PAPER_BANKROLL", "10000")) async def run_trading_loop( poly: PolymarketClient, external: ExternalDataClient, strategy: BayesianStrategy, risk: RiskManager, executor: PaperExecutor, metrics: MetricsTracker, ) -> None: """Main trading loop — runs every 60 seconds.""" log.info("Trading loop started. PAPER_MODE=%s", PAPER_MODE) while True: try: # 1. Fetch active crypto/finance markets markets = await poly.get_active_markets() log.info("Found %d active markets", len(markets)) # Sort: politics markets first (soonest-resolving → highest GNews priority), # then all others. This ensures the 5-query-per-cycle cap hits the most # time-sensitive political markets before the budget runs out. def _sort_key(m): is_pol = m.category == "politics" try: dt = datetime.fromisoformat(m.end_date.replace("Z", "+00:00")) except Exception: dt = datetime(9999, 12, 31, tzinfo=timezone.utc) return (0 if is_pol else 1, dt) markets = sorted(markets, key=_sort_key) for _m in markets: log.info(" [market] %s | ends: %s | yes_price: %.3f", _m.question, _m.end_date, _m.yes_price) # 2. Get external signals ext_data = await external.get_all_signals() # Reset per-cycle GNews counter so the limit applies fresh each cycle strategy.reset_cycle() for market in markets: # 3. Estimate true probability signal = await strategy.evaluate(market, ext_data) if signal is None: continue log.info( "Signal: market=%s poly_price=%.3f our_estimate=%.3f confidence=%.2f", market.question[:50], signal.polymarket_price, signal.estimated_prob, signal.confidence, ) # 4. Risk check + position sizing order = risk.size_order(signal, executor.get_portfolio()) if order is None: log.debug("Risk manager rejected order for %s", market.id) continue # 5. Execute (paper or real) trade = await executor.execute(order) if trade: await metrics.record_trade(trade) log.info("Trade executed: %s", trade) # 6. Update daily metrics await metrics.update_daily_summary() except Exception as e: log.error("Error in trading loop: %s", e, exc_info=True) await asyncio.sleep(60) async def main() -> None: if PAPER_MODE: log.info("=" * 60) log.info(" PAPER TRADING MODE — No real money at risk") log.info(" Bankroll: $%.2f simulated", PAPER_BANKROLL) log.info("=" * 60) else: log.warning("REAL TRADING MODE ACTIVE — Real money at risk!") db = Database() await db.connect() await db.run_migrations() poly = PolymarketClient() external = ExternalDataClient() news = NewsClient() strategy = BayesianStrategy(news=news) risk = RiskManager(max_position_pct=0.05, max_exposure_pct=0.30) executor = PaperExecutor(db=db, bankroll=PAPER_BANKROLL) if PAPER_MODE else None metrics = MetricsTracker(db=db) if executor is None: # Import real executor only when explicitly needed from bot.executor.real import RealExecutor # noqa executor = RealExecutor(db=db) if PAPER_MODE: await executor.initialize() try: await run_trading_loop(poly, external, strategy, risk, executor, metrics) finally: await db.disconnect() await news.close() if __name__ == "__main__": asyncio.run(main())