fix: align unrealized PnL across four exchange instances via hub_position_metrics

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-04 20:25:06 +08:00
parent 806350231e
commit 673bcbdc70
7 changed files with 126 additions and 16 deletions
+53 -7
View File
@@ -119,14 +119,25 @@ def resolve_position_display_upnl(
return exchange_upnl
def _coerce_signed(*values: Any) -> float | None:
"""解析可正可负的数值(未实现盈亏等)。"""
for v in values:
if v is None or v == "":
continue
f = _finite_or_none(v)
if f is not None:
return f
return None
def parse_position_unrealized_pnl(p: dict[str, Any]) -> float | None:
"""四所 ccxt 持仓统一解析未实现盈亏(Gate 常在 info.unrealised_pnl)。"""
"""四所 ccxt 持仓统一解析未实现盈亏(Gate/OKX/Binance 字段名不一致)。"""
if not isinstance(p, dict):
return None
info = p.get("info") or {}
if not isinstance(info, dict):
info = {}
for key in (
return _coerce_signed(
p.get("unrealizedPnl"),
p.get("unrealisedPnl"),
p.get("unrealized_pnl"),
@@ -135,11 +146,46 @@ def parse_position_unrealized_pnl(p: dict[str, Any]) -> float | None:
info.get("unrealized_pnl"),
info.get("unrealisedPnl"),
info.get("unrealizedPnl"),
):
px = _finite_or_none(key)
if px is not None:
return px
return None
info.get("upl"),
info.get("uplLast"),
)
def enrich_ccxt_position_metrics_out(
position: dict[str, Any],
out: dict[str, Any],
*,
contract_size: float = 1.0,
funds_decimals: int = 2,
) -> dict[str, Any]:
"""
四所 parse_ccxt_position_metrics 产出后统一:
- 标记价用 hub 兜底
- 未实现盈亏 = resolve(交易所值, entry/mark/张数/contractSize 推算)
"""
if not isinstance(position, dict) or not isinstance(out, dict):
return out
mark = _finite_or_none(out.get("mark_price"))
if mark is None or mark <= 0:
mp = parse_position_mark_price(position)
if mp is not None and mp > 0:
out["mark_price"] = round(mp, 8)
mark = mp
exchange_upnl = parse_position_unrealized_pnl(position)
if exchange_upnl is None:
exchange_upnl = _coerce_signed(out.get("unrealized_pnl"))
c = position_contracts(position)
if abs(c) < 1e-12:
return out
side = position_side_from_ccxt(position, c)
entry = parse_position_entry_price(position)
cs = contract_size if contract_size and contract_size > 0 else 1.0
upnl = resolve_position_display_upnl(
side, entry, mark, abs(c), cs, exchange_upnl
)
if upnl is not None:
out["unrealized_pnl"] = round(upnl, funds_decimals)
return out
def parse_position_mark_price(p: dict[str, Any]) -> float | None: