Fix instance dashboard order PnL columns showing empty dashes.

Compute float_pnl and tp_profit from mark/entry/contracts using each exchange contract size during dashboard enrich.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-06 14:12:57 +08:00
parent 993189ce13
commit 75a4175522
5 changed files with 104 additions and 17 deletions
+3 -1
View File
@@ -9604,7 +9604,9 @@ register_trade_records_api(
def _dashboard_enrich_orders(items):
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
return enrich_order_items_with_marks(items, get_price=get_price)
return enrich_order_items_with_marks(
items, get_price=get_price, get_contract_size=get_contract_size
)
from lib.instance.instance_dashboard_register import register_instance_dashboard_routes
+3 -1
View File
@@ -9446,7 +9446,9 @@ register_trade_records_api(
def _dashboard_enrich_orders(items):
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
return enrich_order_items_with_marks(items, get_price=get_price)
return enrich_order_items_with_marks(
items, get_price=get_price, get_contract_size=get_contract_size
)
from lib.instance.instance_dashboard_register import register_instance_dashboard_routes
+3 -1
View File
@@ -9181,7 +9181,9 @@ def _dashboard_fetch_options_positions():
def _dashboard_enrich_orders(items):
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
return enrich_order_items_with_marks(items, get_price=get_price)
return enrich_order_items_with_marks(
items, get_price=get_price, get_contract_size=get_contract_size
)
from lib.hedge_plan.okx_trade_mode_lib import hedge_module_enabled
+61 -14
View File
@@ -358,34 +358,81 @@ def collect_options_items(
return out
def _resolve_contract_size(
sym: str,
*,
get_contract_size: Optional[Callable[[str], Any]] = None,
) -> float:
if not callable(get_contract_size) or not sym:
return 1.0
try:
cs = float(get_contract_size(sym) or 1.0)
return cs if cs > 0 else 1.0
except Exception:
return 1.0
def _fill_order_pnl_fields(row: dict[str, Any], *, mark: Optional[float], contract_size: float) -> None:
"""按线性 U 本位补看板「盈利金额 / 浮盈」."""
direction = str(row.get("direction") or "long").lower()
entry = _safe_float(row.get("entry"))
contracts = _safe_float(row.get("contracts"))
tp = _safe_float(row.get("take_profit"))
if entry is None or contracts is None or contracts <= 0:
return
cs = float(contract_size) if contract_size and contract_size > 0 else 1.0
if mark is not None:
try:
from lib.hub.hub_position_metrics import estimate_linear_swap_upnl_usdt
upnl = estimate_linear_swap_upnl_usdt(direction, entry, mark, contracts, cs)
if upnl is not None:
row["float_pnl"] = upnl
except Exception:
pass
if tp is not None and tp > 0:
try:
from lib.strategy.strategy_trend_lib import calc_tp_profit_usdt
profit = calc_tp_profit_usdt(direction, entry, tp, contracts, cs)
if profit is not None:
row["tp_profit"] = round(float(profit), 2)
except Exception:
pass
def enrich_order_items_with_marks(
items: list[dict[str, Any]],
*,
get_price: Optional[Callable[[str], Any]] = None,
get_contract_size: Optional[Callable[[str], Any]] = None,
) -> list[dict[str, Any]]:
"""后台聚合时补标记价(不打全量 fetch_positions;浮盈仍由实盘页口径负责)."""
if not items or not callable(get_price):
"""后台聚合时补标记价,并按张数×合约面值估算盈利金额/浮盈."""
if not items:
return items
if not callable(get_price) and not callable(get_contract_size):
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:
mark = _safe_float(row.get("mark_price"))
if callable(get_price) and sym:
try:
px = get_price(sym.split(":", 1)[0])
px = get_price(sym)
except Exception:
px = None
mark = _safe_float(px)
if mark is not None:
row["mark_price"] = mark
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
cs = _resolve_contract_size(sym, get_contract_size=get_contract_size)
_fill_order_pnl_fields(row, mark=mark, contract_size=cs)
out.append(row)
return out
+34
View File
@@ -167,6 +167,40 @@ class TestInstanceDashboardLib(unittest.TestCase):
self.assertIn("对冲#2", opt["target_monitor"])
conn.close()
def test_enrich_order_items_fills_float_pnl_and_tp_profit(self):
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
items = [
{
"id": 1,
"symbol": "BTC/USDT:USDT",
"price_symbol": "BTC/USDT:USDT",
"direction": "long",
"entry": 64693.6,
"contracts": 132,
"take_profit": 66000.0,
"mark_price": None,
"tp_profit": None,
"float_pnl": None,
}
]
def get_price(sym):
return 64809.5
def get_cs(sym):
return 0.0001
out = enrich_order_items_with_marks(
items, get_price=get_price, get_contract_size=get_cs
)
self.assertEqual(len(out), 1)
self.assertEqual(out[0]["mark_price"], 64809.5)
# (64809.5 - 64693.6) * 132 * 0.0001 ≈ 1.53
self.assertAlmostEqual(out[0]["float_pnl"], 1.53, places=2)
self.assertIsNotNone(out[0]["tp_profit"])
self.assertGreater(out[0]["tp_profit"], 0)
if __name__ == "__main__":
unittest.main()