from fastapi import FastAPI, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse import requests import os import pandas as pd app = FastAPI(title="Trading Dashboard API") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # Serve static files app.mount("/static", StaticFiles(directory="dashboard/public"), name="static") @app.get("/") async def read_index(): return FileResponse('dashboard/public/index.html') DB_USER = os.getenv("DB_USER", "admin") DB_PASSWORD = os.getenv("DB_PASSWORD", "quest") DB_AUTH = (DB_USER, DB_PASSWORD) if DB_USER and DB_PASSWORD else None DB_HOST = os.getenv("DB_HOST", "questdb") @app.get("/api/trades") async def get_trades(isin: str = None, days: int = 7): query = f"select * from trades where timestamp > dateadd('d', -{days}, now())" if isin: query += f" and isin = '{isin}'" query += " order by timestamp asc" try: response = requests.get(f"http://{DB_HOST}:9000/exec", params={'query': query}, auth=DB_AUTH) if response.status_code == 200: return response.json() throw_http_error(response) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/api/metadata") async def get_metadata(): query = "select * from metadata" try: response = requests.get(f"http://{DB_HOST}:9000/exec", params={'query': query}, auth=DB_AUTH) if response.status_code == 200: return response.json() throw_http_error(response) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/api/summary") async def get_summary(): # Coalesce null values to 'Unknown' and group properly query = """ select coalesce(m.continent, 'Unknown') as continent, count(*) as trade_count, sum(t.price * t.quantity) as total_volume from trades t left join metadata m on t.isin = m.isin group by continent """ try: response = requests.get(f"http://{DB_HOST}:9000/exec", params={'query': query}, auth=DB_AUTH) if response.status_code == 200: return response.json() throw_http_error(response) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/api/comparison") async def get_comparison(days: int = 7): # Aggregated volume per exchange per day # QuestDB date_trunc('day', timestamp) is good. # We ensure we get exchange, day, and metrics. query = f""" select exchange, date_trunc('day', timestamp) as day, sum(price * quantity) as daily_volume, count(*) as trade_count from trades where timestamp > dateadd('d', -{days}, now()) group by exchange, day order by day asc """ try: response = requests.get(f"http://{DB_HOST}:9000/exec", params={'query': query}, auth=DB_AUTH) if response.status_code == 200: return response.json() throw_http_error(response) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) def throw_http_error(res): raise HTTPException(status_code=res.status_code, detail=f"QuestDB error: {res.text}") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)