Replace amp-stats straddle/perp overlays with move-points amplitude ratio.

Input points now drives amplitude hit share; table keeps both-side moves and amp达标.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-28 14:56:48 +08:00
parent 90be23e845
commit c81ba147cc
6 changed files with 885 additions and 1768 deletions
+12 -68
View File
@@ -12,33 +12,20 @@ from lib.hub.amp_stats_lib import (
build_export_csv,
compute_amp_stats,
export_filename,
normalize_straddle_premium,
normalize_take_profit,
normalize_move_points,
normalize_weekend_filter,
reframe_amp_stats,
rows_page,
)
class PerpHedgeBody(BaseModel):
spot: Optional[float] = None
target_profit_u: Optional[float] = None
perp_leverage: Optional[float] = None
option_leverage: Optional[float] = None
ratio_perp: float = 1.0
ratio_opt: float = 2.0
ct_mult: float = 0.01
class ComputeBody(BaseModel):
symbol: str = "eth"
start_hour: int = 16
period: str = "2m"
custom_days: Optional[int] = None
straddle_premium: Optional[float] = None
take_profit: Optional[float] = None
move_points: Optional[float] = None
weekend_filter: str = "all"
perp_hedge: Optional[PerpHedgeBody] = None
page: int = 1
page_size: int = 20
@@ -48,29 +35,21 @@ class SaveBody(BaseModel):
class ReframeBody(BaseModel):
"""已有日表上改周末/权利金/止盈/永期参数(不拉 K 线)."""
"""已有日表上改周末/波动点数(不拉 K 线)."""
rows_all: list[dict[str, Any]] = Field(default_factory=list)
symbol: str = "eth"
start_hour: int = 16
period: str = "2m"
sample_days: int = 60
straddle_premium: Optional[float] = None
take_profit: Optional[float] = None
move_points: Optional[float] = None
weekend_filter: str = "all"
perp_hedge: Optional[PerpHedgeBody] = None
price_source: str = ""
inst_id: str = ""
page: int = 1
page_size: int = 20
def _hedge_dict(body_hedge: Optional[PerpHedgeBody]) -> Optional[dict[str, Any]]:
if body_hedge is None:
return None
return body_hedge.model_dump()
def create_amp_stats_router() -> APIRouter:
router = APIRouter(prefix="/api/amp-stats", tags=["amp-stats"])
@@ -102,12 +81,7 @@ def create_amp_stats_router() -> APIRouter:
"default_weekend_filter": "all",
"timeframe": "1H",
"metric_note": "振幅与距离均为点数:振幅=最高-最低=(开→高)+(开→低)",
"straddle_note": "买跨:越过权利金用>;止盈≥触达用止盈点否则|涨跌|;收益=有效波动-权利金",
"perp_hedge_note": "永期对冲:永续多1币+买期权;入场按日开盘;比例默认1:2;与买跨二选一对照",
"overlay_modes": [
{"key": "straddle", "label": "买跨双边"},
{"key": "perp", "label": "永期对冲"},
],
"move_points_note": "填波动点数后统计振幅≥该点数的天数占比;日表显示开→高/开→低两边点数与振幅是否达标",
}
@router.post("/compute")
@@ -118,10 +92,8 @@ def create_amp_stats_router() -> APIRouter:
start_hour=body.start_hour,
period=body.period,
custom_days=body.custom_days,
straddle_premium=body.straddle_premium,
take_profit=body.take_profit,
move_points=body.move_points,
weekend_filter=body.weekend_filter,
perp_hedge=_hedge_dict(body.perp_hedge),
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@@ -146,10 +118,8 @@ def create_amp_stats_router() -> APIRouter:
start_hour=body.start_hour,
period=body.period,
sample_days=body.sample_days,
straddle_premium=body.straddle_premium,
take_profit=body.take_profit,
move_points=body.move_points,
weekend_filter=body.weekend_filter,
perp_hedge=_hedge_dict(body.perp_hedge),
price_source=body.price_source,
inst_id=body.inst_id,
)
@@ -190,33 +160,15 @@ def create_amp_stats_router() -> APIRouter:
start_hour: int = Query(default=16),
period: str = Query(default="2m"),
custom_days: Optional[int] = Query(default=None),
straddle_premium: Optional[float] = Query(default=None),
take_profit: Optional[float] = Query(default=None),
move_points: Optional[float] = Query(default=None),
weekend_filter: str = Query(default="all"),
hedge_spot: Optional[float] = Query(default=None),
hedge_target: Optional[float] = Query(default=None),
hedge_perp_lev: Optional[float] = Query(default=None),
hedge_opt_lev: Optional[float] = Query(default=None),
hedge_ratio_perp: float = Query(default=1.0),
hedge_ratio_opt: float = Query(default=2.0),
hedge_ct_mult: float = Query(default=0.01),
):
hedge_q = {
"target_profit_u": hedge_target,
"perp_leverage": hedge_perp_lev,
"option_leverage": hedge_opt_lev,
"ratio_perp": hedge_ratio_perp,
"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_q_ready else item_hedge
use_mp = move_points if move_points is not None else item.get("move_points")
try:
payload = reframe_amp_stats(
rows_all=rows_all,
@@ -224,12 +176,8 @@ def create_amp_stats_router() -> APIRouter:
start_hour=int(item.get("start_hour") if item.get("start_hour") is not None else start_hour),
period=str(item.get("period") or period),
sample_days=int(item.get("sample_days_requested") or 60),
straddle_premium=straddle_premium
if straddle_premium is not None
else item.get("straddle_premium"),
take_profit=take_profit if take_profit is not None else item.get("take_profit"),
move_points=use_mp,
weekend_filter=weekend_filter or item.get("weekend_filter") or "all",
perp_hedge=use_hedge,
price_source=str(item.get("price_source") or ""),
inst_id=str(item.get("inst_id") or ""),
missing=item.get("missing_days") or [],
@@ -238,19 +186,15 @@ def create_amp_stats_router() -> APIRouter:
raise HTTPException(status_code=400, detail=str(exc)) from exc
else:
try:
# validate enums early
normalize_weekend_filter(weekend_filter)
normalize_straddle_premium(straddle_premium)
normalize_take_profit(take_profit)
normalize_move_points(move_points)
payload = compute_amp_stats(
symbol=symbol,
start_hour=start_hour,
period=period,
custom_days=custom_days,
straddle_premium=straddle_premium,
take_profit=take_profit,
move_points=move_points,
weekend_filter=weekend_filter,
perp_hedge=hedge_q if hedge_q_ready else None,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
+38 -197
View File
@@ -1,5 +1,5 @@
/**
* 中控振幅统计:OKX ETH/BTC + 买跨/止盈/周末筛选.
* 中控振幅统计:OKX ETH/BTC + 波动点数振幅占比 + 周末筛选.
*/
(function () {
const page = document.getElementById("page-amp-stats");
@@ -39,32 +39,8 @@
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);
if (!Number.isFinite(n) || n <= 0) return null;
return n;
}
function readTakeProfit() {
if (overlayMode() !== "straddle") return null;
const raw = (el("amp-take-profit")?.value || "").trim();
function readMovePoints() {
const raw = (el("amp-move-points")?.value || "").trim();
if (!raw) return null;
const n = Number(raw);
if (!Number.isFinite(n) || n <= 0) return null;
@@ -75,30 +51,6 @@
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() {
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 (target == null || perpLev == null || optLev == null) return null;
if (target < 0 || perpLev <= 0 || optLev <= 0) return null;
return {
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 || "";
@@ -135,21 +87,13 @@
}
}
function pnlClass(v) {
const n = Number(v);
if (!Number.isFinite(n) || n === 0) return "";
return n > 0 ? "is-pos" : "is-neg";
}
function renderSummary(summary, result) {
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);
renderPerpHedge(null);
renderMoveStats(null);
return;
}
box.innerHTML =
@@ -163,76 +107,23 @@
`<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>`;
if (overlayMode() === "perp") {
renderStraddle(null);
renderPerpHedge(s.perp_hedge);
} else {
renderPerpHedge(null);
renderStraddle(s.straddle);
}
renderMoveStats(s.move_points_stats);
}
function renderStraddle(st) {
const box = el("amp-straddle");
function renderMoveStats(ms) {
const box = el("amp-move-stats");
if (!box) return;
if (!st) {
box.innerHTML = '<p class="amp-empty">填写「买跨·双边权利金」后计算,可看越过天数与买跨点数盈亏</p>';
if (!ms) {
box.innerHTML = '<p class="amp-empty">填写「波动点数」后计算,可看振幅≥该点数的天数占比</p>';
return;
}
const verdict =
st.pnl_total == null
? "—"
: Number(st.pnl_total) > 0
? "样本合计盈利"
: Number(st.pnl_total) < 0
? "样本合计亏损"
: "样本合计持平";
const tpLine =
st.take_profit != null
? `<div><span class="amp-sum-k">止盈点 / 触达</span><span class="amp-sum-v">${esc(st.take_profit)} · ${esc(st.tp_hit_days)} 天 · ${esc(pct(st.tp_hit_ratio))}</span></div>`
: `<div><span class="amp-sum-k">止盈点</span><span class="amp-sum-v">未设(按|涨跌|)</span></div>`;
box.innerHTML =
`<div class="amp-sum-grid">` +
`<div><span class="amp-sum-k">双边权利金</span><span class="amp-sum-v">${esc(st.premium)}</span></div>` +
tpLine +
`<div><span class="amp-sum-k">开→高超过权利金</span><span class="amp-sum-v">${esc(st.up_exceed_days)} 天 · ${esc(pct(st.up_exceed_ratio))}</span></div>` +
`<div><span class="amp-sum-k">开→低超过权利金</span><span class="amp-sum-v">${esc(st.down_exceed_days)} 天 · ${esc(pct(st.down_exceed_ratio))}</span></div>` +
`<div><span class="amp-sum-k">|涨跌|超过权利金</span><span class="amp-sum-v">${esc(st.abs_change_exceed_days)} 天 · ${esc(pct(st.abs_change_exceed_ratio))}</span></div>` +
`<div><span class="amp-sum-k">买跨盈亏合计</span><span class="amp-sum-v ${pnlClass(st.pnl_total)}">${esc(st.pnl_total)} <small>(${esc(verdict)})</small></span></div>` +
`<div><span class="amp-sum-k">日均盈亏</span><span class="amp-sum-v ${pnlClass(st.pnl_avg)}">${esc(st.pnl_avg)}</span></div>` +
`<div><span class="amp-sum-k">赚钱天数/胜率</span><span class="amp-sum-v">${esc(st.win_days)} · ${esc(pct(st.win_ratio))}</span></div>` +
`<div><span class="amp-sum-k">单日最大赚/亏</span><span class="amp-sum-v">${esc(st.pnl_max)} / ${esc(st.pnl_min)}</span></div>` +
`</div>`;
}
function renderPerpHedge(ph) {
const box = el("amp-perp-hedge");
if (!box) return;
if (!ph) {
box.innerHTML =
'<p class="amp-empty">填写「目标 / 杠杆」后计算;入场按日开盘;触达目标点数按目标盈利出场,否则收盘结算</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.target_profit_u)}U 或收盘</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">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">${esc(ph.target_exit_days)} / ${esc(ph.eod_days)} 天</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><span class="amp-sum-k">对照点数</span><span class="amp-sum-v">${esc(ms.move_points)}</span></div>` +
`<div><span class="amp-sum-k">振幅≥点数</span><span class="amp-sum-v">${esc(ms.amp_hit_days)} 天 · <strong>${esc(pct(ms.amp_hit_ratio))}</strong></span></div>` +
`<div><span class="amp-sum-k">开→高≥点数</span><span class="amp-sum-v">${esc(ms.up_hit_days)} 天 · ${esc(pct(ms.up_hit_ratio))}</span></div>` +
`<div><span class="amp-sum-k">开→低≥点数</span><span class="amp-sum-v">${esc(ms.down_hit_days)} 天 · ${esc(pct(ms.down_hit_ratio))}</span></div>` +
`<div><span class="amp-sum-k">|涨跌|≥点数</span><span class="amp-sum-v">${esc(ms.abs_change_hit_days)} 天 · ${esc(pct(ms.abs_change_hit_ratio))}</span></div>` +
`</div>`;
}
@@ -244,24 +135,26 @@
return day;
}
function hitCell(r) {
if (r.move_points == null) return "—";
if (r.amp_hit) return '<span class="amp-pnl is-pos">是</span>';
return '<span class="amp-pnl is-neg">否</span>';
}
function renderTable(pagePayload) {
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="11" class="amp-empty">暂无数据</td></tr>';
} else {
body.innerHTML = rows
.map((r) => {
const pnlVal = isPerp ? r.perp_hedge_pnl : r.profit;
const pnlCell =
pnlVal == null || pnlVal === ""
? "—"
: `<span class="amp-pnl ${pnlClass(pnlVal)}">${esc(pnlVal)}</span>`;
const trClass = r.is_weekend ? ' class="amp-row-weekend"' : "";
const upCls = r.hit_up ? ' class="amp-pnl is-pos"' : "";
const downCls = r.hit_down ? ' class="amp-pnl is-pos"' : "";
const ampCls = r.amp_hit ? ' class="amp-pnl is-pos"' : "";
return (
`<tr${trClass}>` +
`<td>${dayLabel(r)}</td>` +
@@ -270,11 +163,11 @@
`<td>${esc(r.high)}</td>` +
`<td>${esc(r.low)}</td>` +
`<td>${esc(r.close)}</td>` +
`<td>${esc(r.up_points)}</td>` +
`<td>${esc(r.down_points)}</td>` +
`<td><strong>${esc(r.amplitude)}</strong></td>` +
`<td${upCls}>${esc(r.up_points)}</td>` +
`<td${downCls}>${esc(r.down_points)}</td>` +
`<td${ampCls}><strong>${esc(r.amplitude)}</strong></td>` +
`<td>${esc(r.change)}</td>` +
`<td>${pnlCell}</td>` +
`<td>${hitCell(r)}</td>` +
`</tr>`
);
})
@@ -308,8 +201,7 @@
async function reframe(resetPage) {
if (!lastResult) {
renderStraddle(null);
renderPerpHedge(null);
renderMoveStats(null);
return;
}
if (resetPage) pageNo = 1;
@@ -325,10 +217,8 @@
start_hour: lastResult.start_hour ?? Number(el("amp-start-hour")?.value || 16),
period: lastResult.period || el("amp-period")?.value || "2m",
sample_days: lastResult.sample_days_requested || 60,
straddle_premium: readPremium(),
take_profit: readTakeProfit(),
move_points: readMovePoints(),
weekend_filter: readWeekend(),
perp_hedge: readPerpHedge(),
price_source: lastResult.price_source || "",
inst_id: lastResult.inst_id || "",
page: pageNo,
@@ -368,10 +258,8 @@
start_hour: startHour,
period,
custom_days: period === "custom" ? customDays : null,
straddle_premium: readPremium(),
take_profit: readTakeProfit(),
move_points: readMovePoints(),
weekend_filter: readWeekend(),
perp_hedge: readPerpHedge(),
page: pageNo,
page_size: 20,
}),
@@ -407,17 +295,6 @@
}
}
function appendHedgeQuery(q) {
const h = readPerpHedge();
if (!h) return;
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("请先计算");
@@ -427,8 +304,7 @@
const startHour = Number(el("amp-start-hour")?.value || 16);
const period = el("amp-period")?.value || "2m";
const customDays = Number(el("amp-custom-days")?.value || 60);
const prem = readPremium();
const tp = readTakeProfit();
const mp = readMovePoints();
const q = new URLSearchParams({
symbol,
start_hour: String(startHour),
@@ -436,9 +312,7 @@
weekend_filter: readWeekend(),
});
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);
if (mp != null) q.set("move_points", String(mp));
window.location.href = "/api/amp-stats/export?" + q.toString();
}
@@ -472,15 +346,12 @@
const id = card.getAttribute("data-id");
card.querySelector(".amp-hist-view")?.addEventListener("click", () => void openHistory(id));
card.querySelector(".amp-hist-dl")?.addEventListener("click", () => {
const prem = readPremium();
const tp = readTakeProfit();
const mp = readMovePoints();
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);
if (mp != null) q.set("move_points", String(mp));
window.location.href = "/api/amp-stats/export?" + q.toString();
});
card.querySelector(".amp-hist-del")?.addEventListener("click", async () => {
@@ -502,27 +373,12 @@
if (lastResult) {
if (el("amp-symbol")) el("amp-symbol").value = lastResult.symbol || "eth";
if (el("amp-start-hour")) el("amp-start-hour").value = String(lastResult.start_hour ?? 16);
if (lastResult.straddle_premium != null && el("amp-straddle-premium")) {
el("amp-straddle-premium").value = String(lastResult.straddle_premium);
}
if (lastResult.take_profit != null && el("amp-take-profit")) {
el("amp-take-profit").value = String(lastResult.take_profit);
if (lastResult.move_points != null && el("amp-move-points")) {
el("amp-move-points").value = String(lastResult.move_points);
}
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 (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);
@@ -543,22 +399,9 @@
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-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-move-points")?.addEventListener("input", scheduleReframe);
el("amp-weekend-filter")?.addEventListener("change", () => void reframe(true));
syncCustomDays();
syncOverlayMode();
}
window.hubAmpStatsPage = {
@@ -566,9 +409,7 @@
bind();
setView("stats");
setStatus("");
syncOverlayMode();
renderStraddle(null);
renderPerpHedge(null);
renderMoveStats(null);
},
};
})();
+6 -43
View File
@@ -1265,39 +1265,8 @@
</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 amp-overlay-straddle">
<span>止盈点(点)</span>
<input id="amp-take-profit" type="number" min="0" step="any" placeholder="空=按涨跌" />
</label>
<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 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 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 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 amp-overlay-perp hidden">
<span>永期·期权比例</span>
<input id="amp-hedge-ratio-opt" type="number" min="0.01" step="any" value="2" />
<span>波动点数</span>
<input id="amp-move-points" type="number" min="0" step="any" placeholder="如 50" />
</label>
<div class="amp-actions">
<button type="button" id="amp-btn-compute" class="primary">计算</button>
@@ -1306,24 +1275,18 @@
</div>
</div>
<p id="amp-status" class="toolbar-meta amp-status"></p>
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.对照模式二选一:买跨收益=有效波动−权利金;永期对冲=永续多1币+买期权(默认1:2),入场按日开盘,触达目标点数按目标盈利出场否则收盘结算.周末按结算日标注/筛选.</p>
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.填写波动点数后看振幅≥该点数的天数占比;日表显示两边波动(开→高/开→低)与振幅是否达标.周末按结算日标注/筛选.</p>
<h3 class="amp-block-title">汇总</h3>
<div id="amp-summary" class="amp-summary"></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 id="amp-move-stats" class="amp-summary amp-move-stats"></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 id="amp-col-pnl">收益</th>
<th>开→高</th><th>开→低</th><th>振幅</th><th>涨跌</th><th>振幅达标</th>
</tr>
</thead>
<tbody id="amp-table-body">