Add long-straddle premium overlay to hub amp stats.
Configurable bilateral premium with exceed counts/ratios and settlement PnL for buying volatility. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+101
-3
@@ -165,9 +165,74 @@ def compute_day_row(
|
||||
}
|
||||
|
||||
|
||||
def summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
def normalize_straddle_premium(raw: Any) -> Optional[float]:
|
||||
"""双边权利金(点数).空/≤0 表示不做跨式对照."""
|
||||
if raw is None or raw == "":
|
||||
return None
|
||||
try:
|
||||
v = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("双边权利金须为数字") from None
|
||||
if v <= 0:
|
||||
return None
|
||||
return v
|
||||
|
||||
|
||||
def straddle_long_stats(rows: list[dict[str, Any]], premium: float) -> dict[str, Any]:
|
||||
"""买跨(赌波动):盈亏按收盘 |C−O| − 双边权利金;越过阈值用严格 >."""
|
||||
prem = float(premium)
|
||||
if prem <= 0:
|
||||
raise ValueError("双边权利金须 > 0")
|
||||
if not rows:
|
||||
return {
|
||||
"side": "long_straddle",
|
||||
"premium": prem,
|
||||
"sample_count": 0,
|
||||
"up_exceed_days": 0,
|
||||
"up_exceed_ratio": None,
|
||||
"down_exceed_days": 0,
|
||||
"down_exceed_ratio": None,
|
||||
"abs_change_exceed_days": 0,
|
||||
"abs_change_exceed_ratio": None,
|
||||
"pnl_total": None,
|
||||
"pnl_avg": None,
|
||||
"win_days": 0,
|
||||
"win_ratio": None,
|
||||
"pnl_max": None,
|
||||
"pnl_min": None,
|
||||
}
|
||||
n = len(rows)
|
||||
up_ex = sum(1 for r in rows if float(r["up_points"]) > prem)
|
||||
down_ex = sum(1 for r in rows if float(r["down_points"]) > prem)
|
||||
abs_ex = sum(1 for r in rows if abs(float(r["change"])) > prem)
|
||||
pnls = [abs(float(r["change"])) - prem for r in rows]
|
||||
win = sum(1 for p in pnls if p > 0)
|
||||
return {
|
||||
"side": "long_straddle",
|
||||
"premium": round(prem, 4),
|
||||
"sample_count": n,
|
||||
"up_exceed_days": up_ex,
|
||||
"up_exceed_ratio": round(up_ex / n, 4),
|
||||
"down_exceed_days": down_ex,
|
||||
"down_exceed_ratio": round(down_ex / n, 4),
|
||||
"abs_change_exceed_days": abs_ex,
|
||||
"abs_change_exceed_ratio": round(abs_ex / n, 4),
|
||||
"pnl_total": round(sum(pnls), 4),
|
||||
"pnl_avg": round(statistics.fmean(pnls), 4),
|
||||
"win_days": win,
|
||||
"win_ratio": round(win / n, 4),
|
||||
"pnl_max": round(max(pnls), 4),
|
||||
"pnl_min": round(min(pnls), 4),
|
||||
}
|
||||
|
||||
|
||||
def summarize_rows(
|
||||
rows: list[dict[str, Any]],
|
||||
*,
|
||||
straddle_premium: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
if not rows:
|
||||
out = {
|
||||
"sample_count": 0,
|
||||
"max_amplitude": None,
|
||||
"max_amplitude_day": None,
|
||||
@@ -179,7 +244,12 @@ def summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"avg_down_points": None,
|
||||
"up_day_ratio": None,
|
||||
"down_day_ratio": None,
|
||||
"straddle": None,
|
||||
}
|
||||
prem = normalize_straddle_premium(straddle_premium)
|
||||
if prem is not None:
|
||||
out["straddle"] = straddle_long_stats([], prem)
|
||||
return out
|
||||
amps = [float(r["amplitude"]) for r in rows]
|
||||
ups = [float(r["up_points"]) for r in rows]
|
||||
downs = [float(r["down_points"]) for r in rows]
|
||||
@@ -188,7 +258,7 @@ def summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
up_days = sum(1 for r in rows if float(r["change"]) > 0)
|
||||
down_days = sum(1 for r in rows if float(r["change"]) < 0)
|
||||
n = len(rows)
|
||||
return {
|
||||
out: dict[str, Any] = {
|
||||
"sample_count": n,
|
||||
"max_amplitude": round(max_amp, 4),
|
||||
"max_amplitude_day": max_amp_day,
|
||||
@@ -200,7 +270,12 @@ def summarize_rows(rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"avg_down_points": round(statistics.fmean(downs), 4),
|
||||
"up_day_ratio": round(up_days / n, 4),
|
||||
"down_day_ratio": round(down_days / n, 4),
|
||||
"straddle": None,
|
||||
}
|
||||
prem = normalize_straddle_premium(straddle_premium)
|
||||
if prem is not None:
|
||||
out["straddle"] = straddle_long_stats(rows, prem)
|
||||
return out
|
||||
|
||||
|
||||
def _parse_okx_candle_row(row: list) -> Optional[dict[str, Any]]:
|
||||
@@ -312,6 +387,7 @@ def compute_amp_stats(
|
||||
start_hour: int = 16,
|
||||
period: str = "2m",
|
||||
custom_days: Any = None,
|
||||
straddle_premium: Any = None,
|
||||
now: Optional[datetime] = None,
|
||||
fetch_fn: Optional[Callable[..., list[dict[str, Any]]]] = None,
|
||||
) -> dict[str, Any]:
|
||||
@@ -319,6 +395,7 @@ def compute_amp_stats(
|
||||
sh = int(start_hour)
|
||||
if sh < 0 or sh > 23:
|
||||
raise ValueError("起点须为 0-23 整点")
|
||||
prem = normalize_straddle_premium(straddle_premium)
|
||||
sample_days = resolve_sample_days(period, custom_days)
|
||||
settlements = list_settlement_dates(sample_days=sample_days, now=now)
|
||||
if not settlements:
|
||||
@@ -342,7 +419,7 @@ def compute_amp_stats(
|
||||
missing.append(d.isoformat())
|
||||
continue
|
||||
rows.append(row)
|
||||
summary = summarize_rows(rows)
|
||||
summary = summarize_rows(rows, straddle_premium=prem)
|
||||
period_label = period if period != "custom" else f"custom:{sample_days}"
|
||||
return {
|
||||
"ok": True,
|
||||
@@ -353,6 +430,7 @@ def compute_amp_stats(
|
||||
"end_hour": END_HOUR,
|
||||
"period": period_label,
|
||||
"sample_days_requested": sample_days,
|
||||
"straddle_premium": prem,
|
||||
"timeframe": TIMEFRAME,
|
||||
"price_source": price_source,
|
||||
"inst_id": inst_id,
|
||||
@@ -398,6 +476,26 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
w.writerow(["开→高最大", s.get("max_up_points"), "均值", s.get("avg_up_points")])
|
||||
w.writerow(["开→低最大", s.get("max_down_points"), "均值", s.get("avg_down_points")])
|
||||
w.writerow(["上涨窗占比", s.get("up_day_ratio"), "下跌窗占比", s.get("down_day_ratio")])
|
||||
st = s.get("straddle") or {}
|
||||
if st:
|
||||
w.writerow([])
|
||||
w.writerow(["【买跨对照·双边权利金】", st.get("premium")])
|
||||
w.writerow(["开→高超过", st.get("up_exceed_days"), "占比", st.get("up_exceed_ratio")])
|
||||
w.writerow(["开→低超过", st.get("down_exceed_days"), "占比", st.get("down_exceed_ratio")])
|
||||
w.writerow(["|涨跌|超过", st.get("abs_change_exceed_days"), "占比", st.get("abs_change_exceed_ratio")])
|
||||
w.writerow(
|
||||
[
|
||||
"买跨点数盈亏合计",
|
||||
st.get("pnl_total"),
|
||||
"日均",
|
||||
st.get("pnl_avg"),
|
||||
"赚钱天数",
|
||||
st.get("win_days"),
|
||||
"胜率",
|
||||
st.get("win_ratio"),
|
||||
]
|
||||
)
|
||||
w.writerow(["单日最大赚", st.get("pnl_max"), "单日最大亏", st.get("pnl_min")])
|
||||
w.writerow([])
|
||||
w.writerow(["【日表明细】"])
|
||||
w.writerow(
|
||||
|
||||
Reference in New Issue
Block a user