"""涨跌停/异动监控 API""" from fastapi import APIRouter from app.data.tushare_client import tushare_client from app.config import is_market_session router = APIRouter(prefix="/api/monitor", tags=["monitor"]) @router.get("/limits") async def get_limits(): """获取涨跌停数据""" trade_date = tushare_client.get_latest_trade_date() limit_df = tushare_client.get_limit_list(trade_date) if limit_df.empty: return {"trade_date": trade_date, "limit_up": [], "limit_down": []} # 拆分涨停和跌停 up_df = limit_df[limit_df["limit"] == "U"].sort_values("pct_chg", ascending=False) down_df = limit_df[limit_df["limit"] == "D"].sort_values("pct_chg", ascending=True) def _parse(df): results = [] for _, r in df.head(50).iterrows(): results.append({ "ts_code": r.get("ts_code", ""), "name": r.get("name", ""), "close": float(r.get("close", 0)), "pct_chg": float(r.get("pct_chg", 0)), "limit_times": int(r.get("limit_times", 0)), "first_time": str(r.get("first_time", "")), "last_time": str(r.get("last_time", "")), "open_times": int(r.get("open_times", 0)), "fd_amount": float(r.get("fd_amount", 0)) if r.get("fd_amount") else 0, "up_stat": str(r.get("up_stat", "")), }) return results return { "trade_date": trade_date, "is_realtime": is_market_session(), "limit_up": _parse(up_df), "limit_down": _parse(down_df), } @router.get("/unusual") async def get_unusual(): """获取异动股(量比>3、振幅>8%、快速拉升)""" trade_date = tushare_client.get_latest_trade_date() # 获取全市场日线 daily = tushare_client.get_daily_all(trade_date) if daily.empty: return {"trade_date": trade_date, "stocks": []} basic = tushare_client.get_daily_basic(trade_date) if not basic.empty: daily = daily.merge(basic[["ts_code", "turnover_rate", "volume_ratio"]], on="ts_code", how="left") stock_basic = tushare_client.get_stock_basic() name_map = {} if not stock_basic.empty: for _, r in stock_basic.iterrows(): name_map[r["ts_code"]] = r["name"] # 筛选异动条件 unusual = [] for _, r in daily.iterrows(): ts = r.get("ts_code", "") if not ts.endswith((".SH", ".SZ")): continue pct = r.get("pct_chg", 0) vol_ratio = r.get("volume_ratio", 0) high = r.get("high", 0) low = r.get("low", 0) pre_close = r.get("pre_close", 0) amplitude = (high - low) / pre_close * 100 if pre_close > 0 else 0 tags = [] if vol_ratio and vol_ratio > 3: tags.append("巨量") if amplitude > 8: tags.append("高振幅") if pct > 7: tags.append("急涨") elif pct < -7: tags.append("急跌") if tags: unusual.append({ "ts_code": ts, "name": name_map.get(ts, ts), "close": float(r.get("close", 0)), "pct_chg": float(pct), "amplitude": round(float(amplitude), 2), "volume_ratio": round(float(vol_ratio), 2) if vol_ratio and vol_ratio == vol_ratio else 0, "turnover_rate": round(float(r.get("turnover_rate", 0)), 2), "tags": tags, }) unusual.sort(key=lambda x: abs(x["pct_chg"]), reverse=True) return {"trade_date": trade_date, "stocks": unusual[:50]}