Files
Chan/research/step13_robustness.py
T
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

155 lines
5.5 KiB
Python
Raw 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 13:中枢突破的三关稳健性验证。
Step 12 发现中枢突破跨周期单调为正(PF 1.18/1.52/2.17),但样本偏小、
且切过多个维度,必须过三关才能当真:
关一 多品种 BTC / ETH / SOL —— 信号是否只存在于 BTC
关二 分时段 按年切 —— 是否只靠某一段行情
关三 参数面 sl/tp 网格 —— 是否只在某个参数点成立
任何一关塌掉,都说明 Step 12 是数据挖掘的产物。
"""
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.data import fetch_ohlcv
from lib.nested_level import build_htf_zones
from step12_zs_breakout import collect_breakouts
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from chanlun import TF_DF
pd.set_option("display.width", 260)
SYMBOLS = ["BTC/USDT:USDT", "ETH/USDT:USDT", "SOL/USDT:USDT"]
TFS = ["15m", "1h", "4h"]
def main() -> None:
ap = argparse.ArgumentParser()
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()
pool: list[pd.DataFrame] = []
print("########## 关一:多品种 × 多周期(突破即入,sl1.5 tp3##########")
rows = []
for sym in SYMBOLS:
for tf in TFS:
try:
df = fetch_ohlcv(sym, tf, 10**9)
except Exception:
continue
if df is None or len(df) < 2000:
continue
chan = TF_DF(df, 1, tf)
cdf = chan.dataframe
zones = build_htf_zones(df, tf)
bo = collect_breakouts(cdf, zones)
if bo.empty or len(bo) < 15:
continue
entries = list(zip(bo["bo_idx"].astype(int), bo["dir"].astype(int)))
tr = run_trades(cdf, entries, args.sl, args.tp, args.max_bars)
if tr.empty:
continue
s = summarize_trades(tr, f"{sym.split('/')[0]:>4} {tf:>3}")
s["假突破率"] = f"{bo['is_fake'].mean() * 100:.0f}%"
rows.append(s)
tr = tr.copy()
tr["symbol"] = sym.split("/")[0]
tr["tf"] = tf
tr["date"] = cdf["date"].to_numpy()[tr["entry_idx"].to_numpy()]
pool.append(tr)
print(pd.DataFrame(rows).to_string(index=False))
if not pool:
print("无足够样本")
return
allt = pd.concat(pool, ignore_index=True)
print(f"\n 合并 {len(allt)} 笔:", end="")
r = allt["ret"].to_numpy()
sd = r.std(ddof=1)
win, loss = r[r > 0], r[r <= 0]
print(f"胜率 {(r > 0).mean() * 100:.1f}% 均收益 {r.mean() * 100:+.3f}% "
f"PF {win.sum() / abs(loss.sum()):.2f} t值 {r.mean() / (sd / np.sqrt(len(r))):+.2f}")
print("\n########## 关二:按年分段(合并全部品种周期)##########")
allt["year"] = pd.to_datetime(allt["date"]).dt.year
rows = []
for y, g in allt.groupby("year"):
if len(g) < 25:
continue
s = summarize_trades(g, str(y))
rows.append(s)
print(pd.DataFrame(rows).to_string(index=False))
pos_years = sum(1 for _, g in allt.groupby("year")
if len(g) >= 25 and g["ret"].mean() > 0)
tot_years = sum(1 for _, g in allt.groupby("year") if len(g) >= 25)
print(f"\n 盈利年份 {pos_years}/{tot_years}")
print("\n########## 关三:参数网格(合并全部品种周期,1h 为主)##########")
rows = []
for sl in (1.0, 1.5, 2.0, 2.5):
for tp in (2.0, 3.0, 4.0):
sub = []
for sym in SYMBOLS:
for tf in TFS:
try:
df = fetch_ohlcv(sym, tf, 10**9)
except Exception:
continue
if df is None or len(df) < 2000:
continue
chan = TF_DF(df, 1, tf)
cdf = chan.dataframe
bo = collect_breakouts(cdf, build_htf_zones(df, tf))
if bo.empty:
continue
e = list(zip(bo["bo_idx"].astype(int), bo["dir"].astype(int)))
t = run_trades(cdf, e, sl, tp, args.max_bars)
if not t.empty:
sub.append(t)
if sub:
rows.append(summarize_trades(pd.concat(sub, ignore_index=True),
f"sl{sl} tp{tp}"))
grid = pd.DataFrame(rows)
print(grid.to_string(index=False))
pos = sum(1 for v in grid["均收益"] if v.startswith("+"))
print(f"\n 正收益参数点 {pos}/{len(grid)}")
print("\n########## 补充:合并样本的方向与周期拆分 ##########")
rows = []
for d, nm in [(1, "向上突破"), (-1, "向下突破")]:
g = allt[allt["direction"] == d]
if len(g) >= 25:
rows.append(summarize_trades(g, nm))
for tf in TFS:
g = allt[allt["tf"] == tf]
if len(g) >= 25:
rows.append(summarize_trades(g, f"周期 {tf}"))
print(pd.DataFrame(rows).to_string(index=False))
out = Path(__file__).parent / "out" / "step13_all_trades.csv"
out.parent.mkdir(exist_ok=True)
allt.to_csv(out, index=False)
print(f"\n明细已写入 {out}")
if __name__ == "__main__":
main()