refactor(hedge): env-driven option-primary UI with grouped params
Move mode switch to HEDGE_PLAN_OPTION_PRIMARY, split left into capital/select/exit groups, and show perp quote above options with leverage auto-match. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,7 +37,7 @@
|
||||
ooCloseModeEnabled: root.getAttribute("data-oo-close-mode-enabled") !== "0",
|
||||
ooCloseMode: "close_all",
|
||||
direction: "long",
|
||||
optionPrimary: true,
|
||||
optionPrimary: root.getAttribute("data-option-primary") !== "0",
|
||||
opLevTouched: false,
|
||||
opRatioTouched: false,
|
||||
tradingUsdc: null,
|
||||
@@ -283,14 +283,9 @@
|
||||
|
||||
function syncOptionPrimaryUI() {
|
||||
const on = isOptionPrimary();
|
||||
document.querySelectorAll(".hp-po-mode").forEach(function (b) {
|
||||
const v = b.getAttribute("data-option-primary") === "1";
|
||||
b.classList.toggle("is-selected", v === on);
|
||||
b.classList.toggle("active", v === on);
|
||||
});
|
||||
const ins = $("hp-po-fields-insurance");
|
||||
const op = $("hp-po-fields-option-primary");
|
||||
// 保险模式只显示开仓价/张数/止盈止损;以期权为主只显示权利金等执行参数
|
||||
// 保险模式只显示开仓价/张数/止盈止损;以期权为主显示三组参数(env 切换,页内不可改)
|
||||
if (ins) {
|
||||
ins.classList.toggle("hidden", on);
|
||||
if (on) ins.setAttribute("hidden", "hidden");
|
||||
@@ -303,48 +298,39 @@
|
||||
else op.removeAttribute("hidden");
|
||||
op.style.display = on ? "" : "none";
|
||||
}
|
||||
const otmBtn = document.querySelector(".hp-money-otm");
|
||||
if (otmBtn) {
|
||||
const insMoney = $("hp-po-ins-money");
|
||||
if (insMoney) {
|
||||
if (on) {
|
||||
insMoney.setAttribute("hidden", "hidden");
|
||||
insMoney.classList.add("hidden");
|
||||
} else {
|
||||
insMoney.removeAttribute("hidden");
|
||||
insMoney.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
document.querySelectorAll(".hp-money-otm").forEach(function (otmBtn) {
|
||||
otmBtn.classList.toggle("hidden", !on);
|
||||
if (!on) otmBtn.setAttribute("hidden", "hidden");
|
||||
else otmBtn.removeAttribute("hidden");
|
||||
}
|
||||
});
|
||||
if (!on && state.moneyFilter === "otm") {
|
||||
state.moneyFilter = "itm";
|
||||
syncMoneyUI();
|
||||
}
|
||||
if (on) applyOpDefaultsFromMoney(false);
|
||||
const title = $("hp-po-card-title");
|
||||
if (title) title.textContent = on ? "执行参数" : "永续";
|
||||
const rightQ = $("hp-po-perp-quote-right");
|
||||
if (rightQ) {
|
||||
rightQ.classList.toggle("hidden", !on);
|
||||
if (!on) rightQ.setAttribute("hidden", "hidden");
|
||||
else rightQ.removeAttribute("hidden");
|
||||
const badge = $("hp-po-mode-badge");
|
||||
if (badge) {
|
||||
badge.textContent = on ? "以期权为主" : "保险模式";
|
||||
badge.classList.toggle("is-insurance", !on);
|
||||
}
|
||||
const title = $("hp-po-card-title");
|
||||
if (title) title.textContent = "执行参数";
|
||||
const dirLong = document.querySelector('.hp-po-dir[data-dir="long"]');
|
||||
const dirShort = document.querySelector('.hp-po-dir[data-dir="short"]');
|
||||
if (dirLong) dirLong.title = on ? "做多=买Call+永续空" : "做多永续";
|
||||
if (dirShort) dirShort.title = on ? "做空=买Put+永续多" : "做空永续";
|
||||
}
|
||||
|
||||
function setOptionPrimary(on, forceReload) {
|
||||
const next = !!on;
|
||||
const changed = next !== isOptionPrimary();
|
||||
state.optionPrimary = next;
|
||||
if (changed) {
|
||||
state.opLevTouched = false;
|
||||
state.opRatioTouched = false;
|
||||
state.selected = null;
|
||||
if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
|
||||
}
|
||||
syncOptionPrimaryUI();
|
||||
if (!changed && !forceReload) return;
|
||||
void loadMarket().then(function () {
|
||||
return loadChain();
|
||||
});
|
||||
}
|
||||
|
||||
function hoursFromExpMs(expMs) {
|
||||
const n = Number(expMs);
|
||||
if (!n || Number.isNaN(n)) return null;
|
||||
@@ -440,6 +426,37 @@
|
||||
return (Math.round((k / a) * 10) / 10).toFixed(1) + "×";
|
||||
}
|
||||
|
||||
/** 永期期权行情杠杆:指数÷卖一(与选约杠杆门一致). */
|
||||
function calcIndexAskLeverage(ask) {
|
||||
const idx = indexPx();
|
||||
const a = Number(ask);
|
||||
if (!(idx > 0) || !(a > 0)) return "—";
|
||||
return (Math.round((idx / a) * 10) / 10).toFixed(1) + "×";
|
||||
}
|
||||
|
||||
function clearPoSelection() {
|
||||
state.selected = null;
|
||||
if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
|
||||
updatePremiumLine();
|
||||
updatePerpPnlHint();
|
||||
}
|
||||
|
||||
/** 以期权为主:按类型/间隔/杠杆自动匹配最近合约. */
|
||||
function autoMatchPoOption(force) {
|
||||
if (!isOptionPrimary()) return;
|
||||
const want = optTypeForDirection(getDirection());
|
||||
const sel = state.selected;
|
||||
const stillOk =
|
||||
!force &&
|
||||
sel &&
|
||||
String(sel.opt_type || "").toUpperCase() === want &&
|
||||
matchesMoneyFilter(sel);
|
||||
if (stillOk) return;
|
||||
const c = pickClosestItmAtm(currentContracts("hp-exp-select"), want);
|
||||
if (c) pickContract(c);
|
||||
else clearPoSelection();
|
||||
}
|
||||
|
||||
function findAtmStrikeFromRows(rows, idx) {
|
||||
if (!rows.length) return null;
|
||||
if (idx == null || Number.isNaN(Number(idx))) return rows[0].strike;
|
||||
@@ -913,10 +930,6 @@
|
||||
: "");
|
||||
const q = $("hp-perp-quote");
|
||||
if (q) q.innerHTML = quoteHtml;
|
||||
const rightQ = $("hp-po-perp-quote-right");
|
||||
if (rightQ && isOptionPrimary()) {
|
||||
rightQ.innerHTML = "永续行情 · " + quoteHtml;
|
||||
}
|
||||
const contractsInput = $("hp-contracts");
|
||||
if (contractsInput) {
|
||||
const step = amtPrec <= 0 ? "1" : String(Math.pow(10, -amtPrec));
|
||||
@@ -1130,7 +1143,8 @@
|
||||
const prevInst = state.selected && state.selected.inst_id;
|
||||
tbody.innerHTML = "";
|
||||
if (!exp) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="muted">请选择到期日</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">请选择到期日</td></tr>';
|
||||
if (isOptionPrimary()) clearPoSelection();
|
||||
return;
|
||||
}
|
||||
const sameType = (exp.contracts || []).filter(function (c) {
|
||||
@@ -1158,7 +1172,8 @@
|
||||
(idx ? " · 指数 " + fmt(idx, 2) : "") +
|
||||
" · 可换到期或点刷新链";
|
||||
}
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="muted">' + hint + "</td></tr>";
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">' + hint + "</td></tr>";
|
||||
if (isOptionPrimary()) clearPoSelection();
|
||||
return;
|
||||
}
|
||||
list.forEach(function (c) {
|
||||
@@ -1171,6 +1186,8 @@
|
||||
c.strike +
|
||||
"</td><td>" +
|
||||
moneynessBadge(c) +
|
||||
'</td><td class="opt-chain-lev" title="指数÷卖一">' +
|
||||
calcIndexAskLeverage(c.ask) +
|
||||
'</td><td class="opt-px-sz">' +
|
||||
fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
|
||||
'</td><td class="opt-px-sz">' +
|
||||
@@ -1191,6 +1208,7 @@
|
||||
pickContract(c);
|
||||
});
|
||||
});
|
||||
autoMatchPoOption(false);
|
||||
}
|
||||
|
||||
function updatePremiumLine() {
|
||||
@@ -1729,7 +1747,7 @@
|
||||
b.addEventListener("click", function () {
|
||||
const m = b.getAttribute("data-money") || "itm";
|
||||
if (m === "otm" && !isOptionPrimary()) {
|
||||
alert("永期保险腿仅允许实值或平值;请先打开「以期权为主」");
|
||||
alert("永期保险腿仅允许实值或平值;请在 env 将 HEDGE_PLAN_OPTION_PRIMARY=true");
|
||||
return;
|
||||
}
|
||||
state.moneyFilter = m === "otm" ? "otm" : m === "atm" ? "atm" : "itm";
|
||||
@@ -1738,11 +1756,6 @@
|
||||
renderListStrikes();
|
||||
});
|
||||
});
|
||||
document.querySelectorAll(".hp-po-mode").forEach(function (b) {
|
||||
b.addEventListener("click", function () {
|
||||
setOptionPrimary(b.getAttribute("data-option-primary") === "1", true);
|
||||
});
|
||||
});
|
||||
if ($("hp-opt-leverage")) {
|
||||
$("hp-opt-leverage").addEventListener("input", function () {
|
||||
state.opLevTouched = true;
|
||||
@@ -1789,6 +1802,13 @@
|
||||
});
|
||||
if ($("hp-recommend-opt")) {
|
||||
$("hp-recommend-opt").addEventListener("click", function () {
|
||||
if (isOptionPrimary()) {
|
||||
autoMatchPoOption(true);
|
||||
if (!state.selected) {
|
||||
alert("当前选约条件下无匹配合约,请调整类型/间隔/杠杆或换到期");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const want = optTypeForDirection(getDirection());
|
||||
const c = pickClosestItmAtm(currentContracts("hp-exp-select"), want);
|
||||
if (!c) {
|
||||
|
||||
@@ -3470,6 +3470,34 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
font-size: 0.74rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-mode-badge {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
color: #c4b5fd;
|
||||
background: rgba(139, 92, 246, 0.22);
|
||||
border: 1px solid rgba(167, 139, 250, 0.35);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-mode-badge.is-insurance {
|
||||
color: #93c5fd;
|
||||
background: rgba(59, 130, 246, 0.18);
|
||||
border-color: rgba(96, 165, 250, 0.35);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-section {
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-section-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 650;
|
||||
color: #c5cdd9;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@@ -3480,11 +3508,53 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-fields--section {
|
||||
margin: 0;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-field--type {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-type-seg {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-right-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-quote-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-ins-money {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-ins-money[hidden],
|
||||
.hedge-plan-page-wrap .hp-po-ins-money.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
/* display:grid 会盖掉 [hidden];模式切换必须显式 none */
|
||||
.hedge-plan-page-wrap .hp-po-fields.hidden,
|
||||
.hedge-plan-page-wrap .hp-po-fields[hidden] {
|
||||
.hedge-plan-page-wrap .hp-po-fields[hidden],
|
||||
.hedge-plan-page-wrap #hp-po-fields-option-primary.hidden,
|
||||
.hedge-plan-page-wrap #hp-po-fields-option-primary[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-strike-table-wrap--6 {
|
||||
max-height: 280px;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
margin-top: 4px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -3731,6 +3801,7 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
margin-top: 4px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.hedge-plan-page-wrap .opt-chain-lev,
|
||||
.hedge-plan-page-wrap .hp-oo-table-wrap .opt-chain-lev {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
@@ -3747,12 +3818,20 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
align-items: stretch;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.hedge-plan-page-wrap .options-dual-grid > .card {
|
||||
.hedge-plan-page-wrap .options-dual-grid > .card,
|
||||
.hedge-plan-page-wrap .hp-po-right-stack > .card {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-right-stack > .hp-po-perp-quote-card {
|
||||
height: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-right-stack > .hp-opt-card {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-action-row {
|
||||
margin-top: 10px;
|
||||
gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user