"""监控区看板:三所当日统计聚合。""" from __future__ import annotations from typing import Any def _coerce_float(value: Any) -> float | None: if value is None or value == "": return None try: return float(value) except (TypeError, ValueError): return None def position_unrealized_pnl(pos: dict[str, Any]) -> float: for key in ("unrealized_pnl", "unrealizedPnl", "upnl"): v = _coerce_float(pos.get(key)) if v is not None: return v return 0.0 def _open_positions(agent: dict[str, Any] | None) -> list[dict[str, Any]]: if not isinstance(agent, dict): return [] positions = agent.get("positions") if not isinstance(positions, list): return [] out: list[dict[str, Any]] = [] for p in positions: if not isinstance(p, dict): continue try: c = abs(float(p.get("contracts") or 0)) except (TypeError, ValueError): c = 0.0 if c > 1e-12: out.append(p) return out def aggregate_monitor_board_totals( rows: list[dict[str, Any]], *, trading_day: str, reset_hour: int = 8, ) -> dict[str, Any]: """汇总监控 board 各行 → 左上统计卡数据。""" open_count = 0 closed_count = 0 win_count = 0 loss_count = 0 win_pnl_u = 0.0 loss_pnl_u = 0.0 open_position_count = 0 float_pnl_u = 0.0 for row in rows or []: if not isinstance(row, dict): continue day_stats = row.get("day_stats") if isinstance(row.get("day_stats"), dict) else {} if day_stats.get("ok"): open_count += int(day_stats.get("opens_today") or 0) st = day_stats.get("trade_stats") if isinstance(day_stats.get("trade_stats"), dict) else {} closed_count += int(st.get("closed_count") or 0) win_count += int(st.get("win_count") or 0) loss_count += int(st.get("loss_count") or 0) win_pnl_u += float(st.get("win_pnl_u") or 0) loss_pnl_u += float(st.get("loss_pnl_u") or 0) ag = row.get("agent") if isinstance(row.get("agent"), dict) else {} open_pos = _open_positions(ag) open_position_count += len(open_pos) agent_upnl = _coerce_float(ag.get("total_unrealized_pnl")) if agent_upnl is not None: float_pnl_u += agent_upnl else: float_pnl_u += sum(position_unrealized_pnl(p) for p in open_pos) return { "trading_day": trading_day, "reset_hour": int(reset_hour), "open_count": open_count, "closed_count": closed_count, "win_count": win_count, "loss_count": loss_count, "win_pnl_u": round(win_pnl_u, 4), "loss_pnl_u": round(loss_pnl_u, 4), "realized_pnl_u": round(win_pnl_u + loss_pnl_u, 4), "open_position_count": open_position_count, "float_pnl_u": round(float_pnl_u, 4), }