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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user