永期手动下单:修币本位杠杆显示,张数定仓与双出场模式.
右侧以张数+比例算永续,杠杆取 env(BTC10/ETH5);开仓买币+5%滑点;出场可选盈利金额(扣双边费)或目标位;平仓后卖光币回 U. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+193
-19
@@ -39,6 +39,7 @@
|
|||||||
ooCloseMode: "close_all",
|
ooCloseMode: "close_all",
|
||||||
direction: "long",
|
direction: "long",
|
||||||
optionPrimary: root.getAttribute("data-option-primary") !== "0",
|
optionPrimary: root.getAttribute("data-option-primary") !== "0",
|
||||||
|
exitMode: "profit", // profit=盈利金额出场 | target=目标位出场
|
||||||
opLevTouched: false,
|
opLevTouched: false,
|
||||||
opRatioTouched: false,
|
opRatioTouched: false,
|
||||||
perpLevTouched: 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() {
|
function currentOpSizing() {
|
||||||
if (!state.selected) return null;
|
if (!state.selected) return null;
|
||||||
const sheets = numInput("hp-sheets", 0);
|
const sheets = orderSheets();
|
||||||
return computeOpSizing(
|
return computeOpSizing(
|
||||||
state.selected.ask,
|
state.selected.ask,
|
||||||
state.selected.ct_mult || 0.01,
|
state.selected.ct_mult || 0.01,
|
||||||
@@ -489,6 +505,112 @@
|
|||||||
return Number.isFinite(eth) && eth > 0 ? eth : 5;
|
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() {
|
function syncUnderlyingUI() {
|
||||||
const uly = state.underlying || "ETH";
|
const uly = state.underlying || "ETH";
|
||||||
document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) {
|
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) + "×";
|
return (Math.round((k / a) * 10) / 10).toFixed(1) + "×";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 永期期权行情杠杆:指数÷卖一(与选约杠杆门一致). */
|
/** CoinOX 币本位:卖一为币报价 → 杠杆≈1÷卖一(与期权页一致). */
|
||||||
function calcIndexAskLeverage(ask) {
|
function calcIndexAskLeverage(ask) {
|
||||||
const idx = indexPx();
|
|
||||||
const a = Number(ask);
|
const a = Number(ask);
|
||||||
if (!(idx > 0) || !(a > 0)) return "—";
|
if (!(a > 0)) return "—";
|
||||||
return (Math.round((idx / a) * 10) / 10).toFixed(1) + "×";
|
return (Math.round((1 / a) * 10) / 10).toFixed(1) + "×";
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearPoSelection() {
|
function clearPoSelection() {
|
||||||
@@ -800,6 +921,11 @@
|
|||||||
if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
||||||
state.tradingUsdc = Number(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 !== "") {
|
if (acct.funding_usdc != null && acct.funding_usdc !== "") {
|
||||||
state.fundingUsdc = Number(acct.funding_usdc);
|
state.fundingUsdc = Number(acct.funding_usdc);
|
||||||
}
|
}
|
||||||
@@ -1174,12 +1300,20 @@
|
|||||||
const label = $("hp-opt-type-label");
|
const label = $("hp-opt-type-label");
|
||||||
if (label) label.textContent = d.suggested_opt_type === "C" ? "Call" : "Put";
|
if (label) label.textContent = d.suggested_opt_type === "C" ? "Call" : "Put";
|
||||||
updatePerpPnlHint();
|
updatePerpPnlHint();
|
||||||
|
updatePoOpenSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePerpPnlHint() {
|
function updatePerpPnlHint() {
|
||||||
const el = $("hp-perp-pnl-line");
|
const el = $("hp-perp-pnl-line");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
if (isOptionPrimary()) {
|
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 n = numInput("hp-opt-target-pts", NaN);
|
||||||
const m = numInput("hp-perp-target-pts", NaN);
|
const m = numInput("hp-perp-target-pts", NaN);
|
||||||
const k = state.selected && Number(state.selected.strike);
|
const k = state.selected && Number(state.selected.strike);
|
||||||
@@ -1195,7 +1329,7 @@
|
|||||||
(optT != null ? fmt(optT, 2) : "—") +
|
(optT != null ? fmt(optT, 2) : "—") +
|
||||||
" · 永续目标指数 " +
|
" · 永续目标指数 " +
|
||||||
(perpT != null ? fmt(perpT, 2) : "—") +
|
(perpT != null ? fmt(perpT, 2) : "—") +
|
||||||
' <span class="muted">(相对K;期权目标需买一且扣费净利>0)</span>';
|
' <span class="muted">(相对K;须买一流动性且扣费净利>0)</span>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const entry = Number(($("hp-entry") && $("hp-entry").value) || NaN);
|
const entry = Number(($("hp-entry") && $("hp-entry").value) || NaN);
|
||||||
@@ -1324,10 +1458,17 @@
|
|||||||
}
|
}
|
||||||
updatePremiumLine();
|
updatePremiumLine();
|
||||||
if (isOptionPrimary()) {
|
if (isOptionPrimary()) {
|
||||||
const sized = computeOpSizing(c.ask, c.ct_mult || 0.01);
|
// 手动下单以张数为主:选约后若未填张数则默认 1,并同步左右
|
||||||
if (sized && $("hp-sheets")) $("hp-sheets").value = String(sized.sheets);
|
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();
|
void loadMarket();
|
||||||
updatePerpPnlHint();
|
updatePerpPnlHint();
|
||||||
|
updatePoOpenSummary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2028,6 +2169,7 @@
|
|||||||
const sz = $("hp-sizing-line");
|
const sz = $("hp-sizing-line");
|
||||||
const sized = currentOpSizing();
|
const sized = currentOpSizing();
|
||||||
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
||||||
|
updatePoOpenSummary();
|
||||||
void loadMarket();
|
void loadMarket();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2155,17 +2297,38 @@
|
|||||||
}
|
}
|
||||||
if ($("hp-exp-select")) $("hp-exp-select").addEventListener("change", renderListStrikes);
|
if ($("hp-exp-select")) $("hp-exp-select").addEventListener("change", renderListStrikes);
|
||||||
if ($("hp-oo-exp-select")) $("hp-oo-exp-select").addEventListener("change", renderTStrikes);
|
if ($("hp-oo-exp-select")) $("hp-oo-exp-select").addEventListener("change", renderTStrikes);
|
||||||
if ($("hp-sheets")) {
|
function onSheetsInput(fromId) {
|
||||||
$("hp-sheets").addEventListener("input", function () {
|
syncSheetsInputs(fromId);
|
||||||
updatePremiumLine();
|
updatePremiumLine();
|
||||||
if (isOptionPrimary() && state.selected) {
|
if (isOptionPrimary()) {
|
||||||
const sz = $("hp-sizing-line");
|
const sz = $("hp-sizing-line");
|
||||||
const sized = currentOpSizing();
|
const sized = currentOpSizing();
|
||||||
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
|
||||||
|
updatePoOpenSummary();
|
||||||
void loadMarket();
|
void loadMarket();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if ($("hp-sheets")) {
|
||||||
|
$("hp-sheets").addEventListener("input", function () {
|
||||||
|
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) {
|
["hp-oo-sheets-a", "hp-oo-sheets-b"].forEach(function (id) {
|
||||||
const el = $(id);
|
const el = $(id);
|
||||||
if (el) el.addEventListener("input", updateOoPremiumLine);
|
if (el) el.addEventListener("input", updateOoPremiumLine);
|
||||||
@@ -2820,21 +2983,30 @@
|
|||||||
if (!state.selected) throw new Error("请选用期权腿");
|
if (!state.selected) throw new Error("请选用期权腿");
|
||||||
if (isOptionPrimary()) {
|
if (isOptionPrimary()) {
|
||||||
const sized = currentOpSizing();
|
const sized = currentOpSizing();
|
||||||
if (!sized) throw new Error("请选择期权并填写张数(或权利金预算)");
|
if (!sized || !(sized.sheets > 0)) throw new Error("请选择期权并填写张数");
|
||||||
const optPts = numInput("hp-opt-target-pts", NaN);
|
const exitMode = state.exitMode === "target" ? "target" : "profit";
|
||||||
const perpPts = numInput("hp-perp-target-pts", NaN);
|
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)");
|
if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
|
||||||
|
}
|
||||||
const exp = currentExp("hp-exp-select");
|
const exp = currentExp("hp-exp-select");
|
||||||
const entry = indexPx() || Number((state.market && state.market.mark) || 0);
|
const entry = indexPx() || Number((state.market && state.market.mark) || 0);
|
||||||
const premEst =
|
const premEst =
|
||||||
Number(state.selected.ask || 0) *
|
Number(state.selected.ask || 0) *
|
||||||
sized.sheets *
|
sized.sheets *
|
||||||
Number(state.selected.ct_mult || 0.01);
|
Number(state.selected.ct_mult || 0.01);
|
||||||
const premBudget = numInput("hp-premium-budget", 0);
|
|
||||||
body = {
|
body = {
|
||||||
plan_type: "perp_options",
|
plan_type: "perp_options",
|
||||||
option_primary: true,
|
option_primary: true,
|
||||||
market_order: 1,
|
market_order: 1,
|
||||||
|
exit_mode: exitMode,
|
||||||
underlying: state.underlying,
|
underlying: state.underlying,
|
||||||
direction: getDirection(),
|
direction: getDirection(),
|
||||||
entry: entry,
|
entry: entry,
|
||||||
@@ -2848,16 +3020,18 @@
|
|||||||
ask: state.selected.ask,
|
ask: state.selected.ask,
|
||||||
index_px: entry,
|
index_px: entry,
|
||||||
exchange_symbol: (state.market && state.market.exchange_symbol) || "",
|
exchange_symbol: (state.market && state.market.exchange_symbol) || "",
|
||||||
leverage: numInput("hp-perp-leverage", defaultPerpLeverage(state.underlying)),
|
leverage: defaultPerpLeverage(state.underlying),
|
||||||
option_leverage: 0,
|
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_perp_ratio: numInput("hp-opt-perp-ratio", 2),
|
||||||
option_target_points: optPts,
|
option_target_points: optPts || 0,
|
||||||
perp_target_points: perpPts,
|
perp_target_points: perpPts || 0,
|
||||||
|
profit_target_usdt: profitTarget || 0,
|
||||||
strike_interval: 0,
|
strike_interval: 0,
|
||||||
min_option_hours: 0,
|
min_option_hours: 0,
|
||||||
moneyness: (state.selected.moneyness || opMoneyKind() || "all").toLowerCase(),
|
moneyness: (state.selected.moneyness || opMoneyKind() || "all").toLowerCase(),
|
||||||
hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
|
hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
|
||||||
|
spot_buy_buffer: 1.05,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const m = (state.selected.moneyness || "").toLowerCase();
|
const m = (state.selected.moneyness || "").toLowerCase();
|
||||||
|
|||||||
@@ -3937,6 +3937,22 @@ html[data-theme="light"] .opt-be-dist-down {
|
|||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
line-height: 1.4;
|
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 {
|
.hedge-plan-page-wrap .hp-po-opt-toolbar {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 6px 8px;
|
gap: 6px 8px;
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ def init_hedge_plan_tables(conn: sqlite3.Connection) -> None:
|
|||||||
_ensure_column(conn, "hedge_plans", "option_moneyness", "TEXT")
|
_ensure_column(conn, "hedge_plans", "option_moneyness", "TEXT")
|
||||||
_ensure_column(conn, "hedge_plans", "option_leverage", "REAL")
|
_ensure_column(conn, "hedge_plans", "option_leverage", "REAL")
|
||||||
_ensure_column(conn, "hedge_plans", "perp_direction", "TEXT")
|
_ensure_column(conn, "hedge_plans", "perp_direction", "TEXT")
|
||||||
|
# 出场:profit=盈利金额(扣双边费);target=目标位点数
|
||||||
|
_ensure_column(conn, "hedge_plans", "exit_mode", "TEXT")
|
||||||
|
_ensure_column(conn, "hedge_plans", "profit_target_usdt", "REAL")
|
||||||
_ensure_column(conn, "hedge_plan_legs", "ct_mult", "REAL")
|
_ensure_column(conn, "hedge_plan_legs", "ct_mult", "REAL")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,52 @@ def _notify_end_reload(cfg: dict[str, Any], conn: Any, plan_id: int) -> None:
|
|||||||
notify_plan_end(cfg, conn, plan)
|
notify_plan_end(cfg, conn, plan)
|
||||||
|
|
||||||
|
|
||||||
|
def _sell_all_coin_to_usdt(cfg: dict[str, Any], underlying: str) -> dict[str, Any]:
|
||||||
|
"""平仓后把交易账户标的币全部卖回 USDT."""
|
||||||
|
try:
|
||||||
|
from lib.options.options_spot_bridge_lib import spot_market_sell_coin_to_usdt
|
||||||
|
|
||||||
|
ex = cfg.get("exchange_options") or cfg.get("exchange")
|
||||||
|
if ex is None:
|
||||||
|
return {"ok": False, "msg": "无交易所实例,跳过卖币"}
|
||||||
|
return spot_market_sell_coin_to_usdt(ex, underlying=str(underlying or "ETH").upper())
|
||||||
|
except Exception as e:
|
||||||
|
return {"ok": False, "msg": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def _plan_exit_mode(plan: dict[str, Any]) -> str:
|
||||||
|
raw = str(plan.get("exit_mode") or "").strip().lower()
|
||||||
|
if raw in ("profit", "target"):
|
||||||
|
return raw
|
||||||
|
# 兼容旧单:preview_json
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
meta = json.loads(plan.get("preview_json") or "{}")
|
||||||
|
m = str(meta.get("exit_mode") or "").strip().lower()
|
||||||
|
if m in ("profit", "target"):
|
||||||
|
return m
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return "target"
|
||||||
|
|
||||||
|
|
||||||
|
def _plan_profit_target(plan: dict[str, Any]) -> float:
|
||||||
|
v = _sf(plan.get("profit_target_usdt"))
|
||||||
|
if v is not None and v > 0:
|
||||||
|
return float(v)
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
meta = json.loads(plan.get("preview_json") or "{}")
|
||||||
|
v2 = _sf(meta.get("profit_target_usdt"))
|
||||||
|
if v2 is not None and v2 > 0:
|
||||||
|
return float(v2)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
def resolve_oo_rest_close_mode(plan: dict[str, Any]) -> str:
|
def resolve_oo_rest_close_mode(plan: dict[str, Any]) -> str:
|
||||||
"""盈利腿平后另一腿:close_all(残值平) / hold_expiry(到期平).
|
"""盈利腿平后另一腿:close_all(残值平) / hold_expiry(到期平).
|
||||||
|
|
||||||
@@ -639,7 +685,7 @@ def _tick_po_option_primary_expiry(
|
|||||||
def _tick_po_option_primary(
|
def _tick_po_option_primary(
|
||||||
cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: list[dict[str, Any]]
|
cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: list[dict[str, Any]]
|
||||||
) -> Optional[dict[str, Any]]:
|
) -> Optional[dict[str, Any]]:
|
||||||
"""以期权为主:触达目标位立即执行分叉平仓规则."""
|
"""以期权为主:盈利金额出场或目标位出场;先平期权(须买一流动性)再平永续,再卖币回 U."""
|
||||||
from lib.hedge_plan.hedge_plan_option_primary_lib import (
|
from lib.hedge_plan.hedge_plan_option_primary_lib import (
|
||||||
estimate_combo_net_pnl,
|
estimate_combo_net_pnl,
|
||||||
option_bid_liquidity_ok,
|
option_bid_liquidity_ok,
|
||||||
@@ -662,6 +708,8 @@ def _tick_po_option_primary(
|
|||||||
strike = _sf(opt.get("strike"))
|
strike = _sf(opt.get("strike"))
|
||||||
n = _sf(plan.get("option_target_points"))
|
n = _sf(plan.get("option_target_points"))
|
||||||
m = _sf(plan.get("perp_target_points"))
|
m = _sf(plan.get("perp_target_points"))
|
||||||
|
exit_mode = _plan_exit_mode(plan)
|
||||||
|
profit_target = _plan_profit_target(plan)
|
||||||
if strike is None or strike <= 0:
|
if strike is None or strike <= 0:
|
||||||
return None
|
return None
|
||||||
idx = _index_px(cfg, str(plan.get("underlying") or "ETH"))
|
idx = _index_px(cfg, str(plan.get("underlying") or "ETH"))
|
||||||
@@ -670,7 +718,13 @@ def _tick_po_option_primary(
|
|||||||
|
|
||||||
hit_opt = bool(n is not None and target_hit(view_side=view, index_px=idx, strike=strike, points=float(n)))
|
hit_opt = bool(n is not None and target_hit(view_side=view, index_px=idx, strike=strike, points=float(n)))
|
||||||
hit_perp = bool(m is not None and target_hit(view_side=view, index_px=idx, strike=strike, points=float(m)))
|
hit_perp = bool(m is not None and target_hit(view_side=view, index_px=idx, strike=strike, points=float(m)))
|
||||||
if not hit_opt and not hit_perp:
|
# 盈利金额出场:不依赖点数,后面按净利判断
|
||||||
|
if exit_mode == "profit":
|
||||||
|
if profit_target <= 0:
|
||||||
|
return None
|
||||||
|
hit_opt = True
|
||||||
|
hit_perp = False
|
||||||
|
elif not hit_opt and not hit_perp:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
symbol = str(perp.get("symbol") or "")
|
symbol = str(perp.get("symbol") or "")
|
||||||
@@ -710,7 +764,7 @@ def _tick_po_option_primary(
|
|||||||
ct = float(opt.get("ct_mult") or 0.01)
|
ct = float(opt.get("ct_mult") or 0.01)
|
||||||
contracts = float(perp.get("size") or plan.get("perp_size") or 0)
|
contracts = float(perp.get("size") or plan.get("perp_size") or 0)
|
||||||
|
|
||||||
# 优先期权目标;买一不足或净利≤0 时若永续目标已触达则改走永续目标
|
# 优先期权腿平仓(须买一流动性);盈利金额模式要求扣双边费后净利≥目标
|
||||||
if hit_opt:
|
if hit_opt:
|
||||||
liq_ok, liq_msg = option_bid_liquidity_ok(bid, bid_sz, need_sheets=sheets)
|
liq_ok, liq_msg = option_bid_liquidity_ok(bid, bid_sz, need_sheets=sheets)
|
||||||
net = None
|
net = None
|
||||||
@@ -729,9 +783,14 @@ def _tick_po_option_primary(
|
|||||||
contracts=contracts,
|
contracts=contracts,
|
||||||
contract_size=cs,
|
contract_size=cs,
|
||||||
)
|
)
|
||||||
|
if exit_mode == "profit":
|
||||||
|
can_opt_exit = bool(
|
||||||
|
liq_ok and net is not None and float(net.get("net") or 0) + 1e-9 >= profit_target
|
||||||
|
)
|
||||||
|
else:
|
||||||
can_opt_exit = bool(liq_ok and net is not None and float(net.get("net") or 0) > 0)
|
can_opt_exit = bool(liq_ok and net is not None and float(net.get("net") or 0) > 0)
|
||||||
if can_opt_exit:
|
if can_opt_exit:
|
||||||
reason = "opt_target_points"
|
reason = "profit_target_usdt" if exit_mode == "profit" else "opt_target_points"
|
||||||
close_r = _sell_option(cfg, inst_id=str(opt.get("inst_id") or ""), sheets=sheets)
|
close_r = _sell_option(cfg, inst_id=str(opt.get("inst_id") or ""), sheets=sheets)
|
||||||
if close_r.get("already_flat"):
|
if close_r.get("already_flat"):
|
||||||
opt_pnl = _option_leg_pnl_after_close(cfg, opt, fallback=net["opt_net"])
|
opt_pnl = _option_leg_pnl_after_close(cfg, opt, fallback=net["opt_net"])
|
||||||
@@ -783,8 +842,26 @@ def _tick_po_option_primary(
|
|||||||
stats_bucket="opt_primary",
|
stats_bucket="opt_primary",
|
||||||
closed_at=_now(),
|
closed_at=_now(),
|
||||||
)
|
)
|
||||||
|
sell_r = _sell_all_coin_to_usdt(cfg, str(plan.get("underlying") or "ETH"))
|
||||||
_notify_end_reload(cfg, conn, int(plan["id"]))
|
_notify_end_reload(cfg, conn, int(plan["id"]))
|
||||||
return {"plan_id": plan["id"], "close_reason": reason, "total": total, "net": net}
|
return {
|
||||||
|
"plan_id": plan["id"],
|
||||||
|
"close_reason": reason,
|
||||||
|
"total": total,
|
||||||
|
"net": net,
|
||||||
|
"sell_coin": sell_r,
|
||||||
|
}
|
||||||
|
if exit_mode == "profit":
|
||||||
|
return {
|
||||||
|
"plan_id": plan["id"],
|
||||||
|
"skip": True,
|
||||||
|
"msg": (
|
||||||
|
liq_msg
|
||||||
|
if not liq_ok
|
||||||
|
else f"净利未达目标 {profit_target:g}(当前 {float((net or {}).get('net') or 0):.2f})"
|
||||||
|
),
|
||||||
|
"net": net,
|
||||||
|
}
|
||||||
if not hit_perp:
|
if not hit_perp:
|
||||||
return {
|
return {
|
||||||
"plan_id": plan["id"],
|
"plan_id": plan["id"],
|
||||||
|
|||||||
@@ -350,18 +350,19 @@ def validate_option_primary_watch(body: dict[str, Any]) -> Optional[str]:
|
|||||||
|
|
||||||
def validate_option_primary_start(body: dict[str, Any]) -> Optional[str]:
|
def validate_option_primary_start(body: dict[str, Any]) -> Optional[str]:
|
||||||
market = body.get("market_order") in (True, 1, "1", "true", "yes", "on")
|
market = body.get("market_order") in (True, 1, "1", "true", "yes", "on")
|
||||||
|
exit_mode = str(body.get("exit_mode") or ("target" if not market else "profit")).strip().lower()
|
||||||
|
if exit_mode not in ("profit", "target"):
|
||||||
|
exit_mode = "profit" if market else "target"
|
||||||
need = (
|
need = (
|
||||||
"direction",
|
"direction",
|
||||||
"contracts",
|
"contracts",
|
||||||
"opt_inst_id",
|
"opt_inst_id",
|
||||||
"sheets",
|
"sheets",
|
||||||
"exchange_symbol",
|
"exchange_symbol",
|
||||||
"option_target_points",
|
|
||||||
"perp_target_points",
|
|
||||||
"option_perp_ratio",
|
"option_perp_ratio",
|
||||||
)
|
)
|
||||||
if not market:
|
if not market:
|
||||||
need = need + ("premium_budget",)
|
need = need + ("premium_budget", "option_target_points", "perp_target_points")
|
||||||
for k in need:
|
for k in need:
|
||||||
if body.get(k) in (None, ""):
|
if body.get(k) in (None, ""):
|
||||||
return f"缺少字段: {k}"
|
return f"缺少字段: {k}"
|
||||||
@@ -371,7 +372,12 @@ def validate_option_primary_start(body: dict[str, Any]) -> Optional[str]:
|
|||||||
prem = _sf(body.get("premium_budget"))
|
prem = _sf(body.get("premium_budget"))
|
||||||
if not market and (prem is None or prem <= 0):
|
if not market and (prem is None or prem <= 0):
|
||||||
return "权利金须大于 0"
|
return "权利金须大于 0"
|
||||||
if float(body["option_target_points"]) <= 0 or float(body["perp_target_points"]) <= 0:
|
if exit_mode == "profit":
|
||||||
|
pt = _sf(body.get("profit_target_usdt"))
|
||||||
|
if pt is None or pt <= 0:
|
||||||
|
return "盈利金额出场须填写目标盈利(USDT)"
|
||||||
|
else:
|
||||||
|
if float(body.get("option_target_points") or 0) <= 0 or float(body.get("perp_target_points") or 0) <= 0:
|
||||||
return "目标位点数须大于 0"
|
return "目标位点数须大于 0"
|
||||||
if float(body["option_perp_ratio"]) <= 0:
|
if float(body["option_perp_ratio"]) <= 0:
|
||||||
return "期权永续比例须大于 0"
|
return "期权永续比例须大于 0"
|
||||||
|
|||||||
@@ -204,7 +204,10 @@ def _buy_option(
|
|||||||
)
|
)
|
||||||
if idx <= 0:
|
if idx <= 0:
|
||||||
return {"ok": False, "msg": "缺少指数价,无法估算买币 USDT", "quote": q}
|
return {"ok": False, "msg": "缺少指数价,无法估算买币 USDT", "quote": q}
|
||||||
buf = normalize_coin_spot_buy_buffer(None)
|
# 权利金币×缓冲买币;默认 +5%滑点(1.05),可经 cfg.spot_buy_buffer / env 覆盖
|
||||||
|
buf = normalize_coin_spot_buy_buffer(
|
||||||
|
cfg.get("spot_buy_buffer") if cfg.get("spot_buy_buffer") is not None else 1.05
|
||||||
|
)
|
||||||
need_coin = float(premium) * float(buf)
|
need_coin = float(premium) * float(buf)
|
||||||
avail = float(fetch_trading_coin_available(ex, underlying) or 0)
|
avail = float(fetch_trading_coin_available(ex, underlying) or 0)
|
||||||
if avail + 1e-12 < need_coin:
|
if avail + 1e-12 < need_coin:
|
||||||
@@ -774,11 +777,30 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
|||||||
cs = float(get_cs(sym) or cs)
|
cs = float(get_cs(sym) or cs)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
market = body.get("market_order") in (True, 1, "1", "true", "yes", "on")
|
||||||
|
ct = float(body.get("ct_mult") or q.get("ct_mult") or 0.01)
|
||||||
|
ratio = float(body.get("option_perp_ratio") or 2)
|
||||||
|
body["contract_size"] = cs
|
||||||
|
if market and float(body.get("sheets") or 0) > 0:
|
||||||
|
# 手动下单:以张数为准,永续按比例
|
||||||
|
sheets_i = float(int(float(body["sheets"])))
|
||||||
|
eth = round(sheets_i * ct, 2)
|
||||||
|
body["sheets"] = sheets_i
|
||||||
|
body["eth_qty"] = eth
|
||||||
|
body["contracts"] = (eth / ratio) / cs if ratio > 0 and cs > 0 else 0
|
||||||
|
sized = {
|
||||||
|
"ok": True,
|
||||||
|
"sheets": sheets_i,
|
||||||
|
"eth_qty": eth,
|
||||||
|
"contracts": body["contracts"],
|
||||||
|
"ratio": ratio,
|
||||||
|
}
|
||||||
|
else:
|
||||||
sized = size_from_premium(
|
sized = size_from_premium(
|
||||||
premium_budget=float(body.get("premium_budget") or 0),
|
premium_budget=float(body.get("premium_budget") or 0),
|
||||||
ask=float(body["ask"]),
|
ask=float(body["ask"]),
|
||||||
ct_mult=float(body.get("ct_mult") or 0.01),
|
ct_mult=ct,
|
||||||
ratio=float(body.get("option_perp_ratio") or 2),
|
ratio=ratio,
|
||||||
contract_size=cs,
|
contract_size=cs,
|
||||||
)
|
)
|
||||||
if not sized.get("ok"):
|
if not sized.get("ok"):
|
||||||
@@ -786,16 +808,15 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
|||||||
body["sheets"] = sized["sheets"]
|
body["sheets"] = sized["sheets"]
|
||||||
body["contracts"] = sized["contracts"]
|
body["contracts"] = sized["contracts"]
|
||||||
body["eth_qty"] = sized["eth_qty"]
|
body["eth_qty"] = sized["eth_qty"]
|
||||||
body["contract_size"] = cs
|
|
||||||
# 深度不足则缩量
|
# 深度不足则缩量
|
||||||
ask_sz = float(q.get("ask_sz") or 0)
|
ask_sz = float(q.get("ask_sz") or 0)
|
||||||
if ask_sz > 0 and float(body["sheets"]) > ask_sz:
|
if ask_sz > 0 and float(body["sheets"]) > ask_sz:
|
||||||
body["sheets"] = float(int(ask_sz))
|
body["sheets"] = float(int(ask_sz))
|
||||||
if body["sheets"] <= 0:
|
if body["sheets"] <= 0:
|
||||||
return {"ok": False, "msg": "卖一深度不足 1 张", "quote": q, "sizing": sized}
|
return {"ok": False, "msg": "卖一深度不足 1 张", "quote": q, "sizing": sized}
|
||||||
eth = round(float(body["sheets"]) * float(body.get("ct_mult") or 0.01), 2)
|
eth = round(float(body["sheets"]) * ct, 2)
|
||||||
body["eth_qty"] = eth
|
body["eth_qty"] = eth
|
||||||
body["contracts"] = (eth / float(body.get("option_perp_ratio") or 2)) / cs
|
body["contracts"] = (eth / ratio) / cs if ratio > 0 and cs > 0 else 0
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"ask": float(q["ask"]),
|
"ask": float(q["ask"]),
|
||||||
@@ -806,8 +827,13 @@ def refresh_po_option_quote_before_start(cfg: dict[str, Any], body: dict[str, An
|
|||||||
"sizing": sized,
|
"sizing": sized,
|
||||||
"quote": q,
|
"quote": q,
|
||||||
"msg": (
|
"msg": (
|
||||||
|
f"手动定仓: {body.get('eth_qty')}币 / "
|
||||||
|
f"{body.get('sheets')}张期权 / {float(body.get('contracts') or 0):.4f}张永续 @{q['ask']}"
|
||||||
|
if market
|
||||||
|
else (
|
||||||
f"期权为主定仓: 权利金×0.95→{body.get('eth_qty')}ETH / "
|
f"期权为主定仓: 权利金×0.95→{body.get('eth_qty')}ETH / "
|
||||||
f"{body.get('sheets')}张期权 / {float(body.get('contracts') or 0):.4f}张永续 @{q['ask']}"
|
f"{body.get('sheets')}张期权 / {float(body.get('contracts') or 0):.4f}张永续 @{q['ask']}"
|
||||||
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -827,6 +853,13 @@ def execute_perp_options_start(
|
|||||||
dry_run: bool = False,
|
dry_run: bool = False,
|
||||||
persist: Optional[Callable[..., Any]] = None,
|
persist: Optional[Callable[..., Any]] = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
# 买币滑点缓冲:手动单默认 +5%
|
||||||
|
if body.get("spot_buy_buffer") is not None:
|
||||||
|
cfg = dict(cfg)
|
||||||
|
cfg["spot_buy_buffer"] = body.get("spot_buy_buffer")
|
||||||
|
elif body.get("market_order") in (True, 1, "1", "true", "yes", "on"):
|
||||||
|
cfg = dict(cfg)
|
||||||
|
cfg["spot_buy_buffer"] = 1.05
|
||||||
refresh = refresh_po_option_quote_before_start(cfg, body)
|
refresh = refresh_po_option_quote_before_start(cfg, body)
|
||||||
if not refresh.get("ok"):
|
if not refresh.get("ok"):
|
||||||
return {"ok": False, "msg": refresh.get("msg") or "启动前刷新卖一失败", "refresh": refresh}
|
return {"ok": False, "msg": refresh.get("msg") or "启动前刷新卖一失败", "refresh": refresh}
|
||||||
|
|||||||
@@ -340,6 +340,9 @@ def _persist_po(cfg: dict[str, Any], result: dict[str, Any], body: dict[str, Any
|
|||||||
"perp_direction": perp_dir,
|
"perp_direction": perp_dir,
|
||||||
}
|
}
|
||||||
if opt_primary:
|
if opt_primary:
|
||||||
|
exit_mode = str(body.get("exit_mode") or "target").strip().lower()
|
||||||
|
if exit_mode not in ("profit", "target"):
|
||||||
|
exit_mode = "target"
|
||||||
plan_row.update(
|
plan_row.update(
|
||||||
{
|
{
|
||||||
"option_target_points": float(body.get("option_target_points") or 0),
|
"option_target_points": float(body.get("option_target_points") or 0),
|
||||||
@@ -349,6 +352,8 @@ def _persist_po(cfg: dict[str, Any], result: dict[str, Any], body: dict[str, Any
|
|||||||
"strike_interval": float(body.get("strike_interval") or 15),
|
"strike_interval": float(body.get("strike_interval") or 15),
|
||||||
"min_option_hours": float(body.get("min_option_hours") or 36),
|
"min_option_hours": float(body.get("min_option_hours") or 36),
|
||||||
"option_moneyness": str(body.get("moneyness") or body.get("option_moneyness") or ""),
|
"option_moneyness": str(body.get("moneyness") or body.get("option_moneyness") or ""),
|
||||||
|
"exit_mode": exit_mode,
|
||||||
|
"profit_target_usdt": float(body.get("profit_target_usdt") or 0),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
plan_id = insert_plan(conn, plan_row)
|
plan_id = insert_plan(conn, plan_row)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
<th>类型</th>
|
<th>类型</th>
|
||||||
<th>合约</th>
|
<th>合约</th>
|
||||||
<th>卖一/张</th>
|
<th>卖一/张</th>
|
||||||
<th title="指数÷卖一">杠杆</th>
|
<th title="币本位:1÷卖一">杠杆</th>
|
||||||
<th>买一/张</th>
|
<th>买一/张</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -117,9 +117,10 @@
|
|||||||
<details class="tip-collapse hp-rule-collapse">
|
<details class="tip-collapse hp-rule-collapse">
|
||||||
<summary class="tip-collapse-summary">规则说明</summary>
|
<summary class="tip-collapse-summary">规则说明</summary>
|
||||||
<div class="tip-collapse-body rule-tip">
|
<div class="tip-collapse-body rule-tip">
|
||||||
<p><strong>手动下单</strong>:左侧人工选约(同期权页:到期/Call·Put/实虚值),点「选择」后右侧下单;开仓按<strong>卖一限价</strong>(非交易所市价单);非条件盯盘。</p>
|
<p><strong>手动下单</strong>:左侧人工选约,右侧填张数;开仓按<strong>卖一限价</strong>。先开期权(按张数买币并+5%滑点),再开永续。</p>
|
||||||
<p><strong>组合</strong>:买 Call → 永续空;买 Put → 永续多。</p>
|
<p><strong>组合</strong>:买 Call → 永续空;买 Put → 永续多。永续杠杆取 env:BTC=<code>BTC_LEVERAGE</code>(默认10)/ETH=<code>ALT_LEVERAGE</code>(默认5)。</p>
|
||||||
<p><strong>比例</strong>:期权:永续默认 <strong>2</strong>(期权名义 ÷ 比例 = 永续名义)。例:期权 1 BTC → 永续 0.5 BTC。</p>
|
<p><strong>出场</strong>:盈利金额(扣开平双边手续费后净利)或目标位点数二选一。平仓先期权(须买一流动性)再永续,再把币全部卖回 USDT。一账户同时仅一笔对冲仓。</p>
|
||||||
|
<p><strong>比例</strong>:默认 <strong>2</strong>(期权名义 ÷ 比例 = 永续名义)。</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<div class="hp-po-inner-card hp-po-perp-quote-card">
|
<div class="hp-po-inner-card hp-po-perp-quote-card">
|
||||||
@@ -150,25 +151,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="hp-po-fields-option-primary">
|
<div id="hp-po-fields-option-primary">
|
||||||
<section class="hp-po-section" aria-labelledby="hp-po-sec-capital">
|
<section class="hp-po-section" aria-labelledby="hp-po-sec-capital">
|
||||||
<h3 id="hp-po-sec-capital" class="hp-po-section-title">资金与杠杆</h3>
|
<h3 id="hp-po-sec-capital" class="hp-po-section-title">开仓</h3>
|
||||||
<div class="hp-po-fields hp-po-fields--section hp-po-fields--capital">
|
<div class="hp-po-fields hp-po-fields--section hp-po-fields--capital">
|
||||||
<label class="hp-po-field">
|
<label class="hp-po-field">
|
||||||
<span class="hp-po-field-lab">权利金预算 <em>币</em></span>
|
<span class="hp-po-field-lab">张数 <em>期权张</em></span>
|
||||||
<input type="number" step="any" id="hp-premium-budget" placeholder="预算(执行×0.95)" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />
|
<input type="number" step="1" min="1" id="hp-order-sheets" value="1" autocomplete="off" inputmode="numeric" data-lpignore="true" data-1p-ignore="true" data-form-type="other" title="与左侧张数同步" />
|
||||||
</label>
|
|
||||||
<label class="hp-po-field">
|
|
||||||
<span class="hp-po-field-lab">永续杠杆 <em>开仓</em></span>
|
|
||||||
<input type="number" step="1" id="hp-perp-leverage" value="{{ hedge_plan_eth_leverage | default(5) }}" autocomplete="off" inputmode="numeric" data-lpignore="true" data-1p-ignore="true" data-form-type="other" title="默认取 env 永期·永续杠杆" />
|
|
||||||
</label>
|
</label>
|
||||||
<label class="hp-po-field">
|
<label class="hp-po-field">
|
||||||
<span class="hp-po-field-lab">比例 <em>期权:永续</em></span>
|
<span class="hp-po-field-lab">比例 <em>期权:永续</em></span>
|
||||||
<input type="number" step="any" min="0.1" id="hp-opt-perp-ratio" value="2" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" title="默认2:期权名义÷比例=永续名义" />
|
<input type="number" step="any" min="0.1" id="hp-opt-perp-ratio" value="2" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" title="默认2:期权名义÷比例=永续名义" />
|
||||||
</label>
|
</label>
|
||||||
|
<label class="hp-po-field">
|
||||||
|
<span class="hp-po-field-lab">永续杠杆 <em>env</em></span>
|
||||||
|
<input type="number" step="1" id="hp-perp-leverage" value="{{ hedge_plan_eth_leverage | default(5) }}" readonly autocomplete="off" inputmode="numeric" data-lpignore="true" data-1p-ignore="true" data-form-type="other" title="BTC=BTC_LEVERAGE(默认10) ETH=ALT_LEVERAGE(默认5),在 env「永期·永续杠杆」改" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="muted hp-po-max-line" id="hp-po-max-line">最大开仓 —</p>
|
||||||
|
<p class="muted hp-po-perp-auto" id="hp-po-perp-auto">永续开仓 — (按比例自动)</p>
|
||||||
|
{# 兼容旧 JS:权利金隐藏,市价/手动用张数定仓 #}
|
||||||
|
<input type="hidden" id="hp-premium-budget" value="0" />
|
||||||
</section>
|
</section>
|
||||||
<section class="hp-po-section" aria-labelledby="hp-po-sec-exit">
|
<section class="hp-po-section" aria-labelledby="hp-po-sec-exit">
|
||||||
<h3 id="hp-po-sec-exit" class="hp-po-section-title">出场条件</h3>
|
<h3 id="hp-po-sec-exit" class="hp-po-section-title">出场条件</h3>
|
||||||
<div class="hp-po-fields hp-po-fields--section">
|
<div class="hp-oo-seg hp-po-exit-seg" role="group" aria-label="出场方式">
|
||||||
|
<button type="button" class="btn-secondary hp-po-exit-mode is-selected" data-exit="profit" title="组合净利(扣开平双边手续费)达目标金额即平"><span class="hp-oo-check" aria-hidden="true">✓</span>盈利金额出场</button>
|
||||||
|
<button type="button" class="btn-secondary hp-po-exit-mode" data-exit="target" title="指数相对行权价达目标点数出场"><span class="hp-oo-check" aria-hidden="true">✓</span>目标位出场</button>
|
||||||
|
</div>
|
||||||
|
<div class="hp-po-fields hp-po-fields--section" id="hp-po-exit-profit">
|
||||||
|
<label class="hp-po-field">
|
||||||
|
<span class="hp-po-field-lab">目标盈利 <em>USDT</em></span>
|
||||||
|
<input type="number" step="any" min="0.01" id="hp-profit-target" placeholder="扣双边手续费后净利" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="hp-po-fields hp-po-fields--section hidden" id="hp-po-exit-target" hidden>
|
||||||
<label class="hp-po-field">
|
<label class="hp-po-field">
|
||||||
<span class="hp-po-field-lab">期权目标位 <em>相对K</em></span>
|
<span class="hp-po-field-lab">期权目标位 <em>相对K</em></span>
|
||||||
<input type="number" step="any" id="hp-opt-target-pts" placeholder="点数" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />
|
<input type="number" step="any" id="hp-opt-target-pts" placeholder="点数" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />
|
||||||
|
|||||||
Reference in New Issue
Block a user