Files
polymarket-bot/api/main.py
T
chemavx 46f8f4b79a
CI/CD / build-and-push (push) Successful in 1m51s
feat(observability): fine-grained metrics for summary, trades, and cycle log
api/summary — new fields:
  open_trades_count, closed_trades_count, cash_available (bankroll−deployed),
  legacy_incomplete_count, reentry_guard_blocks_24h
  parallel fetch via asyncio.gather for sub-ms overhead

api/trades?status=open — trade enrichment:
  days_open (float, rounded to 1 decimal)
  signal_components {fg, mom, news, mfld} parsed from reasoning via regex
  Old trades without feat_str in reasoning return signal_components: null

bayesian.py — reasoning now embeds feat_str:
  "fg=+0.0600 mom=+0.0000 news=+0.0000 mfld=-0.7483 |"
  Manifold counters: _manifold_fetched / _manifold_on_trade per cycle
  get_cycle_stats() exposes manifold_matches_accepted / manifold_matches_rejected

bot/main.py — CYCLE SUMMARY 4 new fields:
  reentry_guard_blocked, legacy_incomplete_seen,
  family_conflicts_prevented, manifold_matches_accepted/rejected
  legacy_incomplete_count queried from DB once per cycle

db.py — get_legacy_incomplete_count(): open trades with NULL edge_net

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:48:31 +00:00

126 lines
4.0 KiB
Python

"""
FastAPI Backend — serves metrics and trade data to the React dashboard.
"""
import asyncio
from contextlib import asynccontextmanager
from datetime import datetime, timezone
import os
import re
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from bot.data.db import Database
# Matches the feat_str embedded in reasoning for trades from bayesian.py v2+:
# "fg=+0.0600 mom=+0.0000 news=+0.0000 mfld=-0.7483"
_FEAT_RE = re.compile(
r"fg=([+-]?[\d.]+).*?mom=([+-]?[\d.]+).*?news=([+-]?[\d.]+).*?mfld=([+-]?[\d.]+)"
)
def _enrich_trade(trade: dict) -> dict:
"""Add days_open and signal_components to an open trade dict."""
ts = trade.get("timestamp")
if ts is not None:
now = datetime.now(timezone.utc)
if getattr(ts, "tzinfo", None) is None:
ts = ts.replace(tzinfo=timezone.utc)
trade["days_open"] = round((now - ts).total_seconds() / 86400, 1)
else:
trade["days_open"] = None
reasoning = trade.get("reasoning") or ""
m = _FEAT_RE.search(reasoning)
trade["signal_components"] = (
{"fg": float(m.group(1)), "mom": float(m.group(2)),
"news": float(m.group(3)), "mfld": float(m.group(4))}
if m else None
)
return trade
db = Database()
@asynccontextmanager
async def lifespan(app: FastAPI):
await db.connect()
yield
await db.disconnect()
app = FastAPI(title="Polymarket Bot API", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET"],
allow_headers=["*"],
)
@app.get("/health")
async def health():
return {"status": "ok", "paper_mode": os.getenv("PAPER_MODE", "true")}
@app.get("/api/metrics")
async def get_metrics():
history = await db.get_metrics_history(days=42)
if not history:
return {"history": [], "latest": None}
return {"history": history, "latest": history[0]}
@app.get("/api/trades")
async def get_trades(limit: int = 50, status: str = "open"):
"""
status: "open" (default) | "closed" | "all"
Open trades include days_open and signal_components {fg, mom, news, mfld}.
"""
if status not in ("open", "closed", "all"):
status = "open"
filter_status = None if status == "all" else status
trades = await db.get_recent_trades(limit=limit, status=filter_status)
if filter_status == "open":
trades = [_enrich_trade(t) for t in trades]
return {"trades": trades, "count": len(trades), "status_filter": status}
@app.get("/api/summary")
async def get_summary():
"""Dashboard summary card data."""
history = await db.get_metrics_history(days=1)
open_trades, all_trades, inverted, legacy_count = await asyncio.gather(
db.get_recent_trades(limit=500, status="open"),
db.get_recent_trades(limit=500),
db.get_recently_closed_inverted(hours=24),
db.get_legacy_incomplete_count(),
)
latest = history[0] if history else {}
paper_bankroll = float(os.getenv("PAPER_BANKROLL", "10000"))
total_deployed = sum(t.get("net_cost", 0) for t in open_trades)
return {
"paper_mode": os.getenv("PAPER_MODE", "true") == "true",
"paper_bankroll": paper_bankroll,
"total_trades": len(all_trades),
"open_trades_count": len(open_trades),
"closed_trades_count": len(all_trades) - len(open_trades),
"total_deployed": total_deployed,
"cash_available": max(0.0, paper_bankroll - total_deployed),
"legacy_incomplete_count": legacy_count,
"reentry_guard_blocks_24h": len(inverted),
"total_pnl": latest.get("total_pnl", 0),
"win_rate": latest.get("win_rate", 0),
"sharpe_ratio": latest.get("sharpe_ratio", 0),
"calibration_score": latest.get("calibration_score", 0),
"promotion_ready": (
latest.get("sharpe_ratio", 0) >= 0.5
and latest.get("win_rate", 0) >= 0.52
and latest.get("calibration_score", 0) >= 0.7
and len(all_trades) >= 50
),
}