"""个股分析 API""" from fastapi import APIRouter from app.data.tushare_client import tushare_client from app.data import tencent_client from app.analysis.technical import add_all_indicators from app.analysis.signals import generate_signals router = APIRouter(prefix="/api/stocks", tags=["stocks"]) @router.get("/{ts_code}/quote") async def get_quote(ts_code: str): """获取个股实时行情""" quote = await tencent_client.get_realtime_quote(ts_code) if not quote: return {"error": "获取行情失败"} return quote.model_dump() @router.get("/{ts_code}/kline") async def get_kline(ts_code: str, days: int = 120): """获取个股K线数据(含技术指标)""" df = tushare_client.get_stock_daily(ts_code, days=days) if df.empty: return [] df = df.sort_values("trade_date").reset_index(drop=True) df = add_all_indicators(df) # 替换 NaN 为 None(JSON 兼容) import math records = df.to_dict(orient="records") for rec in records: for k, v in rec.items(): if isinstance(v, float) and (math.isnan(v) or math.isinf(v)): rec[k] = None return records @router.get("/{ts_code}/signals") async def get_signals(ts_code: str): """获取个股技术面买卖信号""" signal = generate_signals(ts_code) return signal.model_dump() @router.get("/{ts_code}/capital_flow") async def get_capital_flow(ts_code: str, days: int = 10): """获取个股资金流向""" df = tushare_client.get_stock_moneyflow(ts_code, days=days) if df.empty: return [] df = df.sort_values("trade_date") records = [] for _, row in df.iterrows(): main_net = ( (row.get("buy_elg_amount", 0) or 0) - (row.get("sell_elg_amount", 0) or 0) + (row.get("buy_lg_amount", 0) or 0) - (row.get("sell_lg_amount", 0) or 0) ) records.append({ "trade_date": row["trade_date"], "main_net_inflow": round(main_net, 2), "net_mf_amount": round(float(row.get("net_mf_amount", 0) or 0), 2), }) return records @router.get("/search") async def search_stock(keyword: str): """搜索股票""" basic = tushare_client.get_stock_basic() if basic.empty: return [] matches = basic[ basic["name"].str.contains(keyword, na=False) | basic["ts_code"].str.contains(keyword, na=False) | basic["symbol"].str.contains(keyword, na=False) ].head(20) return matches[["ts_code", "name", "industry"]].to_dict(orient="records")