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:
+23
-7
@@ -58,6 +58,19 @@ K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD /
|
||||
|
||||
---
|
||||
|
||||
## 对照模式(买跨 / 永期二选一)
|
||||
|
||||
表单 **对照模式** 切换:
|
||||
|
||||
| 模式 | 表单 | 汇总块 | 日表末列 |
|
||||
|------|------|--------|----------|
|
||||
| 买跨双边 | 双边权利金、止盈点 | 买跨对照 | 收益 |
|
||||
| 永期对冲 | 目标盈利、杠杆、比例 | 永期对冲对照 | 永期盈亏 |
|
||||
|
||||
同一时刻只计算 / 展示当前模式;切换后在已有日表上本地重算。
|
||||
|
||||
---
|
||||
|
||||
## 买跨对照(赌波动)
|
||||
|
||||
表单可填 **双边权利金(点)**,例如 `30`;旁边可填 **止盈点**(可空):
|
||||
@@ -86,14 +99,16 @@ K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD /
|
||||
|
||||
与中控 [永期对冲计算器](./永期对冲计算器.md) 同口径:**永续做多 1 币 + 买期权**(默认比例 **1:2**),在历史振幅日表上做对照。
|
||||
|
||||
表单填:现价、目标盈利、永续杠杆、期权杠杆、比例(可改)。未填齐现价/目标/杠杆时不计算该块。
|
||||
表单填:目标盈利、永续杠杆、期权杠杆、比例(可改)。**入场价 = 当日开盘**,不再填现价。未填齐目标/杠杆时不计算该块。
|
||||
|
||||
```text
|
||||
单币权利金 = 现价 / 期权杠杆
|
||||
期权币数 = 1 × (期权比例 / 永续比例)
|
||||
权利金总额 = 期权币数 × 单币权利金
|
||||
单币权利金(日) = 当日开盘 / 期权杠杆
|
||||
期权币数 = 1 × (期权比例 / 永续比例)
|
||||
权利金总额(日) = 期权币数 × 单币权利金(日)
|
||||
```
|
||||
|
||||
推「所需点数」时,用样本 **开盘中位数** 作入场参照(汇总里展示的权利金中位同口径)。
|
||||
|
||||
### ① 所需点数达标
|
||||
|
||||
复用计算器「由比例推点数」:
|
||||
@@ -109,11 +124,11 @@ K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD /
|
||||
|
||||
| 日向 | 组合净利 |
|
||||
|------|----------|
|
||||
| 上涨 `涨跌≥0` | `涨跌 − 权利金 − 永续开平手续费(开→收)` |
|
||||
| 下跌 `涨跌<0` | `\|涨跌\|×(期权币数−1) − 权利金` |
|
||||
| 上涨 `涨跌≥0` | `涨跌 − 当日权利金 − 永续开平手续费(开→收)` |
|
||||
| 下跌 `涨跌<0` | `\|涨跌\|×(期权币数−1) − 当日权利金` |
|
||||
|
||||
汇总:合计、日均、胜率、上涨日/下跌日盈亏小计、单日最大赚亏;日表 **永期盈亏** 列。
|
||||
改永期参数 / 周末筛选会**本地重算**(不重拉 K 线)。权利金按表单现价固定,不按日开盘重估。
|
||||
改永期参数 / 周末筛选会**本地重算**(不重拉 K 线)。
|
||||
|
||||
---
|
||||
|
||||
@@ -146,3 +161,4 @@ K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD /
|
||||
| 2026-07-23 | 周末筛选/标注、止盈点(≥)、日表收益列 |
|
||||
| 2026-07-23 | 长周期续拉 history K 线;收益列红绿着色 |
|
||||
| 2026-07-28 | 永期对冲对照:所需点数达标 + 按日组合盈亏 |
|
||||
| 2026-07-28 | 永期入场改按日开盘;买跨/永期对照模式二选一 |
|
||||
|
||||
+71
-43
@@ -279,7 +279,7 @@ def enrich_rows_pnl(
|
||||
change=float(item.get("change") or 0),
|
||||
open_px=float(item.get("open") or 0),
|
||||
close_px=float(item.get("close") or 0),
|
||||
premium_total=float(hedge["premium_total"]),
|
||||
option_leverage=float(hedge["option_leverage"]),
|
||||
opt_coins=float(hedge["opt_coins"]),
|
||||
)
|
||||
else:
|
||||
@@ -291,22 +291,21 @@ def enrich_rows_pnl(
|
||||
def normalize_perp_hedge_params(raw: Any) -> Optional[dict[str, float]]:
|
||||
"""永期对冲对照参数.缺必填则返回 None(不做对照).
|
||||
|
||||
接受 dict 或带 spot/target_profit_u/perp_leverage/option_leverage 的对象字段.
|
||||
入场价按日开盘;表单只需目标盈利/杠杆/比例.旧字段 spot 可忽略.
|
||||
"""
|
||||
if raw is None or raw == "":
|
||||
return None
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
spot = _safe_float(raw.get("spot"))
|
||||
target = _safe_float(raw.get("target_profit_u") if "target_profit_u" in raw else raw.get("target"))
|
||||
p_lev = _safe_float(raw.get("perp_leverage"))
|
||||
o_lev = _safe_float(raw.get("option_leverage"))
|
||||
rp = _safe_float(raw.get("ratio_perp"))
|
||||
ro = _safe_float(raw.get("ratio_opt"))
|
||||
ct = _safe_float(raw.get("ct_mult"))
|
||||
if spot is None or target is None or p_lev is None or o_lev is None:
|
||||
if target is None or p_lev is None or o_lev is None:
|
||||
return None
|
||||
if spot <= 0 or target < 0 or p_lev <= 0 or o_lev <= 0:
|
||||
if target < 0 or p_lev <= 0 or o_lev <= 0:
|
||||
return None
|
||||
if rp is None or rp <= 0:
|
||||
rp = 1.0
|
||||
@@ -314,33 +313,38 @@ def normalize_perp_hedge_params(raw: Any) -> Optional[dict[str, float]]:
|
||||
ro = 2.0
|
||||
if ct is None or ct <= 0:
|
||||
ct = 0.01
|
||||
prem_per_coin = spot / o_lev
|
||||
opt_coins = 1.0 * (ro / rp)
|
||||
premium_total = opt_coins * prem_per_coin
|
||||
return {
|
||||
"spot": spot,
|
||||
"target_profit_u": target,
|
||||
"perp_leverage": p_lev,
|
||||
"option_leverage": o_lev,
|
||||
"ratio_perp": rp,
|
||||
"ratio_opt": ro,
|
||||
"ct_mult": ct,
|
||||
"prem_per_coin": prem_per_coin,
|
||||
"opt_coins": opt_coins,
|
||||
"opt_sheets": opt_coins / ct,
|
||||
"premium_total": premium_total,
|
||||
}
|
||||
|
||||
|
||||
def perp_hedge_day_premium(*, open_px: float, option_leverage: float, opt_coins: float) -> float:
|
||||
"""单日权利金总额 = 开盘 / 期权杠杆 × 期权币数."""
|
||||
o = float(open_px or 0)
|
||||
lev = float(option_leverage or 0)
|
||||
coins = float(opt_coins or 0)
|
||||
if o <= 0 or lev <= 0 or coins < 0:
|
||||
return 0.0
|
||||
return coins * (o / lev)
|
||||
|
||||
|
||||
def perp_hedge_day_pnl(
|
||||
*,
|
||||
change: float,
|
||||
open_px: float,
|
||||
close_px: float,
|
||||
premium_total: float,
|
||||
option_leverage: float,
|
||||
opt_coins: float,
|
||||
) -> float:
|
||||
"""单日组合净利(永续多1币 + 买期权).
|
||||
"""单日组合净利(永续多1币 + 买期权);入场/权利金按当日开盘.
|
||||
|
||||
上涨: change − 权利金 − 永续开平手续费
|
||||
下跌: |change|×(opt_coins−1) − 权利金
|
||||
@@ -348,7 +352,9 @@ def perp_hedge_day_pnl(
|
||||
from lib.trade.trade_fee_lib import estimate_roundtrip_fee_usdt
|
||||
|
||||
chg = float(change or 0)
|
||||
prem = float(premium_total or 0)
|
||||
prem = perp_hedge_day_premium(
|
||||
open_px=open_px, option_leverage=option_leverage, opt_coins=opt_coins
|
||||
)
|
||||
coins = float(opt_coins or 0)
|
||||
if chg >= 0:
|
||||
fee = 0.0
|
||||
@@ -363,54 +369,74 @@ def perp_hedge_stats(
|
||||
rows: list[dict[str, Any]],
|
||||
hedge: dict[str, float],
|
||||
) -> dict[str, Any]:
|
||||
"""永期对冲:所需点数达标 + 按日组合盈亏汇总."""
|
||||
"""永期对冲:所需点数达标 + 按日组合盈亏汇总.
|
||||
|
||||
日盈亏权利金按当日开盘;推所需点数用样本开盘中位数作入场参照.
|
||||
"""
|
||||
from lib.hub.hub_perp_options_calc_lib import calc_perp_options_points
|
||||
|
||||
points_data, points_err = calc_perp_options_points(
|
||||
base="ETH",
|
||||
spot=hedge["spot"],
|
||||
capital_usdt=max(hedge["spot"] / hedge["perp_leverage"] * 2, 1000.0),
|
||||
target_profit_u=hedge["target_profit_u"],
|
||||
perp_leverage=hedge["perp_leverage"],
|
||||
option_leverage=hedge["option_leverage"],
|
||||
ratio_perp=hedge["ratio_perp"],
|
||||
ratio_opt=hedge["ratio_opt"],
|
||||
ct_mult=hedge["ct_mult"],
|
||||
opens = [float(r.get("open") or 0) for r in (rows or []) if float(r.get("open") or 0) > 0]
|
||||
spot_ref = statistics.median(opens) if opens else None
|
||||
prem_ref = (
|
||||
perp_hedge_day_premium(
|
||||
open_px=spot_ref,
|
||||
option_leverage=hedge["option_leverage"],
|
||||
opt_coins=hedge["opt_coins"],
|
||||
)
|
||||
if spot_ref is not None
|
||||
else None
|
||||
)
|
||||
prem_per_coin_ref = (spot_ref / hedge["option_leverage"]) if spot_ref is not None else None
|
||||
|
||||
move_a = None
|
||||
move_b = None
|
||||
if points_data:
|
||||
move_a = float((points_data.get("case_a") or {}).get("move_points") or 0) or None
|
||||
mb = (points_data.get("case_b") or {}).get("move_points_portfolio")
|
||||
move_b = float(mb) if mb is not None else None
|
||||
points_err = None
|
||||
if spot_ref is not None and spot_ref > 0:
|
||||
points_data, points_err = calc_perp_options_points(
|
||||
base="ETH",
|
||||
spot=spot_ref,
|
||||
capital_usdt=max(spot_ref / hedge["perp_leverage"] * 2, 1000.0),
|
||||
target_profit_u=hedge["target_profit_u"],
|
||||
perp_leverage=hedge["perp_leverage"],
|
||||
option_leverage=hedge["option_leverage"],
|
||||
ratio_perp=hedge["ratio_perp"],
|
||||
ratio_opt=hedge["ratio_opt"],
|
||||
ct_mult=hedge["ct_mult"],
|
||||
)
|
||||
if points_data:
|
||||
move_a = float((points_data.get("case_a") or {}).get("move_points") or 0) or None
|
||||
mb = (points_data.get("case_b") or {}).get("move_points_portfolio")
|
||||
move_b = float(mb) if mb is not None else None
|
||||
else:
|
||||
points_err = "样本无有效开盘价,无法推所需点数"
|
||||
|
||||
# 使用已 enrich 的 perp_hedge_pnl;若无则当场补算
|
||||
# 按日开盘重算盈亏(不沿用固定权利金)
|
||||
work: list[dict[str, Any]] = []
|
||||
for r in rows or []:
|
||||
item = dict(r)
|
||||
if item.get("perp_hedge_pnl") is None:
|
||||
item["perp_hedge_pnl"] = perp_hedge_day_pnl(
|
||||
change=float(item.get("change") or 0),
|
||||
open_px=float(item.get("open") or 0),
|
||||
close_px=float(item.get("close") or 0),
|
||||
premium_total=float(hedge["premium_total"]),
|
||||
opt_coins=float(hedge["opt_coins"]),
|
||||
)
|
||||
item["perp_hedge_pnl"] = perp_hedge_day_pnl(
|
||||
change=float(item.get("change") or 0),
|
||||
open_px=float(item.get("open") or 0),
|
||||
close_px=float(item.get("close") or 0),
|
||||
option_leverage=float(hedge["option_leverage"]),
|
||||
opt_coins=float(hedge["opt_coins"]),
|
||||
)
|
||||
work.append(item)
|
||||
n = len(work)
|
||||
empty = {
|
||||
"enabled": True,
|
||||
"spot": round(hedge["spot"], 4),
|
||||
"entry": "open",
|
||||
"spot": None if spot_ref is None else round(spot_ref, 4),
|
||||
"target_profit_u": round(hedge["target_profit_u"], 4),
|
||||
"perp_leverage": round(hedge["perp_leverage"], 4),
|
||||
"option_leverage": round(hedge["option_leverage"], 4),
|
||||
"ratio_perp": round(hedge["ratio_perp"], 4),
|
||||
"ratio_opt": round(hedge["ratio_opt"], 4),
|
||||
"ratio_label": f"{hedge['ratio_perp']:g}:{hedge['ratio_opt']:g}",
|
||||
"prem_per_coin": round(hedge["prem_per_coin"], 4),
|
||||
"prem_per_coin": None if prem_per_coin_ref is None else round(prem_per_coin_ref, 4),
|
||||
"opt_coins": round(hedge["opt_coins"], 4),
|
||||
"opt_sheets": round(hedge["opt_sheets"], 4),
|
||||
"premium_total": round(hedge["premium_total"], 4),
|
||||
"premium_total": None if prem_ref is None else round(prem_ref, 4),
|
||||
"move_a": None if move_a is None else round(move_a, 4),
|
||||
"move_b": None if move_b is None else round(move_b, 4),
|
||||
"points_error": points_err,
|
||||
@@ -995,7 +1021,9 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
"【永期对冲对照】",
|
||||
"比例",
|
||||
ph.get("ratio_label"),
|
||||
"现价",
|
||||
"入场",
|
||||
"按日开盘",
|
||||
"推点数开盘中位",
|
||||
ph.get("spot"),
|
||||
"目标",
|
||||
ph.get("target_profit_u"),
|
||||
@@ -1003,9 +1031,9 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
)
|
||||
w.writerow(
|
||||
[
|
||||
"单币权利金",
|
||||
"单币权利金(开盘中位)",
|
||||
ph.get("prem_per_coin"),
|
||||
"权利金总额",
|
||||
"权利金总额(开盘中位)",
|
||||
ph.get("premium_total"),
|
||||
"期权币数",
|
||||
ph.get("opt_coins"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -95,7 +95,7 @@ class AmpStatsLibTests(unittest.TestCase):
|
||||
def test_perp_hedge_hit_and_pnl(self):
|
||||
from lib.hub.amp_stats_lib import perp_hedge_day_pnl
|
||||
|
||||
# spot=1800 optLev=100 → prem/coin=18; 1:2 → premium=36
|
||||
# 开盘=1800 optLev=100 → prem/coin=18; 1:2 → premium=36
|
||||
# A move ≈ 52.83; B portfolio move = 51
|
||||
rows = [
|
||||
{"open": 1800, "close": 1860, "change": 60, "up_points": 60, "down_points": 0, "amplitude": 60, "settlement_day": "2026-07-01"},
|
||||
@@ -103,7 +103,6 @@ class AmpStatsLibTests(unittest.TestCase):
|
||||
{"open": 1800, "close": 1820, "change": 20, "up_points": 20, "down_points": 0, "amplitude": 20, "settlement_day": "2026-07-03"},
|
||||
]
|
||||
hedge = {
|
||||
"spot": 1800,
|
||||
"target_profit_u": 15,
|
||||
"perp_leverage": 10,
|
||||
"option_leverage": 100,
|
||||
@@ -114,19 +113,31 @@ class AmpStatsLibTests(unittest.TestCase):
|
||||
s = summarize_rows(rows, perp_hedge=hedge)
|
||||
ph = s["perp_hedge"]
|
||||
self.assertIsNotNone(ph)
|
||||
self.assertEqual(ph["entry"], "open")
|
||||
self.assertEqual(ph["spot"], 1800.0)
|
||||
self.assertEqual(ph["opt_coins"], 2.0)
|
||||
self.assertEqual(ph["premium_total"], 36.0)
|
||||
self.assertAlmostEqual(ph["move_b"], 51.0, places=4)
|
||||
self.assertEqual(ph["hit_a_days"], 1) # only +60
|
||||
self.assertEqual(ph["hit_b_days"], 1) # only -60
|
||||
# up day pnl = 60 - 36 - fee
|
||||
up_pnl = perp_hedge_day_pnl(change=60, open_px=1800, close_px=1860, premium_total=36, opt_coins=2)
|
||||
down_pnl = perp_hedge_day_pnl(change=-60, open_px=1800, close_px=1740, premium_total=36, opt_coins=2)
|
||||
up_pnl = perp_hedge_day_pnl(
|
||||
change=60, open_px=1800, close_px=1860, option_leverage=100, opt_coins=2
|
||||
)
|
||||
down_pnl = perp_hedge_day_pnl(
|
||||
change=-60, open_px=1800, close_px=1740, option_leverage=100, opt_coins=2
|
||||
)
|
||||
self.assertAlmostEqual(down_pnl, 60 * (2 - 1) - 36, places=4) # 24
|
||||
self.assertAlmostEqual(ph["down_pnl_total"], down_pnl, places=4)
|
||||
self.assertGreater(up_pnl, 0)
|
||||
self.assertEqual(ph["up_days"], 2)
|
||||
self.assertEqual(ph["down_days"], 1)
|
||||
|
||||
# 不同开盘 → 不同权利金
|
||||
hi_open_pnl = perp_hedge_day_pnl(
|
||||
change=-60, open_px=2000, close_px=1940, option_leverage=100, opt_coins=2
|
||||
)
|
||||
self.assertAlmostEqual(hi_open_pnl, 60 - 40, places=4) # prem=40
|
||||
|
||||
csv_text = build_export_csv(
|
||||
{
|
||||
"exchange": "okx",
|
||||
@@ -139,6 +150,7 @@ class AmpStatsLibTests(unittest.TestCase):
|
||||
)
|
||||
self.assertIn("永期对冲对照", csv_text)
|
||||
self.assertIn("永期盈亏", csv_text)
|
||||
self.assertIn("按日开盘", csv_text)
|
||||
|
||||
def test_long_straddle_stats(self):
|
||||
rows = [
|
||||
|
||||
Reference in New Issue
Block a user