修复趋势回调止盈误显与行情区成交量被裁切的问题

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-02 13:45:19 +08:00
parent 21ef97cbdb
commit 01d26e9833
5 changed files with 245 additions and 54 deletions
+184 -40
View File
@@ -506,14 +506,146 @@
return (row && (row.key || row.id)) || exchangeId;
}
function buildPositionMarketContext(pos, monitorOrder) {
function findTrendPlan(trends, symbol, side) {
const want = (side || "").toLowerCase();
for (const t of trends || []) {
const sym = t.symbol || t.exchange_symbol || "";
if (!symbolsMatchHub(sym, symbol)) continue;
const d = (t.direction || "").toLowerCase();
if (!d || d === want) return t;
}
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 : "";
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 || "" };
})
.filter(function (o) {
return o.price != null && !Number.isNaN(o.price) && o.price > 0;
});
if (!triggers.length) return { sl: sl || "", tp: tp || "" };
const s = (side || "long").toLowerCase();
const e = entry != null && Number.isFinite(Number(entry)) ? Number(entry) : null;
if (e != null) {
const below = triggers.filter(function (t) {
return t.price < e;
});
const above = triggers.filter(function (t) {
return t.price > e;
});
if (s === "long") {
if (sl === "" && below.length) {
sl = Math.max.apply(
null,
below.map(function (t) {
return t.price;
})
);
}
if (tp === "" && above.length) {
tp = Math.min.apply(
null,
above.map(function (t) {
return t.price;
})
);
}
} else {
if (sl === "" && above.length) {
sl = Math.min.apply(
null,
above.map(function (t) {
return t.price;
})
);
}
if (tp === "" && below.length) {
tp = Math.max.apply(
null,
below.map(function (t) {
return t.price;
})
);
}
}
}
if (triggers.length === 1 && sl === "" && tp === "") {
const one = triggers[0];
const p = one.price;
const lbl = one.label;
if (e != null) {
if (s === "long") {
if (p < e) sl = p;
else if (p > e) tp = p;
} else if (p > e) sl = p;
else if (p < e) tp = p;
} else if (/止损/.test(lbl)) sl = p;
else if (/止盈/.test(lbl) && !/止盈止损/.test(lbl)) tp = p;
}
if (sl !== "" && tp !== "" && Number(sl) === Number(tp)) tp = "";
return { sl: sl || "", tp: tp || "" };
}
function resolvePositionTpsl(pos, monitorOrder, trendPlan) {
const mo = monitorOrder || {};
const tp = trendPlan || {};
const cond = condOrdersFromPosition(pos);
const entryRaw =
pos.entry_price != null
? pos.entry_price
: mo.trigger_price != null
? mo.trigger_price
: tp.avg_entry_price;
const entryN = entryRaw != null && entryRaw !== "" ? Number(entryRaw) : null;
const isTrend =
!!(trendPlan && trendPlan.id) || String(mo.monitor_type || "").trim() === "趋势回调";
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) {
tpMonitored = true;
takeProfit = "";
if (trendPlan && trendPlan.stop_loss != null && trendPlan.stop_loss !== "") {
sl = trendPlan.stop_loss;
}
}
const inferred = inferTpslFromCondOrders(pos.side, cond, entryN);
if (sl === "" || sl == null) sl = inferred.sl;
if (!tpMonitored && (takeProfit === "" || takeProfit == null)) takeProfit = inferred.tp;
if (sl !== "" && takeProfit !== "" && Number(sl) === Number(takeProfit)) {
takeProfit = "";
}
return {
entry: entryRaw,
sl,
tp: takeProfit,
tp_monitored: tpMonitored,
is_trend: isTrend,
};
}
function buildPositionMarketContext(pos, monitorOrder, trendPlan) {
const tpsl = resolvePositionTpsl(pos, monitorOrder, trendPlan);
const cond = condOrdersFromPosition(pos);
const reg = Array.isArray(pos.regular_orders) ? pos.regular_orders : [];
const guess = guessTpslFromCondOrders(pos.side, cond);
const entry = pos.entry_price != null ? pos.entry_price : mo.trigger_price;
const sl = mo.stop_loss != null ? mo.stop_loss : guess.sl;
const tp = mo.take_profit != null ? mo.take_profit : guess.tp;
const num = function (v) {
if (v == null || v === "") return null;
const n = Number(v);
@@ -538,9 +670,11 @@
});
return {
side: (pos.side || "long").toLowerCase(),
entry: num(entry),
stop_loss: num(sl),
take_profit: num(tp),
entry: num(tpsl.entry),
stop_loss: num(tpsl.sl),
take_profit: num(tpsl.tp),
tp_monitored: !!tpsl.tp_monitored,
is_trend: !!tpsl.is_trend,
contracts: num(pos.contracts),
orders: orders,
};
@@ -565,10 +699,13 @@
}
}
function marketOpenBtnAttrs(exchangeId, exchangeKey, symbol, pos, monitorOrder) {
function marketOpenBtnAttrs(exchangeId, exchangeKey, symbol, pos, monitorOrder, trendPlan) {
const symAttr = esc(symbol || "").replace(/"/g, "&quot;");
const exKeyAttr = esc(exchangeKey || exchangeId || "").replace(/"/g, "&quot;");
const ctxEnc = esc(encodePosCtx(buildPositionMarketContext(pos, monitorOrder))).replace(/"/g, "&quot;");
const ctxEnc = esc(encodePosCtx(buildPositionMarketContext(pos, monitorOrder, trendPlan))).replace(
/"/g,
"&quot;"
);
return (
'data-ex-id="' +
esc(exchangeId) +
@@ -706,18 +843,8 @@
return `<table class="data-table data-table-sub"><thead><tr><th>类型</th><th>数量</th><th>触发/价格</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table>`;
}
function guessTpslFromCondOrders(side, cond) {
const triggers = (cond || [])
.map((o) => o.trigger_price)
.filter((v) => v != null && !Number.isNaN(Number(v)))
.map(Number);
if (!triggers.length) return { sl: "", tp: "" };
triggers.sort((a, b) => a - b);
const s = (side || "long").toLowerCase();
if (s === "short") {
return { sl: triggers[triggers.length - 1], tp: triggers[0] };
}
return { sl: triggers[0], tp: triggers[triggers.length - 1] };
function guessTpslFromCondOrders(side, cond, entry) {
return inferTpslFromCondOrders(side, cond, entry);
}
function renderOrdersCollapse(exchangeId, symbol, cond, reg) {
@@ -786,7 +913,7 @@
return row("止损", sl) + row("止盈", tp);
}
function renderLivePositionCard(exchangeId, exchangeKey, pos, monitorOrder) {
function renderLivePositionCard(exchangeId, exchangeKey, pos, monitorOrder, trendPlan) {
const symbol = pos.symbol || "";
const exKeyAttr = esc(exchangeKey || exchangeId || "").replace(/"/g, "&quot;");
const side = (pos.side || "long").toLowerCase();
@@ -795,16 +922,17 @@
const mo = monitorOrder || {};
const cond = condOrdersFromPosition(pos);
const reg = Array.isArray(pos.regular_orders) ? pos.regular_orders : [];
const guess = guessTpslFromCondOrders(side, cond);
const tpsl = resolvePositionTpsl(pos, mo, trendPlan);
const symAttr = esc(symbol).replace(/"/g, "&quot;");
const sideAttr = esc(side).replace(/"/g, "&quot;");
const contractsAttr = esc(String(pos.contracts != null ? pos.contracts : "")).replace(/"/g, "&quot;");
const slAttr = esc(String(mo.stop_loss != null ? mo.stop_loss : guess.sl)).replace(/"/g, "&quot;");
const tpAttr = esc(String(mo.take_profit != null ? mo.take_profit : guess.tp)).replace(/"/g, "&quot;");
const entry = pos.entry_price != null ? pos.entry_price : mo.trigger_price;
const sl = mo.stop_loss != null ? mo.stop_loss : guess.sl;
const tp = mo.take_profit != null ? mo.take_profit : guess.tp;
const rr = calcRrRatio(side, entry, sl, tp);
const slAttr = esc(String(tpsl.sl)).replace(/"/g, "&quot;");
const tpAttr = esc(String(tpsl.tp)).replace(/"/g, "&quot;");
const entry = tpsl.entry;
const sl = tpsl.sl;
const tp = tpsl.tp;
const tpMonitored = tpsl.tp_monitored;
const rr = tpMonitored ? null : calcRrRatio(side, entry, sl, tp);
const upnl = pos.unrealized_pnl;
let pnlText = fmt(upnl, 2) + "U";
if (pos.notional_usdt && upnl != null && Math.abs(Number(pos.notional_usdt)) > 1e-8) {
@@ -828,7 +956,7 @@
meta.push(
`<span class="${beOn ? "pos-meta-on" : "pos-meta-off"}">移动保本:${beOn ? "开" : "关"}</span>`
);
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, symbol, pos, monitorOrder);
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, symbol, pos, monitorOrder, trendPlan);
return `<div class="pos-card hub-pos-card">
<div class="pos-card-head">
<div class="pos-card-symbol">
@@ -844,8 +972,8 @@
<div class="pos-grid">
<div class="pos-cell"><span class="pos-label">成交价</span><span class="pos-value">${entry != null ? fmt(entry, 4) : "—"}</span></div>
<div class="pos-cell"><span class="pos-label">止损</span><span class="pos-value">${sl != null && sl !== "" ? fmt(sl, 4) : "—"}</span></div>
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value">${tp != null && tp !== "" ? fmt(tp, 4) : "—"}</span></div>
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${rr != null ? fmt(rr, 2) + ":1" : "-:1"}</span></div>
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value">${tpMonitored ? "程序监控" : tp != null && tp !== "" ? fmt(tp, 4) : "—"}</span></div>
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${tpMonitored ? "—" : rr != null ? fmt(rr, 2) + ":1" : "-:1"}</span></div>
<div class="pos-cell"><span class="pos-label">张数</span><span class="pos-value">${fmt(pos.contracts, 4)}</span></div>
<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ${pnlCls(upnl)}">${pnlText}</span></div>
</div>
@@ -933,17 +1061,17 @@
.join("");
}
function renderPositionBlock(exchangeId, exchangeKey, x, monitorOrder) {
function renderPositionBlock(exchangeId, exchangeKey, x, monitorOrder, trendPlan) {
const symAttr = esc(x.symbol || "").replace(/"/g, "&quot;");
const exKeyAttr = esc(exchangeKey || exchangeId || "").replace(/"/g, "&quot;");
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, "&quot;");
const contractsAttr = esc(String(x.contracts != null ? x.contracts : "")).replace(/"/g, "&quot;");
const cond = condOrdersFromPosition(x);
const reg = Array.isArray(x.regular_orders) ? x.regular_orders : [];
const guess = guessTpslFromCondOrders(x.side, cond);
const slAttr = esc(String(guess.sl)).replace(/"/g, "&quot;");
const tpAttr = esc(String(guess.tp)).replace(/"/g, "&quot;");
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, x.symbol, x, monitorOrder);
const tpsl = resolvePositionTpsl(x, monitorOrder, trendPlan);
const slAttr = esc(String(tpsl.sl)).replace(/"/g, "&quot;");
const tpAttr = esc(String(tpsl.tp)).replace(/"/g, "&quot;");
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, x.symbol, x, monitorOrder, trendPlan);
return `<div class="pos-block">
<div class="table-scroll">
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
@@ -972,7 +1100,17 @@
</div>`;
inner += `<div class="section-title">交易所持仓 · ${pos.length} 仓</div>`;
if (pos.length) {
inner += pos.map((p) => renderPositionBlock(row.id, row.key || row.id, p, findMonitorOrder(orders, p.symbol, p.side))).join("");
inner += pos
.map((p) =>
renderPositionBlock(
row.id,
row.key || row.id,
p,
findMonitorOrder(orders, p.symbol, p.side),
findTrendPlan(trends, p.symbol, p.side)
)
)
.join("");
} else {
inner += '<div class="empty-hint">无持仓</div>';
}
@@ -1065,7 +1203,13 @@
html += `<div class="hub-pos-list ${posListCls}" data-pos-count="${posCount}">`;
if (posCount) {
pos.forEach((p) => {
html += renderLivePositionCard(row.id, row.key || row.id, p, findMonitorOrder(orders, p.symbol, p.side));
html += renderLivePositionCard(
row.id,
row.key || row.id,
p,
findMonitorOrder(orders, p.symbol, p.side),
findTrendPlan(trends, p.symbol, p.side)
);
});
} else {
html += '<div class="pos-empty">暂无持仓</div>';