refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块

删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-27 01:05:12 +08:00
co-authored by Cursor
parent 5c10e35b76
commit 7f393b93ed
360 changed files with 140008 additions and 41167 deletions
+205
View File
@@ -0,0 +1,205 @@
"""Step 19:区间套在不同级别对上的一致性。
15m/1h+4h 只有 57 笔核心样本,无法定论。但区间套是级别无关的结构,
把整套上移一级(1h 小级别有 2524 天,是 15m 的两倍)既能扩样本,
又能验证它究竟是普适结构还是只在某一个级别对上凑巧成立。
级别对:
5m / 15m + 1h
15m / 1h + 4h step16~17 已测)
1h / 4h + 1d
"""
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.fast_bsp3 import find_fast_bsp3
from lib.fx_signal import extract_fx_signals, signals_to_frame
from lib.nested_bsp import attach_htf_context, htf_fx_timeline
from lib.nested_level import build_htf_zones
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"]
DEFAULT_PAIRS = "5m:15m:1h,15m:1h:4h,1h:4h:1d"
SL, TP, MAXB = 1.5, 3.0, 48
def E(g):
return list(zip(g["entry_idx"].astype(int), g["direction"].astype(int)))
# 同一 (品种, 周期) 的 pipeline 在多个级别对之间反复出现,缓存后能省掉大部分耗时
_engine_cache: dict = {}
_timeline_cache: dict = {}
def _engine(symbol: str, tf: str, min_rows: int):
key = (symbol, tf)
if key not in _engine_cache:
try:
df = fetch_ohlcv(symbol, tf, 10**9)
except Exception:
df = None
if df is None or len(df) < min_rows:
_engine_cache[key] = None
else:
_engine_cache[key] = TF_DF(df, 1, tf)
return _engine_cache[key]
def _timeline(symbol: str, tf: str):
key = (symbol, tf)
if key not in _timeline_cache:
chan = _engine(symbol, tf, 300)
if chan is None:
_timeline_cache[key] = None
else:
s = signals_to_frame(extract_fx_signals(chan, chan.dataframe))
_timeline_cache[key] = htf_fx_timeline(s, chan.dataframe)
return _timeline_cache[key]
def build(symbol: str, ltf: str, htf1: str, htf2: str):
chan_l = _engine(symbol, ltf, 3000)
if chan_l is None:
return None, None
cdf_l = chan_l.dataframe
sig = find_fast_bsp3(cdf_l, build_htf_zones(cdf_l, ltf, chan=chan_l))
if sig.empty:
return cdf_l, None
for tf, pref in ((htf1, "h1"), (htf2, "h2")):
tl = _timeline(symbol, tf)
if tl is None or tl.empty:
continue
sig = attach_htf_context(sig, cdf_l, tl, pref)
return cdf_l, sig
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--core-only", action="store_true",
help="只输出核心组合(大级别同向 + 浅回抽)")
ap.add_argument("--pairs", default=DEFAULT_PAIRS,
help="级别对,格式 小级别:大级别1:大级别2,逗号分隔")
ap.add_argument("--symbols", default="BTC,ETH,SOL")
args = ap.parse_args()
global SYMBOLS
SYMBOLS = [f"{s.strip()}/USDT:USDT" for s in args.symbols.split(",") if s.strip()]
pairs = [tuple(p.split(":")) for p in args.pairs.split(",") if p]
all_rows, pool = [], []
for ltf, h1, h2 in pairs:
print(f"\n{'=' * 100}")
print(f"级别对:小级别 {ltf} 大级别 {h1} + {h2}")
rows = []
pair_pool = []
for sym in SYMBOLS:
cdf, sig = build(sym, ltf, h1, h2)
if sig is None or sig.empty or len(sig) < 10:
continue
tr = run_trades(cdf, E(sig), SL, TP, MAXB)
if tr.empty:
continue
s = summarize_trades(tr, f"{sym.split('/')[0]:>4} 全部")
s["滞后"] = f"{sig['lag'].median():.0f}"
rows.append(s)
m = sig.set_index("entry_idx")
tr = tr.copy()
tr["symbol"] = sym.split("/")[0]
tr["pair"] = f"{ltf}/{h1}"
tr["date"] = cdf["date"].to_numpy()[tr["entry_idx"].to_numpy()]
tr["h1_agree"] = tr["entry_idx"].map(m["h1_agree"]) if "h1_agree" in m else np.nan
tr["depth"] = tr["entry_idx"].map(m["depth"])
pair_pool.append(tr)
if not pair_pool:
print(" 样本不足")
continue
if rows and not args.core_only:
print(pd.DataFrame(rows).to_string(index=False))
pt = pd.concat(pair_pool, ignore_index=True)
pool.append(pt)
a1 = pt["h1_agree"] == 1
dq = pt["depth"].quantile(0.66)
core = pt[a1 & (pt["depth"] >= dq)]
sub = [summarize_trades(pt, f"{ltf} 全部")]
if len(pt[a1]) >= 20:
sub.append(summarize_trades(pt[a1], f"{ltf} +{h1}同向"))
if len(core) >= 15:
sub.append(summarize_trades(core, f"{ltf} 核心(同向+浅回抽)"))
if len(pt[~a1]) >= 15:
sub.append(summarize_trades(pt[~a1], f"{ltf} 反向(对照)"))
print(pd.DataFrame(sub).to_string(index=False))
if len(core) >= 15:
r = core["ret"].to_numpy()
all_rows.append({
"级别对": f"{ltf} / {h1}+{h2}", "核心笔数": len(r),
"胜率": f"{(r > 0).mean() * 100:.1f}%",
"均收益": f"{r.mean() * 100:+.3f}%",
"中位数": f"{np.median(r) * 100:+.3f}%",
"PF": f"{r[r > 0].sum() / abs(r[r <= 0].sum()):.2f}",
"偏度": f"{pd.Series(r).skew():.2f}",
"t值": f"{r.mean() / (r.std(ddof=1) / np.sqrt(len(r))):+.2f}",
})
print(f"\n{'=' * 100}")
print("########## 跨级别对:核心组合汇总 ##########")
if all_rows:
print(pd.DataFrame(all_rows).to_string(index=False))
if pool:
allp = pd.concat(pool, ignore_index=True)
a1 = allp["h1_agree"] == 1
cores = []
for p, g in allp.groupby("pair"):
dq = g["depth"].quantile(0.66)
cores.append(g[(g["h1_agree"] == 1) & (g["depth"] >= dq)])
core_all = pd.concat(cores, ignore_index=True)
r = core_all["ret"].to_numpy()
print(f"\n 三个级别对合并核心样本 {len(r)} 笔:")
print(f" 胜率 {(r > 0).mean() * 100:.1f}% 均收益 {r.mean() * 100:+.3f}% "
f"中位数 {np.median(r) * 100:+.3f}%")
print(f" PF {r[r > 0].sum() / abs(r[r <= 0].sum()):.2f} "
f"偏度 {pd.Series(r).skew():.2f} "
f"t值 {r.mean() / (r.std(ddof=1) / np.sqrt(len(r))):+.2f}")
for k in (2, 5, 10):
v = r[r <= np.quantile(r, 1 - k / 100)]
print(f" 剔除最赚{k:>2}%: PF {v[v > 0].sum() / abs(v[v <= 0].sum()):.2f} "
f"t {v.mean() / (v.std(ddof=1) / np.sqrt(len(v))):+.2f} "
f"中位 {np.median(v) * 100:+.3f}%")
core_all["year"] = pd.to_datetime(core_all["date"]).dt.year
print("\n 核心样本分年:")
rows = [{"年份": y, "笔数": len(g), "胜率": f"{(g.ret > 0).mean() * 100:.0f}%",
"均收益": f"{g.ret.mean() * 100:+.3f}%",
"PF": f"{g.ret[g.ret > 0].sum() / abs(g.ret[g.ret <= 0].sum()):.2f}"
if (g.ret <= 0).any() else "inf"}
for y, g in core_all.groupby("year") if len(g) >= 8]
print(pd.DataFrame(rows).to_string(index=False))
out = Path(__file__).parent / "out" / "step19_level_pairs.csv"
out.parent.mkdir(exist_ok=True)
allp.to_csv(out, index=False)
print(f"\n明细已写入 {out}")
if __name__ == "__main__":
main()