TradingAgents/web-ui/backend/main.py

238 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
A股交易分析系统 - FastAPI 后端服务
基于现代 Web 架构的全新实现
"""
import os
import sys
from datetime import datetime
from typing import Any, Dict, List, Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# 添加 TradingShared 路径
SHARED_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'TradingShared')
if SHARED_PATH not in sys.path:
sys.path.insert(0, SHARED_PATH)
if os.path.join(SHARED_PATH, 'api') not in sys.path:
sys.path.insert(0, os.path.join(SHARED_PATH, 'api'))
# 创建 FastAPI 应用
app = FastAPI(
title="A股交易分析系统 API",
description="提供股票分析、智能推荐、竞价排行等功能",
version="2.0.0"
)
# 配置 CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "http://localhost:5173", "*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ==================== 数据模型 ====================
class StockInfo(BaseModel):
code: str
name: str
price: Optional[float] = None
change_percent: Optional[float] = None
score: Optional[int] = None
recommendation: Optional[str] = None
class StockAnalysisRequest(BaseModel):
stock_code: str
llm_model: str = "none"
period: str = "short" # short, medium, long
class StockAnalysisResponse(BaseModel):
stock_code: str
stock_name: str
technical_score: int
fundamental_score: int
combined_score: int
recommendation: str
analysis_detail: str
timestamp: str
class MarketOverview(BaseModel):
total_stocks: int
rising_stocks: int
falling_stocks: int
ai_recommendations: int
last_update: str
class SystemStatus(BaseModel):
data_source: str
kline_data: str
choice_terminal: str
ai_model: str
# ==================== 模拟数据 ====================
MOCK_STOCKS = [
{"code": "600519", "name": "贵州茅台", "price": 1856.00, "change_percent": 5.23, "score": 92},
{"code": "000858", "name": "五粮液", "price": 168.50, "change_percent": 4.87, "score": 88},
{"code": "601318", "name": "中国平安", "price": 48.20, "change_percent": 3.56, "score": 85},
{"code": "600036", "name": "招商银行", "price": 35.80, "change_percent": -1.24, "score": 72},
{"code": "600000", "name": "浦发银行", "price": 8.65, "change_percent": 2.18, "score": 78},
{"code": "300750", "name": "宁德时代", "price": 215.00, "change_percent": 6.32, "score": 89},
{"code": "002594", "name": "比亚迪", "price": 268.00, "change_percent": 4.15, "score": 87},
{"code": "600276", "name": "恒瑞医药", "price": 42.30, "change_percent": -0.85, "score": 75},
]
# ==================== API 路由 ====================
@app.get("/")
async def root():
"""API 根路径"""
return {
"message": "A股交易分析系统 API",
"version": "2.0.0",
"docs": "/docs"
}
@app.get("/api/market/overview", response_model=MarketOverview)
async def get_market_overview():
"""获取市场概览数据"""
rising = len([s for s in MOCK_STOCKS if s["change_percent"] > 0])
falling = len([s for s in MOCK_STOCKS if s["change_percent"] < 0])
high_score = len([s for s in MOCK_STOCKS if s["score"] >= 80])
return MarketOverview(
total_stocks=4396,
rising_stocks=2847,
falling_stocks=1243,
ai_recommendations=86,
last_update=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
@app.get("/api/system/status", response_model=SystemStatus)
async def get_system_status():
"""获取系统状态"""
return SystemStatus(
data_source="正常",
kline_data="已更新",
choice_terminal="待连接",
ai_model="DeepSeek"
)
@app.get("/api/stocks/ranking", response_model=List[StockInfo])
async def get_stock_ranking(limit: int = 10):
"""获取竞价排行榜"""
sorted_stocks = sorted(MOCK_STOCKS, key=lambda x: x["change_percent"], reverse=True)
return [
StockInfo(
code=s["code"],
name=s["name"],
price=s["price"],
change_percent=s["change_percent"],
score=s["score"]
)
for s in sorted_stocks[:limit]
]
@app.get("/api/stocks/recommendations", response_model=List[StockInfo])
async def get_recommendations(limit: int = 5):
"""获取 AI 智能推荐"""
sorted_stocks = sorted(MOCK_STOCKS, key=lambda x: x["score"], reverse=True)
recommendations = ["短期强势", "技术突破", "成长潜力", "价值低估", "趋势向好"]
return [
StockInfo(
code=s["code"],
name=s["name"],
price=s["price"],
change_percent=s["change_percent"],
score=s["score"],
recommendation=recommendations[i] if i < len(recommendations) else "潜力股"
)
for i, s in enumerate(sorted_stocks[:limit])
]
@app.get("/api/stocks/{stock_code}", response_model=StockInfo)
async def get_stock_info(stock_code: str):
"""获取单只股票信息"""
stock = next((s for s in MOCK_STOCKS if s["code"] == stock_code), None)
if not stock:
raise HTTPException(status_code=404, detail=f"股票 {stock_code} 未找到")
return StockInfo(
code=stock["code"],
name=stock["name"],
price=stock["price"],
change_percent=stock["change_percent"],
score=stock["score"]
)
@app.post("/api/stocks/analyze", response_model=StockAnalysisResponse)
async def analyze_stock(request: StockAnalysisRequest):
"""分析单只股票"""
stock = next((s for s in MOCK_STOCKS if s["code"] == request.stock_code), None)
if not stock:
# 如果不在模拟数据中,返回一个默认分析
stock = {"code": request.stock_code, "name": f"股票{request.stock_code}", "score": 75}
# 模拟分析结果
technical_score = min(100, stock.get("score", 75) + 5)
fundamental_score = max(0, stock.get("score", 75) - 5)
combined_score = stock.get("score", 75)
period_map = {
"short": "短期1-5天",
"medium": "中期1-4周",
"long": "长期1-6个月"
}
return StockAnalysisResponse(
stock_code=request.stock_code,
stock_name=stock["name"],
technical_score=technical_score,
fundamental_score=fundamental_score,
combined_score=combined_score,
recommendation="买入" if combined_score >= 80 else "持有" if combined_score >= 60 else "观望",
analysis_detail=f"基于{period_map.get(request.period, '短期')}分析,该股票技术面评分{technical_score}分,"
f"基本面评分{fundamental_score}分,综合评分{combined_score}分。"
f"当前市场表现{'强势' if combined_score >= 80 else '稳健' if combined_score >= 60 else '偏弱'}",
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
@app.get("/api/llm/models")
async def get_llm_models():
"""获取可用的 LLM 模型列表"""
return {
"models": [
{"id": "none", "name": "无 AI 分析", "status": "available"},
{"id": "deepseek", "name": "DeepSeek", "status": "available"},
{"id": "minimax", "name": "MiniMax", "status": "available"},
{"id": "openrouter", "name": "OpenRouter", "status": "available"},
{"id": "gemini", "name": "Gemini", "status": "available"},
],
"default": "deepseek"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)