P0 修复: - runtimeStore: 添加缺失的 lastDayHistory 字段 - Gateway/RuntimeService: 状态同步改为内存优先,消除 glob 竞态 - App.jsx: 从 3075 行重构到 ~500 行,提取 8 个独立文件 P1 修复: - CORS: 4 个服务改为从环境变量读取允许 origins - MarketStore: 改为模块级单例模式 - Domain 层: 删除 trading thin wrapper,保留 news 真实逻辑 - 测试: 补齐 77 个 gateway/runtime 测试 新增文件: - backend/tests/test_gateway.py (43 tests) - frontend/src/hooks/useWebSocketHandler.js - frontend/src/hooks/useStockRequestCallbacks.js - frontend/src/hooks/useAgentCallbacks.js - frontend/src/hooks/useRuntimeCallbacks.js - frontend/src/hooks/useWatchlistCallbacks.js - frontend/src/components/TickerBar.jsx - frontend/src/components/HeaderRight.jsx - frontend/src/components/ChartTabs.jsx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
151 lines
4.5 KiB
Python
151 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Trading data FastAPI surface."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI, Query
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from backend.config.env_config import get_cors_origins
|
|
from backend.services.market import MarketService
|
|
from backend.tools.data_tools import (
|
|
get_company_news,
|
|
get_financial_metrics,
|
|
get_insider_trades,
|
|
get_market_cap,
|
|
get_prices,
|
|
search_line_items,
|
|
)
|
|
from shared.schema import (
|
|
CompanyNewsResponse,
|
|
FinancialMetricsResponse,
|
|
InsiderTradeResponse,
|
|
LineItemResponse,
|
|
PriceResponse,
|
|
)
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Create the trading data service app."""
|
|
app = FastAPI(
|
|
title="EvoTraders Trading Service",
|
|
description="Read-only trading data service surface extracted from the monolith",
|
|
version="0.1.0",
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=get_cors_origins(),
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
@app.get("/health")
|
|
async def health_check() -> dict[str, str]:
|
|
"""Health check endpoint."""
|
|
return {"status": "healthy", "service": "trading-service"}
|
|
|
|
@app.get("/api/prices", response_model=PriceResponse)
|
|
async def api_get_prices(
|
|
ticker: str = Query(..., min_length=1),
|
|
start_date: str = Query(...),
|
|
end_date: str = Query(...),
|
|
) -> PriceResponse:
|
|
prices = get_prices(ticker=ticker, start_date=start_date, end_date=end_date)
|
|
return PriceResponse(ticker=ticker, prices=prices)
|
|
|
|
@app.get("/api/financials", response_model=FinancialMetricsResponse)
|
|
async def api_get_financials(
|
|
ticker: str = Query(..., min_length=1),
|
|
end_date: str = Query(...),
|
|
period: str = Query("ttm"),
|
|
limit: int = Query(10, ge=1, le=100),
|
|
) -> FinancialMetricsResponse:
|
|
metrics = get_financial_metrics(
|
|
ticker=ticker,
|
|
end_date=end_date,
|
|
period=period,
|
|
limit=limit,
|
|
)
|
|
return FinancialMetricsResponse(financial_metrics=metrics)
|
|
|
|
@app.get("/api/news", response_model=CompanyNewsResponse)
|
|
async def api_get_news(
|
|
ticker: str = Query(..., min_length=1),
|
|
end_date: str = Query(...),
|
|
start_date: str | None = Query(None),
|
|
limit: int = Query(1000, ge=1, le=5000),
|
|
) -> CompanyNewsResponse:
|
|
news = get_company_news(
|
|
ticker=ticker,
|
|
end_date=end_date,
|
|
start_date=start_date,
|
|
limit=limit,
|
|
)
|
|
return CompanyNewsResponse(news=news)
|
|
|
|
@app.get("/api/insider-trades", response_model=InsiderTradeResponse)
|
|
async def api_get_insider_trades(
|
|
ticker: str = Query(..., min_length=1),
|
|
end_date: str = Query(...),
|
|
start_date: str | None = Query(None),
|
|
limit: int = Query(1000, ge=1, le=5000),
|
|
) -> InsiderTradeResponse:
|
|
trades = get_insider_trades(
|
|
ticker=ticker,
|
|
end_date=end_date,
|
|
start_date=start_date,
|
|
limit=limit,
|
|
)
|
|
return InsiderTradeResponse(insider_trades=trades)
|
|
|
|
@app.get("/api/market/status")
|
|
async def api_get_market_status() -> dict[str, Any]:
|
|
"""Return current market status using the existing market service logic."""
|
|
service = MarketService(tickers=[])
|
|
return service.get_market_status()
|
|
|
|
@app.get("/api/market-cap")
|
|
async def api_get_market_cap(
|
|
ticker: str = Query(..., min_length=1),
|
|
end_date: str = Query(...),
|
|
) -> dict[str, Any]:
|
|
"""Return market cap for one ticker/date."""
|
|
market_cap = get_market_cap(ticker=ticker, end_date=end_date)
|
|
return {
|
|
"ticker": ticker,
|
|
"end_date": end_date,
|
|
"market_cap": market_cap,
|
|
}
|
|
|
|
@app.get("/api/line-items", response_model=LineItemResponse)
|
|
async def api_get_line_items(
|
|
ticker: str = Query(..., min_length=1),
|
|
line_items: list[str] = Query(...),
|
|
end_date: str = Query(...),
|
|
period: str = Query("ttm"),
|
|
limit: int = Query(10, ge=1, le=100),
|
|
) -> LineItemResponse:
|
|
items = search_line_items(
|
|
ticker=ticker,
|
|
line_items=line_items,
|
|
end_date=end_date,
|
|
period=period,
|
|
limit=limit,
|
|
)
|
|
return LineItemResponse(search_results=items)
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(app, host="0.0.0.0", port=8001)
|