中控增加条件单委托
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
let settingsCache = null;
|
||||
let monitorTimer = null;
|
||||
let authState = { required: false, logged_in: true };
|
||||
let tpslPending = null;
|
||||
|
||||
async function apiFetch(url, opts) {
|
||||
const r = await fetch(url, opts);
|
||||
@@ -146,8 +147,22 @@
|
||||
);
|
||||
});
|
||||
box.querySelectorAll(".btn-cancel-cond-all").forEach((btn) => {
|
||||
btn.onclick = () =>
|
||||
btn.onclick = (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
cancelSymbolOrders(btn.dataset.exId, btn.dataset.symbol, "conditional");
|
||||
};
|
||||
});
|
||||
box.querySelectorAll(".btn-place-tpsl").forEach((btn) => {
|
||||
btn.onclick = () =>
|
||||
openTpslModal(
|
||||
btn.dataset.exId,
|
||||
btn.dataset.symbol,
|
||||
btn.dataset.side,
|
||||
btn.dataset.contracts,
|
||||
btn.dataset.sl || "",
|
||||
btn.dataset.tp || ""
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
box.innerHTML = `<div class="err">${esc(e)}</div>`;
|
||||
@@ -180,15 +195,34 @@
|
||||
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 renderPositionBlock(exchangeId, x) {
|
||||
const symAttr = esc(x.symbol || "").replace(/"/g, """);
|
||||
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, """);
|
||||
const contractsAttr = esc(String(x.contracts != null ? x.contracts : "")).replace(/"/g, """);
|
||||
const cond = Array.isArray(x.conditional_orders) ? x.conditional_orders : [];
|
||||
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 condAllBtn =
|
||||
cond.length > 0
|
||||
? `<button type="button" class="btn-cancel-cond-all ghost" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}">撤销全部条件单</button>`
|
||||
? `<button type="button" class="btn-cancel-cond-all ghost" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}">撤销全部</button>`
|
||||
: "";
|
||||
const condBody = renderOrderRows(exchangeId, x.symbol, cond, "conditional");
|
||||
return `<div class="pos-block">
|
||||
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
|
||||
<tr>
|
||||
@@ -196,15 +230,20 @@
|
||||
<td>${esc(x.side)}</td>
|
||||
<td>${fmt(x.contracts, 4)}</td>
|
||||
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 4)}</td>
|
||||
<td class="td-actions"><button type="button" class="btn-close-pos danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button></td>
|
||||
<td class="td-actions td-actions-row">
|
||||
<button type="button" class="btn-place-tpsl ghost" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}" data-contracts="${contractsAttr}" data-sl="${slAttr}" data-tp="${tpAttr}">委托</button>
|
||||
<button type="button" class="btn-close-pos danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<div class="pos-orders">
|
||||
<div class="pos-orders-head">
|
||||
<span class="pos-orders-title">条件单 · ${cond.length}</span>
|
||||
${condAllBtn}
|
||||
</div>
|
||||
${renderOrderRows(exchangeId, x.symbol, cond, "conditional")}
|
||||
<details class="orders-collapse">
|
||||
<summary class="orders-collapse-summary">
|
||||
<span class="pos-orders-title">条件单 · ${cond.length}</span>
|
||||
${condAllBtn}
|
||||
</summary>
|
||||
<div class="orders-collapse-body">${condBody}</div>
|
||||
</details>
|
||||
<div class="pos-orders-head" style="margin-top:10px">
|
||||
<span class="pos-orders-title">普通委托 · ${reg.length}</span>
|
||||
</div>
|
||||
@@ -213,6 +252,103 @@
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function openTpslModal(exchangeId, symbol, side, contracts, slHint, tpHint) {
|
||||
tpslPending = {
|
||||
exchangeId,
|
||||
symbol,
|
||||
side: (side || "long").toLowerCase(),
|
||||
contracts: parseFloat(contracts),
|
||||
};
|
||||
const modal = document.getElementById("tpsl-modal");
|
||||
const meta = document.getElementById("tpsl-modal-meta");
|
||||
const slIn = document.getElementById("tpsl-sl");
|
||||
const tpIn = document.getElementById("tpsl-tp");
|
||||
if (!modal || !meta || !slIn || !tpIn) return;
|
||||
meta.textContent = `${symbol} · ${side} · ${contracts} 张`;
|
||||
slIn.value = slHint !== "" && slHint != null ? String(slHint) : "";
|
||||
tpIn.value = tpHint !== "" && tpHint != null ? String(tpHint) : "";
|
||||
modal.classList.remove("hidden");
|
||||
modal.setAttribute("aria-hidden", "false");
|
||||
slIn.focus();
|
||||
}
|
||||
|
||||
function closeTpslModal() {
|
||||
tpslPending = null;
|
||||
const modal = document.getElementById("tpsl-modal");
|
||||
if (modal) {
|
||||
modal.classList.add("hidden");
|
||||
modal.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
}
|
||||
|
||||
async function submitTpslModal() {
|
||||
if (!tpslPending) return;
|
||||
const slIn = document.getElementById("tpsl-sl");
|
||||
const tpIn = document.getElementById("tpsl-tp");
|
||||
const sl = parseFloat(slIn && slIn.value);
|
||||
const tp = parseFloat(tpIn && tpIn.value);
|
||||
if (!sl || sl <= 0 || !tp || tp <= 0) {
|
||||
showToast("请填写有效的止损价与止盈价", true);
|
||||
return;
|
||||
}
|
||||
const { exchangeId, symbol, side, contracts } = tpslPending;
|
||||
if (
|
||||
!confirm(
|
||||
`确认 ${symbol} ${side}\n先撤销全部条件单,再挂止损 ${sl}、止盈 ${tp}?`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById("tpsl-submit");
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const r = await apiFetch(
|
||||
"/api/orders/" + encodeURIComponent(exchangeId) + "/place-tpsl",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
symbol,
|
||||
side,
|
||||
stop_loss: sl,
|
||||
take_profit: tp,
|
||||
contracts: contracts > 0 ? contracts : null,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const j = await r.json();
|
||||
const pl = j.payload || {};
|
||||
const ok = j.ok && pl.ok !== false;
|
||||
const n = pl.placed && pl.placed.cancelled_conditional;
|
||||
showToast(
|
||||
ok
|
||||
? `已挂单(已撤 ${n != null ? n : "?"} 笔旧条件单)`
|
||||
: pl.error || JSON.stringify(j),
|
||||
!ok
|
||||
);
|
||||
if (ok) {
|
||||
closeTpslModal();
|
||||
loadMonitorBoard();
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(String(e), true);
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function initTpslModal() {
|
||||
const backdrop = document.getElementById("tpsl-modal-backdrop");
|
||||
const cancel = document.getElementById("tpsl-cancel");
|
||||
const submit = document.getElementById("tpsl-submit");
|
||||
if (backdrop) backdrop.onclick = closeTpslModal;
|
||||
if (cancel) cancel.onclick = closeTpslModal;
|
||||
if (submit) submit.onclick = () => submitTpslModal();
|
||||
document.addEventListener("keydown", (ev) => {
|
||||
if (ev.key === "Escape") closeTpslModal();
|
||||
});
|
||||
}
|
||||
|
||||
async function cancelOneOrder(exchangeId, symbol, orderId, channel) {
|
||||
if (!confirm(`撤销委托 ${symbol} #${orderId}?`)) return;
|
||||
try {
|
||||
@@ -557,6 +693,8 @@
|
||||
showToast("已添加一行,请填写 URL 后点「保存设置」");
|
||||
};
|
||||
|
||||
initTpslModal();
|
||||
|
||||
initAuth().then((ok) => {
|
||||
if (!ok) return;
|
||||
loadSettings().catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user