删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
181 lines
7.0 KiB
Python
181 lines
7.0 KiB
Python
"""Step 8:把问题收敛到「能否提前认出笔端点分型」。
|
|
|
|
Step 7 表明:原始 KLC 分型太密(每 2.9 根一个),是纯噪声;
|
|
但笔端点分型在事后看是真正的转折。二者的差别决定了优化空间:
|
|
|
|
- 若「笔端点分型 + 分型确认时刻入场」有显著 alpha,
|
|
问题就变成一个实时判别任务:在分型刚确认的第 1~2 根,
|
|
预测它会不会成为笔端点。滞后可从 9~10 根压到 1~2 根。
|
|
- 若连笔端点分型都没有 alpha,那这条路直接否掉。
|
|
"""
|
|
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
|
|
from lib.data import fetch_ohlcv
|
|
from lib.fx_signal import add_forward_returns, extract_fx_signals, signals_to_frame
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from chanlun import TF_DF
|
|
from chanlun.core.ChanEnum import Chan_BI_DIR
|
|
|
|
pd.set_option("display.width", 240)
|
|
HORIZONS = (3, 5, 10, 20, 40)
|
|
|
|
|
|
def summarize(g: pd.DataFrame, df: pd.DataFrame, label: str, min_n: int = 15) -> list[dict]:
|
|
base = baseline_stats(df, HORIZONS).set_index("horizon")
|
|
out = []
|
|
for h in HORIZONS:
|
|
col = f"ret_{h}"
|
|
r = g[col].dropna().to_numpy() if col in g else np.array([])
|
|
if len(r) < min_n:
|
|
continue
|
|
dirs = g.loc[g[col].notna(), "direction"].to_numpy()
|
|
sd = r.std(ddof=1)
|
|
out.append({
|
|
"分组": label, "持有": h, "n": len(r),
|
|
"收益": r.mean(), "胜率": (r > 0).mean(),
|
|
"超额": r.mean() - float(np.mean(dirs) * base.loc[h, "base_mean_long"]),
|
|
"t值": r.mean() / (sd / np.sqrt(len(r))) if sd else np.nan,
|
|
})
|
|
return out
|
|
|
|
|
|
def show(rows: list[dict]) -> None:
|
|
if not rows:
|
|
print(" (样本不足)")
|
|
return
|
|
d = pd.DataFrame(rows)
|
|
d["收益"] = d["收益"].map(lambda v: f"{v * 100:+.2f}%")
|
|
d["超额"] = d["超额"].map(lambda v: f"{v * 100:+.2f}%")
|
|
d["胜率"] = d["胜率"].map(lambda v: f"{v * 100:.0f}%")
|
|
d["t值"] = d["t值"].map(lambda v: f"{v:+.2f}")
|
|
print(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")
|
|
args = ap.parse_args()
|
|
|
|
df = fetch_ohlcv(args.symbol, args.tf, 10**9)
|
|
chan = TF_DF(df, 1, args.tf)
|
|
cdf = chan.dataframe
|
|
idx_of = {t: i for i, t in enumerate(cdf["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))}
|
|
|
|
sig = signals_to_frame(extract_fx_signals(chan, cdf))
|
|
sig = add_forward_returns(sig, cdf, HORIZONS)
|
|
|
|
# 标注哪些分型最终成为了笔端点(事后信息,仅用于确认 alpha 是否存在)
|
|
endpoint_idx: set[int] = set()
|
|
bi_lag: dict[int, int] = {}
|
|
for bi in chan.bi_list:
|
|
for klc in (bi.start_klc, bi.end_klc):
|
|
if klc is None:
|
|
continue
|
|
k = str(getattr(klc, "end_time", ""))
|
|
if k in idx_of:
|
|
endpoint_idx.add(idx_of[k])
|
|
e = str(getattr(bi, "end_time", ""))
|
|
s = str(getattr(bi, "sure_time", "") or "")
|
|
if bi.is_sure and e in idx_of and s in idx_of:
|
|
bi_lag[idx_of[e]] = idx_of[s] - idx_of[e]
|
|
|
|
sig["is_endpoint"] = sig["fx_idx"].isin(endpoint_idx)
|
|
sig["bi_confirm_lag"] = sig["fx_idx"].map(bi_lag)
|
|
|
|
n_ep = int(sig["is_endpoint"].sum())
|
|
print(f"[样本] 分型 {len(sig)} 其中笔端点 {n_ep} ({n_ep / len(sig) * 100:.1f}%)")
|
|
print(f"[滞后] 分型确认 {sig['lag'].median():.0f} 根 / "
|
|
f"笔确认 {sig['bi_confirm_lag'].median():.0f} 根 "
|
|
f"→ 可压缩 {sig['bi_confirm_lag'].median() - sig['lag'].median():.0f} 根\n")
|
|
|
|
print("########## 核心对照:笔端点分型 vs 普通分型(均以分型确认时刻入场)##########")
|
|
rows = summarize(sig[sig.is_endpoint], cdf, "笔端点分型")
|
|
rows += summarize(sig[~sig.is_endpoint], cdf, "非端点分型")
|
|
show(rows)
|
|
|
|
print("\n########## 笔端点分型 多空拆分 ##########")
|
|
ep = sig[sig.is_endpoint]
|
|
rows = summarize(ep[ep.direction == 1], cdf, "端点做多")
|
|
rows += summarize(ep[ep.direction == -1], cdf, "端点做空")
|
|
show(rows)
|
|
|
|
print("\n########## 对照:同一批端点,改用笔确认时刻入场(滞后 9~10 根)##########")
|
|
closes = cdf["close"].to_numpy(dtype=float)
|
|
n = len(cdf)
|
|
late = ep.dropna(subset=["bi_confirm_lag"]).copy()
|
|
for h in HORIZONS:
|
|
vals = []
|
|
for _, r in late.iterrows():
|
|
i = int(r["fx_idx"]) + int(r["bi_confirm_lag"])
|
|
j = i + h
|
|
vals.append(
|
|
int(r["direction"]) * (closes[j] - closes[i]) / closes[i]
|
|
if j < n and i < n else np.nan
|
|
)
|
|
late[f"ret_{h}"] = vals
|
|
show(summarize(late, cdf, "端点@笔确认时刻"))
|
|
|
|
print("\n########## 实时可得特征对端点的判别力 ##########")
|
|
# 这些特征在分型确认当根就全部已知,可用于实时预测
|
|
feats = {
|
|
"背驰 is_divergence": sig["is_divergence"].astype(bool),
|
|
"面积比 ratio<0.5": sig["ratio"] < 0.5,
|
|
"面积比 ratio<0.8": sig["ratio"] < 0.8,
|
|
"确认滞后 lag==1": sig["lag"] == 1,
|
|
"确认滞后 lag>=2": sig["lag"] >= 2,
|
|
}
|
|
rows = []
|
|
base_rate = sig["is_endpoint"].mean()
|
|
for name, m in feats.items():
|
|
sub = sig[m]
|
|
if len(sub) < 30:
|
|
continue
|
|
rate = sub["is_endpoint"].mean()
|
|
rows.append({
|
|
"特征": name, "命中数": len(sub),
|
|
"端点率": f"{rate * 100:.1f}%",
|
|
"基准端点率": f"{base_rate * 100:.1f}%",
|
|
"提升": f"{(rate / base_rate - 1) * 100:+.0f}%",
|
|
})
|
|
print(pd.DataFrame(rows).to_string(index=False))
|
|
|
|
print("\n########## 分型间隔的判别力(距上一分型的K线数)##########")
|
|
sig = sig.sort_values("fx_idx").reset_index(drop=True)
|
|
sig["gap"] = sig["fx_idx"].diff().fillna(0).astype(int)
|
|
rows = []
|
|
for lo, hi in [(0, 2), (3, 4), (5, 8), (9, 15), (16, 10**6)]:
|
|
sub = sig[(sig.gap >= lo) & (sig.gap <= hi)]
|
|
if len(sub) < 30:
|
|
continue
|
|
label = f"间隔{lo}-{hi}" if hi < 10**5 else f"间隔>{lo - 1}"
|
|
rows.append({
|
|
"分组": label, "n": len(sub),
|
|
"端点率": f"{sub['is_endpoint'].mean() * 100:.1f}%",
|
|
})
|
|
print(pd.DataFrame(rows).to_string(index=False))
|
|
print("\n 提示:缠论要求笔的两端分型之间至少间隔一个KLC,间隔本身就是实时可得的强过滤。")
|
|
|
|
rows = []
|
|
for lo, hi in [(5, 8), (9, 15), (16, 10**6)]:
|
|
sub = sig[(sig.gap >= lo) & (sig.gap <= hi)]
|
|
label = f"间隔{lo}-{hi}" if hi < 10**5 else f"间隔>{lo - 1}"
|
|
rows += summarize(sub, cdf, label)
|
|
print("\n########## 按间隔分组的收益(全部分型,非仅端点)##########")
|
|
show(rows)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|