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
+166
View File
@@ -0,0 +1,166 @@
"""Step 22:执行假设压力测试。
Step 20/21 的结论建立在「信号K线收盘价成交、只扣 0.08% 手续费」上。
实盘拿不到这个价。本步逐层加码,看结论在哪一层塌掉:
A 收盘入场(基准)
B 次根开盘入场 —— 真实下单节奏
C 次根开盘 + 5bp 滑点
D 次根开盘 + 10bp 滑点
"""
from __future__ import annotations
import argparse
import os
import sys
import warnings
from concurrent.futures import ProcessPoolExecutor, as_completed
from pathlib import Path
import numpy as np
import pandas as pd
warnings.filterwarnings("ignore")
for v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"):
os.environ.setdefault(v, "1")
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(HERE.parent))
pd.set_option("display.width", 280)
SL, TP, MAXB = 1.5, 3.0, 48
VARIANTS = [("A 收盘入场", 0, 0.0), ("B 次根开盘", 1, 0.0),
("C 次根+5bp", 1, 0.0005), ("D 次根+10bp", 1, 0.0010)]
def run_one(task: tuple) -> dict | None:
import warnings as _w
_w.filterwarnings("ignore")
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(HERE.parent))
from chanlun import TF_DF
from lib.breakout import run_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
sym, ltf, h1, h2 = task
try:
df_l = fetch_ohlcv(f"{sym}/USDT:USDT", ltf, 10**9)
if df_l is None or len(df_l) < 3000:
return None
chan_l = TF_DF(df_l, 1, ltf)
cdf = chan_l.dataframe
sig = find_fast_bsp3(cdf, build_htf_zones(cdf, ltf, chan=chan_l))
if sig.empty or len(sig) < 10:
return None
for tf, pref in ((h1, "h1"), (h2, "h2")):
df_h = fetch_ohlcv(f"{sym}/USDT:USDT", tf, 10**9)
if df_h is None or len(df_h) < 300:
continue
chan_h = TF_DF(df_h, 1, tf)
s = signals_to_frame(extract_fx_signals(chan_h, chan_h.dataframe))
sig = attach_htf_context(sig, cdf, htf_fx_timeline(s, chan_h.dataframe), pref)
entries = list(zip(sig["entry_idx"].astype(int), sig["direction"].astype(int)))
m = sig.set_index("entry_idx")
frames = []
for name, delay, slip in VARIANTS:
tr = run_trades(cdf, entries, SL, TP, MAXB, entry_delay=delay, slippage=slip)
if tr.empty:
continue
tr["variant"] = name
tr["symbol"] = sym
tr["ltf"] = ltf
tr["date"] = cdf["date"].to_numpy()[tr["entry_idx"].to_numpy()]
for c in ("h1_agree", "h2_agree"):
tr[c] = tr["entry_idx"].map(m[c]) if c in m.columns else np.nan
frames.append(tr)
return {"task": f"{sym} {ltf}", "trades": pd.concat(frames, ignore_index=True)}
except Exception as e:
return {"task": f"{sym} {ltf}", "error": repr(e)[:200]}
def desc(r: np.ndarray, label: str) -> dict:
if len(r) < 5:
return {}
win, loss = r[r > 0], r[r <= 0]
sd = r.std(ddof=1)
return {
"口径": label, "笔数": len(r),
"胜率": f"{(r > 0).mean() * 100:.1f}%",
"均收益": f"{r.mean() * 100:+.3f}%",
"中位": f"{np.median(r) * 100:+.3f}%",
"PF": f"{win.sum() / abs(loss.sum()):.2f}" if len(loss) else "inf",
"t值": f"{r.mean() / (sd / np.sqrt(len(r))):+.2f}",
}
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--pairs", default="15m:1h:4h,30m:2h:4h,1h:4h:1d")
ap.add_argument("--symbols", default="BTC,ETH,SOL")
ap.add_argument("--workers", type=int, default=6)
args = ap.parse_args()
pairs = [tuple(p.split(":")) for p in args.pairs.split(",") if p]
syms = [s.strip() for s in args.symbols.split(",") if s.strip()]
tasks = [(s, *p) for p in pairs for s in syms]
print(f"[压测] {len(tasks)} 个任务 × {len(VARIANTS)} 种执行口径\n", flush=True)
res = []
with ProcessPoolExecutor(max_workers=args.workers) as ex:
futs = {ex.submit(run_one, t): t for t in tasks}
for i, f in enumerate(as_completed(futs), 1):
r = f.result()
if r is None or "error" in (r or {}):
print(f" [{i}/{len(tasks)}] 跳过 {r['task'] if r else futs[f]}", flush=True)
continue
res.append(r)
print(f" [{i}/{len(tasks)}] {r['task']} ok", flush=True)
if not res:
print("无结果")
return
allt = pd.concat([r["trades"] for r in res], ignore_index=True)
allt.to_csv(HERE / "out" / "step22_execution.csv", index=False)
a1 = allt["h1_agree"] == 1
both = a1 & (allt["h2_agree"] == 1)
print("\n########## 1. 大级别同向,逐层加码执行成本 ##########")
rows = [desc(allt[a1 & (allt.variant == v)]["ret"].to_numpy(), v)
for v, _, _ in VARIANTS]
print(pd.DataFrame([r for r in rows if r]).to_string(index=False))
print("\n########## 2. 双大级别同向 ##########")
rows = [desc(allt[both & (allt.variant == v)]["ret"].to_numpy(), v)
for v, _, _ in VARIANTS]
print(pd.DataFrame([r for r in rows if r]).to_string(index=False))
print("\n########## 3. 最保守口径(D)下的分级别 ##########")
d = allt[(allt.variant == VARIANTS[-1][0]) & a1]
rows = [desc(g["ret"].to_numpy(), f"{tf} 同向") for tf, g in d.groupby("ltf")]
print(pd.DataFrame([r for r in rows if r]).to_string(index=False))
print("\n########## 4. 最保守口径(D)下的分年(同向)##########")
d = d.copy()
d["year"] = pd.to_datetime(d["date"]).dt.year
rows = [desc(g["ret"].to_numpy(), str(y)) for y, g in d.groupby("year") if len(g) >= 25]
print(pd.DataFrame([r for r in rows if r]).to_string(index=False))
print("\n########## 5. 最保守口径(D)下的尾部依赖(同向)##########")
r = d["ret"].to_numpy()
for k in (0, 5, 10, 20):
v = r if k == 0 else r[r <= np.quantile(r, 1 - k / 100)]
win, loss = v[v > 0], v[v <= 0]
sd = v.std(ddof=1)
print(f" 剔除最赚{k:>2}%: n={len(v):>4} PF={win.sum() / abs(loss.sum()):.2f} "
f"t={v.mean() / (sd / np.sqrt(len(v))):+.2f} 中位={np.median(v) * 100:+.3f}%")
if __name__ == "__main__":
main()