删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
150 lines
5.9 KiB
Python
150 lines
5.9 KiB
Python
"""Step 11:突破跟随(分型失败 / 中枢突破),叠加已验证的波动率信号。
|
||
|
||
前十步确立:方向不可预测(AUC 0.52),但大波动可预测(AUC 0.689)。
|
||
反转逻辑已被否定,本步测其镜像——分型转折失败即为趋势延续,
|
||
以及缠论正统的「离开中枢做趋势」。
|
||
"""
|
||
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 find_breakout_entries, run_trades, summarize_trades
|
||
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
|
||
from step9_endpoint_predictability import build_features
|
||
from step10_direct_return_label import ALL_FEATS, walk_forward
|
||
|
||
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)
|
||
|
||
|
||
def zs_breakout_entries(df: pd.DataFrame, zones: pd.DataFrame, window: int = 200):
|
||
"""中枢突破:价格自内部向外突破 zg/zd 的第一根。"""
|
||
if zones.empty:
|
||
return []
|
||
ts = df["timestamp"].to_numpy()
|
||
close = df["close"].to_numpy(dtype=float)
|
||
n = len(df)
|
||
entries = []
|
||
for _, z in zones.iterrows():
|
||
start = int(np.searchsorted(ts, z["available_ts"], side="left"))
|
||
was_inside = False
|
||
for j in range(start, min(start + window, n)):
|
||
c = close[j]
|
||
if z["zd"] <= c <= z["zg"]:
|
||
was_inside = True
|
||
continue
|
||
if not was_inside:
|
||
continue
|
||
entries.append((j, 1 if c > z["zg"] else -1))
|
||
break
|
||
return entries
|
||
|
||
|
||
def main() -> None:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--symbol", default="BTC/USDT:USDT")
|
||
ap.add_argument("--tf", default="1h")
|
||
ap.add_argument("--htf", default="4h")
|
||
ap.add_argument("--window", type=int, default=10, help="分型后等待突破的K线数")
|
||
args = ap.parse_args()
|
||
|
||
df = fetch_ohlcv(args.symbol, args.tf, 10**9)
|
||
chan = TF_DF(df, 1, args.tf)
|
||
cdf = chan.dataframe
|
||
sig = signals_to_frame(extract_fx_signals(chan, cdf))
|
||
sig = add_forward_returns(sig, cdf, HORIZONS)
|
||
zones = build_htf_zones(fetch_ohlcv(args.symbol, args.htf, 10**9), args.htf)
|
||
sig = annotate_position(sig, cdf, zones, tol=0.015)
|
||
print(f"[样本] {len(cdf)} 根K线 {cdf['date'].iloc[0]} -> {cdf['date'].iloc[-1]}")
|
||
print(f"[分型] {len(sig)} [大级别中枢] {len(zones)}\n")
|
||
|
||
bo = find_breakout_entries(sig, cdf, window=args.window, mode="fail")
|
||
rev = find_breakout_entries(sig, cdf, window=args.window, mode="reverse")
|
||
zsb = zs_breakout_entries(cdf, zones)
|
||
print(f"[入场点] 分型失败突破 {len(bo)} 反转对照 {len(rev)} 中枢突破 {len(zsb)}\n")
|
||
|
||
print("########## 1. 突破跟随 vs 反转对照(sl=1.5ATR tp=3ATR max=48)##########")
|
||
rows = [
|
||
summarize_trades(run_trades(cdf, bo, 1.5, 3.0, 48), "分型失败突破"),
|
||
summarize_trades(run_trades(cdf, rev, 1.5, 3.0, 48), "分型反转(对照)"),
|
||
]
|
||
if zsb:
|
||
rows.append(summarize_trades(run_trades(cdf, zsb, 1.5, 3.0, 48), "中枢突破"))
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
print("\n########## 2. 止损止盈网格(分型失败突破)##########")
|
||
rows = []
|
||
for sl in (1.0, 1.5, 2.0):
|
||
for tp in (2.0, 3.0, 5.0):
|
||
rows.append(summarize_trades(
|
||
run_trades(cdf, bo, sl, tp, 48), f"sl{sl} tp{tp}"))
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
print("\n########## 3. 移动止损(trailing)##########")
|
||
rows = []
|
||
for sl in (1.0, 1.5, 2.0, 3.0):
|
||
rows.append(summarize_trades(
|
||
run_trades(cdf, bo, sl, 99.0, 96, trail=True), f"trail {sl}ATR"))
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
# ---- 叠加波动率模型:只在预测到大波动时才跟随突破 ----
|
||
print("\n########## 4. 叠加波动率过滤(标签C:3根内 >1% 波动,AUC~0.69)##########")
|
||
f = build_features(sig, cdf)
|
||
for c in ("near_support", "near_resistance", "inside_zone"):
|
||
f[c] = f[c].astype(float)
|
||
f = f.dropna(subset=[f"ret_{h}" for h in HORIZONS]).reset_index(drop=True)
|
||
y = (f["ret_3"].to_numpy() > 0.01).astype(int)
|
||
X = f[ALL_FEATS].to_numpy(dtype=float)
|
||
oof = walk_forward(X, y, folds=5, purge=60)
|
||
f["volp"] = oof
|
||
ev = f.dropna(subset=["volp"])
|
||
print(f" 样本外 {len(ev)} 个分型带波动率预测")
|
||
|
||
# 把预测概率挂回突破入场点(按其来源分型)
|
||
prob_by_confirm = dict(zip(ev["confirm_idx"].astype(int), ev["volp"]))
|
||
bo_with_p = []
|
||
for _, r in sig.iterrows():
|
||
c0 = int(r["confirm_idx"])
|
||
if c0 not in prob_by_confirm:
|
||
continue
|
||
sub = find_breakout_entries(pd.DataFrame([r]), cdf, args.window, "fail")
|
||
for e, d in sub:
|
||
bo_with_p.append((e, d, prob_by_confirm[c0]))
|
||
|
||
rows = []
|
||
if bo_with_p:
|
||
probs = np.array([p for _, _, p in bo_with_p])
|
||
for q, name in [(0.0, "全部"), (0.5, "top50%"), (0.7, "top30%"), (0.9, "top10%")]:
|
||
thr = np.quantile(probs, q)
|
||
sel = [(e, d) for e, d, p in bo_with_p if p >= thr]
|
||
if len(sel) < 30:
|
||
continue
|
||
rows.append(summarize_trades(run_trades(cdf, sel, 1.5, 3.0, 48), f"波动率{name}"))
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
print("\n########## 5. 多空拆分(分型失败突破,sl1.5 tp3)##########")
|
||
rows = []
|
||
for d, name in [(1, "做多"), (-1, "做空")]:
|
||
sel = [(e, dd) for e, dd in bo if dd == d]
|
||
if sel:
|
||
rows.append(summarize_trades(run_trades(cdf, sel, 1.5, 3.0, 48), name))
|
||
print(pd.DataFrame(rows).to_string(index=False))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|