删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
4.1 KiB
Python
111 lines
4.1 KiB
Python
"""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()
|