Files
Chan/research/step18_compare_final.py
jackyu66gitandCursor 7f393b93ed refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块
删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 01:05:12 +08:00

111 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Step 18:两条存活路径的正面对比与尾部检验。
路径一 同级别中枢突破(step13) 1389 笔 PF 1.63
路径二 区间套快速三买(step17) 239 笔 PF 1.52
样本量差 6 倍,但真正决定能否上实盘的是尾部依赖:
step14 已发现路径一剔掉最赚的 5% 后 t 值就从 6.45 塌到 1.60。
本步用同一把尺子量路径二。
"""
from __future__ import annotations
import sys
import warnings
from pathlib import Path
import numpy as np
import pandas as pd
warnings.filterwarnings("ignore")
pd.set_option("display.width", 260)
OUT = Path(__file__).parent / "out"
FEE = 0.0008
def trim_curve(r: np.ndarray, label: str) -> list[dict]:
rows = []
for k in (0, 1, 2, 5, 10):
v = r if k == 0 else r[r <= np.quantile(r, 1 - k / 100)]
win, loss = v[v > 0], v[v <= 0]
pf = win.sum() / abs(loss.sum()) if len(loss) and loss.sum() != 0 else np.inf
sd = v.std(ddof=1)
rows.append({
"路径": label, "剔除最赚": f"{k}%", "笔数": len(v),
"均收益": f"{v.mean() * 100:+.3f}%", "PF": f"{pf:.2f}",
"t值": f"{v.mean() / (sd / np.sqrt(len(v))):+.2f}",
})
return rows
def profile(r: np.ndarray, label: str) -> dict:
win, loss = r[r > 0], r[r <= 0]
sd = r.std(ddof=1)
return {
"路径": label, "笔数": len(r),
"胜率": f"{(r > 0).mean() * 100:.1f}%",
"均收益": f"{r.mean() * 100:+.3f}%",
"中位数": f"{np.median(r) * 100:+.3f}%",
"PF": f"{win.sum() / abs(loss.sum()):.2f}",
"偏度": f"{pd.Series(r).skew():.2f}",
"t值": f"{r.mean() / (sd / np.sqrt(len(r))):+.2f}",
}
def main() -> None:
p1 = pd.read_csv(OUT / "step13_all_trades.csv", parse_dates=["date"])
p2 = pd.read_csv(OUT / "step17_fast_bsp3_trades.csv", parse_dates=["date"])
r1, r2 = p1["ret"].to_numpy(), p2["ret"].to_numpy()
# 区间套的最优组合:1h 同向 + 浅回抽
dq = p2["depth"].quantile(0.66)
best = p2[(p2["h1_agree"] == 1) & (p2["depth"] >= dq)]
r3 = best["ret"].to_numpy()
print("########## 1. 总体画像 ##########")
print(pd.DataFrame([
profile(r1, "同级别中枢突破"),
profile(r2, "区间套快速三买"),
profile(r3, "区间套(同向+浅回抽)"),
]).to_string(index=False))
print("\n 中位数为正说明多数交易在赚钱,为负则说明靠尾部。")
print("\n########## 2. 尾部依赖检验 ##########")
rows = trim_curve(r1, "中枢突破") + trim_curve(r2, "区间套三买")
d = pd.DataFrame(rows).pivot(index="剔除最赚", columns="路径",
values=["PF", "t值"])
print(d.to_string())
print("\n########## 3. 成本敏感性 ##########")
rows = []
for mult, name in [(1, "0.08%"), (2, "0.16%"), (3, "0.24%"), (5, "0.40%")]:
for r, lab in ((r1, "中枢突破"), (r2, "区间套三买")):
v = r - (mult - 1) * FEE
win, loss = v[v > 0], v[v <= 0]
sd = v.std(ddof=1)
rows.append({
"成本": name, "路径": lab,
"PF": f"{win.sum() / abs(loss.sum()):.2f}",
"t值": f"{v.mean() / (sd / np.sqrt(len(v))):+.2f}",
})
print(pd.DataFrame(rows).pivot(index="成本", columns="路径",
values=["PF", "t值"]).to_string())
print("\n########## 4. 收益分位对比 ##########")
qs = [0.05, 0.25, 0.5, 0.75, 0.95]
print(pd.DataFrame({
"分位": [f"P{int(q * 100)}" for q in qs],
"中枢突破": [f"{np.quantile(r1, q) * 100:+.2f}%" for q in qs],
"区间套三买": [f"{np.quantile(r2, q) * 100:+.2f}%" for q in qs],
}).to_string(index=False))
print("\n########## 5. 交易频率 ##########")
for r, t, lab in ((r1, p1, "中枢突破"), (r2, p2, "区间套三买")):
yrs = (t["date"].max() - t["date"].min()).days / 365.25
print(f" {lab:>10}{len(r) / yrs:.0f} 笔/年 "
f"覆盖 {t['date'].min().date()} -> {t['date'].max().date()}")
if __name__ == "__main__":
main()