删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
6.5 KiB
Python
164 lines
6.5 KiB
Python
"""Step 15:正确的区间套 —— 1h/4h 分型定位 + 15m 三类买卖点入场。
|
||
|
||
此前 step7~step14 一直把中枢放在大级别、并在大级别自己交易,级别关系搞反了。
|
||
本步按缠论正统结构重做:
|
||
|
||
大级别 1h / 4h 只出分型,负责方向与「预设转折点」(确认滞后 1 根)
|
||
小级别 15m 出中枢、出突破、出三类买卖点,负责精确入场
|
||
|
||
逐层验证:
|
||
A 全部 15m 三买三卖(基线)
|
||
B + 1h 分型方向一致
|
||
C + 4h 分型方向一致
|
||
D + 1h 与 4h 同时一致(完整区间套)
|
||
E + 大级别分型带背驰
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import sys
|
||
import warnings
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
warnings.filterwarnings("ignore")
|
||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||
|
||
from lib.breakout import run_trades, summarize_trades
|
||
from lib.bsp_eval import forward_returns, run_pipeline, to_events
|
||
from lib.data import fetch_ohlcv
|
||
from lib.fx_signal import extract_fx_signals, signals_to_frame
|
||
from lib.nested_bsp import attach_htf_context, htf_fx_timeline
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||
from chanlun import TF_DF
|
||
|
||
pd.set_option("display.width", 260)
|
||
HORIZONS = (3, 5, 10, 20, 40)
|
||
|
||
|
||
def show(rows: list[dict]) -> None:
|
||
rows = [r for r in rows if r.get("笔数", 0) > 0]
|
||
if not rows:
|
||
print(" (样本不足)")
|
||
return
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
|
||
def main() -> None:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--symbol", default="BTC/USDT:USDT")
|
||
ap.add_argument("--ltf", default="15m", help="小级别:出中枢与三类买卖点")
|
||
ap.add_argument("--htf1", default="1h", help="大级别一:出分型")
|
||
ap.add_argument("--htf2", default="4h", help="大级别二:出分型")
|
||
ap.add_argument("--zs", default="pure", choices=["pure", "seg"])
|
||
ap.add_argument("--sl", type=float, default=1.5)
|
||
ap.add_argument("--tp", type=float, default=3.0)
|
||
ap.add_argument("--max-bars", type=int, default=48)
|
||
args = ap.parse_args()
|
||
|
||
# ---- 小级别:三类买卖点 ----
|
||
df_l = fetch_ohlcv(args.symbol, args.ltf, 10**9)
|
||
chan_l, bsp_list = run_pipeline(df_l, args.ltf, args.zs)
|
||
cdf_l = chan_l.dataframe
|
||
ev = forward_returns(to_events(bsp_list, cdf_l), cdf_l, HORIZONS)
|
||
if ev.empty:
|
||
print("小级别无买卖点")
|
||
return
|
||
ev3 = ev[ev["bsp_type"].isin(["B3", "S3"])].reset_index(drop=True)
|
||
|
||
print(f"[小级别 {args.ltf}] {len(cdf_l)} 根K线 "
|
||
f"{cdf_l['date'].iloc[0]} -> {cdf_l['date'].iloc[-1]}")
|
||
print(f" 买卖点合计 {len(ev)} 其中三类 {len(ev3)} "
|
||
f"(B3 {(ev3.bsp_type == 'B3').sum()} / S3 {(ev3.bsp_type == 'S3').sum()})")
|
||
if not ev3.empty:
|
||
print(f" 三类买卖点确认滞后:中位 {ev3['lag_bars'].median():.0f} 根 "
|
||
f"均值 {ev3['lag_bars'].mean():.1f} 根")
|
||
|
||
# ---- 大级别:分型时间线 ----
|
||
ctx = ev3
|
||
for tf, pref in ((args.htf1, "h1"), (args.htf2, "h2")):
|
||
df_h = fetch_ohlcv(args.symbol, tf, 10**9)
|
||
chan_h = TF_DF(df_h, 1, tf)
|
||
sig_h = signals_to_frame(extract_fx_signals(chan_h, chan_h.dataframe))
|
||
tl = htf_fx_timeline(sig_h, chan_h.dataframe)
|
||
print(f"[大级别 {tf}] 分型 {len(tl)} 个")
|
||
ctx = attach_htf_context(ctx, cdf_l, tl, pref)
|
||
|
||
a1 = ctx["h1_agree"] == 1
|
||
a2 = ctx["h2_agree"] == 1
|
||
print(f"\n[方向一致率] 1h {a1.mean() * 100:.0f}% 4h {a2.mean() * 100:.0f}% "
|
||
f"两者同时 {(a1 & a2).mean() * 100:.0f}%")
|
||
|
||
entries = lambda g: list(zip(g["entry_idx"].astype(int), g["direction"].astype(int)))
|
||
|
||
print("\n########## 逐层过滤(事件驱动,sl1.5ATR tp3ATR max48)##########")
|
||
rows = [
|
||
summarize_trades(run_trades(cdf_l, entries(ctx), args.sl, args.tp, args.max_bars),
|
||
"A 全部三类"),
|
||
summarize_trades(run_trades(cdf_l, entries(ctx[a1]), args.sl, args.tp, args.max_bars),
|
||
f"B +{args.htf1}分型同向"),
|
||
summarize_trades(run_trades(cdf_l, entries(ctx[a2]), args.sl, args.tp, args.max_bars),
|
||
f"C +{args.htf2}分型同向"),
|
||
summarize_trades(run_trades(cdf_l, entries(ctx[a1 & a2]), args.sl, args.tp, args.max_bars),
|
||
"D 双大级别同向"),
|
||
]
|
||
div1 = ctx["h1_div"] == 1
|
||
rows.append(summarize_trades(
|
||
run_trades(cdf_l, entries(ctx[a1 & div1]), args.sl, args.tp, args.max_bars),
|
||
f"E +{args.htf1}同向且背驰"))
|
||
# 反向对照:大级别分型与信号相悖时应当更差
|
||
rows.append(summarize_trades(
|
||
run_trades(cdf_l, entries(ctx[~a1]), args.sl, args.tp, args.max_bars),
|
||
f"F {args.htf1}分型反向(对照)"))
|
||
show(rows)
|
||
|
||
print("\n########## 大级别分型的新鲜度(距分型确认的15m根数)##########")
|
||
rows = []
|
||
for lo, hi, name in [(0, 8, "0-8根"), (8, 24, "8-24根"),
|
||
(24, 96, "24-96根"), (96, 10**9, ">96根")]:
|
||
g = ctx[a1 & (ctx["h1_age_bars"] >= lo) & (ctx["h1_age_bars"] < hi)]
|
||
if len(g) >= 15:
|
||
rows.append(summarize_trades(
|
||
run_trades(cdf_l, entries(g), args.sl, args.tp, args.max_bars), name))
|
||
show(rows)
|
||
|
||
print("\n########## 多空拆分(双大级别同向)##########")
|
||
rows = []
|
||
both = ctx[a1 & a2]
|
||
for d, nm in ((1, "做多 B3"), (-1, "做空 S3")):
|
||
g = both[both["direction"] == d]
|
||
if len(g) >= 10:
|
||
rows.append(summarize_trades(
|
||
run_trades(cdf_l, entries(g), args.sl, args.tp, args.max_bars), nm))
|
||
show(rows)
|
||
|
||
print("\n########## 固定持有期收益(不设止损,看原始 alpha)##########")
|
||
rows = []
|
||
for name, g in (("A 全部三类", ctx), (f"B +{args.htf1}同向", ctx[a1]),
|
||
("D 双大级别同向", ctx[a1 & a2])):
|
||
if len(g) < 15:
|
||
continue
|
||
r = {"分组": name, "n": len(g)}
|
||
for h in HORIZONS:
|
||
v = g[f"ret_{h}"].dropna().to_numpy()
|
||
if len(v) < 10:
|
||
continue
|
||
sd = v.std(ddof=1)
|
||
r[f"{h}根"] = f"{v.mean() * 100:+.2f}%"
|
||
r[f"{h}根t"] = f"{v.mean() / (sd / np.sqrt(len(v))):+.1f}" if sd else "—"
|
||
rows.append(r)
|
||
if rows:
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
out = Path(__file__).parent / "out" / f"step15_nested_{args.symbol.split('/')[0]}.csv"
|
||
out.parent.mkdir(exist_ok=True)
|
||
ctx.to_csv(out, index=False)
|
||
print(f"\n明细已写入 {out}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|