"""实例数据看板:本户活跃监控 / 持仓只读聚合.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any, Callable, Optional def _row_dict(row: Any) -> dict[str, Any]: if row is None: return {} if isinstance(row, dict): return dict(row) try: return dict(row) except Exception: return {} def _safe_float(v: Any) -> Optional[float]: try: if v is None or v == "": return None return float(v) except (TypeError, ValueError): return None def _dir_label(direction: Any) -> str: d = str(direction or "").strip().lower() if d == "short": return "做空" if d == "long": return "做多" return str(direction or "-") def _format_order_item(od: dict[str, Any]) -> dict[str, Any]: try: from lib.strategy.strategy_trade_labels import apply_order_monitor_source_labels od = apply_order_monitor_source_labels(od) except Exception: pass try: from lib.trade.entry_model_lib import enrich_entry_model_display enrich_entry_model_display(od) except Exception: pass sym = od.get("exchange_symbol") or od.get("symbol") or "-" direction = str(od.get("direction") or "long").lower() mt = od.get("monitor_type_display") or od.get("monitor_type") or "" kst = od.get("key_signal_type") or "" title = f"{sym} {_dir_label(direction)}" bits = [x for x in (mt, kst) if x] subtitle = " · ".join(bits) if bits else "" entry = _safe_float(od.get("trigger_price")) sl = _safe_float(od.get("stop_loss")) tp = _safe_float(od.get("take_profit")) return { "id": od.get("id"), "kind": "order", "tab": "trade", "title": title, "subtitle": subtitle, "symbol": sym, "direction": direction, "direction_label": _dir_label(direction), "entry": entry, "stop_loss": sl, "take_profit": tp, "status": od.get("status") or "active", } def _format_key_item(kd: dict[str, Any]) -> dict[str, Any]: sym = kd.get("exchange_symbol") or kd.get("symbol") or "-" direction = str(kd.get("direction") or "long").lower() signal = kd.get("signal_type") or kd.get("key_signal_type") or kd.get("monitor_type") or "" upper = _safe_float(kd.get("upper")) lower = _safe_float(kd.get("lower")) subtitle_parts = [] if signal: subtitle_parts.append(str(signal)) if upper is not None or lower is not None: subtitle_parts.append( f"上{upper if upper is not None else '-'} / 下{lower if lower is not None else '-'}" ) return { "id": kd.get("id"), "kind": "key", "tab": "key_monitor", "title": f"{sym} {_dir_label(direction)}", "subtitle": " · ".join(subtitle_parts), "symbol": sym, "direction": direction, "direction_label": _dir_label(direction), "upper": upper, "lower": lower, "status": kd.get("status") or "active", } def _format_trend_item(td: dict[str, Any]) -> dict[str, Any]: sym = td.get("exchange_symbol") or td.get("symbol") or "-" direction = str(td.get("direction") or "long").lower() status = td.get("status") or "active" entry = _safe_float(td.get("entry_price") or td.get("trigger_price")) return { "id": td.get("id"), "kind": "trend", "tab": "strategy", "title": f"趋势回调 {sym} {_dir_label(direction)}", "subtitle": f"状态 {status}", "symbol": sym, "direction": direction, "direction_label": _dir_label(direction), "entry": entry, "status": status, } def _format_roll_item(rd: dict[str, Any]) -> dict[str, Any]: sym = rd.get("exchange_symbol") or rd.get("symbol") or "-" direction = str(rd.get("direction") or "long").lower() status = rd.get("status") or "active" return { "id": rd.get("id"), "kind": "roll", "tab": "strategy", "title": f"顺势加仓 {sym} {_dir_label(direction)}", "subtitle": f"状态 {status}", "symbol": sym, "direction": direction, "direction_label": _dir_label(direction), "status": status, } def _format_options_item(p: dict[str, Any]) -> dict[str, Any]: inst = p.get("inst_id") or p.get("instId") or "-" opt_type = str(p.get("opt_type") or p.get("optType") or "").upper() label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else (opt_type or "OPT") upl = _safe_float(p.get("upl")) net = None try: from lib.options.options_positions_lib import net_pnl_from_display_row net = net_pnl_from_display_row(p) except Exception: net = None pnl = net if net is not None else upl pos = _safe_float(p.get("pos")) return { "id": inst, "kind": "options", "tab": "options", "title": f"{inst} {label}", "subtitle": f"张数 {pos if pos is not None else '-'}", "inst_id": inst, "opt_type": opt_type, "pnl": round(pnl, 4) if pnl is not None else None, "pos": pos, } def _format_hedge_item(plan: dict[str, Any]) -> dict[str, Any]: pid = plan.get("id") underlying = plan.get("underlying") or "-" plan_type = plan.get("plan_type") or "" status = plan.get("status") or "" summary = plan.get("contracts_summary") or "" return { "id": pid, "kind": "hedge_plan", "tab": "hedge_plan", "title": f"对冲 #{pid} {underlying}", "subtitle": " · ".join(x for x in (plan_type, status, summary) if x), "underlying": underlying, "plan_type": plan_type, "status": status, } def _table_exists(conn, name: str) -> bool: try: row = conn.execute( "SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1", (name,), ).fetchone() return bool(row) except Exception: return False def collect_orders(conn) -> list[dict[str, Any]]: if not _table_exists(conn, "order_monitors"): return [] rows = conn.execute( "SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC" ).fetchall() return [_format_order_item(_row_dict(r)) for r in rows] def collect_keys(conn) -> list[dict[str, Any]]: if not _table_exists(conn, "key_monitors"): return [] rows = conn.execute("SELECT * FROM key_monitors ORDER BY id DESC").fetchall() return [_format_key_item(_row_dict(r)) for r in rows] def collect_trends(conn) -> list[dict[str, Any]]: if not _table_exists(conn, "trend_pullback_plans"): return [] try: rows = conn.execute( "SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC" ).fetchall() except Exception: return [] return [_format_trend_item(_row_dict(r)) for r in rows] def collect_rolls(conn) -> list[dict[str, Any]]: if not _table_exists(conn, "roll_groups") or not _table_exists(conn, "order_monitors"): return [] try: rows = conn.execute( """SELECT g.* FROM roll_groups g INNER JOIN order_monitors m ON m.id = g.order_monitor_id AND m.status='active' WHERE g.status='active' ORDER BY g.id DESC""" ).fetchall() except Exception: return [] return [_format_roll_item(_row_dict(r)) for r in rows] def collect_hedge_plans(conn) -> list[dict[str, Any]]: if not _table_exists(conn, "hedge_plans"): return [] try: from lib.hedge_plan.hedge_plan_db import attach_legs_to_plans, list_plans rows: list[dict[str, Any]] = [] for status in ("opening", "active", "partial"): rows.extend(list_plans(conn, status=status, limit=80)) rows.sort(key=lambda row: int(row.get("id") or 0), reverse=True) plans = attach_legs_to_plans(conn, rows) return [_format_hedge_item(p) for p in plans] except Exception: return [] def collect_options_items( fetch_options_positions: Optional[Callable[[], list[dict[str, Any]]]] = None, ) -> list[dict[str, Any]]: if not callable(fetch_options_positions): return [] try: raw = fetch_options_positions() or [] except Exception: return [] out: list[dict[str, Any]] = [] for p in raw: if not isinstance(p, dict): continue out.append(_format_options_item(p)) return out def build_instance_dashboard_payload( conn, *, fetch_options_positions: Optional[Callable[[], list[dict[str, Any]]]] = None, hedge_enabled: bool = False, ) -> dict[str, Any]: orders = collect_orders(conn) keys = collect_keys(conn) trends = collect_trends(conn) rolls = collect_rolls(conn) strategy_items = trends + rolls options_items = collect_options_items(fetch_options_positions) hedge_items = collect_hedge_plans(conn) if hedge_enabled else [] now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S") return { "ok": True, "updated_at": now, "orders": {"title": "实盘下单", "count": len(orders), "items": orders, "tab": "trade"}, "keys": {"title": "关键位监控", "count": len(keys), "items": keys, "tab": "key_monitor"}, "strategy": { "title": "策略交易", "count": len(strategy_items), "items": strategy_items, "trends": trends, "rolls": rolls, "tab": "strategy", }, "options": { "title": "期权持仓", "count": len(options_items), "items": options_items, "visible": len(options_items) > 0, "tab": "options", }, "hedge_plan": { "title": "对冲计划", "count": len(hedge_items), "items": hedge_items, "visible": len(hedge_items) > 0, "tab": "hedge_plan", }, }