Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| 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 ββββββββββββββββββββββββββββββ | |
| 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), | |
| } | |
| def get_opening_straddle(): | |
| return engine.get_opening_straddle() | |
| 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) | |
| 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") | |
| def serve_index(): | |
| return FileResponse(os.path.join(FRONTEND_DIR, "index.html")) | |