e51f357b48
Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
2.7 KiB
Python
110 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from .api import router as api_router
|
|
from .config import get_settings
|
|
from .market import MarketGateway, set_gateway
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
|
)
|
|
logger = logging.getLogger("eth_hedge_sim")
|
|
|
|
|
|
def resolve_frontend_dist() -> Path:
|
|
here = Path(__file__).resolve()
|
|
# backend/app/main.py -> repo root is parents[2]
|
|
repo_root = here.parents[2]
|
|
return repo_root / "frontend" / "dist"
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
settings = get_settings()
|
|
if not settings.is_sim:
|
|
logger.warning("MODE=%s — still read-only market in current phase", settings.mode)
|
|
|
|
gw = MarketGateway(settings)
|
|
set_gateway(gw)
|
|
try:
|
|
await gw.start()
|
|
logger.info("market gateway started (SIM read-only)")
|
|
except Exception:
|
|
logger.exception("market gateway failed to start")
|
|
yield
|
|
await gw.stop()
|
|
set_gateway(None)
|
|
|
|
|
|
app = FastAPI(
|
|
title="eth_hedge_sim",
|
|
version="0.2.0",
|
|
description="ETH 自动对冲模拟盘",
|
|
lifespan=lifespan,
|
|
)
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
app.include_router(api_router)
|
|
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict:
|
|
from .market import get_gateway
|
|
|
|
settings = get_settings()
|
|
gw = get_gateway()
|
|
snap = gw.snapshot()
|
|
return {
|
|
"ok": True,
|
|
"mode": settings.mode,
|
|
"env_name": settings.env_name,
|
|
"sim": settings.is_sim,
|
|
"market_connected": snap.connected,
|
|
"pair": snap.pair.to_dict() if snap.pair else None,
|
|
"updated_at_ms": snap.updated_at_ms,
|
|
}
|
|
|
|
|
|
_DIST = resolve_frontend_dist()
|
|
if (_DIST / "assets").is_dir():
|
|
app.mount("/assets", StaticFiles(directory=str(_DIST / "assets")), name="assets")
|
|
|
|
|
|
@app.get("/")
|
|
async def index_page():
|
|
index = _DIST / "index.html"
|
|
if index.exists():
|
|
return FileResponse(index)
|
|
return {
|
|
"ok": True,
|
|
"msg": "frontend not built yet; run: cd frontend && npm ci && npm run build",
|
|
"health": "/health",
|
|
}
|
|
|
|
|
|
@app.get("/app/{full_path:path}")
|
|
@app.get("/plan")
|
|
@app.get("/trades")
|
|
@app.get("/stats")
|
|
@app.get("/settings")
|
|
@app.get("/login")
|
|
async def spa_pages(full_path: str = ""):
|
|
index = _DIST / "index.html"
|
|
if not index.exists():
|
|
raise HTTPException(status_code=404, detail="frontend not built")
|
|
return FileResponse(index)
|