Files
Chan/research/step7_nested_fx.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

136 lines
5.3 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 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()