Show hedge-plan scenario preview in a modal with start/cancel.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-19 08:28:54 +08:00
parent 1c155328b4
commit d5f3f315dc
3 changed files with 101 additions and 66 deletions
+56 -17
View File
@@ -35,6 +35,9 @@
fundingUsdc: null,
tradeBudgetUsdc: null,
budgetBuffer: 0.95,
previewOk: false,
canStart: false,
previewPlanType: null,
};
function $(id) {
@@ -169,13 +172,24 @@
];
if (gates.reasons && gates.reasons.length) parts.push(gates.reasons.join("; "));
el.textContent = parts.join(" · ");
const start = $("hp-start-btn");
const startOo = $("hp-start-btn-oo");
if ((gates.plan_type || state.mode) === "options_options") {
if (startOo) startOo.disabled = !gates.can_start;
} else {
if (start) start.disabled = !gates.can_start;
}
state.canStart = !!gates.can_start;
syncPreviewStartBtn();
}
function syncPreviewStartBtn() {
const start = $("hp-preview-start");
if (!start) return;
start.disabled = !(state.previewOk && state.canStart);
}
function openPreviewModal() {
const modal = $("hp-preview-modal");
if (modal) modal.hidden = false;
}
function closePreviewModal() {
const modal = $("hp-preview-modal");
if (modal) modal.hidden = true;
}
function setOptionsBalance(chain) {
@@ -851,8 +865,18 @@
async function runPreview() {
const isOo = state.mode === "options_options";
const tbody = $(isOo ? "hp-result-tbody-oo" : "hp-result-tbody");
const summary = $(isOo ? "hp-summary-oo" : "hp-summary");
const tbody = $("hp-result-tbody");
const summary = $("hp-preview-summary");
const midTh = $("hp-preview-mid-th");
const title = $("hp-preview-title");
state.previewOk = false;
state.previewPlanType = isOo ? "options_options" : "perp_options";
syncPreviewStartBtn();
if (midTh) midTh.textContent = isOo ? "腿盈亏" : "永续/腿盈亏";
if (title) title.textContent = isOo ? "情景测算 · 期期" : "情景测算 · 永期";
if (summary) summary.textContent = "";
if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="muted">计算中…</td></tr>';
openPreviewModal();
try {
let body;
if (isOo) {
@@ -949,7 +973,11 @@
"</td>";
tbody.appendChild(tr);
});
state.previewOk = true;
syncPreviewStartBtn();
} catch (e) {
state.previewOk = false;
syncPreviewStartBtn();
if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="err">' + (e.message || e) + "</td></tr>";
if (summary) summary.textContent = "";
}
@@ -1075,14 +1103,19 @@
state.mode = "options_options";
void runPreview();
});
if ($("hp-start-btn"))
$("hp-start-btn").addEventListener("click", function () {
void startPlan("perp_options");
if ($("hp-preview-cancel")) $("hp-preview-cancel").addEventListener("click", closePreviewModal);
if ($("hp-preview-cancel-x")) $("hp-preview-cancel-x").addEventListener("click", closePreviewModal);
if ($("hp-preview-start"))
$("hp-preview-start").addEventListener("click", function () {
const planType = state.previewPlanType || state.mode;
void startPlan(planType, true);
});
if ($("hp-start-btn-oo"))
$("hp-start-btn-oo").addEventListener("click", function () {
void startPlan("options_options");
const previewModal = $("hp-preview-modal");
if (previewModal) {
previewModal.addEventListener("click", function (ev) {
if (ev.target === previewModal) closePreviewModal();
});
}
if ($("hp-detail-close")) $("hp-detail-close").addEventListener("click", closeModal);
const modal = $("hp-detail-modal");
if (modal) {
@@ -1442,8 +1475,9 @@
}
}
async function startPlan(planType) {
async function startPlan(planType, fromPreviewModal) {
const isOo = planType === "options_options";
const startBtn = $("hp-preview-start");
try {
let body;
if (isOo) {
@@ -1487,17 +1521,22 @@
margin: state.market && state.market.full_margin_sizing && state.market.full_margin_sizing.margin_capital,
};
}
if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return;
if (!fromPreviewModal) {
if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return;
}
if (startBtn) startBtn.disabled = true;
const d = await apiJson("/api/hedge-plan/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
setGateLine(d.gates);
closePreviewModal();
alert("计划已启动 #" + (d.plan_id || "") + (d.dry_run ? " (dry_run)" : ""));
void loadGates();
} catch (e) {
alert(e.message || String(e));
syncPreviewStartBtn();
}
}
+13 -4
View File
@@ -3536,15 +3536,24 @@ html[data-theme="light"] .opt-be-dist-down {
display: flex;
flex-direction: column;
}
.hedge-plan-page-wrap .hp-preview-card {
margin-top: 0;
clear: both;
}
.hedge-plan-page-wrap .hp-action-row {
margin-top: 10px;
gap: 8px;
justify-content: flex-end;
}
.hedge-plan-page-wrap .hp-preview-modal {
width: min(96vw, 920px);
}
.hedge-plan-page-wrap .hp-preview-summary {
margin: 0 0 10px;
font-size: 0.84rem;
line-height: 1.45;
}
.hedge-plan-page-wrap .hp-preview-actions {
margin-top: 14px;
justify-content: flex-end;
gap: 10px;
}
.hedge-plan-page-wrap .hp-pick.active,
.hedge-plan-page-wrap .opt-row-selected td {
background: rgba(90, 140, 255, 0.18);
+32 -45
View File
@@ -97,31 +97,9 @@
<div id="hp-opt-bal-line" class="muted hp-quote-line hp-opt-bal-line"></div>
<div class="form-row hp-action-row">
<button type="button" class="primary" id="hp-preview-btn">计算</button>
<button type="button" class="btn-secondary" id="hp-start-btn" disabled title="需开启 HEDGE_PLAN_LIVE_ORDER 等门禁">启动计划</button>
</div>
</div>
</div>
<div class="card hp-preview-card" id="hp-preview-card-po">
<h2 style="margin:0 0 8px">情景测算</h2>
<div id="hp-summary" class="muted" style="margin:8px 0"></div>
<div class="options-strike-table-wrap">
<table class="options-strike-table" id="hp-result-table">
<thead>
<tr>
<th>情景</th>
<th>现货价</th>
<th>永续/腿盈亏</th>
<th>期权盈亏</th>
<th>合计≈U</th>
<th>说明</th>
</tr>
</thead>
<tbody id="hp-result-tbody">
<tr><td colspan="6" class="muted">填写参数后点计算</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="hp-tab-options_options" class="hp-tab-panel hidden" role="tabpanel" hidden>
@@ -215,31 +193,9 @@
</div>
<div class="form-row hp-action-row">
<button type="button" class="primary" id="hp-preview-btn-oo">计算</button>
<button type="button" class="btn-secondary" id="hp-start-btn-oo" title="需开启 HEDGE_PLAN_LIVE_ORDER">启动计划</button>
</div>
</div>
</div>
<div class="card hp-preview-card">
<h2 style="margin:0 0 8px">情景测算</h2>
<div id="hp-summary-oo" class="muted" style="margin:8px 0"></div>
<div class="options-strike-table-wrap">
<table class="options-strike-table">
<thead>
<tr>
<th>情景</th>
<th>现货价</th>
<th>腿盈亏</th>
<th>期权</th>
<th>合计≈U</th>
<th>说明</th>
</tr>
</thead>
<tbody id="hp-result-tbody-oo">
<tr><td colspan="6" class="muted">选用两腿并填上破/下破目标后点计算</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="hp-tab-active" class="hp-tab-panel hidden" role="tabpanel" hidden>
@@ -297,5 +253,36 @@
<div id="hp-detail-body" class="hp-modal-body muted">加载中…</div>
</div>
</div>
<div id="hp-preview-modal" class="hp-modal-backdrop" hidden>
<div class="hp-modal hp-preview-modal" role="dialog" aria-modal="true" aria-labelledby="hp-preview-title">
<div class="hp-modal-head">
<h3 id="hp-preview-title">情景测算</h3>
<button type="button" class="btn-secondary" id="hp-preview-cancel-x" aria-label="关闭">关闭</button>
</div>
<div id="hp-preview-summary" class="muted hp-preview-summary"></div>
<div class="options-strike-table-wrap">
<table class="options-strike-table" id="hp-result-table">
<thead>
<tr>
<th>情景</th>
<th>现货价</th>
<th id="hp-preview-mid-th">永续/腿盈亏</th>
<th>期权盈亏</th>
<th>合计≈U</th>
<th>说明</th>
</tr>
</thead>
<tbody id="hp-result-tbody">
<tr><td colspan="6" class="muted">计算中…</td></tr>
</tbody>
</table>
</div>
<div class="form-row hp-preview-actions">
<button type="button" class="btn-secondary" id="hp-preview-cancel">取消</button>
<button type="button" class="primary" id="hp-preview-start" disabled title="需开启 HEDGE_PLAN_LIVE_ORDER 等门禁">启动计划</button>
</div>
</div>
</div>
</div>
<script src="/static/hedge_plan.js?v=19"></script>
<script src="/static/hedge_plan.js?v=20"></script>