fix(hub): sync TP/SL display after trend handoff to order monitor

Use order monitor plan prices on handoff cards and fill exchange TP/SL rows when Gate shows reduce-only orders without algo labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-04 19:39:11 +08:00
parent e39fac2c16
commit ed0805538f
4 changed files with 109 additions and 18 deletions
+61 -12
View File
@@ -455,6 +455,11 @@
return side || "—";
}
function isTrendHandoffOrder(monitorOrder) {
const mo = monitorOrder || {};
return String(mo.trade_style || "").toLowerCase() === "trend_pullback_handoff";
}
function isTrendContext(monitorOrder, trendPlan) {
const mo = monitorOrder || {};
const tp = trendPlan || {};
@@ -1176,20 +1181,36 @@
return null;
}
function orderTriggerOrPrice(o) {
if (!o) return null;
if (o.trigger_price != null && o.trigger_price !== "") {
const t = Number(o.trigger_price);
if (Number.isFinite(t) && t > 0) return t;
}
if (o.price != null && o.price !== "") {
const p = Number(o.price);
if (Number.isFinite(p) && p > 0) return p;
}
return null;
}
function inferTpslFromCondOrders(side, cond, entry) {
const picked = pickExTpslOrders(cond);
let sl = picked.sl && picked.sl.trigger_price != null ? picked.sl.trigger_price : "";
let tp = picked.tp && picked.tp.trigger_price != null ? picked.tp.trigger_price : "";
let sl = picked.sl ? orderTriggerOrPrice(picked.sl) : "";
let tp = picked.tp ? orderTriggerOrPrice(picked.tp) : "";
if (sl !== "" && sl != null) sl = Number(sl);
if (tp !== "" && tp != null) tp = Number(tp);
if (sl !== "" && tp !== "" && Number(sl) !== Number(tp)) {
return { sl, tp };
}
const triggers = (cond || [])
.map(function (o) {
return { price: Number(o.trigger_price), label: o.label || "" };
const px = orderTriggerOrPrice(o);
return px == null ? null : { price: px, label: o.label || "" };
})
.filter(function (o) {
return o.price != null && !Number.isNaN(o.price) && o.price > 0;
return o != null;
});
if (!triggers.length) return { sl: sl || "", tp: tp || "" };
@@ -1270,12 +1291,15 @@
: tp.avg_entry_price;
const entryN = entryRaw != null && entryRaw !== "" ? Number(entryRaw) : null;
const isTrend = isTrendContext(mo, trendPlan);
const handoff = isTrendHandoffOrder(mo);
let sl = mo.stop_loss != null && mo.stop_loss !== "" ? mo.stop_loss : "";
let takeProfit = mo.take_profit != null && mo.take_profit !== "" ? mo.take_profit : "";
let tpMonitored = false;
if (isTrend) {
if (handoff) {
tpMonitored = false;
} else if (isTrend) {
tpMonitored = true;
if (trendPlan && trendPlan.stop_loss != null && trendPlan.stop_loss !== "") {
sl = trendPlan.stop_loss;
@@ -1301,6 +1325,7 @@
tp: takeProfit,
tp_monitored: tpMonitored,
is_trend: isTrend,
is_handoff: handoff,
};
}
@@ -1568,6 +1593,18 @@
</details>`;
}
function syntheticExTpslOrder(role, price, amount) {
if (price == null || price === "" || !Number.isFinite(Number(price))) return null;
return {
label: role === "sl" ? "止损" : "止盈",
trigger_price: Number(price),
price: Number(price),
amount: amount != null ? amount : null,
id: "",
channel: "plan",
};
}
function pickExTpslOrders(cond) {
let sl = cond.find((o) => /^止损\b/.test(o.label || ""));
let tp = cond.find((o) => /^止盈\b/.test(o.label || "") && !(o.label || "").includes("止盈止损"));
@@ -1586,20 +1623,32 @@
return { sl, tp };
}
function renderExTpslRows(exchangeId, symbol, cond, tickMap) {
function renderExTpslRows(exchangeId, symbol, cond, tickMap, resolvedTpsl, contracts) {
const symAttr = esc(symbol || "").replace(/"/g, "&quot;");
const { sl, tp } = pickExTpslOrders(cond);
let { sl, tp } = pickExTpslOrders(cond);
const plan = resolvedTpsl || {};
if (!sl && plan.sl != null && plan.sl !== "") {
sl = syntheticExTpslOrder("sl", plan.sl, contracts);
}
if (!tp && plan.tp != null && plan.tp !== "") {
tp = syntheticExTpslOrder("tp", plan.tp, contracts);
}
function row(label, o) {
if (!o) {
return `<div class="pos-ex-order-row"><span class="pos-ex-order-main">${label}:—</span></div>`;
}
const oid = esc(o.id || "").replace(/"/g, "&quot;");
const ch = esc(o.channel || "regular").replace(/"/g, "&quot;");
const trig =
o.trigger_price != null ? fmtSymbolPrice(o.trigger_price, symbol, tickMap) : "—";
const px = orderTriggerOrPrice(o);
const trig = px != null ? fmtSymbolPrice(px, symbol, tickMap) : "—";
const cancelBtn =
oid && o.channel !== "plan"
? `<button type="button" class="pos-ex-cancel-btn btn-cancel-order" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-order-id="${oid}" data-channel="${ch}">撤单</button>`
: "";
const planHint = o.channel === "plan" ? '<span class="pos-ex-plan-hint">(下单监控)</span>' : "";
return `<div class="pos-ex-order-row">
<span class="pos-ex-order-main">${label}:触发 ${trig} · 数量 ${fmt(o.amount, 4)}</span>
<button type="button" class="pos-ex-cancel-btn btn-cancel-order" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-order-id="${oid}" data-channel="${ch}">撤单</button>
<span class="pos-ex-order-main">${label}:触发 ${trig} · 数量 ${fmt(o.amount, 4)}${planHint}</span>
${cancelBtn}
</div>`;
}
return row("止损", sl) + row("止盈", tp);
@@ -1884,7 +1933,7 @@
</div>
<div class="pos-ex-orders">
<div class="pos-ex-orders-title">交易所止盈止损</div>
${renderExTpslRows(exchangeId, symbol, cond, tickMap)}
${renderExTpslRows(exchangeId, symbol, cond, tickMap, tpsl, pos.contracts)}
</div>
${renderOrdersCollapse(exchangeId, symbol, cond, reg, tickMap)}
</div>`;
+1 -1
View File
@@ -250,6 +250,6 @@
<div id="toast"></div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script src="/assets/chart.js?v=20260604-market-pnl-sl-drag"></script>
<script src="/assets/app.js?v=20260604-hub-inst-theme"></script>
<script src="/assets/app.js?v=20260604-hub-handoff-tpsl"></script>
</body>
</html>