Files
alpaca-llm-bot-v1/app.py

118 lines
4.3 KiB
Python

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy import func
from datetime import datetime, timedelta
from db import init_db, SessionLocal, BotDecision, TradeExecution, CuratedInsight
from bot import start_scheduler, run_cycle, curate_cycle
from services import account_snapshot
app = FastAPI(title="alpaca-llm-bot-v1")
templates = Jinja2Templates(directory="templates")
@app.on_event("startup")
def startup():
init_db()
start_scheduler()
@app.get("/health")
def health():
return {"ok": True}
@app.post("/run-now")
def run_now():
run_cycle()
return {"ok": True, "ran": True}
@app.post("/curate-now")
def curate_now():
curate_cycle()
return {"ok": True, "curated": True}
@app.get("/api/account")
def api_account():
return JSONResponse(account_snapshot())
@app.get("/api/metrics")
def api_metrics(hours: int = 72):
db = SessionLocal()
try:
since = datetime.utcnow() - timedelta(hours=hours)
decs = db.query(BotDecision).filter(BotDecision.ts >= since).order_by(BotDecision.ts.asc()).all()
trades = db.query(TradeExecution).filter(TradeExecution.ts >= since).order_by(TradeExecution.ts.asc()).all()
# hour buckets
buckets = {}
for d in decs:
k = d.ts.strftime("%m-%d %H:00")
buckets.setdefault(k, {"buy": 0, "sell": 0, "hold": 0, "executed": 0, "failed": 0})
if d.action in ("buy", "sell", "hold"):
buckets[k][d.action] += 1
if d.status == "executed":
buckets[k]["executed"] += 1
if d.status == "failed":
buckets[k]["failed"] += 1
labels = list(buckets.keys())
buy = [buckets[k]["buy"] for k in labels]
sell = [buckets[k]["sell"] for k in labels]
hold = [buckets[k]["hold"] for k in labels]
executed = [buckets[k]["executed"] for k in labels]
failed = [buckets[k]["failed"] for k in labels]
# cumulative notional (proxy activity curve)
t_labels, t_values = [], []
c = 0.0
for t in trades:
c += float(t.notional or 0)
t_labels.append(t.ts.strftime("%m-%d %H:%M"))
t_values.append(round(c, 2))
# symbol leaderboard
lb = {}
for d in decs:
row = lb.setdefault(d.symbol, {"symbol": d.symbol, "decisions": 0, "executed": 0})
row["decisions"] += 1
if d.status == "executed":
row["executed"] += 1
leaderboard = sorted(lb.values(), key=lambda x: x["executed"], reverse=True)
return {
"ok": True,
"hours": hours,
"decisionSeries": {
"labels": labels,
"buy": buy,
"sell": sell,
"hold": hold,
"executed": executed,
"failed": failed,
},
"activitySeries": {
"labels": t_labels,
"cumulativeNotional": t_values,
},
"leaderboard": leaderboard,
}
finally:
db.close()
@app.get("/")
def home(request: Request):
db = SessionLocal()
try:
since = datetime.utcnow() - timedelta(hours=24)
decisions = db.query(BotDecision).order_by(BotDecision.ts.desc()).limit(120).all()
trades = db.query(TradeExecution).order_by(TradeExecution.ts.desc()).limit(120).all()
insights = db.query(CuratedInsight).order_by(CuratedInsight.ts.desc()).limit(30).all()
stats = {
"decisions": db.query(func.count(BotDecision.id)).filter(BotDecision.ts >= since).scalar() or 0,
"trades": db.query(func.count(TradeExecution.id)).filter(TradeExecution.ts >= since).scalar() or 0,
"executed": db.query(func.count(BotDecision.id)).filter(BotDecision.ts >= since, BotDecision.status == "executed").scalar() or 0,
"failed": db.query(func.count(BotDecision.id)).filter(BotDecision.ts >= since, BotDecision.status == "failed").scalar() or 0,
"insights": db.query(func.count(CuratedInsight.id)).filter(CuratedInsight.ts >= since).scalar() or 0,
}
return templates.TemplateResponse("index.html", {"request": request, "decisions": decisions, "trades": trades, "insights": insights, "stats": stats})
finally:
db.close()