Add perpetual-options hedge overlay to amp-stats with hit rates and daily PnL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-28 14:20:42 +08:00
parent 845884fc67
commit d049c5d317
7 changed files with 543 additions and 19 deletions
+104 -9
View File
@@ -59,6 +59,31 @@
return el("amp-weekend-filter")?.value || "all";
}
function readNum(id) {
const raw = (el(id)?.value || "").trim();
if (!raw) return null;
const n = Number(raw);
return Number.isFinite(n) ? n : null;
}
function readPerpHedge() {
const spot = readNum("amp-hedge-spot");
const target = readNum("amp-hedge-target");
const perpLev = readNum("amp-hedge-perp-lev");
const optLev = readNum("amp-hedge-opt-lev");
if (spot == null || target == null || perpLev == null || optLev == null) return null;
if (spot <= 0 || target < 0 || perpLev <= 0 || optLev <= 0) return null;
return {
spot,
target_profit_u: target,
perp_leverage: perpLev,
option_leverage: optLev,
ratio_perp: readNum("amp-hedge-ratio-perp") || 1,
ratio_opt: readNum("amp-hedge-ratio-opt") || 2,
ct_mult: 0.01,
};
}
function setStatus(msg) {
const s = el("amp-status");
if (s) s.textContent = msg || "";
@@ -108,6 +133,7 @@
if (!s.sample_count) {
box.innerHTML = '<p class="amp-empty">暂无汇总</p>';
renderStraddle(null);
renderPerpHedge(null);
return;
}
box.innerHTML =
@@ -122,6 +148,7 @@
`<div><span class="amp-sum-k">价源</span><span class="amp-sum-v">${esc(result && result.price_source)}</span></div>` +
`</div>`;
renderStraddle(s.straddle);
renderPerpHedge(s.perp_hedge);
}
function renderStraddle(st) {
@@ -157,6 +184,35 @@
`</div>`;
}
function renderPerpHedge(ph) {
const box = el("amp-perp-hedge");
if (!box) return;
if (!ph) {
box.innerHTML =
'<p class="amp-empty">填写「永期·现价 / 目标 / 杠杆」后计算;对照所需点数达标天数与组合盈亏(永续多1币+买期权)</p>';
return;
}
const err =
ph.points_error
? `<div><span class="amp-sum-k">推点数提示</span><span class="amp-sum-v">${esc(ph.points_error)}</span></div>`
: "";
box.innerHTML =
`<div class="amp-sum-grid">` +
`<div><span class="amp-sum-k">比例 / 期权仓</span><span class="amp-sum-v">${esc(ph.ratio_label)} · ${esc(ph.opt_coins)} 币</span></div>` +
`<div><span class="amp-sum-k">单币/总权利金</span><span class="amp-sum-v">${esc(ph.prem_per_coin)} / ${esc(ph.premium_total)}</span></div>` +
`<div><span class="amp-sum-k">A所需点数(永续对)</span><span class="amp-sum-v">${esc(ph.move_a)}</span></div>` +
`<div><span class="amp-sum-k">A达标</span><span class="amp-sum-v">${esc(ph.hit_a_days)} 天 · ${esc(pct(ph.hit_a_ratio))}</span></div>` +
`<div><span class="amp-sum-k">B所需点数(组合)</span><span class="amp-sum-v">${esc(ph.move_b)}</span></div>` +
`<div><span class="amp-sum-k">B达标</span><span class="amp-sum-v">${esc(ph.hit_b_days)} 天 · ${esc(pct(ph.hit_b_ratio))}</span></div>` +
`<div><span class="amp-sum-k">组合盈亏合计</span><span class="amp-sum-v ${pnlClass(ph.pnl_total)}">${esc(ph.pnl_total)}</span></div>` +
`<div><span class="amp-sum-k">日均 / 胜率</span><span class="amp-sum-v ${pnlClass(ph.pnl_avg)}">${esc(ph.pnl_avg)} · ${esc(pct(ph.win_ratio))}</span></div>` +
`<div><span class="amp-sum-k">上涨日盈亏</span><span class="amp-sum-v ${pnlClass(ph.up_pnl_total)}">${esc(ph.up_pnl_total)} <small>(${esc(ph.up_days)}天)</small></span></div>` +
`<div><span class="amp-sum-k">下跌日盈亏</span><span class="amp-sum-v ${pnlClass(ph.down_pnl_total)}">${esc(ph.down_pnl_total)} <small>(${esc(ph.down_days)}天)</small></span></div>` +
`<div><span class="amp-sum-k">单日最大赚/亏</span><span class="amp-sum-v">${esc(ph.pnl_max)} / ${esc(ph.pnl_min)}</span></div>` +
err +
`</div>`;
}
function dayLabel(r) {
const day = esc(r.settlement_day);
if (r.is_weekend && r.weekday_label) {
@@ -171,7 +227,7 @@
if (!body) return;
const rows = (pagePayload && pagePayload.rows) || [];
if (!rows.length) {
body.innerHTML = '<tr><td colspan="11" class="amp-empty">暂无数据</td></tr>';
body.innerHTML = '<tr><td colspan="12" class="amp-empty">暂无数据</td></tr>';
} else {
body.innerHTML = rows
.map((r) => {
@@ -179,6 +235,10 @@
r.profit == null || r.profit === ""
? "—"
: `<span class="amp-pnl ${pnlClass(r.profit)}">${esc(r.profit)}</span>`;
const hedgePnl =
r.perp_hedge_pnl == null || r.perp_hedge_pnl === ""
? "—"
: `<span class="amp-pnl ${pnlClass(r.perp_hedge_pnl)}">${esc(r.perp_hedge_pnl)}</span>`;
const trClass = r.is_weekend ? ' class="amp-row-weekend"' : "";
return (
`<tr${trClass}>` +
@@ -193,6 +253,7 @@
`<td><strong>${esc(r.amplitude)}</strong></td>` +
`<td>${esc(r.change)}</td>` +
`<td>${profit}</td>` +
`<td>${hedgePnl}</td>` +
`</tr>`
);
})
@@ -227,6 +288,7 @@
async function reframe(resetPage) {
if (!lastResult) {
renderStraddle(null);
renderPerpHedge(null);
return;
}
if (resetPage) pageNo = 1;
@@ -245,6 +307,7 @@
straddle_premium: readPremium(),
take_profit: readTakeProfit(),
weekend_filter: readWeekend(),
perp_hedge: readPerpHedge(),
price_source: lastResult.price_source || "",
inst_id: lastResult.inst_id || "",
page: pageNo,
@@ -287,6 +350,7 @@
straddle_premium: readPremium(),
take_profit: readTakeProfit(),
weekend_filter: readWeekend(),
perp_hedge: readPerpHedge(),
page: pageNo,
page_size: 20,
}),
@@ -322,6 +386,18 @@
}
}
function appendHedgeQuery(q) {
const h = readPerpHedge();
if (!h) return;
q.set("hedge_spot", String(h.spot));
q.set("hedge_target", String(h.target_profit_u));
q.set("hedge_perp_lev", String(h.perp_leverage));
q.set("hedge_opt_lev", String(h.option_leverage));
q.set("hedge_ratio_perp", String(h.ratio_perp));
q.set("hedge_ratio_opt", String(h.ratio_opt));
q.set("hedge_ct_mult", String(h.ct_mult || 0.01));
}
function downloadCurrent() {
if (!lastResult) {
setStatus("请先计算");
@@ -342,6 +418,7 @@
if (period === "custom") q.set("custom_days", String(customDays));
if (prem != null) q.set("straddle_premium", String(prem));
if (tp != null) q.set("take_profit", String(tp));
appendHedgeQuery(q);
window.location.href = "/api/amp-stats/export?" + q.toString();
}
@@ -377,14 +454,14 @@
card.querySelector(".amp-hist-dl")?.addEventListener("click", () => {
const prem = readPremium();
const tp = readTakeProfit();
let url =
"/api/amp-stats/export?history_id=" +
encodeURIComponent(id) +
"&weekend_filter=" +
encodeURIComponent(readWeekend());
if (prem != null) url += "&straddle_premium=" + encodeURIComponent(String(prem));
if (tp != null) url += "&take_profit=" + encodeURIComponent(String(tp));
window.location.href = url;
const q = new URLSearchParams({
history_id: id,
weekend_filter: readWeekend(),
});
if (prem != null) q.set("straddle_premium", String(prem));
if (tp != null) q.set("take_profit", String(tp));
appendHedgeQuery(q);
window.location.href = "/api/amp-stats/export?" + q.toString();
});
card.querySelector(".amp-hist-del")?.addEventListener("click", async () => {
if (!confirm("删除该历史记录?")) return;
@@ -414,6 +491,15 @@
if (lastResult.weekend_filter && el("amp-weekend-filter")) {
el("amp-weekend-filter").value = lastResult.weekend_filter;
}
const h = lastResult.perp_hedge;
if (h && typeof h === "object") {
if (h.spot != null && el("amp-hedge-spot")) el("amp-hedge-spot").value = String(h.spot);
if (h.target_profit_u != null && el("amp-hedge-target")) el("amp-hedge-target").value = String(h.target_profit_u);
if (h.perp_leverage != null && el("amp-hedge-perp-lev")) el("amp-hedge-perp-lev").value = String(h.perp_leverage);
if (h.option_leverage != null && el("amp-hedge-opt-lev")) el("amp-hedge-opt-lev").value = String(h.option_leverage);
if (h.ratio_perp != null && el("amp-hedge-ratio-perp")) el("amp-hedge-ratio-perp").value = String(h.ratio_perp);
if (h.ratio_opt != null && el("amp-hedge-ratio-opt")) el("amp-hedge-ratio-opt").value = String(h.ratio_opt);
}
pageNo = 1;
setStatus("已载入历史 " + id);
await reframe(true);
@@ -436,6 +522,14 @@
el("amp-btn-download")?.addEventListener("click", downloadCurrent);
el("amp-straddle-premium")?.addEventListener("input", scheduleReframe);
el("amp-take-profit")?.addEventListener("input", scheduleReframe);
[
"amp-hedge-spot",
"amp-hedge-target",
"amp-hedge-perp-lev",
"amp-hedge-opt-lev",
"amp-hedge-ratio-perp",
"amp-hedge-ratio-opt",
].forEach((id) => el(id)?.addEventListener("input", scheduleReframe));
el("amp-weekend-filter")?.addEventListener("change", () => void reframe(true));
syncCustomDays();
}
@@ -446,6 +540,7 @@
setView("stats");
setStatus("");
renderStraddle(null);
renderPerpHedge(null);
},
};
})();
+30 -4
View File
@@ -1272,6 +1272,30 @@
<span>止盈点(点)</span>
<input id="amp-take-profit" type="number" min="0" step="any" placeholder="空=按涨跌" />
</label>
<label class="amp-field">
<span>永期·现价</span>
<input id="amp-hedge-spot" type="number" min="0" step="any" placeholder="如 1800" />
</label>
<label class="amp-field">
<span>永期·目标盈利(U)</span>
<input id="amp-hedge-target" type="number" min="0" step="any" value="15" />
</label>
<label class="amp-field">
<span>永期·永续杠杆</span>
<input id="amp-hedge-perp-lev" type="number" min="0.01" step="any" value="10" />
</label>
<label class="amp-field">
<span>永期·期权杠杆</span>
<input id="amp-hedge-opt-lev" type="number" min="0.01" step="any" value="100" />
</label>
<label class="amp-field">
<span>永期·永续比例</span>
<input id="amp-hedge-ratio-perp" type="number" min="0.01" step="any" value="1" />
</label>
<label class="amp-field">
<span>永期·期权比例</span>
<input id="amp-hedge-ratio-opt" type="number" min="0.01" step="any" value="2" />
</label>
<div class="amp-actions">
<button type="button" id="amp-btn-compute" class="primary">计算</button>
<button type="button" id="amp-btn-save" class="ghost">保存到历史</button>
@@ -1279,22 +1303,24 @@
</div>
</div>
<p id="amp-status" class="toolbar-meta amp-status"></p>
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.买跨收益=有效波动−权利金;止盈≥触达则有效波动=止盈点,否则用|涨跌|.周末按结算日标注/筛选.</p>
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.买跨收益=有效波动−权利金.永期对冲=永续多1币+买期权(默认1:2),对照所需点数达标与组合盈亏.周末按结算日标注/筛选.</p>
<h3 class="amp-block-title">汇总</h3>
<div id="amp-summary" class="amp-summary"></div>
<h3 class="amp-block-title">买跨对照</h3>
<div id="amp-straddle" class="amp-summary amp-straddle"></div>
<h3 class="amp-block-title">永期对冲对照</h3>
<div id="amp-perp-hedge" class="amp-summary amp-perp-hedge"></div>
<h3 class="amp-block-title">日表明细</h3>
<div class="amp-table-wrap">
<table class="amp-table">
<thead>
<tr>
<th>结算日</th><th>窗起点</th><th></th><th></th><th></th><th></th>
<th>开→高</th><th>开→低</th><th>振幅</th><th>涨跌</th><th>收益</th>
<th>开→高</th><th>开→低</th><th>振幅</th><th>涨跌</th><th>收益</th><th>永期盈亏</th>
</tr>
</thead>
<tbody id="amp-table-body">
<tr><td colspan="11" class="amp-empty">点击「计算」加载</td></tr>
<tr><td colspan="12" class="amp-empty">点击「计算」加载</td></tr>
</tbody>
</table>
</div>
@@ -1763,7 +1789,7 @@
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
<script src="/assets/dashboard.js?v=20260723-hide-pnl"></script>
<script src="/assets/strategy.js?v=11"></script>
<script src="/assets/amp_stats.js?v=5"></script>
<script src="/assets/amp_stats.js?v=20260728-hedge"></script>
<script src="/assets/help.js?v=1"></script>
<script src="/assets/logs.js?v=1"></script>
<script src="/assets/ai_review_render.js?v=3"></script>