184 lines
5.4 KiB
Python
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,
|
|
},
|
|
)
|
|
|