删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
"""Step 7:区间套验证。
|
||
|
||
逐层加过滤,看每一层带来多少增量:
|
||
L0 所有 1h 分型
|
||
L1 + 背驰(价格创新极值但 MACD 面积衰减)
|
||
L2 + 大级别中枢位置(买贴支撑 / 卖贴压力)
|
||
L3 L1 + L2 组合
|
||
对照组:单级别三类买卖点(滞后 16~21 根)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||
|
||
from lib.bsp_eval import baseline_stats
|
||
from lib.data import fetch_ohlcv
|
||
from lib.fx_signal import add_forward_returns, extract_fx_signals, signals_to_frame
|
||
from lib.nested_level import annotate_position, build_htf_zones
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||
from chanlun import TF_DF
|
||
|
||
pd.set_option("display.width", 240)
|
||
|
||
HORIZONS = (3, 5, 10, 20, 40)
|
||
|
||
|
||
def summarize(g: pd.DataFrame, df: pd.DataFrame, label: str, min_n: int = 10) -> list[dict]:
|
||
base = baseline_stats(df, HORIZONS).set_index("horizon")
|
||
out = []
|
||
for h in HORIZONS:
|
||
col = f"ret_{h}"
|
||
if col not in g:
|
||
continue
|
||
r = g[col].dropna().to_numpy()
|
||
if len(r) < min_n:
|
||
continue
|
||
dirs = g.loc[g[col].notna(), "direction"].to_numpy()
|
||
sd = r.std(ddof=1)
|
||
out.append({
|
||
"分组": label, "持有": h, "n": len(r),
|
||
"收益": r.mean(), "胜率": (r > 0).mean(),
|
||
"超额": r.mean() - float(np.mean(dirs) * base.loc[h, "base_mean_long"]),
|
||
"t值": r.mean() / (sd / np.sqrt(len(r))) if sd else np.nan,
|
||
})
|
||
return out
|
||
|
||
|
||
def show(rows: list[dict]) -> None:
|
||
if not rows:
|
||
print(" (样本不足)")
|
||
return
|
||
d = pd.DataFrame(rows)
|
||
d["收益"] = d["收益"].map(lambda v: f"{v * 100:+.2f}%")
|
||
d["超额"] = d["超额"].map(lambda v: f"{v * 100:+.2f}%")
|
||
d["胜率"] = d["胜率"].map(lambda v: f"{v * 100:.0f}%")
|
||
d["t值"] = d["t值"].map(lambda v: f"{v:+.2f}")
|
||
print(d.to_string(index=False))
|
||
|
||
|
||
def main() -> None:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--symbol", default="BTC/USDT:USDT")
|
||
ap.add_argument("--ltf", default="1h", help="小级别:出分型信号")
|
||
ap.add_argument("--htf", default="1d", help="大级别:出支撑压力中枢")
|
||
ap.add_argument("--tol", type=float, default=0.01, help="贴近边界的相对阈值")
|
||
args = ap.parse_args()
|
||
|
||
df = fetch_ohlcv(args.symbol, args.ltf, 10**9)
|
||
df_htf = fetch_ohlcv(args.symbol, args.htf, 10**9)
|
||
print(f"[data] 小级别 {args.ltf} rows={len(df)} 大级别 {args.htf} rows={len(df_htf)}")
|
||
print(f" {df['date'].iloc[0]} -> {df['date'].iloc[-1]}\n")
|
||
|
||
chan = TF_DF(df, 1, args.ltf)
|
||
sigs = extract_fx_signals(chan, chan.dataframe)
|
||
sig = signals_to_frame(sigs)
|
||
print(f"[分型] 共 {len(sig)} 个(底 {int((sig.direction == 1).sum())} / "
|
||
f"顶 {int((sig.direction == -1).sum())})")
|
||
print(f"[确认滞后] 中位数 {sig['lag'].median():.0f} 根 均值 {sig['lag'].mean():.2f} 根 "
|
||
f"P90 {sig['lag'].quantile(.9):.0f} 根 最大 {sig['lag'].max()} 根")
|
||
print(f" —— 对照:笔 9~10 根、线段 101~121 根、三类买卖点 16~21 根\n")
|
||
|
||
sig = add_forward_returns(sig, chan.dataframe, HORIZONS)
|
||
zones = build_htf_zones(df_htf, args.htf)
|
||
print(f"[大级别中枢] {args.htf} 上共 {len(zones)} 个可用中枢")
|
||
sig = annotate_position(sig, chan.dataframe, zones, tol=args.tol)
|
||
|
||
div = sig["is_divergence"]
|
||
pos = sig["position_ok"].astype(bool)
|
||
print(f"[过滤器覆盖] 背驰 {div.sum()}/{len(sig)} ({div.mean()*100:.0f}%) "
|
||
f"位置正确 {pos.sum()}/{len(sig)} ({pos.mean()*100:.0f}%) "
|
||
f"两者兼备 {(div & pos).sum()}\n")
|
||
|
||
print("########## 逐层过滤效果 ##########")
|
||
rows = []
|
||
rows += summarize(sig, chan.dataframe, "L0 全部分型")
|
||
rows += summarize(sig[div], chan.dataframe, "L1 +背驰")
|
||
rows += summarize(sig[pos], chan.dataframe, "L2 +位置")
|
||
rows += summarize(sig[div & pos], chan.dataframe, "L3 背驰+位置")
|
||
show(rows)
|
||
|
||
print("\n########## L3 多空拆分 ##########")
|
||
both = sig[div & pos]
|
||
rows = summarize(both[both.direction == 1], chan.dataframe, "L3 做多", min_n=5)
|
||
rows += summarize(both[both.direction == -1], chan.dataframe, "L3 做空", min_n=5)
|
||
show(rows)
|
||
|
||
print("\n########## 背驰强度分层(面积比 ratio,越小背驰越强)##########")
|
||
rows = []
|
||
for lo, hi, name in [(0, 0.5, "ratio<0.5"), (0.5, 0.8, "0.5-0.8"),
|
||
(0.8, 1.0, "0.8-1.0"), (1.0, 99, "ratio>1 无背驰")]:
|
||
g = sig[(sig.ratio >= lo) & (sig.ratio < hi)]
|
||
rows += summarize(g, chan.dataframe, name)
|
||
show(rows)
|
||
|
||
print("\n########## 中枢内 vs 中枢外(对应两种玩法)##########")
|
||
rows = summarize(sig[sig.inside_zone.astype(bool)], chan.dataframe, "中枢内做短差")
|
||
rows += summarize(sig[sig.outside_zone.astype(bool)], chan.dataframe, "中枢外做趋势")
|
||
show(rows)
|
||
|
||
out_dir = Path(__file__).parent / "out"
|
||
out_dir.mkdir(exist_ok=True)
|
||
tag = f"{args.symbol.split('/')[0]}_{args.ltf}_{args.htf}"
|
||
sig.to_csv(out_dir / f"step7_fx_{tag}.csv", index=False)
|
||
print(f"\n明细已写入 {out_dir}/step7_fx_{tag}.csv")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|