Files
eth_hedge_sim/backend/app/main.py
T
2026-07-24 17:03:46 +08:00

135 lines
3.3 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
from .models.db import Database, set_db
from .strategy import StrategyEngine, set_engine
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()
repo_root = here.parents[2]
return repo_root / "frontend" / "dist"
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
db = Database()
set_db(db)
engine = StrategyEngine()
set_engine(engine)
gw = MarketGateway(settings)
set_gateway(gw)
try:
await gw.start()
logger.info("market gateway started (SIM)")
except Exception:
logger.exception("market gateway failed to start")
yield
await engine.pause()
if engine._task and not engine._task.done():
engine._task.cancel()
try:
await engine._task
except Exception:
pass
await gw.stop()
set_gateway(None)
set_engine(None)
db.close()
set_db(None)
app = FastAPI(
title="eth_hedge_sim",
version="0.3.0",
description="ETH 自动对冲模拟盘 P1/P2",
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
from .strategy import get_engine
settings = get_settings()
gw = get_gateway()
snap = gw.snapshot()
try:
st = get_engine().state()
except Exception:
st = None
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,
"strategy": {
"running": st.get("running") if st else None,
"phase": st.get("phase") if st else None,
"rounds_done": st.get("rounds_done") if st else None,
},
}
_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)