Fix max option sheets: shared trading USDT = buy premium + perp margin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 15:53:12 +08:00
parent e64c5ab322
commit ab1582ed88
+61 -36
View File
@@ -506,46 +506,50 @@
return Number.isFinite(eth) && eth > 0 ? eth : 5; return Number.isFinite(eth) && eth > 0 ? eth : 5;
} }
/** 按交易账户 USDT(买币+5%滑点)与合约账户永续杠杆算最大期权张数. */ /**
* 期权开仓最大张数:OKX 交易账户 USDT 共用.
* 每张开仓需:买币(权利金×1.05×指数) + 永续保证金(对冲名义÷杠杆);
* 选最大 N 时须保证扣完买币后剩余仍够永续保证金.
*/
function computeMaxSheets() { function computeMaxSheets() {
const ask = state.selected && Number(state.selected.ask); const ask = state.selected && Number(state.selected.ask);
const ct = Number((state.selected && state.selected.ct_mult) || 0.01); const ct = Number((state.selected && state.selected.ct_mult) || 0.01);
const ratio = numInput("hp-opt-perp-ratio", 2); const ratio = numInput("hp-opt-perp-ratio", 2);
const cs = Number((state.market && state.market.contract_size) || 0.01); const mark = Number(
const mark = Number((state.market && (state.market.mark || state.market.index_px)) || indexPx() || 0); (state.market && (state.market.mark || state.market.index_px)) || indexPx() || 0
);
const lev = defaultPerpLeverage(state.underlying); const lev = defaultPerpLeverage(state.underlying);
const tradingUsdt = Number(state.tradingUsdt != null ? state.tradingUsdt : state.tradingUsdc); const tradingUsdt = Number(
const perpAvail = Number((state.market && state.market.available_usdt) || 0); state.tradingUsdt != null ? state.tradingUsdt : state.tradingUsdc
if (!(ask > 0) || !(ct > 0) || !(ratio > 0) || !(cs > 0) || !(mark > 0) || !(lev > 0)) { );
if (!(ask > 0) || !(ct > 0) || !(ratio > 0) || !(mark > 0) || !(lev > 0)) {
return null; return null;
} }
// 买币:权利金币×1.05×指数 ≈ 所需 USDT if (!(tradingUsdt > 0)) {
const coinPerSheet = ask * ct; return {
const usdtPerSheet = coinPerSheet * 1.05 * mark; max_sheets: 0,
let maxByOpt = Infinity; lev: lev,
if (tradingUsdt > 0 && usdtPerSheet > 0) { trading_usdt: tradingUsdt,
maxByOpt = Math.floor(tradingUsdt / usdtPerSheet + 1e-12); buy_usdt_per_sheet: 0,
margin_per_sheet: 0,
total_per_sheet: 0,
};
} }
// 永续:名义 = 保证金×杠杆; 永续币 = 期权币/比例; 合约张 = 永续币/cs // 买币 USDT/张 = 权利金币 × 1.05 × 指数
// 期权张 n → 永续名义 = (n*ct/ratio)*mark ≤ avail*lev const buyUsdtPerSheet = ask * ct * 1.05 * mark;
let maxByPerp = Infinity; // 永续名义/张 = (期权币/比例)×指数; 保证金 = 名义÷杠杆
if (perpAvail > 0) { const perpNotionalPerSheet = (ct / ratio) * mark;
const maxPerpNotional = perpAvail * lev; const marginPerSheet = perpNotionalPerSheet / lev;
const notionalPerSheet = (ct / ratio) * mark; const totalPerSheet = buyUsdtPerSheet + marginPerSheet;
if (notionalPerSheet > 0) { if (!(totalPerSheet > 0)) return null;
maxByPerp = Math.floor(maxPerpNotional / notionalPerSheet + 1e-12); const maxSheets = Math.max(0, Math.floor(tradingUsdt / totalPerSheet + 1e-12));
}
}
const maxSheets = Math.max(0, Math.min(maxByOpt, maxByPerp));
if (!(maxSheets > 0) && !(tradingUsdt > 0) && !(perpAvail > 0)) return null;
return { return {
max_sheets: maxSheets, max_sheets: maxSheets,
max_by_opt: Number.isFinite(maxByOpt) ? maxByOpt : null,
max_by_perp: Number.isFinite(maxByPerp) ? maxByPerp : null,
lev: lev, lev: lev,
trading_usdt: tradingUsdt, trading_usdt: tradingUsdt,
perp_avail: perpAvail, buy_usdt_per_sheet: buyUsdtPerSheet,
usdt_per_sheet: usdtPerSheet, margin_per_sheet: marginPerSheet,
total_per_sheet: totalPerSheet,
}; };
} }
@@ -556,23 +560,37 @@
const sized = currentOpSizing(); const sized = currentOpSizing();
const coin = (state.underlying || "ETH").toUpperCase(); const coin = (state.underlying || "ETH").toUpperCase();
const lev = defaultPerpLeverage(state.underlying); const lev = defaultPerpLeverage(state.underlying);
const mark = Number(
(state.market && (state.market.mark || state.market.index_px)) || indexPx() || 0
);
if (maxEl) { if (maxEl) {
if (maxInfo) { if (maxInfo) {
maxEl.innerHTML = maxEl.innerHTML =
"最大约 <strong>" + "期权开仓最大 <strong>" +
maxInfo.max_sheets + maxInfo.max_sheets +
"</strong> 张 · 永续杠杆 " + "</strong> 张 · 交易 USDT " +
lev +
"x(env) · 交易 USDT " +
fmt(maxInfo.trading_usdt, 2) + fmt(maxInfo.trading_usdt, 2) +
" · 合约可用 " + " · 每张约买币 " +
fmt(maxInfo.perp_avail, 2); fmt(maxInfo.buy_usdt_per_sheet, 2) +
" + 永续保证金 " +
fmt(maxInfo.margin_per_sheet, 2) +
" (杠杆 " +
lev +
"x)";
} else { } else {
maxEl.textContent = "选择期权并刷新行情后显示最大开仓张数(交易账户买币+永续杠杆)"; maxEl.textContent =
"选择期权并刷新后显示:期权开仓最大张数(扣买币后剩余须够永续保证金)";
} }
} }
if (perpEl) { if (perpEl) {
if (sized) { if (sized) {
const buyUsdt = sized.premium_est * 1.05 * (mark > 0 ? mark : 0);
const marginUsdt =
sized.perp_qty * (mark > 0 ? mark : 0) / (lev > 0 ? lev : 1);
const remain =
Number(state.tradingUsdt != null ? state.tradingUsdt : state.tradingUsdc) -
buyUsdt;
const marginOk = remain + 1e-9 >= marginUsdt;
perpEl.innerHTML = perpEl.innerHTML =
"永续自动开 <strong>" + "永续自动开 <strong>" +
fmt(sized.perp_qty, 4) + fmt(sized.perp_qty, 4) +
@@ -584,7 +602,14 @@
fmt(sized.premium_est * 1.05, 4) + fmt(sized.premium_est * 1.05, 4) +
" " + " " +
coin + coin +
"(含5%滑点)"; "(≈" +
fmt(buyUsdt, 2) +
" U含5%) · 扣后剩 " +
fmt(remain, 2) +
" U · 永续保证金约 " +
fmt(marginUsdt, 2) +
" U" +
(marginOk ? "" : ' <span class="is-err">不足</span>');
} else { } else {
perpEl.textContent = "永续开仓 — (填张数并选择期权后按比例自动算)"; perpEl.textContent = "永续开仓 — (填张数并选择期权后按比例自动算)";
} }