107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
"""涨跌停/异动监控 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]}
|