永期手动下单:修币本位杠杆显示,张数定仓与双出场模式.
右侧以张数+比例算永续,杠杆取 env(BTC10/ETH5);开仓买币+5%滑点;出场可选盈利金额(扣双边费)或目标位;平仓后卖光币回 U. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+198
-24
@@ -39,6 +39,7 @@
|
||||
ooCloseMode: "close_all",
|
||||
direction: "long",
|
||||
optionPrimary: root.getAttribute("data-option-primary") !== "0",
|
||||
exitMode: "profit", // profit=盈利金额出场 | target=目标位出场
|
||||
opLevTouched: false,
|
||||
opRatioTouched: false,
|
||||
perpLevTouched: false,
|
||||
@@ -449,9 +450,24 @@
|
||||
};
|
||||
}
|
||||
|
||||
function orderSheets() {
|
||||
const right = numInput("hp-order-sheets", 0);
|
||||
if (right > 0) return Math.floor(right);
|
||||
const left = numInput("hp-sheets", 0);
|
||||
return left > 0 ? Math.floor(left) : 0;
|
||||
}
|
||||
|
||||
function syncSheetsInputs(fromId) {
|
||||
const n = numInput(fromId, 0);
|
||||
if (!(n > 0)) return;
|
||||
const v = String(Math.floor(n));
|
||||
if (fromId !== "hp-sheets" && $("hp-sheets")) $("hp-sheets").value = v;
|
||||
if (fromId !== "hp-order-sheets" && $("hp-order-sheets")) $("hp-order-sheets").value = v;
|
||||
}
|
||||
|
||||
function currentOpSizing() {
|
||||
if (!state.selected) return null;
|
||||
const sheets = numInput("hp-sheets", 0);
|
||||
const sheets = orderSheets();
|
||||
return computeOpSizing(
|
||||
state.selected.ask,
|
||||
state.selected.ct_mult || 0.01,
|
||||
@@ -489,6 +505,112 @@
|
||||
return Number.isFinite(eth) && eth > 0 ? eth : 5;
|
||||
}
|
||||
|
||||
/** 按交易账户 USDT(买币+5%滑点)与合约账户永续杠杆算最大期权张数. */
|
||||
function computeMaxSheets() {
|
||||
const ask = state.selected && Number(state.selected.ask);
|
||||
const ct = Number((state.selected && state.selected.ct_mult) || 0.01);
|
||||
const ratio = numInput("hp-opt-perp-ratio", 2);
|
||||
const cs = Number((state.market && state.market.contract_size) || 0.01);
|
||||
const mark = Number((state.market && (state.market.mark || state.market.index_px)) || indexPx() || 0);
|
||||
const lev = defaultPerpLeverage(state.underlying);
|
||||
const tradingUsdt = Number(state.tradingUsdt != null ? state.tradingUsdt : state.tradingUsdc);
|
||||
const perpAvail = Number((state.market && state.market.available_usdt) || 0);
|
||||
if (!(ask > 0) || !(ct > 0) || !(ratio > 0) || !(cs > 0) || !(mark > 0) || !(lev > 0)) {
|
||||
return null;
|
||||
}
|
||||
// 买币:权利金币×1.05×指数 ≈ 所需 USDT
|
||||
const coinPerSheet = ask * ct;
|
||||
const usdtPerSheet = coinPerSheet * 1.05 * mark;
|
||||
let maxByOpt = Infinity;
|
||||
if (tradingUsdt > 0 && usdtPerSheet > 0) {
|
||||
maxByOpt = Math.floor(tradingUsdt / usdtPerSheet + 1e-12);
|
||||
}
|
||||
// 永续:名义 = 保证金×杠杆; 永续币 = 期权币/比例; 合约张 = 永续币/cs
|
||||
// 期权张 n → 永续名义 = (n*ct/ratio)*mark ≤ avail*lev
|
||||
let maxByPerp = Infinity;
|
||||
if (perpAvail > 0) {
|
||||
const maxPerpNotional = perpAvail * lev;
|
||||
const notionalPerSheet = (ct / ratio) * mark;
|
||||
if (notionalPerSheet > 0) {
|
||||
maxByPerp = Math.floor(maxPerpNotional / notionalPerSheet + 1e-12);
|
||||
}
|
||||
}
|
||||
const maxSheets = Math.max(0, Math.min(maxByOpt, maxByPerp));
|
||||
if (!(maxSheets > 0) && !(tradingUsdt > 0) && !(perpAvail > 0)) return null;
|
||||
return {
|
||||
max_sheets: maxSheets,
|
||||
max_by_opt: Number.isFinite(maxByOpt) ? maxByOpt : null,
|
||||
max_by_perp: Number.isFinite(maxByPerp) ? maxByPerp : null,
|
||||
lev: lev,
|
||||
trading_usdt: tradingUsdt,
|
||||
perp_avail: perpAvail,
|
||||
usdt_per_sheet: usdtPerSheet,
|
||||
};
|
||||
}
|
||||
|
||||
function updatePoOpenSummary() {
|
||||
const maxEl = $("hp-po-max-line");
|
||||
const perpEl = $("hp-po-perp-auto");
|
||||
const maxInfo = computeMaxSheets();
|
||||
const sized = currentOpSizing();
|
||||
const coin = (state.underlying || "ETH").toUpperCase();
|
||||
const lev = defaultPerpLeverage(state.underlying);
|
||||
if (maxEl) {
|
||||
if (maxInfo) {
|
||||
maxEl.innerHTML =
|
||||
"最大约 <strong>" +
|
||||
maxInfo.max_sheets +
|
||||
"</strong> 张 · 永续杠杆 " +
|
||||
lev +
|
||||
"x(env) · 交易 USDT " +
|
||||
fmt(maxInfo.trading_usdt, 2) +
|
||||
" · 合约可用 " +
|
||||
fmt(maxInfo.perp_avail, 2);
|
||||
} else {
|
||||
maxEl.textContent = "选择期权并刷新行情后显示最大开仓张数(交易账户买币+永续杠杆)";
|
||||
}
|
||||
}
|
||||
if (perpEl) {
|
||||
if (sized) {
|
||||
perpEl.innerHTML =
|
||||
"永续自动开 <strong>" +
|
||||
fmt(sized.perp_qty, 4) +
|
||||
"</strong> " +
|
||||
coin +
|
||||
" / <strong>" +
|
||||
fmt(sized.contracts, coin === "BTC" ? 4 : 2) +
|
||||
"</strong> 张 · 买币约 " +
|
||||
fmt(sized.premium_est * 1.05, 4) +
|
||||
" " +
|
||||
coin +
|
||||
"(含5%滑点)";
|
||||
} else {
|
||||
perpEl.textContent = "永续开仓 — (填张数并选择期权后按比例自动算)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncExitModeUI() {
|
||||
const mode = state.exitMode === "target" ? "target" : "profit";
|
||||
document.querySelectorAll(".hp-po-exit-mode").forEach(function (b) {
|
||||
const on = (b.getAttribute("data-exit") || "") === mode;
|
||||
b.classList.toggle("is-selected", on);
|
||||
b.classList.toggle("active", on);
|
||||
});
|
||||
const profitBox = $("hp-po-exit-profit");
|
||||
const targetBox = $("hp-po-exit-target");
|
||||
if (profitBox) {
|
||||
profitBox.classList.toggle("hidden", mode !== "profit");
|
||||
if (mode === "profit") profitBox.removeAttribute("hidden");
|
||||
else profitBox.setAttribute("hidden", "hidden");
|
||||
}
|
||||
if (targetBox) {
|
||||
targetBox.classList.toggle("hidden", mode !== "target");
|
||||
if (mode === "target") targetBox.removeAttribute("hidden");
|
||||
else targetBox.setAttribute("hidden", "hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function syncUnderlyingUI() {
|
||||
const uly = state.underlying || "ETH";
|
||||
document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) {
|
||||
@@ -622,12 +744,11 @@
|
||||
return (Math.round((k / a) * 10) / 10).toFixed(1) + "×";
|
||||
}
|
||||
|
||||
/** 永期期权行情杠杆:指数÷卖一(与选约杠杆门一致). */
|
||||
/** CoinOX 币本位:卖一为币报价 → 杠杆≈1÷卖一(与期权页一致). */
|
||||
function calcIndexAskLeverage(ask) {
|
||||
const idx = indexPx();
|
||||
const a = Number(ask);
|
||||
if (!(idx > 0) || !(a > 0)) return "—";
|
||||
return (Math.round((idx / a) * 10) / 10).toFixed(1) + "×";
|
||||
if (!(a > 0)) return "—";
|
||||
return (Math.round((1 / a) * 10) / 10).toFixed(1) + "×";
|
||||
}
|
||||
|
||||
function clearPoSelection() {
|
||||
@@ -800,6 +921,11 @@
|
||||
if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
||||
state.tradingUsdc = Number(acct.trading_usdc);
|
||||
}
|
||||
if (acct.trading_usdt != null && acct.trading_usdt !== "") {
|
||||
state.tradingUsdt = Number(acct.trading_usdt);
|
||||
} else if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
||||
state.tradingUsdt = Number(acct.trading_usdc);
|
||||
}
|
||||
if (acct.funding_usdc != null && acct.funding_usdc !== "") {
|
||||
state.fundingUsdc = Number(acct.funding_usdc);
|
||||
}
|
||||
@@ -1174,12 +1300,20 @@
|
||||
const label = $("hp-opt-type-label");
|
||||
if (label) label.textContent = d.suggested_opt_type === "C" ? "Call" : "Put";
|
||||
updatePerpPnlHint();
|
||||
updatePoOpenSummary();
|
||||
}
|
||||
|
||||
function updatePerpPnlHint() {
|
||||
const el = $("hp-perp-pnl-line");
|
||||
if (!el) return;
|
||||
if (isOptionPrimary()) {
|
||||
if (state.exitMode === "profit") {
|
||||
const p = numInput("hp-profit-target", NaN);
|
||||
el.innerHTML = p > 0
|
||||
? '目标净利 <strong>' + fmt(p, 2) + '</strong> USDT <span class="muted">(扣开平双边手续费后)</span>'
|
||||
: '<span class="muted">填写目标盈利金额(扣买入+卖出手续费后的组合净利)</span>';
|
||||
return;
|
||||
}
|
||||
const n = numInput("hp-opt-target-pts", NaN);
|
||||
const m = numInput("hp-perp-target-pts", NaN);
|
||||
const k = state.selected && Number(state.selected.strike);
|
||||
@@ -1195,7 +1329,7 @@
|
||||
(optT != null ? fmt(optT, 2) : "—") +
|
||||
" · 永续目标指数 " +
|
||||
(perpT != null ? fmt(perpT, 2) : "—") +
|
||||
' <span class="muted">(相对K;期权目标需买一且扣费净利>0)</span>';
|
||||
' <span class="muted">(相对K;须买一流动性且扣费净利>0)</span>';
|
||||
return;
|
||||
}
|
||||
const entry = Number(($("hp-entry") && $("hp-entry").value) || NaN);
|
||||
@@ -1324,10 +1458,17 @@
|
||||
}
|
||||
updatePremiumLine();
|
||||
if (isOptionPrimary()) {
|
||||
const sized = computeOpSizing(c.ask, c.ct_mult || 0.01);
|
||||
if (sized && $("hp-sheets")) $("hp-sheets").value = String(sized.sheets);
|
||||
// 手动下单以张数为主:选约后若未填张数则默认 1,并同步左右
|
||||
if (!(orderSheets() > 0)) {
|
||||
if ($("hp-sheets")) $("hp-sheets").value = "1";
|
||||
if ($("hp-order-sheets")) $("hp-order-sheets").value = "1";
|
||||
} else {
|
||||
syncSheetsInputs("hp-order-sheets");
|
||||
if (!(numInput("hp-order-sheets", 0) > 0)) syncSheetsInputs("hp-sheets");
|
||||
}
|
||||
void loadMarket();
|
||||
updatePerpPnlHint();
|
||||
updatePoOpenSummary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2028,6 +2169,7 @@
|
||||
const sz = $("hp-sizing-line");
|
||||
const sized = currentOpSizing();
|
||||
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
||||
updatePoOpenSummary();
|
||||
void loadMarket();
|
||||
});
|
||||
}
|
||||
@@ -2155,17 +2297,38 @@
|
||||
}
|
||||
if ($("hp-exp-select")) $("hp-exp-select").addEventListener("change", renderListStrikes);
|
||||
if ($("hp-oo-exp-select")) $("hp-oo-exp-select").addEventListener("change", renderTStrikes);
|
||||
function onSheetsInput(fromId) {
|
||||
syncSheetsInputs(fromId);
|
||||
updatePremiumLine();
|
||||
if (isOptionPrimary()) {
|
||||
const sz = $("hp-sizing-line");
|
||||
const sized = currentOpSizing();
|
||||
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
||||
updatePoOpenSummary();
|
||||
void loadMarket();
|
||||
}
|
||||
}
|
||||
if ($("hp-sheets")) {
|
||||
$("hp-sheets").addEventListener("input", function () {
|
||||
updatePremiumLine();
|
||||
if (isOptionPrimary() && state.selected) {
|
||||
const sz = $("hp-sizing-line");
|
||||
const sized = currentOpSizing();
|
||||
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
||||
void loadMarket();
|
||||
}
|
||||
onSheetsInput("hp-sheets");
|
||||
});
|
||||
}
|
||||
if ($("hp-order-sheets")) {
|
||||
$("hp-order-sheets").addEventListener("input", function () {
|
||||
onSheetsInput("hp-order-sheets");
|
||||
});
|
||||
}
|
||||
document.querySelectorAll(".hp-po-exit-mode").forEach(function (b) {
|
||||
b.addEventListener("click", function () {
|
||||
state.exitMode = b.getAttribute("data-exit") === "target" ? "target" : "profit";
|
||||
syncExitModeUI();
|
||||
updatePerpPnlHint();
|
||||
});
|
||||
});
|
||||
if ($("hp-profit-target")) {
|
||||
$("hp-profit-target").addEventListener("input", updatePerpPnlHint);
|
||||
}
|
||||
syncExitModeUI();
|
||||
["hp-oo-sheets-a", "hp-oo-sheets-b"].forEach(function (id) {
|
||||
const el = $(id);
|
||||
if (el) el.addEventListener("input", updateOoPremiumLine);
|
||||
@@ -2820,21 +2983,30 @@
|
||||
if (!state.selected) throw new Error("请选用期权腿");
|
||||
if (isOptionPrimary()) {
|
||||
const sized = currentOpSizing();
|
||||
if (!sized) throw new Error("请选择期权并填写张数(或权利金预算)");
|
||||
const optPts = numInput("hp-opt-target-pts", NaN);
|
||||
const perpPts = numInput("hp-perp-target-pts", NaN);
|
||||
if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
|
||||
if (!sized || !(sized.sheets > 0)) throw new Error("请选择期权并填写张数");
|
||||
const exitMode = state.exitMode === "target" ? "target" : "profit";
|
||||
let optPts = 0;
|
||||
let perpPts = 0;
|
||||
let profitTarget = 0;
|
||||
if (exitMode === "profit") {
|
||||
profitTarget = numInput("hp-profit-target", NaN);
|
||||
if (!(profitTarget > 0)) throw new Error("请填写目标盈利金额(USDT,扣双边手续费后净利)");
|
||||
} else {
|
||||
optPts = numInput("hp-opt-target-pts", NaN);
|
||||
perpPts = numInput("hp-perp-target-pts", NaN);
|
||||
if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
|
||||
}
|
||||
const exp = currentExp("hp-exp-select");
|
||||
const entry = indexPx() || Number((state.market && state.market.mark) || 0);
|
||||
const premEst =
|
||||
Number(state.selected.ask || 0) *
|
||||
sized.sheets *
|
||||
Number(state.selected.ct_mult || 0.01);
|
||||
const premBudget = numInput("hp-premium-budget", 0);
|
||||
body = {
|
||||
plan_type: "perp_options",
|
||||
option_primary: true,
|
||||
market_order: 1,
|
||||
exit_mode: exitMode,
|
||||
underlying: state.underlying,
|
||||
direction: getDirection(),
|
||||
entry: entry,
|
||||
@@ -2848,16 +3020,18 @@
|
||||
ask: state.selected.ask,
|
||||
index_px: entry,
|
||||
exchange_symbol: (state.market && state.market.exchange_symbol) || "",
|
||||
leverage: numInput("hp-perp-leverage", defaultPerpLeverage(state.underlying)),
|
||||
leverage: defaultPerpLeverage(state.underlying),
|
||||
option_leverage: 0,
|
||||
premium_budget: premBudget > 0 ? premBudget : Math.max(premEst / 0.95, premEst),
|
||||
premium_budget: Math.max(premEst / 0.95, premEst),
|
||||
option_perp_ratio: numInput("hp-opt-perp-ratio", 2),
|
||||
option_target_points: optPts,
|
||||
perp_target_points: perpPts,
|
||||
option_target_points: optPts || 0,
|
||||
perp_target_points: perpPts || 0,
|
||||
profit_target_usdt: profitTarget || 0,
|
||||
strike_interval: 0,
|
||||
min_option_hours: 0,
|
||||
moneyness: (state.selected.moneyness || opMoneyKind() || "all").toLowerCase(),
|
||||
hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
|
||||
spot_buy_buffer: 1.05,
|
||||
};
|
||||
} else {
|
||||
const m = (state.selected.moneyness || "").toLowerCase();
|
||||
|
||||
@@ -3937,6 +3937,22 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-exit-seg {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 6px 0 10px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-max-line,
|
||||
.hedge-plan-page-wrap .hp-po-perp-auto {
|
||||
margin: 4px 0;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hedge-plan-page-wrap #hp-perp-leverage[readonly] {
|
||||
opacity: 0.85;
|
||||
cursor: default;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-opt-toolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 8px;
|
||||
|
||||
Reference in New Issue
Block a user