Add hub strategy compare page for perp vs options vs 7:3 hedge.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-23 14:39:59 +08:00
parent b6156e0049
commit ed3033d793
9 changed files with 1094 additions and 2 deletions
+43
View File
@@ -991,6 +991,7 @@ def root_redirect():
@app.get("/monitor")
@app.get("/plan")
@app.get("/calculator")
@app.get("/compare")
@app.get("/market")
@app.get("/archive")
@app.get("/quotes")
@@ -1113,6 +1114,7 @@ class SettingsDisplayBody(BaseModel):
show_nav_quotes: bool = True
show_nav_ai: bool = True
show_nav_calculator: bool = True
show_nav_compare: bool = True
show_nav_strategy: bool = True
show_nav_amp_stats: bool = True
show_nav_help: bool = True
@@ -1212,6 +1214,27 @@ class RollCalculatorBody(BaseModel):
base: str = "ETH"
class CompareOptionLegBody(BaseModel):
opt_type: str = "C"
strike: float | None = None
ask: float | None = None
class CompareBody(BaseModel):
base: str = "ETH"
direction: str = "long"
entry: float = Field(gt=0)
sl: float = Field(gt=0)
tp: float = Field(gt=0)
risk_u: float = Field(gt=0)
tp_opt: float | None = None
tp_hedge: float | None = None
contract_size: float | None = None
ct_mult: float | None = None
option: CompareOptionLegBody | None = None
hedge: dict | None = None
@app.get("/api/calculator/exchanges")
def api_calculator_exchanges():
from lib.hub.hub_calculator_market_lib import list_calculator_exchanges
@@ -1272,6 +1295,26 @@ def api_calculator_roll(body: RollCalculatorBody):
return {"ok": True, "data": data}
@app.post("/api/compare/calc")
def api_compare_calc(body: CompareBody):
from lib.hub.hub_compare_lib import run_compare
payload = body.model_dump()
hedge = payload.get("hedge") if isinstance(payload.get("hedge"), dict) else {}
# normalize hedge legs from nested dicts
if hedge:
payload["hedge"] = {
"main": hedge.get("main") if isinstance(hedge.get("main"), dict) else {},
"side": hedge.get("side") if isinstance(hedge.get("side"), dict) else {},
}
if payload.get("option") is None:
payload["option"] = {}
data = run_compare(payload)
if not data.get("ok"):
return JSONResponse(data, status_code=400)
return data
def _find_exchange_by_key(exchange_key: str) -> dict | None:
key = (exchange_key or "").strip().lower()
if not key:
+1
View File
@@ -29,6 +29,7 @@ DEFAULT_DISPLAY = {
"show_nav_quotes": True,
"show_nav_ai": True,
"show_nav_calculator": True,
"show_nav_compare": True,
"show_nav_strategy": True,
"show_nav_amp_stats": True,
"show_nav_help": True,
+64
View File
@@ -11008,3 +11008,67 @@ html[data-theme="light"] .hub-logs-card-hint {
.amp-form { grid-template-columns: 1fr 1fr; }
.amp-actions { grid-column: 1 / -1; }
}
/* ©¤©¤ ²ßÂÔ¶Ô±È ©¤©¤ */
.cmp-form { display: flex; flex-direction: column; gap: 12px; margin-bottom: 14px; }
.cmp-common-card h2,
.cmp-form .card h2 { margin: 0 0 10px; font-size: 15px; }
.cmp-subhead { margin: 14px 0 8px; font-size: 13px; color: var(--muted); font-weight: 600; }
.cmp-form-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px 12px;
}
.cmp-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.cmp-field input,
.cmp-field select {
background: var(--inset-surface);
border: 1px solid var(--border-soft);
border-radius: 8px;
color: var(--text);
padding: 8px 10px;
font-size: 13px;
}
.cmp-input-cols {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.cmp-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-bottom: 14px;
}
.cmp-sum-card { padding: 12px 14px; }
.cmp-sum-card h3 { margin: 0 0 8px; font-size: 14px; }
.cmp-sum-row {
display: flex; justify-content: space-between; gap: 8px;
font-size: 12px; margin: 4px 0; color: var(--muted);
}
.cmp-sum-row strong { color: var(--text); font-weight: 600; }
.cmp-muted { color: var(--muted); font-size: 12px; margin: 0; }
.cmp-table-wrap { margin-bottom: 14px; }
.cmp-table-scroll { overflow-x: auto; }
.cmp-table {
width: 100%; border-collapse: collapse; font-size: 13px;
background: var(--card); border: 1px solid var(--border-soft); border-radius: 10px;
}
.cmp-table th, .cmp-table td {
border-bottom: 1px solid var(--border-soft);
padding: 10px 12px; vertical-align: top; text-align: left;
}
.cmp-table th:first-child, .cmp-table td:first-child { width: 22%; }
.cmp-cell-note { margin-top: 4px; font-size: 11px; color: var(--muted); line-height: 1.35; }
.cmp-pnl-pos { color: var(--green); font-weight: 600; }
.cmp-pnl-neg { color: var(--red); font-weight: 600; }
.cmp-rec-card { padding: 14px 16px; }
.cmp-rec-head { font-size: 16px; margin-bottom: 6px; }
.cmp-rec-reason { margin: 0 0 8px; color: var(--muted); font-size: 13px; }
.cmp-rec-list { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.5; }
.cmp-warn { margin-top: 10px; font-size: 12px; color: var(--warn, #e6a23c); }
.cmp-foot-note { margin: 10px 0 0; font-size: 11px; color: var(--muted); }
@media (max-width: 900px) {
.cmp-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cmp-input-cols, .cmp-summary { grid-template-columns: 1fr; }
}
+18
View File
@@ -43,6 +43,10 @@
return displayPref("show_nav_calculator", true);
}
function showNavComparePref() {
return displayPref("show_nav_compare", true);
}
function showNavStrategyPref() {
return displayPref("show_nav_strategy", true);
}
@@ -69,6 +73,7 @@
["nav-quotes", "m-nav-quotes", d.show_nav_quotes === false],
["nav-ai", "m-tab-ai", d.show_nav_ai === false],
["nav-calculator", "m-tab-calculator", d.show_nav_calculator === false],
["nav-compare", "m-nav-compare", d.show_nav_compare === false],
["nav-strategy", "m-nav-strategy", d.show_nav_strategy === false],
["nav-amp-stats", "m-nav-amp-stats", d.show_nav_amp_stats === false],
["nav-help", "m-nav-help", d.show_nav_help === false],
@@ -142,6 +147,7 @@
if (page === "quotes") return showNavQuotesPref();
if (page === "ai") return showNavAiPref();
if (page === "calculator") return showNavCalculatorPref();
if (page === "compare") return showNavComparePref();
if (page === "strategy") return showNavStrategyPref();
if (page === "amp-stats") return showNavAmpStatsPref();
if (page === "help") return showNavHelpPref();
@@ -159,6 +165,7 @@
const quotesCb = document.getElementById("pref-show-nav-quotes");
const aiCb = document.getElementById("pref-show-nav-ai");
const calcCb = document.getElementById("pref-show-nav-calculator");
const compareCb = document.getElementById("pref-show-nav-compare");
const strategyCb = document.getElementById("pref-show-nav-strategy");
const ampCb = document.getElementById("pref-show-nav-amp-stats");
const helpCb = document.getElementById("pref-show-nav-help");
@@ -171,6 +178,7 @@
if (quotesCb) quotesCb.checked = d.show_nav_quotes !== false;
if (aiCb) aiCb.checked = d.show_nav_ai !== false;
if (calcCb) calcCb.checked = d.show_nav_calculator !== false;
if (compareCb) compareCb.checked = d.show_nav_compare !== false;
if (strategyCb) strategyCb.checked = d.show_nav_strategy !== false;
if (ampCb) ampCb.checked = d.show_nav_amp_stats !== false;
if (helpCb) helpCb.checked = d.show_nav_help !== false;
@@ -1287,6 +1295,7 @@
if (p.includes("funds")) return "funds";
if (p.includes("plan")) return "plan";
if (p.includes("calculator")) return "calculator";
if (p.includes("compare")) return "compare";
if (p.includes("help")) return "help";
if (p.includes("amp-stats")) return "amp-stats";
if (p.includes("strategy")) return "strategy";
@@ -1304,6 +1313,7 @@
if (page === "funds") return "page-funds";
if (page === "plan") return "page-plan";
if (page === "calculator") return "page-calculator";
if (page === "compare") return "page-compare";
if (page === "help") return "page-help";
if (page === "strategy") return "page-strategy";
if (page === "amp-stats") return "page-amp-stats";
@@ -1336,6 +1346,7 @@
document.body.classList.toggle("hub-page-monitor", page === "monitor");
document.body.classList.toggle("hub-page-market", page === "market");
document.body.classList.toggle("hub-page-calculator", page === "calculator");
document.body.classList.toggle("hub-page-compare", page === "compare");
document.body.classList.toggle("hub-page-settings", page === "settings");
document.body.classList.toggle("hub-page-archive", page === "archive");
document.body.classList.toggle("hub-page-quotes", page === "quotes");
@@ -1376,6 +1387,11 @@
if (page === "calculator" && window.hubCalculatorPage) {
window.hubCalculatorPage.init();
}
if (page === "compare" && window.hubComparePage) {
window.hubComparePage.init();
} else if (window.hubComparePage && window.hubComparePage.destroy) {
window.hubComparePage.destroy();
}
if (page === "funds" && window.hubFundsPage) {
window.hubFundsPage.init();
} else if (window.hubFundsPage && window.hubFundsPage.destroy) {
@@ -5104,6 +5120,7 @@
const quotesCb = document.getElementById("pref-show-nav-quotes");
const aiCb = document.getElementById("pref-show-nav-ai");
const calcCb = document.getElementById("pref-show-nav-calculator");
const compareCb = document.getElementById("pref-show-nav-compare");
const strategyCb = document.getElementById("pref-show-nav-strategy");
const ampCb = document.getElementById("pref-show-nav-amp-stats");
const helpCb = document.getElementById("pref-show-nav-help");
@@ -5128,6 +5145,7 @@
show_nav_quotes: quotesCb ? !!quotesCb.checked : true,
show_nav_ai: aiCb ? !!aiCb.checked : true,
show_nav_calculator: calcCb ? !!calcCb.checked : true,
show_nav_compare: compareCb ? !!compareCb.checked : true,
show_nav_strategy: strategyCb ? !!strategyCb.checked : true,
show_nav_amp_stats: ampCb ? !!ampCb.checked : true,
show_nav_help: helpCb ? !!helpCb.checked : true,
+296
View File
@@ -0,0 +1,296 @@
/**
* 中控策略对比:同风险额 R 合约 / 单期权 / 期期7:3
*/
(function () {
const page = document.getElementById("page-compare");
if (!page) return;
let inited = false;
let calcTimer = null;
function $(id) {
return document.getElementById(id);
}
function esc(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function num(id) {
const el = $(id);
if (!el) return null;
const n = Number(el.value);
return Number.isFinite(n) ? n : null;
}
function text(id) {
const el = $(id);
return el ? String(el.value || "").trim() : "";
}
function fmtU(v) {
if (v == null || !Number.isFinite(Number(v))) return "—";
const n = Number(v);
const abs = Math.abs(n).toFixed(2);
if (Math.abs(n) < 1e-9) return "0.00U";
return (n > 0 ? "+" : "-") + abs + "U";
}
function pnlClass(v) {
const n = Number(v);
if (!Number.isFinite(n) || Math.abs(n) < 1e-9) return "";
return n > 0 ? "cmp-pnl-pos" : "cmp-pnl-neg";
}
function setStatus(msg, isErr) {
const el = $("cmp-status");
if (!el) return;
el.textContent = msg || "";
el.className = "toolbar-meta" + (isErr ? " err" : "");
}
function syncDirectionDefaults() {
const dir = text("cmp-direction") || "long";
const isLong = dir === "long";
const optType = $("cmp-opt-type");
const mainType = $("cmp-hedge-main-type");
const sideType = $("cmp-hedge-side-type");
if (optType && !optType.dataset.touched) optType.value = isLong ? "C" : "P";
if (mainType && !mainType.dataset.touched) mainType.value = isLong ? "C" : "P";
if (sideType && !sideType.dataset.touched) sideType.value = isLong ? "P" : "C";
}
function collectPayload() {
const tp = num("cmp-tp");
return {
base: text("cmp-base") || "ETH",
direction: text("cmp-direction") || "long",
entry: num("cmp-entry"),
sl: num("cmp-sl"),
tp: tp,
risk_u: num("cmp-risk"),
tp_opt: num("cmp-tp-opt") != null ? num("cmp-tp-opt") : tp,
tp_hedge: num("cmp-tp-hedge") != null ? num("cmp-tp-hedge") : tp,
option: {
opt_type: text("cmp-opt-type") || "C",
strike: num("cmp-opt-strike"),
ask: num("cmp-opt-ask"),
},
hedge: {
main: {
opt_type: text("cmp-hedge-main-type") || "C",
strike: num("cmp-hedge-main-strike"),
ask: num("cmp-hedge-main-ask"),
},
side: {
opt_type: text("cmp-hedge-side-type") || "P",
strike: num("cmp-hedge-side-strike"),
ask: num("cmp-hedge-side-ask"),
},
},
};
}
function renderSummaryCards(data) {
const box = $("cmp-summary");
if (!box) return;
const perp = data.perp || {};
const opt = data.option || {};
const hedge = data.hedge || {};
const cards = [];
cards.push(`<article class="cmp-sum-card card">
<h3>单独合约</h3>
<div class="cmp-sum-row"><span>张数</span><strong>${esc(perp.sheets)}</strong></div>
<div class="cmp-sum-row"><span>止损占用</span><strong>${fmtU(perp.risk_used_u)}</strong></div>
<div class="cmp-sum-row"><span>面值</span><strong>${esc(perp.contract_size)} /</strong></div>
</article>`);
if (opt.ok) {
cards.push(`<article class="cmp-sum-card card">
<h3>单独期权 · ${esc(opt.opt_type)} ${esc(opt.strike)}</h3>
<div class="cmp-sum-row"><span>张数</span><strong>${esc(opt.sheets)}</strong></div>
<div class="cmp-sum-row"><span>权利金</span><strong>${fmtU(opt.premium_u)}</strong></div>
<div class="cmp-sum-row"><span>单张成本</span><strong>${fmtU(opt.unit_cost_u)}</strong></div>
</article>`);
} else {
cards.push(`<article class="cmp-sum-card card">
<h3>单独期权</h3>
<p class="cmp-muted">${esc(opt.msg || "输入不完整")}</p>
</article>`);
}
if (hedge.ok) {
const m = hedge.main || {};
const s = hedge.side || {};
cards.push(`<article class="cmp-sum-card card">
<h3>期期对冲 7:3</h3>
<div class="cmp-sum-row"><span>主腿 ${esc(m.opt_type)} ${esc(m.strike)}</span><strong>${esc(m.sheets)} · ${fmtU(m.premium_u)}</strong></div>
<div class="cmp-sum-row"><span>次腿 ${esc(s.opt_type)} ${esc(s.strike)}</span><strong>${esc(s.sheets)} · ${fmtU(s.premium_u)}</strong></div>
<div class="cmp-sum-row"><span>总权利金</span><strong>${fmtU(hedge.premium_u)}</strong></div>
</article>`);
} else {
cards.push(`<article class="cmp-sum-card card">
<h3>期期对冲</h3>
<p class="cmp-muted">${esc(hedge.msg || "输入不完整")}</p>
</article>`);
}
box.innerHTML = cards.join("");
}
function cell(v, note) {
const main = `<span class="${pnlClass(v)}">${fmtU(v)}</span>`;
if (!note) return main;
return `${main}<div class="cmp-cell-note">${esc(note)}</div>`;
}
function renderTable(data) {
const box = $("cmp-table-wrap");
if (!box) return;
const perp = data.perp || {};
const opt = data.option && data.option.ok ? data.option : null;
const hedge = data.hedge && data.hedge.ok ? data.hedge : null;
const dash = "—";
box.innerHTML = `<div class="cmp-table-scroll"><table class="cmp-table">
<thead>
<tr>
<th>路径</th>
<th>单独合约</th>
<th>单独期权</th>
<th>期期对冲</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>A 干净止盈</strong><div class="cmp-cell-note"></div></td>
<td>${cell(perp.path_a_tp)}</td>
<td>${opt ? cell(opt.path_a_tp) : dash}</td>
<td>${hedge ? cell(hedge.path_a_tp) : dash}</td>
</tr>
<tr>
<td><strong>B 打止损</strong><div class="cmp-cell-note">;</div></td>
<td>${cell(perp.path_b_sl)}</td>
<td>${
opt
? cell(opt.path_b_sl, "最坏到期亏满权利金 " + fmtU(opt.path_b_worst))
: dash
}</td>
<td>${
hedge
? cell(hedge.path_b_sl, "最坏双腿归零 " + fmtU(hedge.path_b_worst))
: dash
}</td>
</tr>
<tr>
<td><strong>C 先止损再去止盈</strong><div class="cmp-cell-note"></div></td>
<td>${cell(
perp.path_c_realized,
"踏空未拿到 " + fmtU(perp.path_c_missed)
)}</td>
<td>${opt ? cell(opt.path_c_hold_to_tp, opt.path_c_note || "") : dash}</td>
<td>${hedge ? cell(hedge.path_c_hold_to_tp, hedge.path_c_note || "") : dash}</td>
</tr>
</tbody>
</table></div>`;
}
function renderRecommend(data) {
const box = $("cmp-recommend");
if (!box) return;
const rec = data.recommend || {};
const bullets = Array.isArray(rec.bullets) ? rec.bullets : [];
const warns = Array.isArray(data.warnings) ? data.warnings : [];
box.innerHTML = `<div class="cmp-rec-card card">
<div class="cmp-rec-head">推荐:<strong>${esc(rec.choice || "—")}</strong></div>
<p class="cmp-rec-reason">${esc(rec.reason || "")}</p>
<ul class="cmp-rec-list">${bullets.map((b) => `<li>${esc(b)}</li>`).join("")}</ul>
${
warns.length
? `<div class="cmp-warn">${warns.map((w) => esc(w)).join(" · ")}</div>`
: ""
}
<p class="cmp-foot-note">${(data.notes || []).map(esc).join(" · ")}</p>
</div>`;
}
async function runCalc() {
const payload = collectPayload();
if (
payload.entry == null ||
payload.sl == null ||
payload.tp == null ||
payload.risk_u == null
) {
setStatus("请填写入场 / 止损 / 止盈 / 风险额", true);
return;
}
setStatus("计算中…");
try {
const r = await fetch("/api/compare/calc", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await r.json();
if (!data.ok) {
setStatus(data.msg || "计算失败", true);
return;
}
renderSummaryCards(data);
renderTable(data);
renderRecommend(data);
setStatus("已更新");
} catch (e) {
setStatus(String(e.message || e), true);
}
}
function scheduleCalc() {
if (calcTimer) clearTimeout(calcTimer);
calcTimer = setTimeout(() => {
void runCalc();
}, 280);
}
function bind() {
const form = $("cmp-form");
if (!form || form.dataset.bound === "1") return;
form.dataset.bound = "1";
form.addEventListener("submit", (ev) => {
ev.preventDefault();
void runCalc();
});
form.querySelectorAll("input, select").forEach((el) => {
el.addEventListener("change", () => {
if (el.id === "cmp-direction") syncDirectionDefaults();
if (
el.id === "cmp-opt-type" ||
el.id === "cmp-hedge-main-type" ||
el.id === "cmp-hedge-side-type"
) {
el.dataset.touched = "1";
}
scheduleCalc();
});
el.addEventListener("input", scheduleCalc);
});
const btn = $("cmp-btn-run");
if (btn) btn.addEventListener("click", () => void runCalc());
}
window.hubComparePage = {
init() {
if (!inited) {
bind();
syncDirectionDefaults();
inited = true;
}
scheduleCalc();
},
destroy() {
/* keep form state */
},
};
})();
+128 -2
View File
@@ -16,7 +16,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
<link rel="stylesheet" href="/assets/app.css?v=20260723-hide-pnl" />
<link rel="stylesheet" href="/assets/app.css?v=20260723-compare" />
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
<script src="/assets/account_risk_badge.js?v=4"></script>
@@ -57,6 +57,7 @@
<a href="/help" id="nav-help">使用说明</a>
<a href="/market" id="nav-market">行情区</a>
<a href="/calculator" id="nav-calculator">计算器</a>
<a href="/compare" id="nav-compare">策略对比</a>
<a href="/archive" id="nav-archive">内照明心</a>
<a href="/quotes" id="nav-quotes">语录</a>
<a href="/dashboard" id="nav-dashboard">数据看板</a>
@@ -957,6 +958,125 @@
</div>
</div>
<div id="page-compare" class="page hidden">
<div class="page-head">
<h1><span class="head-tag">CMP</span> 策略对比</h1>
<p class="page-desc">同风险额下对比 · 单独合约 / 单独期权 / 期期对冲(7:3) · 看止盈谁强、谁更易踏空</p>
</div>
<div class="toolbar">
<button type="button" id="cmp-btn-run" class="primary">计算对比</button>
<span id="cmp-status" class="toolbar-meta"></span>
</div>
<form id="cmp-form" class="cmp-form">
<section class="card cmp-common-card">
<h2>公共参数</h2>
<div class="cmp-form-grid">
<label class="cmp-field">
<span>标的</span>
<select id="cmp-base">
<option value="ETH" selected>ETH</option>
<option value="BTC">BTC</option>
</select>
</label>
<label class="cmp-field">
<span>方向</span>
<select id="cmp-direction">
<option value="long" selected>做多</option>
<option value="short">做空</option>
</select>
</label>
<label class="cmp-field">
<span>入场价</span>
<input id="cmp-entry" type="number" min="0" step="any" value="3500" required />
</label>
<label class="cmp-field">
<span>统一风险 R (U)</span>
<input id="cmp-risk" type="number" min="0.01" step="any" value="10" required />
</label>
<label class="cmp-field">
<span>统一止损价</span>
<input id="cmp-sl" type="number" min="0" step="any" value="3400" required />
</label>
<label class="cmp-field">
<span>止盈价</span>
<input id="cmp-tp" type="number" min="0" step="any" value="3700" required />
</label>
</div>
</section>
<div class="cmp-input-cols">
<section class="card">
<h2>单独期权</h2>
<div class="cmp-form-grid">
<label class="cmp-field">
<span>类型</span>
<select id="cmp-opt-type">
<option value="C" selected>Call</option>
<option value="P">Put</option>
</select>
</label>
<label class="cmp-field">
<span>行权价</span>
<input id="cmp-opt-strike" type="number" min="0" step="any" value="3600" />
</label>
<label class="cmp-field">
<span>卖一价(每币)</span>
<input id="cmp-opt-ask" type="number" min="0" step="any" value="50" />
</label>
<label class="cmp-field">
<span>期权目标价(默认同止盈)</span>
<input id="cmp-tp-opt" type="number" min="0" step="any" placeholder="空=用止盈价" />
</label>
</div>
</section>
<section class="card">
<h2>期期对冲 · 主腿 70%</h2>
<div class="cmp-form-grid">
<label class="cmp-field">
<span>类型</span>
<select id="cmp-hedge-main-type">
<option value="C" selected>Call</option>
<option value="P">Put</option>
</select>
</label>
<label class="cmp-field">
<span>行权价</span>
<input id="cmp-hedge-main-strike" type="number" min="0" step="any" value="3600" />
</label>
<label class="cmp-field">
<span>卖一价</span>
<input id="cmp-hedge-main-ask" type="number" min="0" step="any" value="50" />
</label>
</div>
<h2 class="cmp-subhead">次腿 30%</h2>
<div class="cmp-form-grid">
<label class="cmp-field">
<span>类型</span>
<select id="cmp-hedge-side-type">
<option value="C">Call</option>
<option value="P" selected>Put</option>
</select>
</label>
<label class="cmp-field">
<span>行权价</span>
<input id="cmp-hedge-side-strike" type="number" min="0" step="any" value="3400" />
</label>
<label class="cmp-field">
<span>卖一价</span>
<input id="cmp-hedge-side-ask" type="number" min="0" step="any" value="30" />
</label>
<label class="cmp-field">
<span>对冲目标价(默认同止盈)</span>
<input id="cmp-tp-hedge" type="number" min="0" step="any" placeholder="空=用止盈价" />
</label>
</div>
</section>
</div>
</form>
<div id="cmp-summary" class="cmp-summary"></div>
<div id="cmp-table-wrap" class="cmp-table-wrap"></div>
<div id="cmp-recommend" class="cmp-recommend"></div>
</div>
<div id="page-strategy" class="page hidden">
<div class="page-head strategy-page-head">
<div>
@@ -1242,6 +1362,10 @@
<input type="checkbox" id="pref-show-nav-calculator" checked />
顶栏显示「计算器」
</label>
<label class="chk-label settings-display-chk">
<input type="checkbox" id="pref-show-nav-compare" checked />
顶栏显示「策略对比」
</label>
<label class="chk-label settings-display-chk">
<input type="checkbox" id="pref-show-nav-strategy" checked />
顶栏显示「策略说明」
@@ -1445,6 +1569,7 @@
<a href="/dashboard" id="m-nav-dashboard">数据看板</a>
<a href="/strategy" id="m-nav-strategy">策略说明</a>
<a href="/amp-stats" id="m-nav-amp-stats">振幅统计</a>
<a href="/compare" id="m-nav-compare">策略对比</a>
<a href="/help" id="m-nav-help">使用说明</a>
<a href="/logs" id="m-nav-logs">系统日志</a>
<a href="/settings" id="m-nav-settings">系统设置</a>
@@ -1502,6 +1627,7 @@
<script src="/assets/chart.js?v=20260720-option-day-1600"></script>
<script src="/assets/plan.js?v=20260720-autofill"></script>
<script src="/assets/calculator.js?v=20260715-calc-tabs"></script>
<script src="/assets/compare.js?v=20260723-compare"></script>
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
@@ -1516,6 +1642,6 @@
<script src="/assets/options_expiry_countdown.js?v=1"></script>
<script src="/assets/options_position_cards.js?v=3"></script>
<script src="/assets/backup.js?v=1"></script>
<script src="/assets/app.js?v=20260723-dash-hide-pnl"></script>
<script src="/assets/app.js?v=20260723-compare"></script>
</body>
</html>