astock-agent/backend/app/api/stocks.py
2026-04-11 08:24:50 +08:00

92 lines
3.1 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.

"""个股分析 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 为 NoneJSON 兼容)
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),
"elg_net": round(
(row.get("buy_elg_amount", 0) or 0) - (row.get("sell_elg_amount", 0) or 0), 2
),
"lg_net": round(
(row.get("buy_lg_amount", 0) or 0) - (row.get("sell_lg_amount", 0) or 0), 2
),
"md_net": round(
(row.get("buy_md_amount", 0) or 0) - (row.get("sell_md_amount", 0) or 0), 2
),
"sm_net": round(
(row.get("buy_sm_amount", 0) or 0) - (row.get("sell_sm_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")