diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js
index 7eecf8f..e25df2c 100644
--- a/lib/common/static/options_panel.js
+++ b/lib/common/static/options_panel.js
@@ -868,9 +868,46 @@
return Number(v).toFixed(2);
}
- function fmtClosePreview(preview, premiumPaid) {
+ function posPremiumCcy(p) {
+ const ccy = String((p && p.premium_ccy) || "").trim().toUpperCase();
+ if (ccy) return ccy;
+ const mode = String((p && p.margin_mode) || "").toLowerCase();
+ const inst = String((p && p.inst_id) || "");
+ if (mode === "coin" || (inst.indexOf("-USD-") >= 0 && inst.indexOf("_UM") < 0)) {
+ return (inst.split("-")[0] || "ETH").toUpperCase() || "ETH";
+ }
+ if (isCoinMarginMode && isCoinMarginMode()) {
+ return ((inst.split("-")[0]) || "ETH").toUpperCase() || "ETH";
+ }
+ return "USDC";
+ }
+
+ function isCoinPos(p) {
+ return posPremiumCcy(p) !== "USDC";
+ }
+
+ function fmtPremiumAmt(v, ccy) {
+ if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
+ const n = Number(v);
+ const unit = String(ccy || "USDC").toUpperCase();
+ if (unit === "ETH" || unit === "BTC") {
+ let s = n.toFixed(8).replace(/\.?0+$/, "");
+ return s || "0";
+ }
+ return fmtUsdc(n);
+ }
+
+ function fmtPremiumAmtSigned(v, ccy) {
+ if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
+ const n = Number(v);
+ const sign = n > 0 ? "+" : "";
+ return sign + fmtPremiumAmt(n, ccy) + " " + (String(ccy || "USDC").toUpperCase());
+ }
+
+ function fmtClosePreview(preview, premiumPaid, p) {
if (!preview || preview.total_received == null) return "—";
- const recvTxt = fmtUsdc(preview.total_received);
+ const ccy = posPremiumCcy(p);
+ const recvTxt = fmtPremiumAmt(preview.total_received, ccy);
let cls = "";
const prem = Number(premiumPaid);
const recv = Number(preview.total_received);
@@ -878,12 +915,13 @@
if (recv > prem) cls = " pos-pnl-profit";
else if (recv < prem) cls = " pos-pnl-loss";
}
- return '' + recvTxt + " USDC";
+ return '' + recvTxt + " " + ccy + "";
}
- function fmtClosePreviewText(preview) {
+ function fmtClosePreviewText(preview, p) {
if (!preview || preview.total_received == null) return "—";
- let text = fmt(preview.total_received, 4) + " USDC";
+ const ccy = posPremiumCcy(p);
+ let text = fmtPremiumAmt(preview.total_received, ccy) + " " + ccy;
if (preview.covered_sheets != null) {
text += " · 覆盖 " + preview.covered_sheets + "张";
}
@@ -893,11 +931,13 @@
return text;
}
- function fmtPreviewLevels(preview) {
+ function fmtPreviewLevels(preview, p) {
const levels = (preview && preview.levels) || [];
if (!levels.length) return "暂无可用买盘深度";
+ const ccy = posPremiumCcy(p);
return levels.map(function (x) {
- return "买" + x.level + " " + fmt(x.px, 4) + " × " + x.sheets + "张 ≈ " + fmt(x.received, 4) + " USDC";
+ return "买" + x.level + " " + fmt(x.px, 4) + " × " + x.sheets + "张 ≈ " +
+ fmtPremiumAmt(x.received, ccy) + " " + ccy;
}).join("\n");
}
@@ -1555,10 +1595,18 @@
const closePreview = p.close_preview || {};
const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos;
const tickSz = p.tick_sz;
- const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null);
+ const premCcy = posPremiumCcy(p);
+ const coinPos = isCoinPos(p);
+ const premTxt = fmtDisplay(
+ p.premium_paid_fmt,
+ p.premium_paid != null ? fmtPremiumAmt(p.premium_paid, premCcy) : null
+ );
// 优先用数值+tick 现算,避免接口侧 mark_px_fmt 带着浮点毛刺直出
const avgTxt = p.avg_px != null ? fmtOptionPx(p.avg_px, tickSz) : fmtDisplay(p.avg_px_fmt);
const markTxt = p.mark_px != null ? fmtOptionPx(p.mark_px, tickSz) : fmtDisplay(p.mark_px_fmt);
+ const netTxt = closePreview.bid_invalid || net == null
+ ? "—"
+ : (fmtPremiumAmt(net, premCcy) + (coinPos ? (" " + premCcy) : ""));
return (
'
' +
'
' + (p.inst_id || "") + '' +
@@ -1577,21 +1625,21 @@
: "") +
"
" +
'
' +
- '
权利金' + premTxt + " USDC
" +
+ '
权利金' + premTxt + " " + premCcy + "
" +
'
开仓均价' + avgTxt + "
" +
'
标记价' + markTxt + "
" +
'
指数价' + fmt(p.idx_px, 0) + "
" +
'
到期平衡' + fmt(p.expiry_be_px, 0) + "
" +
'
平掉回本' + fmt(p.close_be_px, 0) + "
" +
'
净盈亏' +
- (closePreview.bid_invalid || net == null ? "—" : fmt(net, 2)) + "
" +
+ netTxt + "
" +
'
收益率' +
(closePreview.bid_invalid || roi == null ? "—" : fmt(roi, 2) + "%") + "
" +
'
买盘深度' + fmtCloseLevels(closePreview, tickSz) + "
" +
'
按买盘回收' +
(closePreview.bid_invalid
? '暂无有效买盘'
- : fmtClosePreview(closePreview, p.premium_paid)) + "
" +
+ : fmtClosePreview(closePreview, p.premium_paid, p)) + "
" +
"" +
(function () {
const hint = closeGateHint(closePreview);
@@ -1628,6 +1676,7 @@
);
const statePe = String(p.profit_exit_state || (enabled ? "active" : "idle"));
const req = p.profit_exit_required_recycle;
+ const premCcy = posPremiumCcy(p);
let statusTxt = enabled ? ("监控中 · " + multLabel) : "未开启";
if (enabled && statePe === "closing") statusTxt = "平仓挂单中 · " + multLabel;
return (
@@ -1644,7 +1693,7 @@
'' + statusTxt + "" +
'' +
(enabled
- ? ("1倍=盈利=权利金" + (req != null ? (" · 需回收≥" + fmtUsdc(req)) : ""))
+ ? ("1倍=盈利=权利金" + (req != null ? (" · 需回收≥" + fmtPremiumAmt(req, premCcy) + " " + premCcy) : ""))
: "开启后自选倍数;达标按买一限价平;可随时关闭") +
"" +
""
@@ -1659,16 +1708,23 @@
return null;
}
- function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid) {
+ function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid, indexPx, p) {
const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount);
- const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid);
- const rr = estimateProfitRr(profit, premiumPaid);
+ const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid, indexPx);
+ const rr = estimateProfitRr(profit, premiumPaid, indexPx);
if (value == null && profit == null && rr == null) return "";
+ const coinPos = isCoinPos(p);
+ const valueUnit = coinPos ? " U(估)" : " USDC";
+ const profitTxt = profit == null
+ ? "—"
+ : (coinPos
+ ? ((Number(profit) > 0 ? "+" : "") + fmtUsdc(profit) + " U(估)")
+ : fmtUsdcSigned(profit));
let html = '';
html += '价值' +
- (value == null ? "—" : fmtUsdc(value) + " USDC") + "";
+ (value == null ? "—" : fmtUsdc(value) + valueUnit) + "";
html += '预估盈利' +
- (profit == null ? "—" : fmtUsdcSigned(profit)) + "";
+ profitTxt + "";
html += '盈亏比' +
(rr == null ? "—" : fmtProfitRr(rr)) + "";
html += "";
@@ -1715,7 +1771,7 @@
const ethAmt = posEthAmount(p);
const prem = p.premium_paid;
const estHtml = armed
- ? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem)
+ ? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem, p.idx_px, p)
: '';
return (
'' +
'
委托' +
'
到期
—'
: "") +
- '
' + (net == null ? "—" : fmt(net, 2) + " USDC") + "" +
+ '
' +
+ (net == null ? "—" : (fmtPremiumAmt(net, posPremiumCcy(p)) + " " + posPremiumCcy(p))) + "" +
'
' +
(roi == null ? "—" : fmt(roi, 2) + "%") + "" +
"" +
@@ -2060,12 +2121,20 @@
return;
}
const lv = (preview.levels && preview.levels[0]) || {};
+ const posLike = {
+ inst_id: inst,
+ premium_ccy: q.premium_ccy || (preview.close_gate && preview.close_gate.premium_ccy) || null,
+ margin_mode: q.options_margin_mode || q.margin_mode || null,
+ };
+ const premCcy = posPremiumCcy(posLike);
const msg = [
"按买一限价卖出本轮可平张数?",
"合约: " + inst,
"锁定买一: " + (lv.px != null ? lv.px : "—") + " × " + (lv.sheets != null ? lv.sheets : preview.covered_sheets) + " 张",
- "预计收回: " + fmtClosePreviewText(preview),
- preview.estimated_pnl != null ? "预估盈亏: " + fmt(preview.estimated_pnl, 4) + " USDC" : "",
+ "预计收回: " + fmtClosePreviewText(preview, posLike),
+ preview.estimated_pnl != null
+ ? ("预估盈亏: " + fmtPremiumAmtSigned(preview.estimated_pnl, premCcy))
+ : "",
preview.uncovered_sheets > 0 ? "\n注意: 买一深度不足,预计仍剩 " + preview.uncovered_sheets + " 张,需下次再平。" : ""
].filter(function (x) { return x !== ""; }).join("\n");
if (!confirm(msg)) return;
@@ -2079,7 +2148,9 @@
if (r.ok) {
let okMsg = "买一平仓已提交 " + (r.submitted_sheets || 0) + " 张";
if (r.locked_bid_px != null) okMsg += "\n锁定买一: " + r.locked_bid_px;
- if (r.premium_received != null) okMsg += "\n预估收回: " + fmt(r.premium_received, 4) + " USDC";
+ if (r.premium_received != null) {
+ okMsg += "\n预估收回: " + fmtPremiumAmt(r.premium_received, premCcy) + " " + premCcy;
+ }
if (r.remaining_sheets > 0) okMsg += "\n剩余: " + r.remaining_sheets + " 张(下次再平)";
if (r.stopped_reason) okMsg += "\n状态: " + r.stopped_reason;
alert(okMsg);
diff --git a/lib/common/static/options_position_cards.js b/lib/common/static/options_position_cards.js
index d2010d2..e1079f4 100644
--- a/lib/common/static/options_position_cards.js
+++ b/lib/common/static/options_position_cards.js
@@ -34,6 +34,28 @@
return Number(v).toFixed(2);
}
+ function posPremiumCcy(p) {
+ const ccy = String((p && p.premium_ccy) || "").trim().toUpperCase();
+ if (ccy) return ccy;
+ const mode = String((p && p.margin_mode) || "").toLowerCase();
+ const inst = String((p && p.inst_id) || "");
+ if (mode === "coin" || (inst.indexOf("-USD-") >= 0 && inst.indexOf("_UM") < 0)) {
+ return (inst.split("-")[0] || "ETH").toUpperCase() || "ETH";
+ }
+ return "USDC";
+ }
+
+ function fmtPremiumAmt(v, ccy) {
+ if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
+ const n = Number(v);
+ const unit = String(ccy || "USDC").toUpperCase();
+ if (unit === "ETH" || unit === "BTC") {
+ let s = n.toFixed(8).replace(/\.?0+$/, "");
+ return s || "0";
+ }
+ return fmtUsdc(n);
+ }
+
function optTypeLabel(t) {
return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
}
@@ -136,9 +158,10 @@
return (net / prem) * 100;
}
- function fmtClosePreview(preview, premiumPaid, hub) {
+ function fmtClosePreview(preview, premiumPaid, hub, p) {
if (!preview || preview.total_received == null) return "—";
- const recvTxt = fmtUsdc(preview.total_received);
+ const ccy = posPremiumCcy(p);
+ const recvTxt = fmtPremiumAmt(preview.total_received, ccy);
let cls = "";
const prem = Number(premiumPaid);
const recv = Number(preview.total_received);
@@ -146,7 +169,7 @@
if (recv > prem) cls = " " + pnlCls(1, hub);
else if (recv < prem) cls = " " + pnlCls(-1, hub);
}
- return '
' + recvTxt + " USDC";
+ return '
' + recvTxt + " " + ccy + "";
}
function expiryCdHtml(expMs) {
@@ -168,7 +191,11 @@
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
const closePreview = p.close_preview || {};
const tickSz = p.tick_sz;
- const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null);
+ const premCcy = posPremiumCcy(p);
+ const premTxt = fmtDisplay(
+ p.premium_paid_fmt,
+ p.premium_paid != null ? fmtPremiumAmt(p.premium_paid, premCcy) : null
+ );
const avgTxt = p.avg_px != null ? fmtOptionPx(p.avg_px, tickSz) : fmtDisplay(p.avg_px_fmt);
const markTxt = p.mark_px != null ? fmtOptionPx(p.mark_px, tickSz) : fmtDisplay(p.mark_px_fmt);
let headActions = "";
@@ -182,7 +209,7 @@
const pnlCells = hidePnl
? ""
: '
净盈亏' +
- (net == null ? "—" : fmt(net, 2)) + "
" +
+ (net == null ? "—" : (fmtPremiumAmt(net, premCcy) + (premCcy !== "USDC" ? (" " + premCcy) : ""))) + "
" +
'收益率' +
(roi == null ? "—" : fmt(roi, 2) + "%") + "
";
return (
@@ -202,7 +229,7 @@
: "") +
"" +
'' +
- '
权利金' + premTxt + " USDC
" +
+ '
权利金' + premTxt + " " + premCcy + "
" +
'
开仓均价' + avgTxt + "
" +
'
标记价' + markTxt + "
" +
'
指数价' + fmt(p.idx_px, 0) + "
" +
@@ -213,7 +240,7 @@
'
按买盘回收' +
(closePreview.bid_invalid
? '暂无有效买盘'
- : fmtClosePreview(closePreview, hidePnl ? null : p.premium_paid, hub)) + "
" +
+ : fmtClosePreview(closePreview, hidePnl ? null : p.premium_paid, hub, p)) + "
" +
"" +
(function () {
const hint = closeGateHint(closePreview);
@@ -226,6 +253,7 @@
const strike = Number(p.strike);
const tgt = Number(p.target_index);
const prem = Number(p.premium_paid);
+ const idx = Number(p.idx_px);
let profit = null;
let value = null;
if (Number.isFinite(tgt) && Number.isFinite(strike) && eth > 0) {
@@ -233,10 +261,17 @@
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 (!hidePnl && Number.isFinite(prem)) profit = Math.round((value - prem) * 100) / 100;
+ if (!hidePnl && Number.isFinite(prem)) {
+ let premUsd = prem;
+ if (premCcy !== "USDC" && Number.isFinite(idx) && idx > 0) premUsd = prem * idx;
+ profit = Math.round((value - premUsd) * 100) / 100;
+ }
}
}
- const profitTxt = profit == null ? "—" : ((profit > 0 ? "+" : "") + fmtUsdc(profit) + " USDC");
+ const valueUnit = premCcy !== "USDC" ? " U(估)" : " USDC";
+ const profitTxt = profit == null
+ ? "—"
+ : ((profit > 0 ? "+" : "") + fmtUsdc(profit) + (premCcy !== "USDC" ? " U(估)" : " USDC"));
const profitCls = profit > 0 ? " pnl-pos" : profit < 0 ? " pnl-neg" : "";
const hedgeTarget = p.hedge_plan_target || null;
const managed = hedgeTarget && hedgeTarget.managed_by === "hedge_plan";
@@ -247,7 +282,7 @@
'' +
'' + (managed ? "对冲计划 #" + hedgeTarget.plan_id : "委托") + "" +
'目标 ' + fmt(p.target_index, 1) + "" +
- '价值 ' + (value == null ? "—" : fmtUsdc(value) + " USDC") + "" +
+ '价值 ' + (value == null ? "—" : fmtUsdc(value) + valueUnit) + "" +
profitSpan +
'' +
(managed ? "进行中 · 由对冲计划监控,到位后仅平盈利腿" : "监控中 · 到位按买一限价平") +
diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py
index 3633521..acd878d 100644
--- a/lib/exchange/okx_options_lib.py
+++ b/lib/exchange/okx_options_lib.py
@@ -171,6 +171,21 @@ def format_usdc_amount(v: float | None) -> str | None:
return f"{float(v):.2f}"
+def format_premium_amount(v: float | None, *, ccy: str | None = "USDC") -> str | None:
+ """权利金/回收金额文案:USDC 2 位;币本位 ETH/BTC 最多 8 位去尾零."""
+ if v is None:
+ return None
+ try:
+ n = float(v)
+ except (TypeError, ValueError):
+ return None
+ unit = (ccy or "USDC").strip().upper() or "USDC"
+ if unit in ("ETH", "BTC"):
+ txt = f"{n:.8f}".rstrip("0").rstrip(".")
+ return txt or "0"
+ return f"{n:.2f}"
+
+
def is_option_full_close_history(raw: dict[str, Any]) -> bool:
"""仅保留 OKX 历史仓位中的「全部平仓/强平/ADL 全平」记录,排除部分平仓."""
close_type = str(raw.get("type") or "").strip()
@@ -1797,6 +1812,16 @@ def format_position_row(
ct_mult=ct_mult,
)
exp_time_ms = normalize_option_exp_ms(pos.get("expTime"), inst_id)
+ try:
+ from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
+
+ row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc"
+ underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH"
+ premium_ccy = premium_ccy_for_mode(row_mode, underly)
+ except Exception:
+ row_mode = "usdc"
+ underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH"
+ premium_ccy = "USDC"
return {
"inst_id": inst_id or pos.get("instId"),
"pos": sheets,
@@ -1805,11 +1830,14 @@ def format_position_row(
"mark_px": mark,
"avg_px_fmt": format_option_px(avg, tick_sz) if avg is not None else None,
"mark_px_fmt": format_option_px(mark, tick_sz) if mark is not None else None,
- "premium_paid_fmt": format_usdc_amount(premium_paid),
+ "premium_paid_fmt": format_premium_amount(premium_paid, ccy=premium_ccy),
"tick_sz": tick_sz,
"ct_mult": ct_mult,
"idx_px": idx_px,
"premium_paid": premium_paid,
+ "margin_mode": row_mode,
+ "premium_ccy": premium_ccy,
+ "underlying": underly,
"upl": upl,
"upl_ratio_pct": round(upl_ratio * 100, 2) if upl_ratio is not None else None,
"exp_time": exp_time_ms,
diff --git a/lib/options/options_close_gate_lib.py b/lib/options/options_close_gate_lib.py
index d263db2..743eebe 100644
--- a/lib/options/options_close_gate_lib.py
+++ b/lib/options/options_close_gate_lib.py
@@ -60,6 +60,14 @@ def is_close_gate_passed(inst_id: str) -> bool:
return bool((_gates.get(inst) or {}).get("passed"))
+def _fmt_gate_amt(v: float, *, ccy: str) -> str:
+ unit = (ccy or "USDC").strip().upper() or "USDC"
+ if unit in ("ETH", "BTC"):
+ txt = f"{float(v):.8f}".rstrip("0").rstrip(".")
+ return txt or "0"
+ return f"{float(v):.4f}"
+
+
def update_close_gate(
inst_id: str,
*,
@@ -68,6 +76,7 @@ def update_close_gate(
now: float | None = None,
min_mult: float | None = None,
hold_seconds: float | None = None,
+ premium_ccy: str | None = None,
) -> dict[str, Any]:
"""
根据当前买盘可回收金额刷新门控.
@@ -89,9 +98,16 @@ def update_close_gate(
if hold < 0:
hold = 0.0
+ with _lock:
+ prev_ccy = (_gates.get(inst) or {}).get("premium_ccy")
+ ccy = (premium_ccy or prev_ccy or "USDC").strip().upper() or "USDC"
+ if ccy not in ("ETH", "BTC", "USDC"):
+ ccy = "USDC"
+ need_decimals = 8 if ccy in ("ETH", "BTC") else 4
+
prem = _safe_float(premium_paid)
recv = _safe_float(recycle_usdc)
- need = round(prem * mult, 4) if prem is not None and prem > 0 else None
+ need = round(prem * mult, need_decimals) if prem is not None and prem > 0 else None
recycle_ok = bool(
prem is not None and prem > 0 and recv is not None and need is not None and recv + 1e-12 >= need
)
@@ -117,6 +133,7 @@ def update_close_gate(
"min_mult": mult,
"hold_seconds": hold,
"passed": passed,
+ "premium_ccy": ccy,
}
_gates[inst] = state
@@ -126,10 +143,13 @@ def update_close_gate(
elif recv is None:
msg = "暂无有效买盘可回收金额"
elif not recycle_ok:
- msg = f"可回收 {recv:.4f} USDC < 权利金×{mult:g}({need:.4f}),目标平仓门控未过"
+ msg = (
+ f"可回收 {_fmt_gate_amt(recv, ccy=ccy)} {ccy} < 权利金×{mult:g}"
+ f"({_fmt_gate_amt(need, ccy=ccy)}),目标平仓门控未过"
+ )
elif not ready:
msg = (
- f"可回收已达×{mult:g}({recv:.4f}/{need:.4f}),"
+ f"可回收已达×{mult:g}({_fmt_gate_amt(recv, ccy=ccy)}/{_fmt_gate_amt(need, ccy=ccy)} {ccy}),"
f"需再持续 {remain:.0f}s(已 {held:.0f}/{hold:.0f}s)门控才通过"
)
else:
@@ -144,6 +164,7 @@ def update_close_gate(
"recycle_usdc": recv,
"premium_paid": prem,
"need_recycle_usdc": need,
+ "premium_ccy": ccy,
"min_mult": mult,
"hold_seconds": hold,
"held_seconds": round(held, 1) if recycle_ok else 0.0,
@@ -160,25 +181,39 @@ def check_close_gate(
*,
recycle_usdc: float | None = None,
premium_paid: float | None = None,
+ premium_ccy: str | None = None,
refresh: bool = True,
) -> dict[str, Any]:
"""检查是否允许平仓;默认先用最新回收/权利金刷新."""
inst = (inst_id or "").strip()
if refresh:
- if recycle_usdc is None or premium_paid is None:
+ if recycle_usdc is None or premium_paid is None or premium_ccy is None:
with _lock:
prev = _gates.get(inst) or {}
if recycle_usdc is None:
recycle_usdc = prev.get("recycle")
if premium_paid is None:
premium_paid = prev.get("premium")
- return update_close_gate(inst, recycle_usdc=recycle_usdc, premium_paid=premium_paid)
+ if premium_ccy is None:
+ premium_ccy = prev.get("premium_ccy")
+ return update_close_gate(
+ inst,
+ recycle_usdc=recycle_usdc,
+ premium_paid=premium_paid,
+ premium_ccy=premium_ccy,
+ )
with _lock:
prev = _gates.get(inst)
if not prev:
- return update_close_gate(inst, recycle_usdc=recycle_usdc, premium_paid=premium_paid)
+ return update_close_gate(
+ inst,
+ recycle_usdc=recycle_usdc,
+ premium_paid=premium_paid,
+ premium_ccy=premium_ccy,
+ )
return update_close_gate(
inst,
recycle_usdc=recycle_usdc if recycle_usdc is not None else prev.get("recycle"),
premium_paid=premium_paid if premium_paid is not None else prev.get("premium"),
+ premium_ccy=premium_ccy if premium_ccy is not None else prev.get("premium_ccy"),
)
diff --git a/lib/options/options_db.py b/lib/options/options_db.py
index 377ed08..d7fef4f 100644
--- a/lib/options/options_db.py
+++ b/lib/options/options_db.py
@@ -134,7 +134,8 @@ def sum_open_premium_paid(conn: sqlite3.Connection, inst_id: str) -> float | Non
).fetchone()
if not row or int(row["n"] or 0) < 1:
return None
- return round(float(row["total"] or 0), 4)
+ # 币本位权利金常 <1e-4,保留 8 位避免被裁成 0
+ return round(float(row["total"] or 0), 8)
def sum_open_sheets(conn: sqlite3.Connection, inst_id: str) -> int | None:
diff --git a/lib/options/options_history_lib.py b/lib/options/options_history_lib.py
index e54d868..0dec61a 100644
--- a/lib/options/options_history_lib.py
+++ b/lib/options/options_history_lib.py
@@ -14,14 +14,21 @@ def enrich_position_row_display(
meta_cache: dict[str, dict[str, Any] | None] | None = None,
premium_override: float | None = None,
) -> dict[str, Any]:
- from lib.exchange.okx_options_lib import format_position_row, format_usdc_amount, tick_sz_and_ct_mult
+ from lib.exchange.okx_options_lib import format_position_row, format_premium_amount, tick_sz_and_ct_mult
+ from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
inst_id = str(raw_pos.get("instId") or "").strip()
tick_sz, ct_mult = tick_sz_and_ct_mult(ex, inst_id, meta_cache)
row = format_position_row(raw_pos, ct_mult=ct_mult, tick_sz=tick_sz)
+ row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc"
+ underly = str(row.get("underlying") or (inst_id.split("-")[0] if inst_id else "ETH") or "ETH")
+ premium_ccy = premium_ccy_for_mode(row_mode, underly)
+ row["margin_mode"] = row_mode
+ row["premium_ccy"] = premium_ccy
+ row["margin_mode_label"] = "币本位" if row_mode == "coin" else "USDC"
if premium_override is not None:
row["premium_paid"] = premium_override
- row["premium_paid_fmt"] = format_usdc_amount(premium_override)
+ row["premium_paid_fmt"] = format_premium_amount(row.get("premium_paid"), ccy=premium_ccy)
return row
diff --git a/lib/options/options_positions_lib.py b/lib/options/options_positions_lib.py
index a917f2c..c442411 100644
--- a/lib/options/options_positions_lib.py
+++ b/lib/options/options_positions_lib.py
@@ -51,9 +51,12 @@ def attach_close_preview(
intrinsic_px=intrinsic,
max_levels=1,
)
+ premium_ccy = str(row.get("premium_ccy") or "USDC").strip().upper() or "USDC"
# 残档时不累计 2×门控;有效买一时刷新计时(仅自动平仓需要)
if preview.get("bid_invalid") or preview.get("auto_close_blocked"):
- gate = update_close_gate(inst_id, recycle_usdc=None, premium_paid=paid)
+ gate = update_close_gate(
+ inst_id, recycle_usdc=None, premium_paid=paid, premium_ccy=premium_ccy
+ )
preview["close_gate"] = gate
preview["close_gate_blocked"] = True
preview["close_gate_msg"] = preview.get("bid_invalid_reason") or gate.get("msg")
@@ -64,6 +67,7 @@ def attach_close_preview(
inst_id,
recycle_usdc=_safe_float(preview.get("total_received")),
premium_paid=paid,
+ premium_ccy=premium_ccy,
)
passed = bool(gate.get("passed") or is_close_gate_passed(inst_id) or gate.get("ready"))
preview["close_gate"] = gate
diff --git a/lib/options/options_register.py b/lib/options/options_register.py
index 4c63a5f..1cc100e 100644
--- a/lib/options/options_register.py
+++ b/lib/options/options_register.py
@@ -533,7 +533,21 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
if mode == "close_preview":
paid = _open_premium_paid(cfg, inst_id)
target = sheet_count if sheet_count is not None else 0
- return jsonify(_attach_close_preview(cfg, ex, {**q, "pos": target, "premium_paid": paid}, sheets=target, premium_paid=paid))
+ from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
+
+ row_mode = margin_mode_from_inst_id(inst_id)
+ prem_ccy = premium_ccy_for_mode(row_mode, (inst_id.split("-")[0] if inst_id else "ETH"))
+ preview_row = {
+ **q,
+ "pos": target,
+ "premium_paid": paid,
+ "margin_mode": row_mode,
+ "premium_ccy": prem_ccy,
+ }
+ out = _attach_close_preview(cfg, ex, preview_row, sheets=target, premium_paid=paid)
+ out["options_margin_mode"] = row_mode
+ out["premium_ccy"] = prem_ccy
+ return jsonify(out)
mode, mode_note = _normalize_size_mode(mode)
# 币本位:报价预览走 USDT 预算→估币→张数,禁止再查 USDC