Fix dashboard TP profit display; add options ROI column.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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]:
|
def resolve_position_reward_at_tp(pos: dict, hub_mon: Optional[dict]) -> Optional[float]:
|
||||||
"""与监控区「盈利金额」一致:优先匹配下单监控 reward_at_tp_usdt,否则仓位自身字段."""
|
"""与监控区「盈利金额」一致:有字段用字段,否则按止盈价×张数推算."""
|
||||||
sym = str(pos.get("symbol") or "")
|
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:
|
if isinstance(hub_mon, dict) and hub_mon.get("ok") is not False and sym:
|
||||||
# 与前端 findMonitorOrder 一致:先扫 orders
|
for bucket in ("orders", "rolls", "trends"):
|
||||||
for o in hub_mon.get("orders") or []:
|
for o in hub_mon.get(bucket) or []:
|
||||||
if isinstance(o, dict) and _monitor_item_matches_position(o, sym, side):
|
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"))
|
v = _safe_float(o.get("reward_at_tp_usdt"))
|
||||||
if v is not None:
|
if v is not None:
|
||||||
return v
|
return v
|
||||||
for r in hub_mon.get("rolls") or []:
|
break
|
||||||
if isinstance(r, dict) and _monitor_item_matches_position(r, sym, side):
|
if matched is not None:
|
||||||
v = _safe_float(r.get("reward_at_tp_usdt"))
|
break
|
||||||
|
|
||||||
|
v = _safe_float(pos.get("reward_at_tp_usdt"))
|
||||||
if v is not None:
|
if v is not None:
|
||||||
return v
|
return v
|
||||||
for t in hub_mon.get("trends") or []:
|
|
||||||
if isinstance(t, dict) and _monitor_item_matches_position(t, sym, side):
|
entry = _safe_float(pos.get("entry_price"))
|
||||||
v = _safe_float(t.get("reward_at_tp_usdt"))
|
if entry is None and matched is not None:
|
||||||
if v is not None:
|
entry = _safe_float(
|
||||||
return v
|
matched.get("avg_entry_price")
|
||||||
return _safe_float(pos.get("reward_at_tp_usdt"))
|
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:
|
def _options_source_label(p: dict) -> str:
|
||||||
|
|||||||
@@ -259,6 +259,20 @@
|
|||||||
return "solo:" + ex + ":" + String((p && p.inst_id) || Math.random());
|
return "solo:" + ex + ":" + String((p && p.inst_id) || Math.random());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function optionsRoiPct(p) {
|
||||||
|
if (!p || typeof p !== "object") return null;
|
||||||
|
const preview = p.close_preview || {};
|
||||||
|
if (preview.estimated_pnl_ratio_pct != null && Number.isFinite(Number(preview.estimated_pnl_ratio_pct))) {
|
||||||
|
return Number(preview.estimated_pnl_ratio_pct);
|
||||||
|
}
|
||||||
|
const net = optionsNetPnl(p);
|
||||||
|
const paid = Number(p.premium_paid);
|
||||||
|
if (net != null && Number.isFinite(paid) && paid > 0) {
|
||||||
|
return (net / paid) * 100;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function renderOptionsLegRow(ac, p, groupCls) {
|
function renderOptionsLegRow(ac, p, groupCls) {
|
||||||
const optType =
|
const optType =
|
||||||
(p.opt_type || "").toUpperCase() === "C"
|
(p.opt_type || "").toUpperCase() === "C"
|
||||||
@@ -270,6 +284,7 @@
|
|||||||
const target = String(p.target_monitor_text || "—");
|
const target = String(p.target_monitor_text || "—");
|
||||||
const targetCls = target && target !== "—" ? "dash-target-monitor is-on" : "dash-target-monitor";
|
const targetCls = target && target !== "—" ? "dash-target-monitor is-on" : "dash-target-monitor";
|
||||||
const net = optionsNetPnl(p);
|
const net = optionsNetPnl(p);
|
||||||
|
const roi = optionsRoiPct(p);
|
||||||
return `<tr class="${groupCls || ""}">
|
return `<tr class="${groupCls || ""}">
|
||||||
<td>${exchangeLinkCell(ac)}</td>
|
<td>${exchangeLinkCell(ac)}</td>
|
||||||
<td>${sourceTypeCell(source)}</td>
|
<td>${sourceTypeCell(source)}</td>
|
||||||
@@ -279,6 +294,7 @@
|
|||||||
<td>${p.idx_px != null ? fmt(p.idx_px, 0) : "—"}</td>
|
<td>${p.idx_px != null ? fmt(p.idx_px, 0) : "—"}</td>
|
||||||
<td><span class="${targetCls}">${esc(target)}</span></td>
|
<td><span class="${targetCls}">${esc(target)}</span></td>
|
||||||
<td class="${pnlClass(net)}">${net != null ? pnlSigned(net, 2) : "—"}</td>
|
<td class="${pnlClass(net)}">${net != null ? pnlSigned(net, 2) : "—"}</td>
|
||||||
|
<td class="${pnlClass(roi)}">${roi != null ? esc(Number(roi).toFixed(2)) + "%" : "—"}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,14 +338,22 @@
|
|||||||
groups.forEach((g) => {
|
groups.forEach((g) => {
|
||||||
if (g.isHedge && g.items.length >= 1) {
|
if (g.isHedge && g.items.length >= 1) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
let paidSum = 0;
|
||||||
let hasSum = false;
|
let hasSum = false;
|
||||||
|
let hasPaid = false;
|
||||||
g.items.forEach(({ p }) => {
|
g.items.forEach(({ p }) => {
|
||||||
const n = optionsNetPnl(p);
|
const n = optionsNetPnl(p);
|
||||||
if (n != null) {
|
if (n != null) {
|
||||||
sum += n;
|
sum += n;
|
||||||
hasSum = true;
|
hasSum = true;
|
||||||
}
|
}
|
||||||
|
const paid = Number(p && p.premium_paid);
|
||||||
|
if (Number.isFinite(paid) && paid > 0) {
|
||||||
|
paidSum += paid;
|
||||||
|
hasPaid = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
const groupRoi = hasSum && hasPaid && paidSum > 0 ? (sum / paidSum) * 100 : null;
|
||||||
const title =
|
const title =
|
||||||
(g.source && g.source !== "—" ? g.source : "对冲") +
|
(g.source && g.source !== "—" ? g.source : "对冲") +
|
||||||
(g.planId ? " #" + g.planId : "") +
|
(g.planId ? " #" + g.planId : "") +
|
||||||
@@ -339,6 +363,9 @@
|
|||||||
body += `<tr class="dash-opt-group-head">
|
body += `<tr class="dash-opt-group-head">
|
||||||
<td colspan="7"><span class="dash-opt-group-label">${esc(title)}</span></td>
|
<td colspan="7"><span class="dash-opt-group-label">${esc(title)}</span></td>
|
||||||
<td class="${hasSum ? pnlClass(sum) : ""}">${hasSum ? pnlSigned(sum, 2) : "—"}</td>
|
<td class="${hasSum ? pnlClass(sum) : ""}">${hasSum ? pnlSigned(sum, 2) : "—"}</td>
|
||||||
|
<td class="${groupRoi != null ? pnlClass(groupRoi) : ""}">${
|
||||||
|
groupRoi != null ? esc(Number(groupRoi).toFixed(2)) + "%" : "—"
|
||||||
|
}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
g.items.forEach(({ ac, p }, idx) => {
|
g.items.forEach(({ ac, p }, idx) => {
|
||||||
const cls =
|
const cls =
|
||||||
@@ -359,7 +386,7 @@
|
|||||||
<div class="dash-table-wrap dash-options-table-wrap">
|
<div class="dash-table-wrap dash-options-table-wrap">
|
||||||
<table class="dash-table dash-options-table">
|
<table class="dash-table dash-options-table">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th>交易所</th><th>类型</th><th>合约</th><th>Call/Put</th><th>到期倒计时</th><th>指数</th><th>目标监控</th><th>净盈亏</th>
|
<th>交易所</th><th>类型</th><th>合约</th><th>Call/Put</th><th>到期倒计时</th><th>指数</th><th>目标监控</th><th>净盈亏</th><th>收益率</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>${body}</tbody>
|
<tbody>${body}</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
<link rel="stylesheet" href="/assets/dashboard.css?v=20260720-dash-hedge-group" />
|
<link rel="stylesheet" href="/assets/dashboard.css?v=20260720-dash-tp-roi" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-bg" aria-hidden="true"></div>
|
<div class="app-bg" aria-hidden="true"></div>
|
||||||
@@ -1385,7 +1385,7 @@
|
|||||||
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
|
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
|
||||||
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
|
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
|
||||||
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
|
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260720-dash-hedge-group"></script>
|
<script src="/assets/dashboard.js?v=20260720-dash-tp-roi"></script>
|
||||||
<script src="/assets/strategy.js?v=8"></script>
|
<script src="/assets/strategy.js?v=8"></script>
|
||||||
<script src="/assets/help.js?v=1"></script>
|
<script src="/assets/help.js?v=1"></script>
|
||||||
<script src="/assets/logs.js?v=1"></script>
|
<script src="/assets/logs.js?v=1"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user