" +
+ "" +
+ (d.base || state.underlying) +
+ " · " +
+ acctLabel +
+ "可用 " +
+ fmt(d.available_usdt, 2) +
+ " U
标记 " +
fmt(d.mark, 2) +
" · 最新 " +
fmt(d.last, 2) +
@@ -110,18 +194,17 @@
fmt(d.ask, 2) +
" · 买一 " +
fmt(d.bid, 2) +
- "
面值 " +
- 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 = '| 请选择到期日 |
';
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 = '| 无匹配合约 |
';
@@ -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 =
"" +
c.strike +
" | " +
- c.opt_type +
- " | " +
- fmt(c.ask, 4) +
- " | " +
- fmt(c.bid, 4) +
- ' | | ' +
+ fmtPxSz(c.bid, c.bid_sz, false, c.tick_sz) +
+ ' | | ';
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 = '| 请选择到期日 |
';
+ tbody.innerHTML = '| 请选择到期日 |
';
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 =
- "" +
+ ' | ' +
callAsk +
- ' | ' +
- (row.call
+ ' | ' +
+ (call ? moneynessBadge(call) : "—") +
+ " | " +
+ (call
? ''
: "—") +
' | ' +
row.strike +
- " | " +
+ ' | ' +
+ (put ? moneynessBadge(put) : "—") +
+ ' | ' +
putAsk +
- ' | ' +
- (row.put
+ " | " +
+ (put
? ''
: "—") +
" | ";
@@ -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();
})();
diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css
index b4e6b56..216cf8d 100644
--- a/lib/common/static/instance_theme.css
+++ b/lib/common/static/instance_theme.css
@@ -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);
diff --git a/lib/hedge_plan/hedge_plan_register.py b/lib/hedge_plan/hedge_plan_register.py
index 9fe91b9..548e3c6 100644
--- a/lib/hedge_plan/hedge_plan_register.py
+++ b/lib/hedge_plan/hedge_plan_register.py
@@ -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
diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html
index 3fb70f9..46a9c86 100644
--- a/lib/hedge_plan/templates/hedge_plan_panel.html
+++ b/lib/hedge_plan/templates/hedge_plan_panel.html
@@ -23,13 +23,14 @@
+ 永续腿→合约账户 · 期权腿→期权账户