132 lines
4.2 KiB
Python
132 lines
4.2 KiB
Python
"""三日数据统计:连续三日 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, "前日"),
|
|
}
|