Files
polymarket-bot/bot/main.py
T
chemavx 33ad86f352
CI/CD / build-and-push (push) Successful in 1m32s
feat(news): 6h cache, politics-only, max 5/cycle, 2s sleep between calls
- CACHE_TTL: 4h → 6h (≤36 req/day with ≤9 politics markets)
- GNews only called for is_politics markets (BTC/F&G cover crypto/macro)
- MAX_NEWS_QUERIES_PER_CYCLE=5: BayesianStrategy.reset_cycle() called each
  iteration; counter increments only on actual API call (cache hits free)
- 2s asyncio.sleep in news.py finally block after each real HTTP request
- main.py sorts markets: politics first by end_date ascending, so soonest-
  resolving markets consume the 5-query budget before others

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:33:26 +00:00

142 lines
4.7 KiB
Python

"""
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())