Files
polymarket-bot/bot/main.py
T
chemavx 4dadd3c2c4
CI/CD / build-and-push (push) Successful in 1m28s
feat: add GNews sentiment signal for politics/tech/events markets
bot/data/news.py (new):
- NewsClient with in-memory cache (TTL=4h) to stay within 100 req/day limit
- _build_query(): strips dates, punctuation and stopwords from market question
- _score_headlines(): keyword-based pos/neg vote per article, averaged ∈ [-1, +1]
- Degrades to 0.0 on missing key, 403 quota, or network error

bot/strategy/bayesian.py:
- BayesianStrategy(news=NewsClient) — optional, backwards compatible
- Signal 4: GNews sentiment applied as direct log-odds shift (weight=1.5)
  so a ±1.0 sentiment score moves a 50% prior to 82%/18%
- +0.10 confidence boost when news signal is present
- NEWS_LOGODDS_WEIGHT constant documented at module level

bot/main.py:
- Instantiate NewsClient, pass to BayesianStrategy, close in finally block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:24:11 +00:00

125 lines
3.9 KiB
Python

"""
Polymarket Trading Bot — Main Entry Point
# ci-test: 2026-04-13
"""
import asyncio
import logging
import os
from contextlib import asynccontextmanager
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))
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()
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())