Files
2026-07-13 12:36:27 +08:00

184 lines
5.4 KiB
Python

from __future__ import annotations
import uuid
from typing import Any
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from backend.api.schemas import (
ApiEnvelope,
BatchQuoteRequest,
BacktestJobRequest,
DashboardRefreshRequest,
LrsChatRequest,
WheelEvaluateRequest,
XBriefRefreshRequest,
)
from backend.api.services import (
DEFAULT_SESSION_STATE,
answer_lrs_chat,
batch_quotes,
create_backtest_job,
get_dashboard_snapshot,
get_backtest_job,
refresh_dashboard_snapshot,
refresh_x_brief,
strategy_metadata,
get_x_brief_latest,
wheel_evaluate,
wheel_profiles,
)
from web.services.backtesting_core import _PERIOD_ORDER, _PERIOD_SPECS
app = FastAPI(title="TradingAgents API", version="0.1.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def _trace_id(req: Request) -> str:
return req.headers.get("x-trace-id") or uuid.uuid4().hex
def _ok(request: Request, data: dict[str, Any] | list[Any] | None = None) -> JSONResponse:
payload = ApiEnvelope(success=True, data=data, trace_id=_trace_id(request))
return JSONResponse(status_code=200, content=payload.model_dump())
def _err(request: Request, msg: str, code: int = 500) -> JSONResponse:
payload = ApiEnvelope(success=False, error=msg, data=None, trace_id=_trace_id(request))
return JSONResponse(status_code=code, content=payload.model_dump())
@app.get("/health")
def health(request: Request) -> JSONResponse:
return _ok(request, {"status": "ok"})
@app.get("/api/v1/state/defaults")
def state_defaults(request: Request) -> JSONResponse:
return _ok(request, DEFAULT_SESSION_STATE)
@app.get("/api/v1/strategy/metadata")
def get_strategy_metadata(request: Request) -> JSONResponse:
return _ok(request, strategy_metadata())
@app.get("/api/v1/dashboard")
def get_dashboard(request: Request) -> JSONResponse:
try:
return _ok(request, get_dashboard_snapshot())
except Exception as exc:
return _err(request, f"仪表盘数据读取失败:{exc}", 500)
@app.post("/api/v1/dashboard/refresh")
def post_dashboard_refresh(body: DashboardRefreshRequest, request: Request) -> JSONResponse:
try:
return _ok(request, refresh_dashboard_snapshot(limit=body.limit))
except Exception as exc:
return _err(request, f"仪表盘刷新失败:{exc}", 500)
@app.post("/api/v1/quotes/batch")
def post_batch_quotes(body: BatchQuoteRequest, request: Request) -> JSONResponse:
try:
return _ok(request, batch_quotes(body.codes))
except Exception as exc:
return _err(request, f"批量报价失败:{exc}", 500)
@app.get("/api/v1/x-brief/latest")
def get_xbrief_latest(request: Request) -> JSONResponse:
data = get_x_brief_latest()
if data is None:
return _ok(request, {"ready": False})
return _ok(request, {"ready": True, **data})
@app.post("/api/v1/x-brief/refresh")
def post_xbrief_refresh(body: XBriefRefreshRequest, request: Request) -> JSONResponse:
try:
data = refresh_x_brief(days=body.days, per_account_limit=body.per_account_limit)
return _ok(request, {"ready": True, **data})
except Exception as exc:
return _err(request, f"X资讯简报刷新失败:{exc}", 500)
@app.post("/api/v1/strategy/lrs/chat")
def post_lrs_chat(body: LrsChatRequest, request: Request) -> JSONResponse:
try:
answer = answer_lrs_chat(body.question, body.last_params)
return _ok(request, {"answer": answer})
except Exception as exc:
return _err(request, f"LLM 调用失败:{exc}", 500)
@app.get("/api/v1/wheel/profiles")
def get_wheel_profiles(request: Request) -> JSONResponse:
return _ok(request, wheel_profiles())
@app.post("/api/v1/wheel/evaluate")
def post_wheel_evaluate(body: WheelEvaluateRequest, request: Request) -> JSONResponse:
try:
data = wheel_evaluate(
style_key=body.style_key,
underlying_ticker=body.underlying_ticker,
signal_ticker=body.signal_ticker,
)
return _ok(request, data)
except Exception as exc:
return _err(request, f"Wheel 评估失败:{exc}", 500)
@app.get("/api/v1/backtest/periods")
def get_backtest_periods(request: Request) -> JSONResponse:
periods = [
{"key": key, "label": _PERIOD_SPECS[key]["label"]}
for key in _PERIOD_ORDER
]
return _ok(request, periods)
@app.post("/api/v1/backtest/jobs")
def post_backtest_job(body: BacktestJobRequest, request: Request) -> JSONResponse:
job = create_backtest_job(body.period_key)
return _ok(
request,
{
"job_id": job.job_id,
"status": job.status,
"period_key": job.period_key,
"created_at": job.created_at,
},
)
@app.get("/api/v1/backtest/jobs/{job_id}")
def get_job(job_id: str, request: Request) -> JSONResponse:
job = get_backtest_job(job_id)
if job is None:
return _err(request, "任务不存在", 404)
return _ok(
request,
{
"job_id": job.job_id,
"status": job.status,
"period_key": job.period_key,
"created_at": job.created_at,
"updated_at": job.updated_at,
"result": job.result,
"error": job.error,
},
)