diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js
index 9a61e86..613e355 100644
--- a/lib/common/static/hedge_plan.js
+++ b/lib/common/static/hedge_plan.js
@@ -22,10 +22,11 @@
tab: pickDefaultTab(),
mode: showPerp ? "perp_options" : showOo ? "options_options" : "perp_options",
underlying: root.getAttribute("data-default-underly") || "ETH",
- moneyFilter: root.getAttribute("data-option-primary") !== "0" ? "otm" : "itm",
+ moneyFilter: root.getAttribute("data-option-primary") !== "0" ? "all" : "itm",
ooMoneyFilter: "atm_otm", // 期期锁定:平值+虚值
ooRecommend: null, // atm_straddle | double_otm | null
ooStrikeExpandAll: false, // 默认 Call/Put 各 3 档
+ poStrikeExpandAll: false, // 永期市价选约:默认平值+实3+虚3
chain: null,
selected: null,
legA: null,
@@ -68,7 +69,9 @@
const hint = $("hp-po-pair-hint");
if (hint) {
if (isOptionPrimary()) {
- hint.textContent = dir === "short" ? "买 Put + 永续多" : "买 Call + 永续空";
+ hint.textContent =
+ (dir === "short" ? "买 Put + 永续多" : "买 Call + 永续空") +
+ " · 点「选择」填入右侧市价下单;比例默认 2(期权 1 → 永续 0.5)。";
} else {
hint.textContent = dir === "short" ? "永续空 + 保险 Call" : "永续多 + 保险 Put";
}
@@ -162,9 +165,9 @@
return fmt(v, 2) + ":1";
}
- /** 列表/盯盘候选杠杆门槛(与后端 effective_min_opt_leverage 对齐). */
+ /** 条件单杠杆门槛;市价下单传 0 则不筛. */
function optionPrimaryMinLev() {
- const minLev = numInput("hp-opt-leverage", opMoneyKind() === "otm" ? 200 : 100);
+ const minLev = numInput("hp-opt-leverage", 0);
if (!(minLev > 0)) return 0;
if (opMoneyKind() === "otm") return Math.max(minLev, 180);
return minLev;
@@ -180,17 +183,17 @@
}
function matchesMoneyFilter(c) {
- const f = state.moneyFilter || "itm";
+ const f = state.moneyFilter || "all";
const m = (c.moneyness || "").toLowerCase();
+ // 保险模式不可选虚值
if (!isOptionPrimary() && f === "otm") return false;
- // 列表:间隔+虚实值+杠杆门槛(达标才显示;启动盯盘后监控同样门槛)
+ // 市价下单:仅按 全部/实值/虚值 筛选,不做间隔/杠杆条件门槛
if (isOptionPrimary()) {
- const idx = indexPx();
- const interval = numInput("hp-strike-interval", 15);
- if (idx && interval > 0 && Math.abs(Number(c.strike) - idx) > interval + 1e-9) {
- return false;
- }
- if (!optionPrimaryLevOk(c)) return false;
+ if (f === "all" || f === "any") return true;
+ if (f === "itm") return m === "itm" || m === "atm";
+ if (f === "atm") return m === "atm";
+ if (f === "otm") return m === "otm";
+ return true;
}
if (f === "itm") return m === "itm" || m === "atm";
if (f === "atm") return m === "atm";
@@ -198,6 +201,31 @@
return m === "itm" || m === "atm";
}
+ const PO_ATM_SIDE = 3;
+
+ function slicePoListByMoneyness(list) {
+ if (state.poStrikeExpandAll || !list.length) return list;
+ const sorted = list.slice().sort(function (a, b) {
+ return Number(a.strike) - Number(b.strike);
+ });
+ const itm = [];
+ const atm = [];
+ const otm = [];
+ sorted.forEach(function (c) {
+ const m = String(c.moneyness || "").toLowerCase();
+ if (m === "atm") atm.push(c);
+ else if (m === "itm") itm.push(c);
+ else if (m === "otm") otm.push(c);
+ });
+ const n = PO_ATM_SIDE;
+ const isPut = optTypeForDirection(getDirection()) === "P";
+ const pickedItm = isPut ? itm.slice(0, n) : itm.slice(-n);
+ const pickedOtm = isPut ? otm.slice(-n) : otm.slice(0, n);
+ return pickedItm.concat(atm, pickedOtm).sort(function (a, b) {
+ return Number(a.strike) - Number(b.strike);
+ });
+ }
+
function matchesOoMoneyFilter(c) {
// 期期:仅平值/虚值(禁实值)
if (!c) return false;
@@ -280,19 +308,15 @@
}
function opMoneyKind() {
- const f = state.moneyFilter || "itm";
+ const f = state.moneyFilter || "all";
if (f === "otm") return "otm";
if (f === "atm") return "atm";
+ if (f === "all" || f === "any") return "all";
return "itm";
}
function applyOpDefaultsFromMoney(force) {
- const kind = opMoneyKind();
- const levEl = $("hp-opt-leverage");
const ratioEl = $("hp-opt-perp-ratio");
- if (levEl && (force || !state.opLevTouched)) {
- levEl.value = kind === "otm" ? "200" : "100";
- }
if (ratioEl && (force || !state.opRatioTouched)) {
// 默认 2:期权名义÷比例=永续名义(例 1 BTC 期权 → 0.5 BTC 永续)
ratioEl.value = "2";
@@ -326,14 +350,28 @@
insMoney.classList.remove("hidden");
}
}
- if (!on && state.moneyFilter === "otm") {
+ const mktMoney = $("hp-po-mkt-money");
+ if (mktMoney) {
+ if (on) {
+ mktMoney.removeAttribute("hidden");
+ mktMoney.classList.remove("hidden");
+ } else {
+ mktMoney.setAttribute("hidden", "hidden");
+ mktMoney.classList.add("hidden");
+ }
+ }
+ if (on && (state.moneyFilter === "atm" || !state.moneyFilter)) {
+ state.moneyFilter = "all";
+ syncMoneyUI();
+ }
+ if (!on && (state.moneyFilter === "otm" || state.moneyFilter === "all")) {
state.moneyFilter = "itm";
syncMoneyUI();
}
if (on) applyOpDefaultsFromMoney(false);
const badge = $("hp-po-mode-badge");
if (badge) {
- badge.textContent = on ? "以期权为主" : "保险模式";
+ badge.textContent = on ? "市价下单" : "保险模式";
badge.classList.toggle("is-insurance", !on);
}
const title = $("hp-po-card-title");
@@ -343,7 +381,7 @@
if (dirLong) {
dirLong.title = on ? "买 Call → 永续空" : "做多永续";
if (on) {
- dirLong.innerHTML = '✓Call';
+ dirLong.innerHTML = '✓看涨 Call';
} else {
dirLong.innerHTML = '✓做多';
}
@@ -351,7 +389,7 @@
if (dirShort) {
dirShort.title = on ? "买 Put → 永续多" : "做空永续";
if (on) {
- dirShort.innerHTML = '✓Put';
+ dirShort.innerHTML = '✓看跌 Put';
} else {
dirShort.innerHTML = '✓做空';
}
@@ -472,7 +510,7 @@
function syncMoneyUI() {
const moneySel = $("hp-money-select");
if (moneySel && isOptionPrimary()) {
- moneySel.value = state.moneyFilter === "otm" ? "otm" : state.moneyFilter === "atm" ? "atm" : "itm";
+ moneySel.value = state.moneyFilter || "all";
}
document.querySelectorAll(".hp-money-btn").forEach(function (b) {
const on = b.getAttribute("data-money") === state.moneyFilter;
@@ -486,14 +524,16 @@
b.classList.toggle("is-selected", on);
b.setAttribute("aria-pressed", on ? "true" : "false");
});
+ const expandCb = $("hp-strike-expand-all");
+ if (expandCb) expandCb.checked = !!state.poStrikeExpandAll;
}
function syncPoActionBtn() {
const btn = $("hp-preview-btn");
if (!btn) return;
if (isOptionPrimary()) {
- btn.textContent = "策略启动";
- btn.title = "按参数启动盯盘;杠杆/间隔达标后自动开仓(非现场开)";
+ btn.textContent = "市价下单";
+ btn.title = "按左侧已选合约立即开仓(买期权+对冲永续);非条件盯盘";
} else {
btn.textContent = "计算";
btn.title = "情景测算后再启动";
@@ -597,7 +637,7 @@
updatePerpPnlHint();
}
- /** 以期权为主:按类型/间隔自动匹配最近合约. */
+ /** 市价选约:不自动代选;仅在已选失效(换 Call/Put 或筛掉)时清空. */
function autoMatchPoOption(force) {
if (!isOptionPrimary()) return;
const want = optTypeForDirection(getDirection());
@@ -608,9 +648,7 @@
String(sel.opt_type || "").toUpperCase() === want &&
matchesMoneyFilter(sel);
if (stillOk) return;
- const c = pickClosestItmAtm(currentContracts("hp-exp-select"), want);
- if (c) pickContract(c);
- else clearPoSelection();
+ clearPoSelection();
}
function findAtmStrikeFromRows(rows, idx) {
@@ -1199,9 +1237,8 @@
function fillExpSelect(sel, chain) {
if (!sel) return;
const prev = sel.value;
- const isPoSel = sel.id === "hp-exp-select";
- // 永期以期权为主:到期下拉按「最低剩余小时」过滤;期期下拉不过滤,保证明天到期可见
- const minH = isPoSel && isOptionPrimary() ? numInput("hp-min-hours", 36) : 0;
+ // 市价选约:到期列表不过滤(与期权页一致,默认最近一期);条件单 hidden 字段 min-hours=0
+ const minH = sel.id === "hp-exp-select" ? numInput("hp-min-hours", 0) : 0;
sel.innerHTML = '';
let firstOk = null;
let skippedNear = 0;
@@ -1222,10 +1259,10 @@
if (!sel.value && firstOk) {
sel.value = String(firstOk.exp_time);
}
- if (isPoSel && skippedNear > 0 && minH > 0) {
+ if (skippedNear > 0 && minH > 0) {
const tip = document.createElement("option");
tip.disabled = true;
- tip.textContent = "(已隐藏 " + skippedNear + " 个不足 " + minH + "h 的到期 · 可改左侧最低剩余小时)";
+ tip.textContent = "(已隐藏 " + skippedNear + " 个不足 " + minH + "h 的到期)";
sel.appendChild(tip);
}
}
@@ -1270,7 +1307,7 @@
return;
}
if (isOptionPrimary() && !matchesMoneyFilter(c)) {
- alert("不符合当前间隔/虚实值/杠杆门槛");
+ alert("不符合当前筛选(全部/实值/虚值),请切换筛选后再选");
return;
}
state.selected = c;
@@ -1300,38 +1337,38 @@
const want = optTypeForDirection(getDirection());
const exp = currentExp("hp-exp-select");
const prevInst = state.selected && state.selected.inst_id;
+ const cols = isOptionPrimary() ? 7 : 6;
tbody.innerHTML = "";
if (!exp) {
- tbody.innerHTML = '
| 请选择到期日 |
';
+ tbody.innerHTML = '| 请选择到期日 |
';
if (isOptionPrimary()) clearPoSelection();
return;
}
const sameType = (exp.contracts || []).filter(function (c) {
return String(c.opt_type || "").toUpperCase() === want;
});
- const list = sameType.filter(matchesMoneyFilter);
+ let list = sameType.filter(matchesMoneyFilter);
+ if (isOptionPrimary() && state.moneyFilter === "all") {
+ list = slicePoListByMoneyness(list);
+ }
if (!list.length) {
const idx = indexPx();
- const interval = isOptionPrimary() ? numInput("hp-strike-interval", 15) : null;
let hint = "无匹配合约";
- if (isOptionPrimary() && sameType.length) {
+ if (sameType.length) {
hint =
- "无匹配" +
+ "当前筛选下无 " +
(want === "C" ? "Call" : "Put") +
- "(已滤 " +
+ "(链上 " +
sameType.length +
- " 档)·检查间隔" +
- (interval != null ? "≤" + interval : "") +
- "点/虚实值/杠杆≥" +
- optionPrimaryMinLev();
- } else if (!sameType.length) {
+ " 档)·可改全部/实值/虚值或展开全部";
+ } else {
hint =
"该到期无 " +
(want === "C" ? "Call" : "Put") +
(idx ? " · 指数 " + fmt(idx, 2) : "") +
" · 可换到期或点刷新链";
}
- tbody.innerHTML = '| ' + hint + " |
";
+ tbody.innerHTML = '| ' + hint + " |
";
if (isOptionPrimary()) clearPoSelection();
return;
}
@@ -1340,24 +1377,53 @@
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 +
- " | " +
- moneynessBadge(c) +
- ' | ' +
- calcIndexAskLeverage(c.ask) +
- ' | ' +
- fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
- ' | ' +
- fmtPxSz(c.bid, c.bid_sz, false, c.tick_sz) +
- ' | | ';
+ if (isOptionPrimary()) {
+ tr.innerHTML =
+ "" +
+ c.strike +
+ " | " +
+ moneynessBadge(c) +
+ " | " +
+ (c.inst_id || "") +
+ ' | ' +
+ fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
+ ' | ' +
+ calcIndexAskLeverage(c.ask) +
+ ' | ' +
+ fmtPxSz(c.bid, c.bid_sz, false, c.tick_sz) +
+ ' | | ';
+ } else {
+ tr.innerHTML =
+ "" +
+ c.strike +
+ " | " +
+ moneynessBadge(c) +
+ ' | ' +
+ calcIndexAskLeverage(c.ask) +
+ ' | ' +
+ fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
+ ' | ' +
+ fmtPxSz(c.bid, c.bid_sz, false, c.tick_sz) +
+ ' | | ';
+ }
tbody.appendChild(tr);
});
+ if (isOptionPrimary() && !state.poStrikeExpandAll && state.moneyFilter === "all") {
+ const hintTr = document.createElement("tr");
+ hintTr.innerHTML =
+ '默认显示平值 + 实值3档 + 虚值3档 · 勾选「展开全部」查看该到期更多行权价 | ';
+ tbody.appendChild(hintTr);
+ }
tbody.querySelectorAll(".hp-pick").forEach(function (btn) {
btn.addEventListener("click", function () {
const inst = btn.getAttribute("data-inst");
@@ -1707,14 +1773,20 @@
if (!state.selected) throw new Error("请选用期权腿");
if (isOptionPrimary()) {
const sized = currentOpSizing();
- if (!sized) throw new Error("请填写权利金或张数并确认卖一有效");
+ 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)");
const exp = currentExp("hp-exp-select");
+ 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,
direction: getDirection(),
entry: indexPx() || Number((state.market && state.market.mark) || 0),
contracts: sized.contracts,
@@ -1725,15 +1797,15 @@
ct_mult: state.selected.ct_mult || 0.01,
ask: state.selected.ask,
index_px: indexPx() || 0,
- premium_budget: numInput("hp-premium-budget", 0),
+ premium_budget: premBudget > 0 ? premBudget : Math.max(premEst / 0.95, premEst),
option_perp_ratio: numInput("hp-opt-perp-ratio", 2),
option_target_points: optPts,
perp_target_points: perpPts,
- strike_interval: numInput("hp-strike-interval", 15),
- min_option_hours: numInput("hp-min-hours", 36),
- option_leverage: numInput("hp-opt-leverage", 100),
+ strike_interval: 0,
+ min_option_hours: 0,
+ option_leverage: 0,
leverage: numInput("hp-perp-leverage", defaultPerpLeverage(state.underlying)),
- moneyness: opMoneyKind(),
+ moneyness: (state.selected.moneyness || opMoneyKind() || "all").toLowerCase(),
hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
};
} else {
@@ -1914,25 +1986,28 @@
});
document.querySelectorAll(".hp-money-btn").forEach(function (b) {
b.addEventListener("click", function () {
- const m = b.getAttribute("data-money") || "itm";
+ const m = b.getAttribute("data-money") || "all";
if (m === "otm" && !isOptionPrimary()) {
alert("永期保险腿仅允许实值或平值;请在 env 将 HEDGE_PLAN_OPTION_PRIMARY=true");
return;
}
- state.moneyFilter = m === "otm" ? "otm" : m === "atm" ? "atm" : "itm";
+ if (isOptionPrimary()) {
+ state.moneyFilter = m === "otm" ? "otm" : m === "itm" ? "itm" : m === "atm" ? "atm" : "all";
+ } else {
+ state.moneyFilter = m === "atm" ? "atm" : "itm";
+ }
if (isOptionPrimary()) applyOpDefaultsFromMoney(false);
syncMoneyUI();
renderListStrikes();
});
});
- if ($("hp-money-select")) {
- $("hp-money-select").addEventListener("change", function () {
- const m = $("hp-money-select").value || "otm";
- state.moneyFilter = m === "otm" ? "otm" : m === "atm" ? "atm" : "itm";
- state.opLevTouched = false;
- state.opRatioTouched = false;
- applyOpDefaultsFromMoney(true);
- syncMoneyUI();
+ if ($("hp-strike-expand-all")) {
+ $("hp-strike-expand-all").addEventListener("change", function () {
+ state.poStrikeExpandAll = !!$("hp-strike-expand-all").checked;
+ if (state.poStrikeExpandAll && state.moneyFilter !== "all" && isOptionPrimary()) {
+ state.moneyFilter = "all";
+ syncMoneyUI();
+ }
renderListStrikes();
});
}
@@ -2154,7 +2229,8 @@
if ($("hp-preview-btn"))
$("hp-preview-btn").addEventListener("click", function () {
state.mode = "perp_options";
- if (isOptionPrimary()) void startOptionPrimaryWatch();
+ // 以期权为主 = 市价下单(左侧人工选约后立即开);条件盯盘不再从此入口
+ if (isOptionPrimary()) void startPlan("perp_options", false);
else void runPreview();
});
if ($("hp-preview-btn-oo"))
@@ -2744,15 +2820,21 @@
if (!state.selected) throw new Error("请选用期权腿");
if (isOptionPrimary()) {
const sized = currentOpSizing();
- if (!sized) throw new Error("请填写权利金或张数并确认卖一有效");
+ 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)");
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,
underlying: state.underlying,
direction: getDirection(),
entry: entry,
@@ -2767,14 +2849,14 @@
index_px: entry,
exchange_symbol: (state.market && state.market.exchange_symbol) || "",
leverage: numInput("hp-perp-leverage", defaultPerpLeverage(state.underlying)),
- option_leverage: numInput("hp-opt-leverage", 100),
- premium_budget: numInput("hp-premium-budget", 0),
+ option_leverage: 0,
+ premium_budget: premBudget > 0 ? premBudget : Math.max(premEst / 0.95, premEst),
option_perp_ratio: numInput("hp-opt-perp-ratio", 2),
option_target_points: optPts,
perp_target_points: perpPts,
- strike_interval: numInput("hp-strike-interval", 15),
- min_option_hours: numInput("hp-min-hours", 36),
- moneyness: opMoneyKind(),
+ strike_interval: 0,
+ min_option_hours: 0,
+ moneyness: (state.selected.moneyness || opMoneyKind() || "all").toLowerCase(),
hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
};
} else {
@@ -2807,7 +2889,14 @@
}
}
if (!fromPreviewModal) {
- if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return;
+ const confMsg = isOptionPrimary()
+ ? "确认市价下单?\n期权 " +
+ (state.selected && state.selected.inst_id) +
+ " × " +
+ (body.sheets || "") +
+ " 张 + 永续对冲\n(期权腿币本位 / 永续腿合约账户)"
+ : "确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)";
+ if (!window.confirm(confMsg)) return;
}
if (startBtn) startBtn.disabled = true;
const d = await apiJson("/api/hedge-plan/start", {
diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css
index f2e5ea3..57e4602 100644
--- a/lib/common/static/instance_theme.css
+++ b/lib/common/static/instance_theme.css
@@ -3931,16 +3931,38 @@ html[data-theme="light"] .opt-be-dist-down {
flex: 1 1 auto;
min-height: 220px;
}
-.hedge-plan-page-wrap .hp-po-pair-hint {
- margin-left: 8px;
- font-size: 0.82rem;
- white-space: nowrap;
-}
+.hedge-plan-page-wrap .hp-po-pair-hint,
.hedge-plan-page-wrap .hp-po-chain-hint {
margin: 4px 0 8px;
font-size: 0.78rem;
line-height: 1.4;
}
+.hedge-plan-page-wrap .hp-po-opt-toolbar {
+ flex-wrap: wrap;
+ gap: 6px 8px;
+ align-items: center;
+}
+.hedge-plan-page-wrap .hp-po-mkt-money {
+ display: inline-flex;
+ gap: 4px;
+ flex-wrap: wrap;
+}
+.hedge-plan-page-wrap .hp-po-mkt-money[hidden],
+.hedge-plan-page-wrap .hp-po-mkt-money.hidden {
+ display: none !important;
+}
+.hedge-plan-page-wrap .hp-po-expand-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 0.8rem;
+ white-space: nowrap;
+ cursor: pointer;
+}
+.hedge-plan-page-wrap .opt-inst-code {
+ font-size: 0.72rem;
+ word-break: break-all;
+}
.hedge-plan-page-wrap .hp-po-order-card .hp-po-perp-quote-card {
height: auto;
flex: 0 0 auto;
diff --git a/lib/hedge_plan/hedge_plan_option_primary_lib.py b/lib/hedge_plan/hedge_plan_option_primary_lib.py
index efeaa27..67fff4e 100644
--- a/lib/hedge_plan/hedge_plan_option_primary_lib.py
+++ b/lib/hedge_plan/hedge_plan_option_primary_lib.py
@@ -266,7 +266,10 @@ def validate_option_primary_moneyness(
return "行权价或指数无效"
m_want = (moneyness or "atm").strip().lower()
m_got = classify_moneyness(opt_type=o, strike=k, index_px=s)
- if m_want == "itm":
+ if m_want in ("all", "any", ""):
+ # 市价人工选约:不限制虚实值
+ pass
+ elif m_want == "itm":
if not is_itm_or_atm(opt_type=o, strike=k, index_px=s):
return "所选须为实值或平值"
elif m_want == "atm":
@@ -280,17 +283,27 @@ def validate_option_primary_moneyness(
else:
return "期权类型(实/平/虚)无效"
- interval = float(_sf(strike_interval) or DEFAULT_STRIKE_INTERVAL)
+ # 间隔/最短到期/杠杆门槛:仅条件单使用;传 0 表示市价下单跳过
+ interval_raw = _sf(strike_interval)
+ if interval_raw is None:
+ interval_raw = DEFAULT_STRIKE_INTERVAL
+ interval = float(interval_raw)
if interval > 0 and abs(k - s) > interval + 1e-9:
return f"行权价偏离指数 {abs(k - s):.1f} > 间隔 {interval:.0f}"
- min_h = float(_sf(min_hours) or DEFAULT_MIN_HOURS)
+ min_h_raw = _sf(min_hours)
+ if min_h_raw is None:
+ min_h_raw = DEFAULT_MIN_HOURS
+ min_h = float(min_h_raw)
h = _sf(hours_to_expiry)
if min_h > 0 and h is not None and h < min_h:
return f"剩余到期约 {h:.1f}h,低于最短 {min_h:.0f}h"
a = _sf(ask)
- min_lev = effective_min_opt_leverage(m_want if m_want != "atm" else m_got or "atm", min_opt_leverage)
+ lev_cfg = _sf(min_opt_leverage)
+ if lev_cfg is not None and lev_cfg <= 0:
+ return None
+ min_lev = effective_min_opt_leverage(m_want if m_want not in ("atm", "all", "any", "") else (m_got or "atm"), min_opt_leverage)
if min_lev > 0 and a is not None and a > 0:
lev = s / a
if lev < min_lev:
@@ -336,24 +349,27 @@ def validate_option_primary_watch(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")
need = (
"direction",
"contracts",
"opt_inst_id",
"sheets",
"exchange_symbol",
- "premium_budget",
"option_target_points",
"perp_target_points",
"option_perp_ratio",
)
+ if not market:
+ need = need + ("premium_budget",)
for k in need:
if body.get(k) in (None, ""):
return f"缺少字段: {k}"
try:
if float(body["contracts"]) <= 0 or float(body["sheets"]) <= 0:
return "张数必须大于 0"
- if float(body["premium_budget"]) <= 0:
+ prem = _sf(body.get("premium_budget"))
+ if not market and (prem is None or prem <= 0):
return "权利金须大于 0"
if float(body["option_target_points"]) <= 0 or float(body["perp_target_points"]) <= 0:
return "目标位点数须大于 0"
@@ -374,6 +390,9 @@ def validate_option_primary_start(body: dict[str, Any]) -> Optional[str]:
want = opt_type_for_view(direction)
if opt_type != want:
return f"以期权为主时做{'多' if direction == 'long' else '空'}须用 {'Call' if want == 'C' else 'Put'}"
+ # 市价下单:左侧人工选约,跳过条件单(间隔/最短到期/杠杆门槛)
+ if market:
+ return None
moneyness = str(body.get("moneyness") or body.get("option_moneyness") or "otm").strip().lower()
from lib.hedge_plan.hedge_plan_moneyness_lib import parse_strike_from_inst
diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html
index dbdeca9..e14b4fe 100644
--- a/lib/hedge_plan/templates/hedge_plan_panel.html
+++ b/lib/hedge_plan/templates/hedge_plan_panel.html
@@ -45,47 +45,55 @@
-
+
-
+
- 以期权为主
+ 市价下单
下单
· ETH
合约账户
@@ -109,9 +117,9 @@
规则说明
+
市价下单:左侧人工选约(同期权页:到期/Call·Put/实虚值),点「选择」后右侧下单;非条件盯盘。
组合:买 Call → 永续空;买 Put → 永续多。
比例:期权:永续默认 2(期权名义 ÷ 比例 = 永续名义)。例:期权 1 BTC → 永续 0.5 BTC。
-
账户:永续腿合约账户(USDT);期权腿币本位(交易账户 USDT 买币)。
@@ -152,37 +160,12 @@
永续杠杆 开仓
-
-
出场条件
@@ -203,7 +186,7 @@
-
+