Files
crypto_monitor/lib/instance/instance_live_pnl_lib.py
T
dekun b733e551a0 Normalize fullwidth punctuation to ASCII across codebase.
Add scripts/normalize_ambiguous_unicode.py; fix corrupted patch_instance_theme_templates.py. Preserves curly quotes in string literals; removes Git homoglyph warnings on .env.example.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 23:42:26 +08:00

128 lines
3.9 KiB
Python

"""实例页:持仓未实现盈亏(实时盈亏)汇总."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from lib.hub.hub_position_metrics import parse_position_unrealized_pnl
def position_row_contracts(pos: dict[str, Any]) -> float:
"""持仓张数:与三所 app 内 _position_row_effective_contracts 规则一致."""
if not isinstance(pos, dict):
return 0.0
info = pos.get("info") or {}
if not isinstance(info, dict):
info = {}
for val in (
pos.get("contracts"),
info.get("positionAmt"),
info.get("size"),
info.get("pos"),
info.get("availPos"),
):
if val is None or val == "":
continue
try:
x = abs(float(val))
if x > 0:
return x
except (TypeError, ValueError):
continue
return 0.0
def sum_unrealized_pnl_from_positions(positions: list[dict[str, Any]] | None) -> float | None:
total = 0.0
found = False
for p in positions or []:
if not isinstance(p, dict):
continue
if position_row_contracts(p) <= 1e-12:
continue
upnl = parse_position_unrealized_pnl(p)
if upnl is None:
continue
found = True
total += float(upnl)
return round(total, 2) if found else None
def _row_field(row: Any, key: str, default: str = "") -> str:
if row is None:
return default
try:
if hasattr(row, "keys") and key in row.keys():
val = row[key]
elif isinstance(row, dict):
val = row.get(key)
else:
val = None
except Exception:
val = None
return str(val or default).strip()
def sum_unrealized_pnl_from_metrics(
rows: list[dict[str, Any]] | list[Any],
get_metrics_fn: Callable[[str, str], dict[str, Any] | None],
) -> float | None:
"""按活跃监控单逐笔拉交易所 metrics 汇总(与持仓卡浮盈亏一致)."""
total = 0.0
found = False
for row in rows or []:
ex_sym = _row_field(row, "exchange_symbol")
sym = _row_field(row, "symbol")
direction = _row_field(row, "direction", "long").lower() or "long"
target = ex_sym or sym
if not target:
continue
metrics = get_metrics_fn(target, direction)
if not isinstance(metrics, dict):
continue
upnl = metrics.get("unrealized_pnl")
if upnl is None:
continue
try:
total += float(upnl)
found = True
except (TypeError, ValueError):
continue
return round(total, 2) if found else None
def fetch_unrealized_pnl(fetch_positions_fn: Callable[[], list[dict[str, Any]] | None]) -> float | None:
try:
return sum_unrealized_pnl_from_positions(fetch_positions_fn() or [])
except Exception:
return None
def resolve_instance_unrealized_pnl(
fetch_positions_fn: Callable[[], list[dict[str, Any]] | None],
active_rows: list[Any] | None,
get_metrics_fn: Callable[[str, str], dict[str, Any] | None] | None,
) -> float | None:
"""先全量持仓汇总,失败或无数据时回退到活跃监控单 metrics."""
total = fetch_unrealized_pnl(fetch_positions_fn)
if total is not None:
return total
if active_rows and get_metrics_fn:
return sum_unrealized_pnl_from_metrics(active_rows, get_metrics_fn)
return None
def merge_unrealized_pnl_components(*parts: float | None) -> float | None:
"""合并永续与期权等多路未实现盈亏(任一路有值即参与合计)."""
total = 0.0
found = False
for part in parts:
if part is None:
continue
try:
total += float(part)
found = True
except (TypeError, ValueError):
continue
return round(total, 2) if found else None