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):
|
def _dashboard_enrich_orders(items):
|
||||||
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
|
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
|
from lib.instance.instance_dashboard_register import register_instance_dashboard_routes
|
||||||
|
|||||||
@@ -9446,7 +9446,9 @@ register_trade_records_api(
|
|||||||
def _dashboard_enrich_orders(items):
|
def _dashboard_enrich_orders(items):
|
||||||
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
|
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
|
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):
|
def _dashboard_enrich_orders(items):
|
||||||
from lib.instance.instance_dashboard_lib import enrich_order_items_with_marks
|
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
|
from lib.hedge_plan.okx_trade_mode_lib import hedge_module_enabled
|
||||||
|
|||||||
@@ -358,21 +358,66 @@ def collect_options_items(
|
|||||||
return out
|
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(
|
def enrich_order_items_with_marks(
|
||||||
items: list[dict[str, Any]],
|
items: list[dict[str, Any]],
|
||||||
*,
|
*,
|
||||||
get_price: Optional[Callable[[str], Any]] = None,
|
get_price: Optional[Callable[[str], Any]] = None,
|
||||||
|
get_contract_size: Optional[Callable[[str], Any]] = None,
|
||||||
) -> list[dict[str, Any]]:
|
) -> 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
|
return items
|
||||||
out: list[dict[str, Any]] = []
|
out: list[dict[str, Any]] = []
|
||||||
for it in items:
|
for it in items:
|
||||||
row = dict(it)
|
row = dict(it)
|
||||||
sym = str(row.get("price_symbol") or row.get("symbol") or "").strip()
|
sym = str(row.get("price_symbol") or row.get("symbol") or "").strip()
|
||||||
if not sym:
|
mark = _safe_float(row.get("mark_price"))
|
||||||
out.append(row)
|
if callable(get_price) and sym:
|
||||||
continue
|
|
||||||
try:
|
try:
|
||||||
px = get_price(sym)
|
px = get_price(sym)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -386,6 +431,8 @@ def enrich_order_items_with_marks(
|
|||||||
mark = _safe_float(px)
|
mark = _safe_float(px)
|
||||||
if mark is not None:
|
if mark is not None:
|
||||||
row["mark_price"] = mark
|
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)
|
out.append(row)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,40 @@ class TestInstanceDashboardLib(unittest.TestCase):
|
|||||||
self.assertIn("对冲#2", opt["target_monitor"])
|
self.assertIn("对冲#2", opt["target_monitor"])
|
||||||
conn.close()
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user