Use daily open as perp-hedge entry and toggle buy-straddle vs perp overlays.

Amp-stats now prices premium from each day's open, and the form switches mutually between straddle and perpetual-options对照.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-28 14:31:17 +08:00
parent d049c5d317
commit 2ce67da8e8
6 changed files with 191 additions and 97 deletions
+8 -4
View File
@@ -103,7 +103,11 @@ def create_amp_stats_router() -> APIRouter:
"timeframe": "1H",
"metric_note": "振幅与距离均为点数:振幅=最高-最低=(开→高)+(开→低)",
"straddle_note": "买跨:越过权利金用>;止盈≥触达用止盈点否则|涨跌|;收益=有效波动-权利金",
"perp_hedge_note": "永期对冲:永续多1币+买期权;比例默认1:2;达标与组合盈亏见文档",
"perp_hedge_note": "永期对冲:永续多1币+买期权;入场按日开盘;比例默认1:2;与买跨二选一对照",
"overlay_modes": [
{"key": "straddle", "label": "买跨双边"},
{"key": "perp", "label": "永期对冲"},
],
}
@router.post("/compute")
@@ -198,7 +202,6 @@ def create_amp_stats_router() -> APIRouter:
hedge_ct_mult: float = Query(default=0.01),
):
hedge_q = {
"spot": hedge_spot,
"target_profit_u": hedge_target,
"perp_leverage": hedge_perp_lev,
"option_leverage": hedge_opt_lev,
@@ -206,13 +209,14 @@ def create_amp_stats_router() -> APIRouter:
"ratio_opt": hedge_ratio_opt,
"ct_mult": hedge_ct_mult,
}
hedge_q_ready = hedge_target is not None and hedge_perp_lev is not None and hedge_opt_lev is not None
if (history_id or "").strip():
item = get_history(history_id.strip())
if not item:
raise HTTPException(status_code=404, detail="历史不存在")
rows_all = item.get("rows_all") or item.get("rows") or []
item_hedge = item.get("perp_hedge") if isinstance(item.get("perp_hedge"), dict) else None
use_hedge = hedge_q if hedge_spot is not None else item_hedge
use_hedge = hedge_q if hedge_q_ready else item_hedge
try:
payload = reframe_amp_stats(
rows_all=rows_all,
@@ -246,7 +250,7 @@ def create_amp_stats_router() -> APIRouter:
straddle_premium=straddle_premium,
take_profit=take_profit,
weekend_filter=weekend_filter,
perp_hedge=hedge_q,
perp_hedge=hedge_q if hedge_q_ready else None,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
+48 -21
View File
@@ -39,7 +39,22 @@
return (n * 100).toFixed(1) + "%";
}
function overlayMode() {
return el("amp-overlay-mode")?.value || "straddle";
}
function syncOverlayMode() {
const isPerp = overlayMode() === "perp";
page.querySelectorAll(".amp-overlay-straddle").forEach((n) => n.classList.toggle("hidden", isPerp));
page.querySelectorAll(".amp-overlay-perp").forEach((n) => n.classList.toggle("hidden", !isPerp));
el("amp-overlay-straddle-block")?.classList.toggle("hidden", isPerp);
el("amp-overlay-perp-block")?.classList.toggle("hidden", !isPerp);
const col = el("amp-col-pnl");
if (col) col.textContent = isPerp ? "永期盈亏" : "收益";
}
function readPremium() {
if (overlayMode() !== "straddle") return null;
const raw = (el("amp-straddle-premium")?.value || "").trim();
if (!raw) return null;
const n = Number(raw);
@@ -48,6 +63,7 @@
}
function readTakeProfit() {
if (overlayMode() !== "straddle") return null;
const raw = (el("amp-take-profit")?.value || "").trim();
if (!raw) return null;
const n = Number(raw);
@@ -67,14 +83,13 @@
}
function readPerpHedge() {
const spot = readNum("amp-hedge-spot");
if (overlayMode() !== "perp") return null;
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;
if (target == null || perpLev == null || optLev == null) return null;
if (target < 0 || perpLev <= 0 || optLev <= 0) return null;
return {
spot,
target_profit_u: target,
perp_leverage: perpLev,
option_leverage: optLev,
@@ -130,6 +145,7 @@
const box = el("amp-summary");
if (!box) return;
const s = summary || {};
syncOverlayMode();
if (!s.sample_count) {
box.innerHTML = '<p class="amp-empty">暂无汇总</p>';
renderStraddle(null);
@@ -147,8 +163,13 @@
`<div><span class="amp-sum-k">涨/跌窗占比</span><span class="amp-sum-v">${esc(s.up_day_ratio)} / ${esc(s.down_day_ratio)}</span></div>` +
`<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);
if (overlayMode() === "perp") {
renderStraddle(null);
renderPerpHedge(s.perp_hedge);
} else {
renderPerpHedge(null);
renderStraddle(s.straddle);
}
}
function renderStraddle(st) {
@@ -189,7 +210,7 @@
if (!box) return;
if (!ph) {
box.innerHTML =
'<p class="amp-empty">填写「永期·现价 / 目标 / 杠杆」后计算;对照所需点数达标天数与组合盈亏(永续多1币+买期权)</p>';
'<p class="amp-empty">填写「目标 / 杠杆」后计算;入场按日开盘;对照所需点数达标与组合盈亏(永续多1币+买期权)</p>';
return;
}
const err =
@@ -198,8 +219,9 @@
: "";
box.innerHTML =
`<div class="amp-sum-grid">` +
`<div><span class="amp-sum-k">入场</span><span class="amp-sum-v">按日开盘 · 推点数中位 ${esc(ph.spot)}</span></div>` +
`<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">单币/总权利金(中位)</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>` +
@@ -225,20 +247,19 @@
const body = el("amp-table-body");
const pager = el("amp-pager");
if (!body) return;
const isPerp = overlayMode() === "perp";
syncOverlayMode();
const rows = (pagePayload && pagePayload.rows) || [];
if (!rows.length) {
body.innerHTML = '<tr><td colspan="12" class="amp-empty">暂无数据</td></tr>';
body.innerHTML = '<tr><td colspan="11" class="amp-empty">暂无数据</td></tr>';
} else {
body.innerHTML = rows
.map((r) => {
const profit =
r.profit == null || r.profit === ""
const pnlVal = isPerp ? r.perp_hedge_pnl : r.profit;
const pnlCell =
pnlVal == null || pnlVal === ""
? "—"
: `<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>`;
: `<span class="amp-pnl ${pnlClass(pnlVal)}">${esc(pnlVal)}</span>`;
const trClass = r.is_weekend ? ' class="amp-row-weekend"' : "";
return (
`<tr${trClass}>` +
@@ -252,8 +273,7 @@
`<td>${esc(r.down_points)}</td>` +
`<td><strong>${esc(r.amplitude)}</strong></td>` +
`<td>${esc(r.change)}</td>` +
`<td>${profit}</td>` +
`<td>${hedgePnl}</td>` +
`<td>${pnlCell}</td>` +
`</tr>`
);
})
@@ -389,7 +409,6 @@
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));
@@ -493,13 +512,16 @@
}
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 (el("amp-overlay-mode")) el("amp-overlay-mode").value = "perp";
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);
} else if (lastResult.straddle_premium != null && el("amp-overlay-mode")) {
el("amp-overlay-mode").value = "straddle";
}
syncOverlayMode();
pageNo = 1;
setStatus("已载入历史 " + id);
await reframe(true);
@@ -520,10 +542,13 @@
el("amp-btn-compute")?.addEventListener("click", () => void compute(true));
el("amp-btn-save")?.addEventListener("click", () => void saveHistory());
el("amp-btn-download")?.addEventListener("click", downloadCurrent);
el("amp-overlay-mode")?.addEventListener("change", () => {
syncOverlayMode();
void reframe(true);
});
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",
@@ -532,6 +557,7 @@
].forEach((id) => el(id)?.addEventListener("input", scheduleReframe));
el("amp-weekend-filter")?.addEventListener("change", () => void reframe(true));
syncCustomDays();
syncOverlayMode();
}
window.hubAmpStatsPage = {
@@ -539,6 +565,7 @@
bind();
setView("stats");
setStatus("");
syncOverlayMode();
renderStraddle(null);
renderPerpHedge(null);
},
+24 -17
View File
@@ -1265,34 +1265,37 @@
</select>
</label>
<label class="amp-field">
<span>对照模式</span>
<select id="amp-overlay-mode">
<option value="straddle" selected>买跨双边</option>
<option value="perp">永期对冲</option>
</select>
</label>
<label class="amp-field amp-overlay-straddle">
<span>买跨·双边权利金(点)</span>
<input id="amp-straddle-premium" type="number" min="0" step="any" placeholder="如 30" />
</label>
<label class="amp-field">
<label class="amp-field amp-overlay-straddle">
<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">
<label class="amp-field amp-overlay-perp hidden">
<span>永期·目标盈利(U)</span>
<input id="amp-hedge-target" type="number" min="0" step="any" value="15" />
</label>
<label class="amp-field">
<label class="amp-field amp-overlay-perp hidden">
<span>永期·永续杠杆</span>
<input id="amp-hedge-perp-lev" type="number" min="0.01" step="any" value="10" />
</label>
<label class="amp-field">
<label class="amp-field amp-overlay-perp hidden">
<span>永期·期权杠杆</span>
<input id="amp-hedge-opt-lev" type="number" min="0.01" step="any" value="100" />
</label>
<label class="amp-field">
<label class="amp-field amp-overlay-perp hidden">
<span>永期·永续比例</span>
<input id="amp-hedge-ratio-perp" type="number" min="0.01" step="any" value="1" />
</label>
<label class="amp-field">
<label class="amp-field amp-overlay-perp hidden">
<span>永期·期权比例</span>
<input id="amp-hedge-ratio-opt" type="number" min="0.01" step="any" value="2" />
</label>
@@ -1303,24 +1306,28 @@
</div>
</div>
<p id="amp-status" class="toolbar-meta amp-status"></p>
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.买跨收益=有效波动−权利金.永期对冲=永续多1币+买期权(默认1:2),对照所需点数达标与组合盈亏.周末按结算日标注/筛选.</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>
<div id="amp-overlay-straddle-block">
<h3 class="amp-block-title">买跨对照</h3>
<div id="amp-straddle" class="amp-summary amp-straddle"></div>
</div>
<div id="amp-overlay-perp-block" class="hidden">
<h3 class="amp-block-title">永期对冲对照</h3>
<div id="amp-perp-hedge" class="amp-summary amp-perp-hedge"></div>
</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 id="amp-col-pnl">收益</th>
</tr>
</thead>
<tbody id="amp-table-body">
<tr><td colspan="12" class="amp-empty">点击「计算」加载</td></tr>
<tr><td colspan="11" class="amp-empty">点击「计算」加载</td></tr>
</tbody>
</table>
</div>