"""中控资金统计:期权 USDC/USDT 按 1:1 计入 USDT 合计.""" from __future__ import annotations from typing import Any, Optional def _safe_float(value: Any) -> Optional[float]: try: if value is None or value == "": return None v = float(value) return v if v >= 0 else None except (TypeError, ValueError): return None def _account_total_usdt(funding: Any, trading: Any) -> Optional[float]: fu = _safe_float(funding) tu = _safe_float(trading) if fu is None or tu is None: return None return round(fu + tu, 4) def stablecoin_usdt_equiv(value: Any) -> Optional[float]: """USDC / USDT 按 1:1 折算为 USDT 统计口径.""" return _safe_float(value) def _sum_optional(*values: Any) -> Optional[float]: parts = [_safe_float(v) for v in values] present = [p for p in parts if p is not None] if not present: return None return round(sum(present), 4) def options_balances_usdt_equiv(options_snap: dict[str, Any] | None) -> dict[str, Any]: """从期权 snapshot 提取资金户/交易户 USDT 等价余额. - funding_usdt / trading_usdt: USDT+USDC(+USDG) 全账户(勿与永续 USDT 再加总) - funding_usdc_equiv / trading_usdc_equiv: 仅非 USDT 稳定币,可安全加到永续 USDT 上 """ snap = options_snap if isinstance(options_snap, dict) else {} if snap.get("enabled") is False: return { "ok": False, "funding_usdt": None, "trading_usdt": None, "funding_usdc_equiv": None, "trading_usdc_equiv": None, } if snap.get("ok") is False: return { "ok": False, "funding_usdt": None, "trading_usdt": None, "funding_usdc_equiv": None, "trading_usdc_equiv": None, } bal = snap.get("balances") if isinstance(snap.get("balances"), dict) else snap funding_usdc_equiv = _sum_optional(bal.get("funding_usdc"), bal.get("funding_usdg")) trading_usdc_equiv = _sum_optional(bal.get("trading_usdc"), bal.get("trading_usdg")) funding = _sum_optional(bal.get("funding_usdt"), funding_usdc_equiv) trading = _sum_optional(bal.get("trading_usdt"), trading_usdc_equiv) ok = funding is not None and trading is not None return { "ok": ok, "funding_usdt": funding, "trading_usdt": trading, "funding_usdc_equiv": funding_usdc_equiv, "trading_usdc_equiv": trading_usdc_equiv, } def options_float_pnl_usdt(options_snap: dict[str, Any] | None) -> Optional[float]: """期权浮盈合计(USDT).币本位按指数换算,勿把 ETH/BTC 数量当 U.""" snap = options_snap if isinstance(options_snap, dict) else {} if snap.get("enabled") is False or snap.get("ok") is False: return None def _safe(v: Any) -> float | None: if v is None or v == "": return None try: return float(v) except (TypeError, ValueError): return None def _index_px() -> float | None: px = _safe(snap.get("options_index_px") or snap.get("index_px")) if px is not None and px > 0: return px for p in snap.get("positions") or []: if not isinstance(p, dict): continue px = _safe(p.get("idx_px") or p.get("idxPx")) if px is not None and px > 0: return px return None def _row_is_coin(p: dict[str, Any]) -> bool: ccy = str(p.get("premium_ccy") or "").strip().upper() if ccy in ("ETH", "BTC"): return True if str(p.get("margin_mode") or "").strip().lower() == "coin": return True mid = str(p.get("inst_id") or "") return "-USD-" in mid.upper() and "_UM" not in mid.upper() mode = str(snap.get("options_margin_mode") or snap.get("margin_mode") or "").strip().lower() snap_coin = mode == "coin" idx = _index_px() try: from lib.options.options_positions_lib import display_pnl_from_option_row total_u = 0.0 found = False missing_fx = False for p in snap.get("positions") or []: if not isinstance(p, dict): continue pnl = display_pnl_from_option_row(p) if pnl is None: continue found = True if snap_coin or _row_is_coin(p): px = _safe(p.get("idx_px") or p.get("idxPx")) if px is None or px <= 0: px = idx if px is None or px <= 0: missing_fx = True continue total_u += float(pnl) * float(px) else: total_u += float(pnl) if found and not missing_fx: return round(total_u, 4) if found and missing_fx and abs(total_u) > 1e-12: # 部分腿已换算成功时仍返回可得合计 return round(total_u, 4) except Exception: pass upl = snap.get("upl_total_usdc") if upl is not None: try: raw = float(upl) except (TypeError, ValueError): return None if snap_coin or any( isinstance(p, dict) and _row_is_coin(p) for p in (snap.get("positions") or []) ): if idx is None or idx <= 0: return None return round(raw * float(idx), 4) return round(raw, 4) return None def options_open_position_count(options_snap: dict[str, Any] | None) -> int: snap = options_snap if isinstance(options_snap, dict) else {} if snap.get("enabled") is False or snap.get("ok") is False: return 0 if snap.get("position_count") is not None: try: return max(0, int(snap.get("position_count"))) except (TypeError, ValueError): pass pos = snap.get("positions") return len(pos) if isinstance(pos, list) else 0 def merge_perp_options_balances( perpetual_funding_usdt: Any, perpetual_trading_usdt: Any, options_snap: dict[str, Any] | None, ) -> dict[str, Any]: """永续 USDT + 期权非 USDT 稳定币合并为中控总资金(避免 OKX 同账户 USDT 双计). 与实例顶栏 total_funds_usdt(..., options_usdc, None, None) 口径一致: 期权 snapshot 里的 USDT 与永续资金/交易户是同一钱包,只把 USDC/USDG 加上. """ opt = options_balances_usdt_equiv(options_snap) # 展示用期权户:优先非 USDT 稳定币;若仅有 USDT 则仍给出全量以便辨识 opt_fund_disp = opt.get("funding_usdc_equiv") opt_trade_disp = opt.get("trading_usdc_equiv") if opt_fund_disp is None and opt_trade_disp is None and opt.get("ok"): opt_fund_disp = opt.get("funding_usdt") opt_trade_disp = opt.get("trading_usdt") if opt.get("ok"): if perpetual_funding_usdt is None and perpetual_trading_usdt is None: # 永续账户未取到时,期权 snapshot 已含同账户 USDT+USDC,直接用全量 funding = opt.get("funding_usdt") trading = opt.get("trading_usdt") else: funding = _sum_optional(perpetual_funding_usdt, opt.get("funding_usdc_equiv")) trading = _sum_optional(perpetual_trading_usdt, opt.get("trading_usdc_equiv")) else: funding = _safe_float(perpetual_funding_usdt) trading = _safe_float(perpetual_trading_usdt) total = _account_total_usdt(funding, trading) # 任一侧齐全即可展示;永续缺一侧但有期权 USDC 时仍尽量给出合计 if total is None: total = _sum_optional(funding, trading) perp_total = _account_total_usdt(perpetual_funding_usdt, perpetual_trading_usdt) opt_total = _account_total_usdt(opt.get("funding_usdt"), opt.get("trading_usdt")) data_ok = total is not None return { "perpetual_funding_usdt": _safe_float(perpetual_funding_usdt), "perpetual_trading_usdt": _safe_float(perpetual_trading_usdt), "options_funding_usdt": opt_fund_disp, "options_trading_usdt": opt_trade_disp, "options_funding_full_usdt": opt.get("funding_usdt"), "options_trading_full_usdt": opt.get("trading_usdt"), "options_ok": bool(opt.get("ok")), "funding_usdt": funding, "trading_usdt": trading, "total_usdt": total, "perpetual_total_usdt": perp_total, "options_total_usdt": opt_total, "data_ok": data_ok, } def repair_double_counted_fund_entry(ac: dict[str, Any]) -> dict[str, Any]: """识别并修复历史快照中「永续 USDT + 期权(USDT+USDC)」的双计. 旧口径 options_* 存的是 USDT+USDC 全量,且 funding≈2×期权资金户 USDT 部分. 新口径 options_* 多为纯 USDC,不会误伤. """ if not isinstance(ac, dict): return {} out = dict(ac) ofu = _safe_float(ac.get("options_funding_usdt")) otu = _safe_float(ac.get("options_trading_usdt")) fu = _safe_float(ac.get("funding_usdt")) tu = _safe_float(ac.get("trading_usdt")) if ofu is None or otu is None or fu is None or tu is None: return out if ofu < 1.0: return out ratio = fu / ofu if ofu > 0 else 0.0 # 经典双计:合并资金户 ≈ 2 × 期权资金户(同钱包 USDT 加了两遍) if 1.8 <= ratio <= 2.25: out["funding_usdt"] = ofu out["trading_usdt"] = otu out["total_usdt"] = round(ofu + otu, 4) out["repaired_double_count"] = True return out def merge_board_row_balances(row: dict[str, Any]) -> dict[str, Any]: """监控板行 → 含期权的资金统计.""" caps = row.get("capabilities") or [] options_snap = row.get("options") if "options" in caps else None merged = merge_perp_options_balances( row.get("funding_usdt") if row.get("account_ok") else None, row.get("trading_usdt") if row.get("account_ok") else None, options_snap, ) merged["options_float_pnl_u"] = options_float_pnl_usdt(options_snap) merged["options_open_position_count"] = options_open_position_count(options_snap) return merged