删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
155 lines
5.5 KiB
Python
155 lines
5.5 KiB
Python
"""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()
|