Fix dashboard TP profit display; add options ROI column.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-20 12:41:32 +08:00
parent 977d62bddf
commit 0f5fe801e2
3 changed files with 92 additions and 19 deletions
+62 -16
View File
@@ -1060,27 +1060,73 @@ def resolve_position_monitor_source(pos: dict, hub_mon: Optional[dict]) -> str:
def resolve_position_reward_at_tp(pos: dict, hub_mon: Optional[dict]) -> Optional[float]:
"""与监控区「盈利金额」一致:优先匹配下单监控 reward_at_tp_usdt,否则仓位自身字段."""
"""与监控区「盈利金额」一致:有字段用字段,否则按止盈价×张数推算."""
sym = str(pos.get("symbol") or "")
side = str(pos.get("side") or "")
side = str(pos.get("side") or "").lower()
if side in ("buy",):
side = "long"
elif side in ("sell",):
side = "short"
matched: Optional[dict] = None
if isinstance(hub_mon, dict) and hub_mon.get("ok") is not False and sym:
# 与前端 findMonitorOrder 一致:先扫 orders
for o in hub_mon.get("orders") or []:
if isinstance(o, dict) and _monitor_item_matches_position(o, sym, side):
for bucket in ("orders", "rolls", "trends"):
for o in hub_mon.get(bucket) or []:
if not isinstance(o, dict):
continue
o_sym = o.get("exchange_symbol") or o.get("symbol") or ""
if not _symbols_match(sym, str(o_sym)):
continue
o_side = str(o.get("direction") or "").lower()
# 与前端 findMonitorOrder 一致:方向为空也可匹配
if o_side and o_side != side:
continue
matched = o
v = _safe_float(o.get("reward_at_tp_usdt"))
if v is not None:
return v
for r in hub_mon.get("rolls") or []:
if isinstance(r, dict) and _monitor_item_matches_position(r, sym, side):
v = _safe_float(r.get("reward_at_tp_usdt"))
if v is not None:
return v
for t in hub_mon.get("trends") or []:
if isinstance(t, dict) and _monitor_item_matches_position(t, sym, side):
v = _safe_float(t.get("reward_at_tp_usdt"))
if v is not None:
return v
return _safe_float(pos.get("reward_at_tp_usdt"))
break
if matched is not None:
break
v = _safe_float(pos.get("reward_at_tp_usdt"))
if v is not None:
return v
entry = _safe_float(pos.get("entry_price"))
if entry is None and matched is not None:
entry = _safe_float(
matched.get("avg_entry_price")
or matched.get("entry_price")
or matched.get("avg_px")
)
tp = None
if matched is not None:
tp = _safe_float(matched.get("take_profit"))
if tp is None:
tp = _safe_float(matched.get("take_profit_display"))
if tp is None:
tpsl = _resolve_position_tpsl(pos, hub_mon)
tp = tpsl.get("tp")
contracts = pos.get("contracts")
if contracts is None:
contracts = pos.get("size")
if contracts is None and matched is not None:
contracts = matched.get("contracts")
try:
qty = abs(float(contracts)) if contracts is not None else None
except (TypeError, ValueError):
qty = None
cs = _safe_float(pos.get("contract_size"))
if cs is None or cs <= 0:
cs = 1.0
if entry is None or tp is None or not qty:
return None
try:
from lib.strategy.strategy_roll_ui_lib import reward_at_tp_usdt
return reward_at_tp_usdt(side or "long", float(entry), float(tp), float(qty), contract_size=float(cs))
except Exception:
return None
def _options_source_label(p: dict) -> str: