diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js index 4c35a65..f296605 100644 --- a/lib/common/static/hedge_plan.js +++ b/lib/common/static/hedge_plan.js @@ -15,12 +15,13 @@ function pickDefaultTab() { if (showPerp) return "perp_options"; if (showOo) return "options_options"; - return "active"; + return "perp_options"; } const state = { tab: pickDefaultTab(), mode: showPerp ? "perp_options" : showOo ? "options_options" : "perp_options", + posTab: "live", underlying: root.getAttribute("data-default-underly") || "ETH", moneyFilter: root.getAttribute("data-option-primary") !== "0" ? "all" : "itm", ooMoneyFilter: "atm_otm", // 期期锁定:平值+虚值 @@ -29,6 +30,7 @@ poStrikeExpandAll: false, // 永期手动选约:默认平值+实3+虚3 chain: null, selected: null, + orderDialogOpen: false, legA: null, legB: null, market: null, @@ -72,7 +74,7 @@ if (isOptionPrimary()) { hint.textContent = (dir === "short" ? "买 Put + 永续多" : "买 Call + 永续空") + - " · 点「选择」填入右侧手动下单;比例默认 2(期权 1 → 永续 0.5)。"; + " · 点「选择」弹窗下单;比例默认 2(期权 1 → 永续 0.5)。"; } else { hint.textContent = dir === "short" ? "永续空 + 保险 Call" : "永续多 + 保险 Put"; } @@ -89,8 +91,11 @@ state.direction = next; syncPoDirUI(); if (!changed && !forceReload) return; - state.selected = null; - if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—"; + if (state.orderDialogOpen) closeOrderDialog(); + else { + state.selected = null; + if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—"; + } void loadMarket().then(function () { renderListStrikes(); }); @@ -681,7 +686,7 @@ if (!btn) return; if (isOptionPrimary()) { btn.textContent = "手动下单"; - btn.title = "按左侧已选合约卖一限价开仓(买期权+对冲永续);非交易所市价单、非条件盯盘"; + btn.title = "弹窗内确认:按已选合约卖一限价开仓(买期权+对冲永续);非交易所市价单"; } else { btn.textContent = "计算"; btn.title = "情景测算后再启动"; @@ -777,13 +782,110 @@ return (Math.round((1 / a) * 10) / 10).toFixed(1) + "×"; } - function clearPoSelection() { + const orderPanelHome = (function () { + const host = document.getElementById("hp-order-panel-host"); + return host ? host.parentElement : null; + })(); + + function orderPanel() { + return $("hp-order-panel"); + } + + function orderPanelHost() { + return $("hp-order-panel-host"); + } + + function parkOrderPanel() { + const panel = orderPanel(); + const host = orderPanelHost(); + if (panel && host && panel.parentElement !== host) host.appendChild(panel); + if (host) { + host.hidden = true; + host.setAttribute("aria-hidden", "true"); + if (orderPanelHome && host.parentElement !== orderPanelHome) { + orderPanelHome.appendChild(host); + } + } + if (panel) panel.style.display = "none"; + state.orderDialogOpen = false; + const tbody = $("hp-strike-tbody"); + if (tbody) { + tbody.querySelectorAll(".opt-strike-row").forEach(function (r) { + r.classList.remove("opt-row-selected"); + }); + tbody.querySelectorAll(".hp-pick").forEach(function (b) { + b.classList.remove("active"); + if (b.dataset.origText) b.textContent = b.dataset.origText; + }); + } + } + + function openOrderDialog(instId) { + const panel = orderPanel(); + const host = orderPanelHost(); + if (!panel || !host) return false; + if (panel.parentElement !== host) host.appendChild(panel); + if (host.parentElement !== document.body) document.body.appendChild(host); + host.hidden = false; + host.setAttribute("aria-hidden", "false"); + panel.style.display = ""; + state.orderDialogOpen = true; + const tbody = $("hp-strike-tbody"); + if (tbody && instId) { + tbody.querySelectorAll(".opt-strike-row").forEach(function (r) { + r.classList.toggle("opt-row-selected", r.getAttribute("data-inst") === instId); + }); + tbody.querySelectorAll(".hp-pick").forEach(function (b) { + if (!b.dataset.origText) b.dataset.origText = "选择"; + const on = b.getAttribute("data-inst") === instId; + b.classList.toggle("active", on); + b.textContent = on ? "已选" : b.dataset.origText; + }); + } + return true; + } + + function closeOrderDialog() { state.selected = null; + parkOrderPanel(); if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—"; updatePremiumLine(); updatePerpPnlHint(); } + function clearPoSelection() { + state.selected = null; + if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—"; + if (state.orderDialogOpen) parkOrderPanel(); + updatePremiumLine(); + updatePerpPnlHint(); + } + + function setHpPosTab(tab) { + const next = tab === "pending" || tab === "history" || tab === "stats" ? tab : "live"; + state.posTab = next; + document.querySelectorAll(".hp-pos-tab").forEach(function (btn) { + const on = btn.getAttribute("data-hp-pos-tab") === next; + btn.classList.toggle("active", on); + btn.setAttribute("aria-selected", on ? "true" : "false"); + }); + document.querySelectorAll("[data-hp-pos-pane]").forEach(function (pane) { + const on = pane.getAttribute("data-hp-pos-pane") === next; + pane.classList.toggle("is-active", on); + if (on) pane.removeAttribute("hidden"); + else pane.setAttribute("hidden", ""); + }); + if (next === "live" || next === "pending") void loadActivePlans(); + else if (next === "history") void loadHistory(); + else if (next === "stats") void loadStats(); + } + + function refreshHpPosPanel() { + void loadActivePlans(); + void loadHistory(); + if (state.posTab === "stats") void loadStats(); + } + /** 手动选约:不自动代选;仅在已选失效(换 Call/Put 或筛掉)时清空. */ function autoMatchPoOption(force) { if (!isOptionPrimary()) return; @@ -873,7 +975,7 @@ b.classList.toggle("active", on); b.setAttribute("aria-selected", on ? "true" : "false"); }); - ["perp_options", "options_options", "active", "history", "stats"].forEach(function (id) { + ["perp_options", "options_options"].forEach(function (id) { const panel = $("hp-tab-" + id); if (!panel) return; const on = id === tab; @@ -884,6 +986,9 @@ if (tab === "perp_options" || tab === "options_options") { state.mode = tab; } + if (tab === "perp_options") { + refreshHpPosPanel(); + } const hint = $("hp-acct-hint"); if (hint) { if (tab === "options_options") { @@ -1472,18 +1577,9 @@ state.selected = c; const el = $("hp-sel-inst"); if (el) el.textContent = c.inst_id; - const tbody = $("hp-strike-tbody"); - if (tbody) { - tbody.querySelectorAll(".opt-strike-row").forEach(function (r) { - r.classList.toggle("opt-row-selected", r.getAttribute("data-inst") === c.inst_id); - }); - tbody.querySelectorAll(".hp-pick").forEach(function (b) { - b.classList.toggle("active", b.getAttribute("data-inst") === c.inst_id); - }); - } updatePremiumLine(); if (isOptionPrimary()) { - // 手动下单以张数为主:选约后若未填张数则默认 1,并同步左右 + // 手动下单以张数为主:选约后若未填张数则默认 1 if (!(orderSheets() > 0)) { if ($("hp-sheets")) $("hp-sheets").value = "1"; if ($("hp-order-sheets")) $("hp-order-sheets").value = "1"; @@ -1491,10 +1587,11 @@ syncSheetsInputs("hp-order-sheets"); if (!(numInput("hp-order-sheets", 0) > 0)) syncSheetsInputs("hp-sheets"); } - void loadMarket(); updatePerpPnlHint(); updatePoOpenSummary(); } + openOrderDialog(c.inst_id); + void loadMarket(); } function renderListStrikes() { @@ -1608,11 +1705,11 @@ if (line) line.textContent = ""; return; } - const sheets = Number(($("hp-sheets") && $("hp-sheets").value) || 1); + const sheets = Math.max(1, orderSheets() || Number(($("hp-sheets") && $("hp-sheets").value) || 1)); 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) + " 币(币本位)"; + line.textContent = "预估权利金 ≈ " + fmt(prem, 4) + " 币(币本位) · 卖一 " + fmt(ask, 4); } function buildStraddleRows(contracts) { @@ -1881,7 +1978,8 @@ state.underlying = next; syncUnderlyingUI(); if (!changed && !forceReload) return; - state.selected = null; + if (state.orderDialogOpen) closeOrderDialog(); + else state.selected = null; state.legA = null; state.legB = null; state.ooRecommend = null; @@ -2133,18 +2231,36 @@ if (state.tab === "perp_options" || state.tab === "options_options") { void loadGates(); if (state.tab === "perp_options") void refreshPoStrategyStatus(); - } else if (state.tab === "active") { - void loadActivePlans(); - } else if (state.tab === "history") { - void loadHistory(); - } else if (state.tab === "stats") { - void loadStats(); } else { const el = $("hp-gate-line"); if (el) el.textContent = ""; } }); }); + document.querySelectorAll(".hp-pos-tab").forEach(function (btn) { + btn.addEventListener("click", function () { + setHpPosTab(btn.getAttribute("data-hp-pos-tab") || "live"); + }); + }); + if ($("hp-pos-refresh")) { + $("hp-pos-refresh").addEventListener("click", function () { + refreshHpPosPanel(); + }); + } + function onOrderDialogClose() { + closeOrderDialog(); + } + if ($("hp-order-close-btn")) $("hp-order-close-btn").addEventListener("click", onOrderDialogClose); + if ($("hp-order-cancel-btn")) $("hp-order-cancel-btn").addEventListener("click", onOrderDialogClose); + const orderHost = orderPanelHost(); + if (orderHost) { + orderHost.addEventListener("click", function (ev) { + if (ev.target === orderHost) closeOrderDialog(); + }); + } + document.addEventListener("keydown", function (ev) { + if (ev.key === "Escape" && state.orderDialogOpen) closeOrderDialog(); + }); document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) { b.addEventListener("click", function () { setUnderlying(b.getAttribute("data-uly") || "ETH", true); @@ -2617,65 +2733,92 @@ return map[st] || st || "—"; } - async function loadActivePlans() { - const tbody = $("hp-active-tbody"); + function bindActivePlanRowActions(tbody) { if (!tbody) return; + tbody.querySelectorAll(".hp-btn-detail").forEach(function (btn) { + btn.addEventListener("click", function () { + void showPlanDetail(Number(btn.getAttribute("data-id"))); + }); + }); + tbody.querySelectorAll(".hp-btn-complete").forEach(function (btn) { + btn.addEventListener("click", function () { + void completeMissingLeg(Number(btn.getAttribute("data-id"))); + }); + }); + tbody.querySelectorAll(".hp-btn-end").forEach(function (btn) { + btn.addEventListener("click", function () { + void endActivePlan(Number(btn.getAttribute("data-id"))); + }); + }); + } + + function isPendingPlan(p) { + const st = String((p && p.status) || "").toLowerCase(); + return st === "watching" || st === "pending"; + } + + function renderActivePlanRows(tbody, rows, emptyMsg, colMode) { + if (!tbody) return; + if (!rows.length) { + tbody.innerHTML = '' + (emptyMsg || "暂无") + ""; + return; + } + tbody.innerHTML = ""; + rows.forEach(function (p) { + const tr = document.createElement("tr"); + const typeLabel = p.plan_type === "perp_options" ? "永期" : "期期"; + const contracts = p.contracts_summary || "—"; + const midCol = + colMode === "pending" + ? p.note || activeTargetLabel(p) || "盯盘/待成交" + : activeTargetLabel(p); + tr.innerHTML = + "#" + + p.id + + "" + + typeLabel + + "" + + (p.underlying || "") + + "" + + contracts + + "" + + activeStatusLabel(p) + + "" + + midCol + + "" + + (p.opened_at || "—") + + '' + + completeLegButtonHtml(p) + + ' ' + + ''; + tbody.appendChild(tr); + }); + bindActivePlanRowActions(tbody); + } + + async function loadActivePlans() { + const liveBody = $("hp-active-tbody"); + const pendingBody = $("hp-pending-tbody"); + if (!liveBody && !pendingBody) return; try { const d = await apiJson("/api/hedge-plan/active"); const rows = d.plans || []; - if (!rows.length) { - tbody.innerHTML = '暂无进行中的计划'; - return; - } - tbody.innerHTML = ""; - rows.forEach(function (p) { - const tr = document.createElement("tr"); - const typeLabel = p.plan_type === "perp_options" ? "永期" : "期期"; - const contracts = p.contracts_summary || "—"; - tr.innerHTML = - "#" + - p.id + - "" + - typeLabel + - "" + - (p.underlying || "") + - "" + - contracts + - "" + - activeStatusLabel(p) + - "" + - activeTargetLabel(p) + - "" + - (p.opened_at || "—") + - '' + - completeLegButtonHtml(p) + - ' ' + - ''; - tbody.appendChild(tr); - }); - tbody.querySelectorAll(".hp-btn-detail").forEach(function (btn) { - btn.addEventListener("click", function () { - void showPlanDetail(Number(btn.getAttribute("data-id"))); - }); - }); - tbody.querySelectorAll(".hp-btn-complete").forEach(function (btn) { - btn.addEventListener("click", function () { - void completeMissingLeg(Number(btn.getAttribute("data-id"))); - }); - }); - tbody.querySelectorAll(".hp-btn-end").forEach(function (btn) { - btn.addEventListener("click", function () { - void endActivePlan(Number(btn.getAttribute("data-id"))); - }); + const pending = rows.filter(isPendingPlan); + const live = rows.filter(function (p) { + return !isPendingPlan(p); }); + renderActivePlanRows(liveBody, live, "暂无当前持仓", "live"); + renderActivePlanRows(pendingBody, pending, "暂无当前委托", "pending"); } catch (e) { - tbody.innerHTML = '' + (e.message || e) + ""; + const msg = '' + (e.message || e) + ""; + if (liveBody) liveBody.innerHTML = msg; + if (pendingBody) pendingBody.innerHTML = msg; } } @@ -2832,7 +2975,7 @@ try { await apiJson("/api/hedge-plan/" + planId, { method: "DELETE" }); await loadHistory(); - if (state.tab === "stats") await loadStats(); + if (state.posTab === "stats") await loadStats(); } catch (e) { window.alert(e.message || String(e)); } @@ -3107,16 +3250,14 @@ closePreviewModal(); const refreshHint = d.refresh && d.refresh.msg ? "\n" + String(d.refresh.msg) : ""; + if (state.orderDialogOpen) closeOrderDialog(); if (d.partial) { alert( (d.msg || "半腿失败,已挂待补") + (d.plan_id ? "\n计划 #" + d.plan_id : "") + refreshHint + - "\n请到「进行中的计划」补开缺失腿" + "\n请到右侧「当前持仓」补开缺失腿" ); - state.tab = "active"; - syncTabUI(); - void loadActivePlans(); } else { alert( "计划已启动 #" + @@ -3125,6 +3266,12 @@ refreshHint ); } + if (showPerp) { + state.tab = "perp_options"; + syncTabUI(); + setHpPosTab(d.partial ? "live" : "live"); + } + refreshHpPosPanel(); void loadGates(); if (isOo) void loadChain(); } catch (e) { @@ -3164,10 +3311,18 @@ updateOoBudgetLine(null); bind(); syncOptionPrimaryUI(); + setHpPosTab(state.posTab || "live"); + parkOrderPanel(); void refreshAll().then(function () { void refreshPoStrategyStatus(); + refreshHpPosPanel(); }); setInterval(function () { if (state.tab === "perp_options" && isOptionPrimary()) void refreshPoStrategyStatus(); }, 15000); + setInterval(function () { + if (state.tab === "perp_options" && (state.posTab === "live" || state.posTab === "pending")) { + void loadActivePlans(); + } + }, 20000); })(); diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index e45bc7a..ae69829 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -3911,6 +3911,29 @@ html[data-theme="light"] .opt-be-dist-down { .hedge-plan-page-wrap #hp-po-layout > .card { align-self: stretch; } +.hedge-plan-page-wrap .hp-po-pos-card { + min-height: 0; +} +.hedge-plan-page-wrap .hp-po-pos-card .options-pos-tab-body { + flex: 1 1 auto; + min-height: 240px; +} +.hedge-plan-page-wrap .hp-po-pos-card .options-pos-pane { + overflow: auto; +} +.hedge-plan-page-wrap .hp-po-order-dialog .hp-po-section-title { + margin: 0 0 6px; + font-size: 0.85rem; +} +.hedge-plan-page-wrap .hp-po-order-dialog .hp-po-fields--section { + margin-bottom: 4px; +} +.hedge-plan-page-wrap .hp-pos-tab.active, +.hedge-plan-page-wrap .opt-pos-tab.active { + border-color: #5b8cff; + color: #cfe0ff; + background: rgba(74, 124, 255, 0.28); +} .hedge-plan-page-wrap .hp-po-left-stack, .hedge-plan-page-wrap .hp-po-right-stack { display: flex; diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html index 990f718..6e67db8 100644 --- a/lib/hedge_plan/templates/hedge_plan_panel.html +++ b/lib/hedge_plan/templates/hedge_plan_panel.html @@ -18,7 +18,7 @@
期权模块未启用,无法拉期权链.请先配置期权账户.
{% endif %} {% if hedge_plan_enabled and not hedge_plan_show_perp_options and not hedge_plan_show_options_options %} -
永期与期期 Tab 均已隐藏:请在 env「期权/对冲模式」切换交易模式;进行中/历史仍可查看.
+
永期与期期 Tab 均已隐藏:请在 env「期权/对冲模式」切换交易模式.
{% endif %}
@@ -35,9 +35,6 @@ {% if hedge_plan_show_options_options %} {% endif %} - - -

永续腿→合约账户 · 期权腿→期权账户

@@ -45,163 +42,220 @@
- -
-
-
-

手动选约 · Call 币本位

-
- - - - - - - - - - - - - - - - 指数 — + +
+

手动选约 · Call 币本位

+
+ + + + + + + + + + + + + + + + 指数 — +
+

买 Call + 永续空 · 点「选择」弹窗下单;比例默认 2(期权 1 → 永续 0.5)。

+ + + + + + +
+ + + + + + + + + + + + + + + +
行权价类型合约卖一/张杠杆买一/张操作
请刷新期权链
+
+
+ + + - -
-

- 手动下单 - 下单 - · ETH - 合约账户 -

-
- 规则说明 -
-

手动下单:左侧人工选约,右侧填张数;开仓按卖一限价。先开期权(按张数买币并+5%滑点),再开永续。

-

组合:买 Call → 永续空;买 Put → 永续多。永续杠杆取 env:BTC=BTC_LEVERAGE(默认10)/ETH=ALT_LEVERAGE(默认5)。

-

出场:盈利金额(扣开平双边手续费后净利)或目标位点数二选一。平仓先期权(须买一流动性)再永续,再把币全部卖回 USDT。一账户同时仅一笔对冲仓。

-

比例:默认 2(期权名义 ÷ 比例 = 永续名义)。

+ + +
+
+

持仓

+
+
-
-
-

永续行情

-
- 标记 — +
+
+ + + + +
+
+
+

进行中的对冲计划(持仓中 / 半腿待补)

+
+ + + + + + + + + +
ID类型标的合约状态目标/止盈止损开仓操作
加载中…
+
-

加载中…

- -
- -
-
-

开仓

-
- - - +
-
-

出场条件

-
- - +
+
-
-
-
-
-
-
- - +
+
@@ -321,52 +375,6 @@
- - - - - - - +