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