tbs-dashboard / main.py
iwilldoit's picture
Upload folder using huggingface_hub
9df26df verified
Raw
History Blame Contribute Delete
3.66 kB
"""
FastAPI backend for the TBS Straddle Backtest Dashboard.
Serves both the API endpoints and the static frontend files.
"""
import os
import sys
import traceback
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from engine import BacktestEngine, MONEYNESS, SL_LEVELS
# ─────────────────────────── DATA ─────────────────────────────
HERE = os.path.dirname(os.path.abspath(__file__))
DATA_PATH = os.environ.get(
"DATA_PATH",
os.path.join(HERE, "nifty_minute_chain.parquet"),
)
FRONTEND_DIR = os.path.join(HERE, "frontend")
engine = None # loaded in lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
global engine
print(f"[startup] Loading data from {DATA_PATH} …", flush=True)
try:
engine = BacktestEngine(DATA_PATH)
print(f"[startup] Engine ready β€” {engine.data_info['expiry_days']} expiry days loaded.", flush=True)
except Exception as e:
print(f"[startup] FATAL: {e}", flush=True)
traceback.print_exc()
sys.exit(1)
yield
# ─────────────────────────── APP ──────────────────────────────
app = FastAPI(title="TBS Straddle Backtest Engine", version="1.0.0",
lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ─────────────────────────── MODELS ───────────────────────────
class BacktestRequest(BaseModel):
moneyness: str = "ATM"
min_straddle: float = 0.0
class BasketRequest(BaseModel):
moneyness: str = "ATM"
min_straddle: float = 0.0
basket_type: str = "15min"
# ─────────────────────────── API ──────────────────────────────
@app.get("/api/info")
def get_info():
return {
"data": engine.data_info,
"moneyness_options": list(MONEYNESS.keys()),
"moneyness_details": MONEYNESS,
"sl_levels": SL_LEVELS,
"panel_days": len(engine.PANEL),
}
@app.get("/api/opening-straddle")
def get_opening_straddle():
return engine.get_opening_straddle()
@app.post("/api/backtest/single")
def run_single(req: BacktestRequest):
if req.moneyness not in MONEYNESS:
raise HTTPException(400, f"Unknown moneyness: {req.moneyness}")
return engine.run_single_entry(req.moneyness, req.min_straddle)
@app.post("/api/backtest/baskets")
def run_baskets(req: BasketRequest):
if req.moneyness not in MONEYNESS:
raise HTTPException(400, f"Unknown moneyness: {req.moneyness}")
if req.basket_type not in ('15min', '2hr'):
raise HTTPException(400, "basket_type must be '15min' or '2hr'")
return engine.run_basket_backtest(req.moneyness, req.min_straddle, req.basket_type)
# ─────────────── SERVE FRONTEND (static files) ───────────────
if os.path.isdir(FRONTEND_DIR):
app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static")
@app.get("/")
def serve_index():
return FileResponse(os.path.join(FRONTEND_DIR, "index.html"))