e2fb697c0c
CI/CD / build-and-push (push) Successful in 1m54s
- db: update_family_key() persists corrected family slugs for open trades - db: get_recently_closed_inverted() returns markets closed for inversion within N hours; used as reentry guard in the trading loop - db: get_recent_trades() accepts status=open|closed|None and adds a computed "status" field to every row - bot/main.py: legacy scan now computes family_key from stored question alone (dummy Market) when a position's market is no longer active — fixes NULL family_key on legacy trades like Ken Paxton (562186) - bot/main.py: legacy scan (Step 2.5) persists corrected family_keys in DB so family conflict guards work correctly on next restart - bot/main.py: positions with NULL edge_net and no live market are tagged legacy_incomplete instead of OK; counted separately in scan summary - bot/main.py: reentry_guard blocks re-entering any market closed for inversion bug within 24h; logs reentry_guard_triggered per skip - api/main.py: /api/trades now accepts ?status=open|closed|all (default open) and includes status_filter in response DB fix (applied directly): 629558 family_key politics-2026 → ohio-gubernatorial-2026; 562186 family_key NULL → texas-republican-2026 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
2.3 KiB
Python
83 lines
2.3 KiB
Python
"""
|
|
FastAPI Backend — serves metrics and trade data to the React dashboard.
|
|
"""
|
|
from contextlib import asynccontextmanager
|
|
import os
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from bot.data.db import Database
|
|
|
|
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"
|
|
Each trade includes a computed "status" field.
|
|
"""
|
|
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)
|
|
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)
|
|
trades = await db.get_recent_trades(limit=500)
|
|
|
|
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 trades)
|
|
|
|
return {
|
|
"paper_mode": os.getenv("PAPER_MODE", "true") == "true",
|
|
"paper_bankroll": paper_bankroll,
|
|
"total_trades": len(trades),
|
|
"total_deployed": total_deployed,
|
|
"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(trades) >= 50
|
|
),
|
|
}
|