feat: show ops-map summaries as detailed period tables

Replace paragraph text with day-hit stats (days/days_hit/pct) so leverage and move charts list each hour with counts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-05 17:12:23 +08:00
parent e213db8d2d
commit 270e114658
8 changed files with 513 additions and 210 deletions
+26 -2
View File
@@ -6,7 +6,7 @@ import math
from collections import defaultdict
from typing import Any, Iterable, Sequence
from packages.domain.buckets import shanghai_bucket
from packages.domain.buckets import shanghai_bucket, shanghai_day
from packages.domain.leverage import LEVERAGE_FORMULA_VERSION
@@ -87,9 +87,15 @@ def aggregate_leverage(
"""
rows: 需含 ts_ms, leverage, side。
返回按桶排序的聚合列表(含空桶)。
额外字段:
days — 该时段有样本的上海自然日数
days_hit — 当日该时段均值 ≥ min_leverage 的天数
pct_days_hit — days_hit / days
"""
want = (side or "both").upper()
by_bucket: dict[int, list[float]] = defaultdict(list)
# bucket -> day_ymd -> leverages
by_bucket_day: dict[int, dict[str, list[float]]] = defaultdict(lambda: defaultdict(list))
for r in rows:
lev = r.get("leverage")
@@ -106,18 +112,32 @@ def aggregate_leverage(
continue
if want == "BOTH" and s not in ("C", "P"):
continue
b = shanghai_bucket(int(r["ts_ms"]), bucket_minutes)
ts = int(r["ts_ms"])
b = shanghai_bucket(ts, bucket_minutes)
day = shanghai_day(ts)
by_bucket[b].append(lev_f)
by_bucket_day[b][day].append(lev_f)
out: list[dict[str, Any]] = []
thr = float(min_leverage)
for b in all_bucket_starts(bucket_minutes):
stats = summarize_values(by_bucket.get(b, []), min_leverage=min_leverage)
day_map = by_bucket_day.get(b, {})
days = len(day_map)
days_hit = 0
for vals in day_map.values():
if vals and (sum(vals) / len(vals)) >= thr:
days_hit += 1
pct_days = (days_hit / days) if days else None
out.append(
{
"bucket_start_min": b,
"bucket_hour": b // 60 if bucket_minutes >= 60 else None,
"label": bucket_label(b, bucket_minutes),
**stats,
"days": days,
"days_hit": days_hit,
"pct_days_hit": pct_days,
}
)
return out
@@ -156,9 +176,11 @@ def aggregate_move_points(
"""
samples: {ts_ms, move_signed, move_abs}
桶内同时给出 signed / abs 分布。
额外:days — 该时段有结算样本的自然日数。
"""
by_signed: dict[int, list[float]] = defaultdict(list)
by_abs: dict[int, list[float]] = defaultdict(list)
by_bucket_days: dict[int, set[str]] = defaultdict(set)
for s in samples:
ts = s.get("ts_ms")
signed = s.get("move_signed")
@@ -174,6 +196,7 @@ def aggregate_move_points(
b = shanghai_bucket(int(ts), bucket_minutes)
by_signed[b].append(signed_f)
by_abs[b].append(abs_f)
by_bucket_days[b].add(shanghai_day(int(ts)))
out: list[dict[str, Any]] = []
for b in all_bucket_starts(bucket_minutes):
@@ -185,6 +208,7 @@ def aggregate_move_points(
"bucket_hour": b // 60 if bucket_minutes >= 60 else None,
"label": bucket_label(b, bucket_minutes),
"n": signed_stats["n"],
"days": len(by_bucket_days.get(b, set())),
"signed": signed_stats,
"abs": abs_stats,
# 便捷字段(看板默认用 abs 均值)