"""三日数据统计:连续三日 Top30 且 |涨跌|>=5%。""" from typing import Any from .config import settings from .db import get_latest_snapshot def _items_by_symbol(items: list[dict]) -> dict[str, dict]: return {x["symbol"]: x for x in items if x.get("symbol")} def compute_three_day_stats() -> dict[str, Any]: today_snap = get_latest_snapshot("today") yesterday_snap = get_latest_snapshot("yesterday") daybefore_snap = get_latest_snapshot("daybefore") threshold = settings.change_threshold top_n = settings.top_n missing = [] if not today_snap: missing.append("今日") if not yesterday_snap: missing.append("昨日") if not daybefore_snap: missing.append("前日") if missing: return { "ok": False, "missing_periods": missing, "message": f"缺少快照:{', '.join(missing)},请等待刷新或手动触发", "criteria": _criteria_text(threshold, top_n), "count": 0, "items": [], "periods": _period_meta(today_snap, yesterday_snap, daybefore_snap), } today_map = _items_by_symbol(today_snap["items"]) yesterday_map = _items_by_symbol(yesterday_snap["items"]) daybefore_map = _items_by_symbol(daybefore_snap["items"]) symbols = set(today_map) & set(yesterday_map) & set(daybefore_map) qualified: list[dict] = [] for sym in sorted(symbols): t, y, b = today_map[sym], yesterday_map[sym], daybefore_map[sym] if not ( abs(t.get("price_change_pct", 0)) >= threshold and abs(y.get("price_change_pct", 0)) >= threshold and abs(b.get("price_change_pct", 0)) >= threshold ): continue qualified.append( { "symbol": sym, "today": _pick_fields(t), "yesterday": _pick_fields(y), "daybefore": _pick_fields(b), "avg_change_pct": round( ( t.get("price_change_pct", 0) + y.get("price_change_pct", 0) + b.get("price_change_pct", 0) ) / 3, 4, ), "total_quote_volume": ( (t.get("quote_volume") or 0) + (y.get("quote_volume") or 0) + (b.get("quote_volume") or 0) ), } ) qualified.sort(key=lambda x: x["total_quote_volume"], reverse=True) return { "ok": True, "criteria": _criteria_text(threshold, top_n), "count": len(qualified), "items": qualified, "periods": _period_meta(today_snap, yesterday_snap, daybefore_snap), "summary": { "today_top30": len(today_map), "yesterday_top30": len(yesterday_map), "daybefore_top30": len(daybefore_map), "intersection": len(symbols), }, } def _criteria_text(threshold: float, top_n: int) -> str: return ( f"连续三日成交额 Top{top_n} 且每日 |涨跌幅| ≥ {threshold:g}%" f"(今日/昨日/前日三个完整切日周期)" ) def _pick_fields(row: dict) -> dict: return { "rank": row.get("rank"), "quote_volume": row.get("quote_volume"), "quote_volume_fmt": row.get("quote_volume_fmt"), "price_change_pct": row.get("price_change_pct"), "price_change_pct_fmt": row.get("price_change_pct_fmt"), "funding_rate_fmt": row.get("funding_rate_fmt"), "is_high_volume": row.get("is_high_volume"), "is_high_change": row.get("is_high_change"), } def _period_meta(today, yesterday, daybefore) -> dict: def one(snap, label): if not snap: return {"label": label, "ready": False} return { "label": label, "ready": True, "period_start": snap["period_start"], "period_end": snap["period_end"], "updated_at": snap.get("created_at"), } return { "today": one(today, "今日"), "yesterday": one(yesterday, "昨日"), "daybefore": one(daybefore, "前日"), }