File size: 4,143 Bytes
75d9b3c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import logging
import os
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
import uvicorn
from pydantic import BaseModel

# Import our quant engine modules
from backend.agents.scan_pipeline import run_full_scan
from backend.agents.supervisor_agent import invoke_supervisor
from backend.data.store import get_store

# Setup logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

# Initialize FastAPI App
app = FastAPI(title="Swing Quant Engine API", version="2.0.0")

# CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Paths
BASE_DIR = Path(__file__).parent.parent
FRONTEND_DIR = BASE_DIR / "frontend"
FRONTEND_DIR.mkdir(exist_ok=True)
(FRONTEND_DIR / "css").mkdir(exist_ok=True)
(FRONTEND_DIR / "js").mkdir(exist_ok=True)

# Mount static files
app.mount("/css", StaticFiles(directory=str(FRONTEND_DIR / "css")), name="css")
app.mount("/js", StaticFiles(directory=str(FRONTEND_DIR / "js")), name="js")

# ── In-memory cache for last scan results ──
_last_scan_result: dict | None = None

class ChatRequest(BaseModel):
    query: str

class ScanRequest(BaseModel):
    market: str = "both"
    top_n: int = 10

@app.get("/", response_class=HTMLResponse)
async def serve_index():
    index_path = FRONTEND_DIR / "index.html"
    if index_path.exists():
        return index_path.read_text()
    return "Frontend not built yet. Create frontend/index.html"

@app.get("/api/dashboard")
async def get_dashboard_data():
    """Get the latest saved signals and stats for the dashboard."""
    global _last_scan_result
    store = get_store()
    history = store.get_scan_history(limit=1)
    
    if not history:
        return JSONResponse({"status": "no_data", "signals": [], "stats": {}, "pipeline_stages": [], "regimes": {}})
        
    signals = store.get_active_signals(limit=10)
    
    # Use cached pipeline stages from last scan if available
    pipeline_stages = []
    regimes = {}
    stats = {
        "universe_size": history[0]["universe_size"],
        "raw_signals": history[0]["signals_found"],
        "final_signals": len(signals),
    }
    
    if _last_scan_result:
        pipeline_stages = _last_scan_result.get("pipeline_stages", [])
        regimes = _last_scan_result.get("regimes", {})
        stats = _last_scan_result.get("stats", stats)
    
    return {
        "status": "success",
        "last_scan_date": history[0]["created_at"] * 1000,
        "stats": stats,
        "signals": signals,
        "pipeline_stages": pipeline_stages,
        "regimes": regimes,
    }

@app.post("/api/scan")
async def trigger_scan(req: ScanRequest):
    """Trigger a new full scan with step-by-step pipeline tracking."""
    global _last_scan_result
    logger.info(f"Triggering manual scan for market: {req.market}")
    results = run_full_scan(market=req.market, top_n=req.top_n)
    
    if "error" in results and not results.get("pipeline_stages"):
        return JSONResponse({"status": "error", "message": results["error"]}, status_code=500)
    
    # Cache the full result for dashboard
    _last_scan_result = results
    
    return {
        "status": "success",
        "stats": results.get("stats"),
        "pipeline_stages": results.get("pipeline_stages", []),
        "regimes": results.get("regimes", {}),
        "signals": results.get("signals", []),
    }

@app.post("/api/chat")
async def chat_with_supervisor(req: ChatRequest):
    """Chat with the LangGraph supervisor."""
    try:
        response = invoke_supervisor(req.query)
        return {"status": "success", "response": response}
    except Exception as e:
        logger.error(f"Chat error: {e}")
        return JSONResponse({"status": "error", "message": str(e)}, status_code=500)

if __name__ == "__main__":
    uvicorn.run("backend.server:app", host="0.0.0.0", port=8001, reload=True)