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
+169
View File
@@ -0,0 +1,169 @@
"""Step 4:中枢来源 A/B —— cal_bi_zs(seg_list) vs cal_bi_zs_list_pure(bi_list)。
假设:seg 口径下笔中枢必须等所属线段成形,多叠了一层确认滞后;
pure 口径直接在扁平笔序列上滚动,应当显著更快出信号。
用同一套 walk-forward 重放,只切换中枢来源,对比滞后与真实收益。
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parent))
from lib.bsp_eval import baseline_stats, run_pipeline
from lib.data import fetch_ohlcv
from lib.walkforward import analyze_stability, replay
pd.set_option("display.width", 240)
from chanlun.core.ChanEnum import Chan_BSP_DIR
HORIZONS = (1, 3, 5, 10, 20, 40)
def evaluate(df: pd.DataFrame, tf: str, symbol: str, zs_source: str, window: int, step: int):
_, final_bsp = run_pipeline(df, tf, zs_source=zs_source)
final_keys = {
(str(b.type).replace("Chan_BSP_TYPE.", ""),
1 if b.dir == Chan_BSP_DIR.BUY else -1, str(b.end_time))
for b in final_bsp if b.is_sure and b.sure_time is not None
}
result = replay(
df, tf, window=window, step=step, zs_source=zs_source,
cache_key=f"{symbol.replace('/', '_').replace(':', '-')}_{tf}",
)
life = analyze_stability(result, final_keys, df, window=window)
valid = life[~life["truncated"]].dropna(subset=["fx_idx"]).copy()
valid["fx_idx"] = valid["fx_idx"].astype(int)
mature = valid[~valid["immature"]]
return final_keys, valid, mature
def rt_returns(valid: pd.DataFrame, df: pd.DataFrame) -> pd.DataFrame:
"""以实时首见时刻入场的方向调整收益。"""
closes = df["close"].to_numpy(dtype=float)
n = len(df)
rows = []
for _, r in valid.iterrows():
i, d = int(r["first_seen_idx"]), int(r["direction"])
row = {"bsp_type": r["bsp_type"], "direction": d}
for h in HORIZONS:
j = i + h
row[f"ret_{h}"] = d * (closes[j] - closes[i]) / closes[i] if j < n else np.nan
rows.append(row)
return pd.DataFrame(rows)
def stats_block(fwd: pd.DataFrame, df: pd.DataFrame, mask=None, label="") -> list[dict]:
base = baseline_stats(df, HORIZONS).set_index("horizon")
g = fwd if mask is None else fwd[mask]
out = []
for h in HORIZONS:
r = g[f"ret_{h}"].dropna().to_numpy()
if len(r) == 0:
continue
dirs = g.loc[g[f"ret_{h}"].notna(), "direction"].to_numpy()
sd = r.std(ddof=1) if len(r) > 1 else np.nan
out.append({
"group": label, "horizon": h, "n": len(r), "mean": r.mean(),
"winrate": (r > 0).mean(),
"excess": r.mean() - float(np.mean(dirs) * base.loc[h, "base_mean_long"]),
"tstat": r.mean() / (sd / np.sqrt(len(r))) if sd else np.nan,
})
return out
def fmt(rows: list[dict]) -> str:
d = pd.DataFrame(rows)
if d.empty:
return "(无数据)"
d["mean"] = d["mean"].map(lambda v: f"{v * 100:+.2f}%")
d["excess"] = d["excess"].map(lambda v: f"{v * 100:+.2f}%")
d["winrate"] = d["winrate"].map(lambda v: f"{v * 100:.0f}%")
d["tstat"] = d["tstat"].map(lambda v: f"{v:+.2f}")
return d.to_string(index=False)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--symbol", default="BTC/USDT:USDT")
ap.add_argument("--tf", default="1h")
ap.add_argument("--window", type=int, default=3000)
ap.add_argument("--step", type=int, default=4)
args = ap.parse_args()
df = fetch_ohlcv(args.symbol, args.tf, 50000)
print(f"[data] {args.symbol} {args.tf} rows={len(df)} "
f"{df['date'].iloc[0]} -> {df['date'].iloc[-1]}\n")
store = {}
for src in ("seg", "pure"):
print(f"===== 中枢来源: {src} =====")
final_keys, valid, mature = evaluate(df, args.tf, args.symbol, src, args.window, args.step)
fwd = rt_returns(valid, df)
store[src] = (final_keys, valid, mature, fwd)
print(f" 全量口径买卖点 = {len(final_keys)}")
print(f" 重放有效信号 = {len(valid)}(已成熟 {len(mature)}")
print(f" 实时首见滞后 = 中位数 {valid['observed_lag'].median():.0f} 根 / "
f"均值 {valid['observed_lag'].mean():.1f}")
print(f" 幻影率 = {(~mature['in_final']).mean() * 100:.1f}%")
print(f" 存活度 = {mature['persist_ratio'].mean():.3f}\n")
print("\n########## 滞后对比 ##########")
for src in ("seg", "pure"):
v = store[src][1]
q = v["observed_lag"].quantile([0.25, 0.5, 0.75])
print(f" {src:5s} P25={q[0.25]:5.0f} 中位数={q[0.5]:5.0f} P75={q[0.75]:5.0f} n={len(v)}")
print("\n########## 分类型滞后中位数 ##########")
comp = pd.DataFrame({
src: store[src][1].groupby("bsp_type")["observed_lag"].median()
for src in ("seg", "pure")
})
comp["改善(根)"] = comp["seg"] - comp["pure"]
print(comp.to_string())
print("\n########## 分类型信号数量与幻影率 ##########")
cnt = pd.DataFrame({
f"{src}_n": store[src][1].groupby("bsp_type").size() for src in ("seg", "pure")
})
ph = pd.DataFrame({
f"{src}_幻影": store[src][2].groupby("bsp_type")["in_final"].apply(lambda s: (1 - s.mean()) * 100)
for src in ("seg", "pure")
})
print(pd.concat([cnt, ph], axis=1).round(1).to_string())
print("\n########## 无未来函数收益:全部信号 ##########")
for src in ("seg", "pure"):
print(f"--- {src} ---")
print(fmt(stats_block(store[src][3], df, None, "ALL")))
print("\n########## 无未来函数收益:仅做多 ##########")
for src in ("seg", "pure"):
fwd = store[src][3]
print(f"--- {src} ---")
print(fmt(stats_block(fwd, df, fwd.direction == 1, "LONG")))
print("\n########## 无未来函数收益:仅做空 ##########")
for src in ("seg", "pure"):
fwd = store[src][3]
print(f"--- {src} ---")
print(fmt(stats_block(fwd, df, fwd.direction == -1, "SHORT")))
print("\n########## pure 口径分类型(持有 5 / 20 根)##########")
fwd = store["pure"][3]
rows = []
for t in sorted(fwd["bsp_type"].unique()):
rows += stats_block(fwd, df, fwd.bsp_type == t, t)
d = pd.DataFrame(rows)
d = d[d["horizon"].isin([5, 20])]
print(fmt(d.to_dict("records")))
if __name__ == "__main__":
main()