fix(hub): show contract-based unrealized PnL in monitor and chart
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+31
-25
@@ -31,7 +31,12 @@ _REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_REPO_ROOT))
|
||||
from hub_ohlcv_lib import format_price_by_tick, price_tick_from_market
|
||||
from hub_position_metrics import parse_position_mark_price, parse_position_unrealized_pnl
|
||||
from hub_position_metrics import (
|
||||
parse_position_entry_price,
|
||||
parse_position_mark_price,
|
||||
parse_position_unrealized_pnl,
|
||||
resolve_position_display_upnl,
|
||||
)
|
||||
|
||||
import ccxt
|
||||
from fastapi import FastAPI, Header, HTTPException, Request
|
||||
@@ -388,25 +393,16 @@ def _position_price_fmt(ex: Any, symbol: str, price: float | None) -> tuple[floa
|
||||
|
||||
def _position_entry_price(p: dict[str, Any]) -> float | None:
|
||||
"""四所 ccxt 持仓统一解析开仓均价(Binance/OKX/Gate 字段名不一致)。"""
|
||||
info = p.get("info") or {}
|
||||
if not isinstance(info, dict):
|
||||
info = {}
|
||||
for key in (
|
||||
p.get("entryPrice"),
|
||||
p.get("entry_price"),
|
||||
p.get("average"),
|
||||
info.get("entryPrice"),
|
||||
info.get("entry_price"),
|
||||
info.get("avgPx"),
|
||||
info.get("avgEntryPrice"),
|
||||
info.get("avg_entry_price"),
|
||||
info.get("avgPrice"),
|
||||
info.get("openAvgPx"),
|
||||
):
|
||||
px = _finite_or_none(key)
|
||||
if px is not None and px > 0:
|
||||
return px
|
||||
return None
|
||||
return parse_position_entry_price(p)
|
||||
|
||||
|
||||
def _position_contract_size(ex: Any, symbol: str) -> float:
|
||||
try:
|
||||
market = ex.market((symbol or "").strip())
|
||||
cs = float(market.get("contractSize") or 1)
|
||||
return cs if cs > 0 else 1.0
|
||||
except Exception:
|
||||
return 1.0
|
||||
|
||||
|
||||
def _position_mark_price(p: dict[str, Any]) -> float | None:
|
||||
@@ -602,7 +598,20 @@ def _status_inner(x_control_token: str | None) -> Any:
|
||||
continue
|
||||
sym = p.get("symbol") or ""
|
||||
side = _position_side(p, c)
|
||||
upnl_f = parse_position_unrealized_pnl(p)
|
||||
entry_f = _position_entry_price(p)
|
||||
mark_f = _position_mark_price(p)
|
||||
if mark_f is None and sym:
|
||||
mark_f = _ticker_mark_price(ex, sym)
|
||||
cs = _position_contract_size(ex, sym) if sym else 1.0
|
||||
exchange_upnl = parse_position_unrealized_pnl(p)
|
||||
upnl_f = resolve_position_display_upnl(
|
||||
side,
|
||||
entry_f,
|
||||
mark_f,
|
||||
abs(c),
|
||||
cs,
|
||||
exchange_upnl,
|
||||
)
|
||||
if upnl_f is None:
|
||||
upnl_f = 0.0
|
||||
total_upnl += upnl_f
|
||||
@@ -611,11 +620,7 @@ def _status_inner(x_control_token: str | None) -> Any:
|
||||
notional_f = float(notional) if notional is not None else None
|
||||
except (TypeError, ValueError):
|
||||
notional_f = None
|
||||
entry_f = _position_entry_price(p)
|
||||
_, entry_fmt, price_tick = _position_price_fmt(ex, sym, entry_f)
|
||||
mark_f = _position_mark_price(p)
|
||||
if mark_f is None and sym:
|
||||
mark_f = _ticker_mark_price(ex, sym)
|
||||
_, mark_fmt, mark_tick = _position_price_fmt(ex, sym, mark_f)
|
||||
if price_tick is None and mark_tick is not None:
|
||||
price_tick = mark_tick
|
||||
@@ -631,6 +636,7 @@ def _status_inner(x_control_token: str | None) -> Any:
|
||||
"entry_price_fmt": entry_fmt,
|
||||
"mark_price": mark_f,
|
||||
"mark_price_fmt": mark_fmt,
|
||||
"contract_size": _finite_or_none(cs),
|
||||
"price_tick": _finite_or_none(price_tick) if price_tick is not None else None,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user