Improve hedge-plan option board: moneyness, px/sz, accounts, and denser UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+173
-41
@@ -9,6 +9,7 @@
|
||||
tab: "perp_options",
|
||||
mode: "perp_options",
|
||||
underlying: root.getAttribute("data-default-underly") || "ETH",
|
||||
moneyFilter: "all",
|
||||
chain: null,
|
||||
selected: null,
|
||||
legA: null,
|
||||
@@ -34,10 +35,71 @@
|
||||
return Number(v).toFixed(d == null ? 2 : d);
|
||||
}
|
||||
|
||||
function fmtOptionPx(v, tickSz) {
|
||||
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
const tick = Number(tickSz);
|
||||
if (!tickSz || Number.isNaN(tick) || tick <= 0) {
|
||||
return String(n).replace(/(\.\d*?[1-9])0+$/, "$1").replace(/\.0+$/, "");
|
||||
}
|
||||
let decimals = 0;
|
||||
if (tick < 1) decimals = Math.max(0, -Math.round(Math.log10(tick)));
|
||||
else if (String(tick).indexOf(".") >= 0) decimals = String(tick).split(".")[1].length;
|
||||
let s = n.toFixed(decimals).replace(/\.?0+$/, "");
|
||||
return s || "0";
|
||||
}
|
||||
|
||||
/** 价格/流动性(张),价格按 tick_sz 对齐交易所精度. */
|
||||
function fmtPxSz(px, sz, estimated, tickSz) {
|
||||
if (px === null || px === undefined || Number.isNaN(Number(px))) return "—";
|
||||
let price = fmtOptionPx(px, tickSz);
|
||||
if (price === "—") return "—";
|
||||
if (estimated) price += "~";
|
||||
if (sz === null || sz === undefined || sz === "" || Number.isNaN(Number(sz))) return price;
|
||||
const s = Number(sz);
|
||||
const size = Math.abs(s - Math.round(s)) < 1e-9 ? String(Math.round(s)) : String(s);
|
||||
return price + "/" + size;
|
||||
}
|
||||
|
||||
function moneynessBadge(c) {
|
||||
const m = (c && c.moneyness) || "";
|
||||
const label = (c && c.moneyness_label) || "—";
|
||||
return '<span class="opt-moneyness opt-moneyness-' + m + '">' + label + "</span>";
|
||||
}
|
||||
|
||||
function matchesMoneyFilter(c) {
|
||||
const f = state.moneyFilter || "all";
|
||||
if (f === "all") return true;
|
||||
const m = (c.moneyness || "").toLowerCase();
|
||||
if (f === "itm") return m === "itm" || m === "atm";
|
||||
if (f === "otm") return m === "otm";
|
||||
return true;
|
||||
}
|
||||
|
||||
function optTypeForDirection(dir) {
|
||||
return dir === "short" ? "C" : "P";
|
||||
}
|
||||
|
||||
function syncUnderlyingUI() {
|
||||
const uly = state.underlying || "ETH";
|
||||
document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) {
|
||||
const on = b.getAttribute("data-uly") === uly;
|
||||
b.classList.toggle("active", on);
|
||||
b.setAttribute("aria-pressed", on ? "true" : "false");
|
||||
});
|
||||
const lab = $("hp-perp-uly-label");
|
||||
if (lab) lab.textContent = uly;
|
||||
const ooLab = $("hp-oo-uly-label");
|
||||
if (ooLab) ooLab.textContent = uly;
|
||||
}
|
||||
|
||||
function syncMoneyUI() {
|
||||
document.querySelectorAll(".hp-money-btn").forEach(function (b) {
|
||||
const on = b.getAttribute("data-money") === state.moneyFilter;
|
||||
b.classList.toggle("active", on);
|
||||
});
|
||||
}
|
||||
|
||||
function syncTabUI() {
|
||||
const tab = state.tab || "perp_options";
|
||||
document.querySelectorAll(".hp-tab").forEach(function (b) {
|
||||
@@ -58,10 +120,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function syncModeUI() {
|
||||
syncTabUI();
|
||||
}
|
||||
|
||||
function setGateLine(gates) {
|
||||
const el = $("hp-gate-line");
|
||||
if (!el) return;
|
||||
@@ -80,6 +138,23 @@
|
||||
if (start) start.disabled = !gates.can_start;
|
||||
}
|
||||
|
||||
function setOptionsBalance(chain) {
|
||||
const acct = (chain && chain.options_account) || {};
|
||||
const label = (chain && chain.account_label) || acct.label || "期权账户";
|
||||
const tag = $("hp-opt-acct-tag");
|
||||
if (tag) tag.textContent = label;
|
||||
const line =
|
||||
label +
|
||||
" · 交易 USDC " +
|
||||
fmt(acct.trading_usdc, 2) +
|
||||
" · 资金 USDC " +
|
||||
fmt(acct.funding_usdc, 2);
|
||||
const el = $("hp-opt-bal-line");
|
||||
if (el) el.textContent = line;
|
||||
const oo = $("hp-oo-bal-line");
|
||||
if (oo) oo.textContent = line;
|
||||
}
|
||||
|
||||
async function loadGates() {
|
||||
try {
|
||||
const d = await apiJson("/api/hedge-plan/gates?plan_type=" + encodeURIComponent(state.mode));
|
||||
@@ -99,10 +174,19 @@
|
||||
);
|
||||
state.market = d;
|
||||
setGateLine(d.gates);
|
||||
const acctLabel = d.account_label || "合约账户";
|
||||
const tag = $("hp-perp-acct-tag");
|
||||
if (tag) tag.textContent = acctLabel;
|
||||
const q = $("hp-perp-quote");
|
||||
if (q) {
|
||||
q.innerHTML =
|
||||
"标记 <strong>" +
|
||||
"<strong>" +
|
||||
(d.base || state.underlying) +
|
||||
"</strong> · " +
|
||||
acctLabel +
|
||||
"可用 <strong>" +
|
||||
fmt(d.available_usdt, 2) +
|
||||
"</strong> U<br/>标记 <strong>" +
|
||||
fmt(d.mark, 2) +
|
||||
"</strong> · 最新 " +
|
||||
fmt(d.last, 2) +
|
||||
@@ -110,18 +194,17 @@
|
||||
fmt(d.ask, 2) +
|
||||
" · 买一 " +
|
||||
fmt(d.bid, 2) +
|
||||
"<br/>面值 " +
|
||||
fmt(d.contract_size, 4) +
|
||||
" · 可用 " +
|
||||
fmt(d.available_usdt, 2) +
|
||||
" U";
|
||||
" · 面值 " +
|
||||
fmt(d.contract_size, 4);
|
||||
}
|
||||
const sz = $("hp-sizing-line");
|
||||
if (sz) {
|
||||
if (d.full_margin_sizing) {
|
||||
const s = d.full_margin_sizing;
|
||||
sz.textContent =
|
||||
"全仓建议:保证金 " +
|
||||
"全仓建议(" +
|
||||
acctLabel +
|
||||
"):保证金 " +
|
||||
fmt(s.margin_capital, 2) +
|
||||
"U × " +
|
||||
s.leverage +
|
||||
@@ -166,10 +249,15 @@
|
||||
"/api/hedge-plan/options-chain?underlying=" + encodeURIComponent(state.underlying)
|
||||
);
|
||||
state.chain = d;
|
||||
const uly = d.underlying || state.underlying;
|
||||
const idx = $("hp-index-line");
|
||||
if (idx) idx.textContent = "指数 " + fmt(d.index_px, 2) + " · " + (d.inst_family || "");
|
||||
if (idx) {
|
||||
idx.textContent =
|
||||
"指数 " + uly + " " + fmt(d.index_px, 2) + " · " + (d.inst_family || "") + " · 实值含平值 · 虚值=价外";
|
||||
}
|
||||
const ooIdx = $("hp-oo-index");
|
||||
if (ooIdx) ooIdx.textContent = "指数 " + fmt(d.index_px, 2);
|
||||
if (ooIdx) ooIdx.textContent = "指数 " + uly + " " + fmt(d.index_px, 2);
|
||||
setOptionsBalance(d);
|
||||
fillExpSelect($("hp-exp-select"), d);
|
||||
fillExpSelect($("hp-oo-exp-select"), d);
|
||||
renderListStrikes();
|
||||
@@ -194,13 +282,14 @@
|
||||
const dir = ($("hp-direction") && $("hp-direction").value) || "long";
|
||||
const want = optTypeForDirection(dir);
|
||||
const exp = currentExp("hp-exp-select");
|
||||
const prevInst = state.selected && state.selected.inst_id;
|
||||
tbody.innerHTML = "";
|
||||
if (!exp) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="muted">请选择到期日</td></tr>';
|
||||
return;
|
||||
}
|
||||
const list = (exp.contracts || []).filter(function (c) {
|
||||
return String(c.opt_type || "").toUpperCase() === want;
|
||||
return String(c.opt_type || "").toUpperCase() === want && matchesMoneyFilter(c);
|
||||
});
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="muted">无匹配合约</td></tr>';
|
||||
@@ -208,16 +297,21 @@
|
||||
}
|
||||
list.forEach(function (c) {
|
||||
const tr = document.createElement("tr");
|
||||
tr.className = "opt-strike-row" + (c.moneyness ? " opt-row-" + c.moneyness : "");
|
||||
if (prevInst && c.inst_id === prevInst) tr.classList.add("opt-row-selected");
|
||||
tr.setAttribute("data-inst", c.inst_id);
|
||||
tr.innerHTML =
|
||||
"<td>" +
|
||||
c.strike +
|
||||
"</td><td>" +
|
||||
c.opt_type +
|
||||
"</td><td>" +
|
||||
fmt(c.ask, 4) +
|
||||
"</td><td>" +
|
||||
fmt(c.bid, 4) +
|
||||
'</td><td><button type="button" class="btn-secondary hp-pick" data-inst="' +
|
||||
moneynessBadge(c) +
|
||||
'</td><td class="opt-px-sz">' +
|
||||
fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
|
||||
'</td><td class="opt-px-sz">' +
|
||||
fmtPxSz(c.bid, c.bid_sz, false, c.tick_sz) +
|
||||
'</td><td><button type="button" class="btn-secondary hp-pick' +
|
||||
(prevInst && c.inst_id === prevInst ? " active" : "") +
|
||||
'" data-inst="' +
|
||||
c.inst_id +
|
||||
'">选用</button></td>';
|
||||
tbody.appendChild(tr);
|
||||
@@ -232,6 +326,12 @@
|
||||
state.selected = c;
|
||||
const el = $("hp-sel-inst");
|
||||
if (el) el.textContent = c.inst_id;
|
||||
tbody.querySelectorAll(".opt-strike-row").forEach(function (r) {
|
||||
r.classList.toggle("opt-row-selected", r.getAttribute("data-inst") === inst);
|
||||
});
|
||||
tbody.querySelectorAll(".hp-pick").forEach(function (b) {
|
||||
b.classList.toggle("active", b.getAttribute("data-inst") === inst);
|
||||
});
|
||||
updatePremiumLine();
|
||||
});
|
||||
});
|
||||
@@ -247,7 +347,7 @@
|
||||
const ct = Number(state.selected.ct_mult || 0.01);
|
||||
const ask = Number(state.selected.ask || 0);
|
||||
const prem = ask * sheets * ct;
|
||||
line.textContent = "预估权利金 ≈ " + fmt(prem, 4) + " USDC";
|
||||
line.textContent = "预估权利金 ≈ " + fmt(prem, 4) + " USDC(期权账户)";
|
||||
}
|
||||
|
||||
function buildStraddleRows(contracts) {
|
||||
@@ -274,31 +374,37 @@
|
||||
const exp = currentExp("hp-oo-exp-select");
|
||||
tbody.innerHTML = "";
|
||||
if (!exp) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="muted">请选择到期日</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="muted">请选择到期日</td></tr>';
|
||||
return;
|
||||
}
|
||||
const rows = buildStraddleRows(exp.contracts);
|
||||
rows.forEach(function (row) {
|
||||
const tr = document.createElement("tr");
|
||||
const callAsk = row.call ? fmt(row.call.ask, 4) : "—";
|
||||
const putAsk = row.put ? fmt(row.put.ask, 4) : "—";
|
||||
const call = row.call;
|
||||
const put = row.put;
|
||||
const callAsk = call ? fmtPxSz(call.ask, call.ask_sz, call.ask_estimated, call.tick_sz) : "—";
|
||||
const putAsk = put ? fmtPxSz(put.ask, put.ask_sz, put.ask_estimated, put.tick_sz) : "—";
|
||||
tr.innerHTML =
|
||||
"<td>" +
|
||||
'<td class="opt-t-call opt-px-sz">' +
|
||||
callAsk +
|
||||
'</td><td>' +
|
||||
(row.call
|
||||
'</td><td class="opt-t-call">' +
|
||||
(call ? moneynessBadge(call) : "—") +
|
||||
"</td><td>" +
|
||||
(call
|
||||
? '<button type="button" class="btn-secondary hp-oo-pick" data-side="C" data-inst="' +
|
||||
row.call.inst_id +
|
||||
call.inst_id +
|
||||
'">Call</button>'
|
||||
: "—") +
|
||||
'</td><td class="opt-t-strike"><strong>' +
|
||||
row.strike +
|
||||
"</strong></td><td>" +
|
||||
'</strong></td><td class="opt-t-put">' +
|
||||
(put ? moneynessBadge(put) : "—") +
|
||||
'</td><td class="opt-t-put opt-px-sz">' +
|
||||
putAsk +
|
||||
'</td><td>' +
|
||||
(row.put
|
||||
"</td><td>" +
|
||||
(put
|
||||
? '<button type="button" class="btn-secondary hp-oo-pick" data-side="P" data-inst="' +
|
||||
row.put.inst_id +
|
||||
put.inst_id +
|
||||
'">Put</button>'
|
||||
: "—") +
|
||||
"</td>";
|
||||
@@ -334,8 +440,10 @@
|
||||
c.opt_type +
|
||||
" K" +
|
||||
c.strike +
|
||||
" " +
|
||||
(c.moneyness_label || "") +
|
||||
" ask=" +
|
||||
fmt(c.ask, 4) +
|
||||
fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
|
||||
" (" +
|
||||
c.inst_id +
|
||||
")"
|
||||
@@ -355,6 +463,26 @@
|
||||
};
|
||||
}
|
||||
|
||||
function setUnderlying(uly, forceReload) {
|
||||
const next = (uly || "ETH").toUpperCase();
|
||||
const changed = next !== state.underlying;
|
||||
state.underlying = next;
|
||||
syncUnderlyingUI();
|
||||
if (!changed && !forceReload) return;
|
||||
state.selected = null;
|
||||
state.legA = null;
|
||||
state.legB = null;
|
||||
if ($("hp-entry")) $("hp-entry").value = "";
|
||||
if ($("hp-contracts")) $("hp-contracts").value = "";
|
||||
if ($("hp-tp")) $("hp-tp").value = "";
|
||||
if ($("hp-sl")) $("hp-sl").value = "";
|
||||
if ($("hp-target")) $("hp-target").value = "";
|
||||
if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
|
||||
if ($("hp-premium-line")) $("hp-premium-line").textContent = "";
|
||||
if ($("hp-oo-legs")) $("hp-oo-legs").textContent = "尚未选用两腿";
|
||||
void refreshAll();
|
||||
}
|
||||
|
||||
async function runPreview() {
|
||||
const isOo = state.mode === "options_options";
|
||||
const tbody = $(isOo ? "hp-result-tbody-oo" : "hp-result-tbody");
|
||||
@@ -477,19 +605,21 @@
|
||||
});
|
||||
document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) {
|
||||
b.addEventListener("click", function () {
|
||||
state.underlying = b.getAttribute("data-uly") || "ETH";
|
||||
document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (x) {
|
||||
x.classList.toggle("active", x.getAttribute("data-uly") === state.underlying);
|
||||
});
|
||||
state.selected = null;
|
||||
state.legA = null;
|
||||
state.legB = null;
|
||||
void refreshAll();
|
||||
setUnderlying(b.getAttribute("data-uly") || "ETH", true);
|
||||
});
|
||||
});
|
||||
document.querySelectorAll(".hp-money-btn").forEach(function (b) {
|
||||
b.addEventListener("click", function () {
|
||||
state.moneyFilter = b.getAttribute("data-money") || "all";
|
||||
syncMoneyUI();
|
||||
renderListStrikes();
|
||||
});
|
||||
});
|
||||
const dir = $("hp-direction");
|
||||
if (dir) {
|
||||
dir.addEventListener("change", function () {
|
||||
state.selected = null;
|
||||
if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
|
||||
void loadMarket().then(function () {
|
||||
renderListStrikes();
|
||||
});
|
||||
@@ -541,6 +671,8 @@
|
||||
}
|
||||
|
||||
syncTabUI();
|
||||
syncUnderlyingUI();
|
||||
syncMoneyUI();
|
||||
bind();
|
||||
void refreshAll();
|
||||
})();
|
||||
|
||||
@@ -3169,6 +3169,18 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
}
|
||||
|
||||
/* 对冲计划 Tab:高对比选中态 */
|
||||
.hedge-plan-page-wrap {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .card {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
.hedge-plan-page-wrap .card h2,
|
||||
.hedge-plan-page-wrap .hp-title {
|
||||
font-size: 0.9rem;
|
||||
margin: 0 0 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-head-card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -3178,20 +3190,55 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-title {
|
||||
margin: 0;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-title-sub {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-quote-line,
|
||||
.hedge-plan-page-wrap .hp-acct-hint,
|
||||
.hedge-plan-page-wrap #hp-sizing-line,
|
||||
.hedge-plan-page-wrap #hp-gate-line {
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.45;
|
||||
margin: 4px 0 6px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-acct-tag {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.hedge-plan-page-wrap .form-row label,
|
||||
.hedge-plan-page-wrap .form-row select,
|
||||
.hedge-plan-page-wrap .form-row input,
|
||||
.hedge-plan-page-wrap .form-row .btn-secondary,
|
||||
.hedge-plan-page-wrap .form-row .primary {
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .form-row input[type="number"] {
|
||||
max-width: 110px;
|
||||
padding: 4px 6px;
|
||||
min-height: 28px;
|
||||
}
|
||||
.hedge-plan-page-wrap .options-strike-table {
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .options-strike-table th,
|
||||
.hedge-plan-page-wrap .options-strike-table td {
|
||||
padding: 5px 4px;
|
||||
}
|
||||
.hedge-plan-page-wrap .options-strike-table code {
|
||||
font-size: 0.66rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 0 0 10px;
|
||||
margin: 0 0 8px;
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
@@ -3202,9 +3249,9 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: #aeb6c5;
|
||||
font-size: 0.92rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
padding: 9px 16px;
|
||||
padding: 7px 14px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
@@ -3221,6 +3268,31 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 0 2px rgba(142, 182, 255, 0.55), 0 6px 16px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-uly-btn,
|
||||
.hedge-plan-page-wrap .hp-uly-btn-oo,
|
||||
.hedge-plan-page-wrap .hp-money-btn {
|
||||
min-width: 52px;
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: #9aa4b2;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-uly-btn.active,
|
||||
.hedge-plan-page-wrap .hp-uly-btn-oo.active,
|
||||
.hedge-plan-page-wrap .hp-money-btn.active {
|
||||
color: #0b1220;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #9ec0ff 100%);
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 0 2px rgba(100, 160, 255, 0.5);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-money-hint {
|
||||
font-size: 0.68rem;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-pick.active,
|
||||
.hedge-plan-page-wrap .opt-row-selected td {
|
||||
background: rgba(90, 140, 255, 0.18);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-tab-panel.hidden,
|
||||
.hedge-plan-page-wrap .hp-tab-panel[hidden] {
|
||||
display: none !important;
|
||||
@@ -3233,9 +3305,7 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
border: 1px dashed rgba(255, 255, 255, 0.16);
|
||||
color: #9aa4b2;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.hedge-plan-page-wrap #hp-gate-line {
|
||||
margin: 0;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
html[data-theme="light"] .hedge-plan-page-wrap .hp-tabs {
|
||||
background: rgba(15, 23, 42, 0.06);
|
||||
@@ -3252,6 +3322,13 @@ html[data-theme="light"] .hedge-plan-page-wrap .hp-tab.active {
|
||||
border-color: #2f6fed;
|
||||
box-shadow: 0 0 0 2px rgba(47, 111, 237, 0.25);
|
||||
}
|
||||
html[data-theme="light"] .hedge-plan-page-wrap .hp-uly-btn.active,
|
||||
html[data-theme="light"] .hedge-plan-page-wrap .hp-uly-btn-oo.active,
|
||||
html[data-theme="light"] .hedge-plan-page-wrap .hp-money-btn.active {
|
||||
color: #0b1220;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #b9d2ff 100%);
|
||||
border-color: #2f6fed;
|
||||
}
|
||||
html[data-theme="light"] .hedge-plan-page-wrap .hp-placeholder {
|
||||
border-color: rgba(15, 23, 42, 0.18);
|
||||
background: rgba(15, 23, 42, 0.03);
|
||||
|
||||
@@ -49,7 +49,7 @@ def install_hedge_plan(app: Flask, repo_root: str, app_module: Any) -> None:
|
||||
|
||||
|
||||
def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
from lib.exchange.okx_options_lib import build_option_chain
|
||||
from lib.exchange.okx_options_lib import build_option_chain, options_header_balances
|
||||
|
||||
return {
|
||||
"get_db": app_module.get_db,
|
||||
@@ -62,6 +62,7 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
"normalize_exchange_symbol": getattr(app_module, "normalize_exchange_symbol", None),
|
||||
"ensure_markets_loaded": getattr(app_module, "ensure_markets_loaded", None),
|
||||
"build_option_chain": build_option_chain,
|
||||
"options_header_balances": options_header_balances,
|
||||
"btc_leverage": int(getattr(app_module, "BTC_LEVERAGE", 10) or 10),
|
||||
"alt_leverage": int(getattr(app_module, "ALT_LEVERAGE", 5) or 5),
|
||||
"full_margin_buffer": float(getattr(app_module, "FULL_MARGIN_BUFFER_RATIO", 0.98) or 0.98),
|
||||
@@ -69,6 +70,8 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
"options_enabled": _env_bool("OKX_OPTIONS_ENABLED", False),
|
||||
"default_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper(),
|
||||
"chain_max_dte": float(os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS") or os.getenv("OKX_OPTIONS_MAX_DTE_DAYS") or "14"),
|
||||
"perp_account_label": (os.getenv("OKX_ACCOUNT_LABEL") or "合约账户").strip(),
|
||||
"options_account_label": (os.getenv("OKX_OPTIONS_ACCOUNT_LABEL") or "期权账户").strip(),
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +135,9 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
**data,
|
||||
"gates": gates,
|
||||
"sizing_mode": sizing_mode,
|
||||
"account_kind": "perp",
|
||||
"account_label": cfg.get("perp_account_label") or "合约账户",
|
||||
"account_note": "永续腿使用合约(交易)账户可用 USDT",
|
||||
}
|
||||
return jsonify(out)
|
||||
|
||||
@@ -154,7 +160,19 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
)
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": f"拉取期权链失败: {e}"}), 500
|
||||
return jsonify({"ok": True, **chain, "chain_max_dte_days": cfg.get("chain_max_dte")})
|
||||
opt_acct = _options_account_snapshot(cfg)
|
||||
return jsonify(
|
||||
{
|
||||
"ok": True,
|
||||
**chain,
|
||||
"underlying": u,
|
||||
"chain_max_dte_days": cfg.get("chain_max_dte"),
|
||||
"account_kind": "options",
|
||||
"account_label": cfg.get("options_account_label") or "期权账户",
|
||||
"account_note": "期权腿使用期权账户(交易 USDC)",
|
||||
"options_account": opt_acct,
|
||||
}
|
||||
)
|
||||
|
||||
@app.route("/api/hedge-plan/preview", methods=["POST"])
|
||||
@lr
|
||||
@@ -318,6 +336,34 @@ def _fetch_perp_market(cfg: dict[str, Any], base: str) -> tuple[dict[str, Any],
|
||||
}, None
|
||||
|
||||
|
||||
def _options_account_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
||||
"""期权账户资金快照(与期权页同源: exchange_options)."""
|
||||
out: dict[str, Any] = {
|
||||
"label": cfg.get("options_account_label") or "期权账户",
|
||||
"trading_usdc": None,
|
||||
"funding_usdc": None,
|
||||
"trading_usdt": None,
|
||||
"funding_usdt": None,
|
||||
}
|
||||
ex = cfg.get("exchange_options")
|
||||
hdr = cfg.get("options_header_balances")
|
||||
if ex is None or not callable(hdr):
|
||||
return out
|
||||
try:
|
||||
trading_usdc, funding_usdc, funding_usdt, trading_usdt = hdr(ex, force=False)
|
||||
out.update(
|
||||
{
|
||||
"trading_usdc": trading_usdc,
|
||||
"funding_usdc": funding_usdc,
|
||||
"trading_usdt": trading_usdt,
|
||||
"funding_usdt": funding_usdt,
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def _sf(v: Any) -> float | None:
|
||||
if v is None or v == "":
|
||||
return None
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
<button type="button" class="hp-tab" role="tab" aria-selected="false" data-tab="stats">统计分析</button>
|
||||
</div>
|
||||
<p class="muted" id="hp-gate-line"></p>
|
||||
<p class="muted hp-acct-hint" id="hp-acct-hint">永续腿→合约账户 · 期权腿→期权账户</p>
|
||||
</div>
|
||||
|
||||
<div id="hp-tab-perp_options" class="hp-tab-panel" role="tabpanel">
|
||||
<div class="options-dual-grid" id="hp-po-layout">
|
||||
<div class="card">
|
||||
<h2>永续(列表行情)</h2>
|
||||
<div class="form-row">
|
||||
<h2>永续 · <span id="hp-perp-uly-label">ETH</span> <span class="muted hp-acct-tag" id="hp-perp-acct-tag">合约账户</span></h2>
|
||||
<div class="form-row hp-uly-row">
|
||||
<button type="button" class="btn-secondary hp-uly-btn active" data-uly="ETH">ETH</button>
|
||||
<button type="button" class="btn-secondary hp-uly-btn" data-uly="BTC">BTC</button>
|
||||
<select id="hp-direction">
|
||||
@@ -37,7 +38,7 @@
|
||||
<option value="short">做空</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="hp-perp-quote" class="muted" style="margin:8px 0;line-height:1.6">加载中…</div>
|
||||
<div id="hp-perp-quote" class="muted hp-quote-line">加载中…</div>
|
||||
<div class="form-row" style="flex-wrap:wrap">
|
||||
<label>开仓价 <input type="number" step="any" id="hp-entry" /></label>
|
||||
<label>止盈 <input type="number" step="any" id="hp-tp" /></label>
|
||||
@@ -47,20 +48,27 @@
|
||||
<p class="muted" id="hp-sizing-line"></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>期权(列表) · <span id="hp-opt-type-label">Put</span></h2>
|
||||
<h2>期权(列表) · <span id="hp-opt-type-label">Put</span> <span class="muted hp-acct-tag" id="hp-opt-acct-tag">期权账户</span></h2>
|
||||
<div class="form-row">
|
||||
<select id="hp-exp-select"><option value="">选择到期日</option></select>
|
||||
<button type="button" class="btn-secondary" id="hp-load-chain">刷新链</button>
|
||||
</div>
|
||||
<div id="hp-index-line" class="muted"></div>
|
||||
<div class="form-row hp-money-row">
|
||||
<button type="button" class="btn-secondary hp-money-btn active" data-money="all">全部</button>
|
||||
<button type="button" class="btn-secondary hp-money-btn" data-money="itm">实值</button>
|
||||
<button type="button" class="btn-secondary hp-money-btn" data-money="otm">虚值</button>
|
||||
<span class="muted hp-money-hint">实值含平值</span>
|
||||
</div>
|
||||
<div id="hp-index-line" class="muted hp-quote-line"></div>
|
||||
<div id="hp-opt-bal-line" class="muted hp-quote-line"></div>
|
||||
<div class="options-strike-table-wrap">
|
||||
<table class="options-strike-table" id="hp-strike-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>行权价</th>
|
||||
<th>类型</th>
|
||||
<th>实虚值</th>
|
||||
<th>卖一/张</th>
|
||||
<th>买一</th>
|
||||
<th>买一/张</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -108,13 +116,14 @@
|
||||
<div id="hp-tab-options_options" class="hp-tab-panel hidden" role="tabpanel" hidden>
|
||||
<div class="options-dual-grid" id="hp-oo-layout">
|
||||
<div class="card">
|
||||
<h2>期期参数</h2>
|
||||
<div class="form-row">
|
||||
<h2>期期参数 · <span id="hp-oo-uly-label">ETH</span> <span class="muted hp-acct-tag">期权账户</span></h2>
|
||||
<div class="form-row hp-uly-row">
|
||||
<button type="button" class="btn-secondary hp-uly-btn-oo active" data-uly="ETH">ETH</button>
|
||||
<button type="button" class="btn-secondary hp-uly-btn-oo" data-uly="BTC">BTC</button>
|
||||
<label>目标价 S* <input type="number" step="any" id="hp-target" /></label>
|
||||
</div>
|
||||
<div id="hp-oo-index" class="muted"></div>
|
||||
<div id="hp-oo-index" class="muted hp-quote-line"></div>
|
||||
<div id="hp-oo-bal-line" class="muted hp-quote-line"></div>
|
||||
<div id="hp-oo-legs" class="muted" style="margin-top:8px;line-height:1.6">尚未选用两腿</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
@@ -127,18 +136,18 @@
|
||||
<table class="options-strike-table options-strike-table--t" id="hp-oo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2" class="opt-t-head-call">Call</th>
|
||||
<th colspan="3" class="opt-t-head-call">Call</th>
|
||||
<th class="opt-t-head-mid">行权</th>
|
||||
<th colspan="2" class="opt-t-head-put">Put</th>
|
||||
<th colspan="3" class="opt-t-head-put">Put</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>卖一</th><th>选用</th>
|
||||
<th>卖一/张</th><th>实虚值</th><th>选用</th>
|
||||
<th>K</th>
|
||||
<th>卖一</th><th>选用</th>
|
||||
<th>实虚值</th><th>卖一/张</th><th>选用</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="hp-oo-tbody">
|
||||
<tr><td colspan="5" class="muted">请刷新期权链</td></tr>
|
||||
<tr><td colspan="7" class="muted">请刷新期权链</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -186,4 +195,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/hedge_plan.js?v=3"></script>
|
||||
<script src="/static/hedge_plan.js?v=4"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=6">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=81">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=82">
|
||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=81">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=82">
|
||||
|
||||
</head>
|
||||
<body
|
||||
|
||||
Reference in New Issue
Block a user