173f09300b
Background poll builds a memory snapshot; the page reads snapshot and refreshes on SSE instead of hitting heavy exchange APIs on each load. Co-authored-by: Cursor <cursoragent@cursor.com>
428 lines
14 KiB
Python
428 lines
14 KiB
Python
"""实例数据看板:本户活跃监控 / 持仓只读聚合."""
|
|
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,
|
|
"price_symbol": od.get("symbol") or sym,
|
|
"direction": direction,
|
|
"direction_label": _dir_label(direction),
|
|
"entry": entry,
|
|
"mark_price": None,
|
|
"contracts": _safe_float(od.get("order_amount")),
|
|
"tp_profit": None,
|
|
"float_pnl": None,
|
|
"stop_loss": sl,
|
|
"take_profit": tp,
|
|
"status": od.get("status") or "active",
|
|
}
|
|
|
|
|
|
OPTIONS_SOURCE_LABELS = {
|
|
"option": "纯期权",
|
|
"perp_options": "永期对冲",
|
|
"options_options": "期期对冲",
|
|
}
|
|
|
|
HEDGE_ACTIVE_STATUSES = frozenset({"opening", "active", "partial"})
|
|
|
|
|
|
def _resolve_options_source(conn, inst_id: str) -> tuple[str, str]:
|
|
"""根据进行中对冲计划腿判定来源;默认纯期权."""
|
|
if not inst_id or not _table_exists(conn, "hedge_plans") or not _table_exists(conn, "hedge_plan_legs"):
|
|
return "option", OPTIONS_SOURCE_LABELS["option"]
|
|
try:
|
|
row = conn.execute(
|
|
"""
|
|
SELECT p.plan_type
|
|
FROM hedge_plans p
|
|
JOIN hedge_plan_legs l ON l.plan_id = p.id
|
|
WHERE p.status IN ('opening', 'active', 'partial')
|
|
AND l.status = 'open'
|
|
AND l.inst_id = ?
|
|
ORDER BY p.id DESC
|
|
LIMIT 1
|
|
""",
|
|
(inst_id,),
|
|
).fetchone()
|
|
except Exception:
|
|
return "option", OPTIONS_SOURCE_LABELS["option"]
|
|
if not row:
|
|
return "option", OPTIONS_SOURCE_LABELS["option"]
|
|
pt = str((_row_dict(row).get("plan_type") if isinstance(row, dict) else row[0]) or "").strip()
|
|
if pt in OPTIONS_SOURCE_LABELS:
|
|
return pt, OPTIONS_SOURCE_LABELS[pt]
|
|
return "option", OPTIONS_SOURCE_LABELS["option"]
|
|
|
|
|
|
def _format_options_target(p: dict[str, Any]) -> str:
|
|
hedge = p.get("hedge_plan_target") if isinstance(p.get("hedge_plan_target"), dict) else None
|
|
opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
|
|
if hedge:
|
|
ot = str(hedge.get("opt_type") or opt_type).upper()
|
|
side = "Put ≤" if ot == "P" else "Call ≥"
|
|
tgt = _safe_float(hedge.get("target_index"))
|
|
pid = hedge.get("plan_id")
|
|
if tgt is not None:
|
|
return f"对冲#{pid} {side} {tgt:g}" if pid is not None else f"{side} {tgt:g}"
|
|
tgt = _safe_float(p.get("target_index"))
|
|
if tgt is not None and tgt > 0:
|
|
side = "Put ≤" if opt_type == "P" else "Call ≥"
|
|
return f"{side} {tgt:g}"
|
|
return "—"
|
|
|
|
|
|
def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]:
|
|
inst = str(p.get("inst_id") or p.get("instId") or "-").strip() 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"))
|
|
exp_ms = p.get("exp_time_ms")
|
|
if exp_ms is None:
|
|
exp_ms = p.get("exp_time")
|
|
try:
|
|
exp_ms = int(float(exp_ms)) if exp_ms not in (None, "") else None
|
|
except (TypeError, ValueError):
|
|
exp_ms = None
|
|
source_key, source_label = (
|
|
_resolve_options_source(conn, inst) if conn is not None else ("option", OPTIONS_SOURCE_LABELS["option"])
|
|
)
|
|
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,
|
|
"opt_type_label": label,
|
|
"source": source_key,
|
|
"source_label": source_label,
|
|
"pos": pos,
|
|
"exp_time_ms": exp_ms,
|
|
"target_monitor": _format_options_target(p),
|
|
"pnl": round(pnl, 4) if pnl is not None else None,
|
|
}
|
|
|
|
|
|
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 = str(plan.get("status") or "")
|
|
summary = plan.get("contracts_summary") or ""
|
|
plan_type_label = OPTIONS_SOURCE_LABELS.get(plan_type, plan_type)
|
|
active = status in HEDGE_ACTIVE_STATUSES
|
|
status_label = "进行中" if active else (status or "—")
|
|
return {
|
|
"id": pid,
|
|
"kind": "hedge_plan",
|
|
"tab": "hedge_plan",
|
|
"title": f"对冲 #{pid} {underlying}",
|
|
"subtitle": " · ".join(x for x in (plan_type_label, status_label, summary) if x),
|
|
"underlying": underlying,
|
|
"plan_type": plan_type,
|
|
"plan_type_label": plan_type_label,
|
|
"status": status,
|
|
"status_label": status_label,
|
|
"status_active": active,
|
|
"contracts_summary": summary,
|
|
}
|
|
|
|
|
|
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 _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,
|
|
*,
|
|
conn=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, conn=conn))
|
|
return out
|
|
|
|
|
|
def enrich_order_items_with_marks(
|
|
items: list[dict[str, Any]],
|
|
*,
|
|
get_price: Optional[Callable[[str], Any]] = None,
|
|
) -> list[dict[str, Any]]:
|
|
"""后台聚合时补标记价(不打全量 fetch_positions;浮盈仍由实盘页口径负责)."""
|
|
if not items or not callable(get_price):
|
|
return items
|
|
out: list[dict[str, Any]] = []
|
|
for it in items:
|
|
row = dict(it)
|
|
sym = str(row.get("price_symbol") or row.get("symbol") or "").strip()
|
|
if not sym:
|
|
out.append(row)
|
|
continue
|
|
try:
|
|
px = get_price(sym)
|
|
except Exception:
|
|
px = None
|
|
mark = _safe_float(px)
|
|
if mark is None and ":" in sym:
|
|
try:
|
|
px = get_price(sym.split(":", 1)[0])
|
|
except Exception:
|
|
px = None
|
|
mark = _safe_float(px)
|
|
if mark is not None:
|
|
row["mark_price"] = mark
|
|
out.append(row)
|
|
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, conn=conn)
|
|
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",
|
|
},
|
|
}
|