refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块
删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user