Files
AITrading/python-app/app/web_app.py
2026-03-26 14:13:44 +08:00

83 lines
2.6 KiB
Python

from __future__ import annotations
import asyncio
from pathlib import Path
from typing import Any
from fastapi import FastAPI, HTTPException, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse
from market import create_provider, load_market_config
from market.service import build_dashboard, build_realtime_info
app = FastAPI(title="AITrading Simple Frontend API", version="1.0.0")
BASE_DIR = Path(__file__).resolve().parent
WEB_INDEX = BASE_DIR / "web" / "index.html"
@app.get("/")
def index() -> FileResponse:
if not WEB_INDEX.exists():
raise HTTPException(status_code=500, detail="frontend page not found")
return FileResponse(WEB_INDEX)
@app.get("/api/stock")
def stock_dashboard(
query: str = Query(..., description="stock code or name"),
days: int = Query(120, ge=30, le=365),
sma_fast: int = Query(5, ge=2, le=60),
sma_slow: int = Query(20, ge=3, le=120),
) -> dict[str, Any]:
try:
provider = create_provider(load_market_config())
return build_dashboard(
provider=provider,
query=query,
days=days,
sma_fast=sma_fast,
sma_slow=sma_slow,
)
except RuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.get("/api/stock/realtime")
def stock_realtime(
query: str = Query(..., description="stock code or name"),
) -> dict[str, Any]:
try:
provider = create_provider(load_market_config())
return build_realtime_info(provider=provider, query=query)
except RuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.websocket("/ws/stock/realtime")
async def stock_realtime_ws(websocket: WebSocket) -> None:
await websocket.accept()
query = (websocket.query_params.get("query") or "").strip()
if not query:
await websocket.send_json({"error": "query is required"})
await websocket.close(code=1008)
return
try:
while True:
provider = create_provider(load_market_config())
payload = build_realtime_info(provider=provider, query=query)
await websocket.send_json(payload)
# WebSocket channel keeps the connection alive, front-end no longer polls via HTTP.
await asyncio.sleep(2)
except WebSocketDisconnect:
return
except Exception as exc: # noqa: BLE001
await websocket.send_json({"error": str(exc)})
await websocket.close(code=1011)
if __name__ == "__main__":
import uvicorn
uvicorn.run("web_app:app", host="0.0.0.0", port=8000, reload=False)