修复趋势回调止盈误显与行情区成交量被裁切的问题
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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, """);
|
||||
const exKeyAttr = esc(exchangeKey || exchangeId || "").replace(/"/g, """);
|
||||
const ctxEnc = esc(encodePosCtx(buildPositionMarketContext(pos, monitorOrder))).replace(/"/g, """);
|
||||
const ctxEnc = esc(encodePosCtx(buildPositionMarketContext(pos, monitorOrder, trendPlan))).replace(
|
||||
/"/g,
|
||||
"""
|
||||
);
|
||||
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, """);
|
||||
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, """);
|
||||
const sideAttr = esc(side).replace(/"/g, """);
|
||||
const contractsAttr = esc(String(pos.contracts != null ? pos.contracts : "")).replace(/"/g, """);
|
||||
const slAttr = esc(String(mo.stop_loss != null ? mo.stop_loss : guess.sl)).replace(/"/g, """);
|
||||
const tpAttr = esc(String(mo.take_profit != null ? mo.take_profit : guess.tp)).replace(/"/g, """);
|
||||
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, """);
|
||||
const tpAttr = esc(String(tpsl.tp)).replace(/"/g, """);
|
||||
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, """);
|
||||
const exKeyAttr = esc(exchangeKey || exchangeId || "").replace(/"/g, """);
|
||||
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, """);
|
||||
const contractsAttr = esc(String(x.contracts != null ? x.contracts : "")).replace(/"/g, """);
|
||||
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, """);
|
||||
const tpAttr = esc(String(guess.tp)).replace(/"/g, """);
|
||||
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, x.symbol, x, monitorOrder);
|
||||
const tpsl = resolvePositionTpsl(x, monitorOrder, trendPlan);
|
||||
const slAttr = esc(String(tpsl.sl)).replace(/"/g, """);
|
||||
const tpAttr = esc(String(tpsl.tp)).replace(/"/g, """);
|
||||
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>';
|
||||
|
||||
Reference in New Issue
Block a user