Clear target input after set and show estimated value/profit.
Position委托 row keeps the field empty for new entry and displays target value plus estimated PnL after arming. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4122,6 +4122,34 @@ html[data-theme="light"] .options-estimate-row {
|
||||
.options-page-wrap .opt-target-row-hint {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.options-page-wrap .opt-target-armed {
|
||||
font-size: 0.78rem;
|
||||
color: #9ad0ff;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.options-page-wrap .opt-target-est {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.options-page-wrap .opt-target-est--idle:empty {
|
||||
display: none;
|
||||
}
|
||||
.options-page-wrap .opt-target-est-item {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
.options-page-wrap .opt-target-est-item .k {
|
||||
color: #9aa8c7;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.options-page-wrap .opt-target-est-item .v {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
.opt-target-monitors {
|
||||
margin: 0 0 10px;
|
||||
padding: 10px 12px;
|
||||
|
||||
@@ -859,23 +859,90 @@
|
||||
);
|
||||
}
|
||||
|
||||
function posEthAmount(p) {
|
||||
if (p.eth_amount != null && Number(p.eth_amount) > 0) return Number(p.eth_amount);
|
||||
const sheets = Number(p.avail_pos != null ? p.avail_pos : p.pos);
|
||||
const ct = Number(p.ct_mult != null ? p.ct_mult : 0.01);
|
||||
if (Number.isFinite(sheets) && sheets > 0 && Number.isFinite(ct) && ct > 0) return sheets * ct;
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid) {
|
||||
const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount);
|
||||
const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid);
|
||||
if (value == null && profit == null) return "";
|
||||
let html = '<span class="opt-target-est">';
|
||||
html += '<span class="opt-target-est-item"><span class="k">价值</span><span class="v">' +
|
||||
(value == null ? "—" : fmtUsdc(value) + " USDC") + "</span></span>";
|
||||
html += '<span class="opt-target-est-item"><span class="k">预估盈利</span><span class="v ' + pnlCls(profit) + '">' +
|
||||
(profit == null ? "—" : fmtUsdcSigned(profit)) + "</span></span>";
|
||||
html += "</span>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderTargetDelegateRow(p) {
|
||||
const inst = p.inst_id || "";
|
||||
const tgt = p.target_index != null && p.target_index !== "" ? String(p.target_index) : "";
|
||||
const armed = tgt !== "";
|
||||
const tgt = p.target_index != null && p.target_index !== "" ? Number(p.target_index) : null;
|
||||
const armed = tgt != null && Number.isFinite(tgt) && tgt > 0;
|
||||
const ethAmt = posEthAmount(p);
|
||||
const prem = p.premium_paid;
|
||||
const estHtml = armed
|
||||
? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem)
|
||||
: '<span class="opt-target-est opt-target-est--idle"></span>';
|
||||
return (
|
||||
'<div class="opt-target-row">' +
|
||||
'<div class="opt-target-row" data-inst="' + inst + '"' +
|
||||
' data-opt-type="' + (p.opt_type || "") + '"' +
|
||||
' data-strike="' + (p.strike != null ? p.strike : "") + '"' +
|
||||
' data-eth="' + (ethAmt != null ? ethAmt : "") + '"' +
|
||||
' data-prem="' + (prem != null ? prem : "") + '"' +
|
||||
' data-armed-target="' + (armed ? tgt : "") + '">' +
|
||||
'<span class="opt-target-row-label">委托</span>' +
|
||||
'<input type="number" class="opt-pos-target-input" data-inst="' + inst + '" step="0.1" min="0" placeholder="监控目标指数" value="' + tgt + '">' +
|
||||
'<input type="number" class="opt-pos-target-input" data-inst="' + inst + '" step="0.1" min="0" placeholder="监控目标指数" value="">' +
|
||||
'<button type="button" class="btn-secondary opt-target-set-btn" data-inst="' + inst + '">设定</button>' +
|
||||
'<button type="button" class="btn-secondary opt-target-cancel-btn" data-inst="' + inst + '"' + (armed ? "" : " disabled") + ">取消</button>" +
|
||||
(armed
|
||||
? '<span class="opt-target-armed">目标 ' + fmt(tgt, 1) + "</span>"
|
||||
: "") +
|
||||
estHtml +
|
||||
'<span class="muted opt-target-row-hint">' +
|
||||
(armed ? "监控中 · 到位按买一限价平 · 无止损" : "目标=监控指数 · 到位按买一限价平 · 到期即止损") +
|
||||
(armed ? "监控中 · 到位按买一限价平" : "输入后设定 · 到位按买一限价平 · 到期即止损") +
|
||||
"</span>" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
|
||||
function updatePosTargetEstimate(row) {
|
||||
if (!row) return;
|
||||
const est = row.querySelector(".opt-target-est");
|
||||
if (!est) return;
|
||||
const inp = row.querySelector(".opt-pos-target-input");
|
||||
const typed = inp ? String(inp.value || "").trim() : "";
|
||||
const armed = row.getAttribute("data-armed-target") || "";
|
||||
const targetRaw = typed !== "" ? typed : armed;
|
||||
if (targetRaw === "") {
|
||||
est.className = "opt-target-est opt-target-est--idle";
|
||||
est.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
const html = formatTargetEstimateHtml(
|
||||
row.getAttribute("data-opt-type"),
|
||||
row.getAttribute("data-strike"),
|
||||
targetRaw,
|
||||
row.getAttribute("data-eth"),
|
||||
row.getAttribute("data-prem")
|
||||
);
|
||||
if (!html) {
|
||||
est.className = "opt-target-est opt-target-est--idle";
|
||||
est.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
const tmp = document.createElement("div");
|
||||
tmp.innerHTML = html;
|
||||
const node = tmp.firstChild;
|
||||
est.className = "opt-target-est";
|
||||
est.innerHTML = node ? node.innerHTML : "";
|
||||
}
|
||||
|
||||
function renderPositionCard(p) {
|
||||
return (
|
||||
'<div class="pos-card opt-pos-card" data-inst="' + (p.inst_id || "") + '">' +
|
||||
@@ -953,6 +1020,9 @@
|
||||
});
|
||||
container.querySelectorAll(".opt-pos-target-input").forEach(function (inp) {
|
||||
inp.addEventListener("click", function (e) { e.stopPropagation(); });
|
||||
inp.addEventListener("input", function () {
|
||||
updatePosTargetEstimate(inp.closest(".opt-target-row"));
|
||||
});
|
||||
inp.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
@@ -979,6 +1049,7 @@
|
||||
if (!inst) return;
|
||||
const card = document.querySelector('.opt-pos-card[data-inst="' + inst + '"]') ||
|
||||
document.querySelector('.opt-pos-accordion-item[data-inst="' + inst + '"]');
|
||||
const row = card ? card.querySelector(".opt-target-row") : null;
|
||||
const inp = card ? card.querySelector(".opt-pos-target-input") : null;
|
||||
const raw = inp ? String(inp.value || "").trim() : "";
|
||||
const tgt = parseFloat(raw);
|
||||
@@ -997,6 +1068,11 @@
|
||||
alert(d.msg || "设定失败");
|
||||
return;
|
||||
}
|
||||
if (inp) inp.value = "";
|
||||
if (row) {
|
||||
row.setAttribute("data-armed-target", String(tgt));
|
||||
updatePosTargetEstimate(row);
|
||||
}
|
||||
await refreshAllPositions();
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
|
||||
@@ -115,7 +115,33 @@
|
||||
'<div class="pos-cell opt-pos-cell--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' + fmtClosePreview(closePreview, p.premium_paid, hub) + "</span></div>" +
|
||||
"</div>" +
|
||||
(p.target_index != null
|
||||
? '<div class="opt-target-row opt-target-row--ro"><span class="opt-target-row-label">委托</span><span class="pos-value">目标指数 ' + fmt(p.target_index, 1) + "</span><span class="muted opt-target-row-hint">监控中 · 到位按买一限价平</span></div>"
|
||||
? (function () {
|
||||
const eth = p.eth_amount != null ? Number(p.eth_amount)
|
||||
: (Number(p.pos) > 0 ? Number(p.pos) * Number(p.ct_mult || 0.01) : null);
|
||||
const strike = Number(p.strike);
|
||||
const tgt = Number(p.target_index);
|
||||
const prem = Number(p.premium_paid);
|
||||
let profit = null;
|
||||
let value = null;
|
||||
if (Number.isFinite(tgt) && Number.isFinite(strike) && eth > 0) {
|
||||
const o = String(p.opt_type || "").toUpperCase();
|
||||
const intrinsic = o === "C" ? Math.max(0, tgt - strike) : o === "P" ? Math.max(0, strike - tgt) : null;
|
||||
if (intrinsic != null) {
|
||||
value = Math.round(intrinsic * eth * 100) / 100;
|
||||
if (Number.isFinite(prem)) profit = Math.round((value - prem) * 100) / 100;
|
||||
}
|
||||
}
|
||||
const profitTxt = profit == null ? "—" : ((profit > 0 ? "+" : "") + fmtUsdc(profit) + " USDC");
|
||||
const profitCls = profit > 0 ? " pnl-pos" : profit < 0 ? " pnl-neg" : "";
|
||||
return (
|
||||
'<div class="opt-target-row opt-target-row--ro">' +
|
||||
'<span class="opt-target-row-label">委托</span>' +
|
||||
'<span class="pos-value">目标 ' + fmt(p.target_index, 1) + "</span>" +
|
||||
'<span class="pos-value">价值 ' + (value == null ? "—" : fmtUsdc(value) + " USDC") + "</span>" +
|
||||
'<span class="pos-value' + profitCls + '">预估盈利 ' + profitTxt + "</span>" +
|
||||
'<span class="muted opt-target-row-hint">监控中 · 到位按买一限价平</span></div>'
|
||||
);
|
||||
})()
|
||||
: "")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user