Files
eth_hedge_sim/backend/app/main.py
T
dekun 586469482e Fix Binance boot using OKX symbols after restart.
Startup ignored DB exchange choice and kept OKX ATM ids under a Binance health label, so option bid/ask stayed empty.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 12:13:19 +08:00

154 lines
4.0 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 .models.db import Database, set_db
from .strategy import StrategyEngine, set_engine
from .strategy.session import bootstrap_session
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):
db = Database()
set_db(db)
# 必须在 set_db 之后读 DB 覆盖,否则会按 .env 默认 okx 起会话,
# 而 health 显示 DB 里的 binance → 合约号/盘口错乱(期权一直 -/-)
from .exchange.runtime import load_runtime_settings
settings = load_runtime_settings()
engine = StrategyEngine()
set_engine(engine)
engine.ensure_loop()
session = bootstrap_session(settings)
try:
await session.start()
logger.info(
"exchange=%s strategy session started (SIM)",
settings.exchange,
)
except Exception:
logger.exception("strategy session 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 session.stop()
from .exchange import set_exchange
from .strategy.session import set_session
set_session(None)
set_exchange(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 .strategy import get_engine
from .strategy.session import get_session
settings = get_settings()
try:
from .exchange.runtime import load_runtime_settings
rt = load_runtime_settings()
exchange_name = rt.exchange
except Exception:
exchange_name = settings.exchange
sess = get_session()
snap = sess.snapshot()
try:
st = get_engine().state()
except Exception:
st = None
return {
"ok": True,
"mode": settings.mode,
"env_name": settings.env_name,
"exchange": exchange_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)