用户问一类二类的识别是不是错了。查下来是代码没写错、信号该反着用。 先排除一个错误猜测:check_bi_div 只比 MACD 面积、不检查创新极值,等于把 缠论背驰定义丢了一半。看着像致命缺陷,实测 93% 的离开笔本来就创了新极值, 解释不了 PF 0.2。 真正的线索是胜率方向:B1 胜率 18.6%,而 SL2/TP3 随机入场约 40%,远低于 随机说明样本带信息只是指反了。拿相反方向重跑出场(路径依赖,不能取负号), 5m/15m 上 PF 2.35~3.03、t +10~+13。 step57 三关全过:5 币全正、前后半段 2.81/2.81 与 2.55/2.51 几乎不变、 2021~2026 连续六年全正、多空两边都正。余量 15m 33~76bp,B4 只有约 3bp。 机理是滞后把信号变成了相反的交易:信号在 leave_bi.sure_time 发出,中位滞后 8~9 根,此时价格已从低点反弹 1.68 ATR。引擎想说抄底,它给的时间戳对应的却是 反弹已走完——在下跌趋势里于此处做空是标准的顺势回调入场。与 B4 的突破延续 是不同的入场原型,可能互补。 三项验证未做完前不许上实盘:因果性(中枢右边缘已知会重画,需流式回放)、 消融(是不是反弹就做空都赚)、与 B4 的重叠度。 Co-authored-by: Cursor <cursoragent@cursor.com>
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""一类反手(fade B1/S1)是不是真的——逐币、分时段、与 B4 的重叠。
|
|
|
|
step55 的反向对照给出 5m/15m 上 PF 2.35~3.03、t +10~+13,且多空两边都正。
|
|
数字太好,先按 §3.392 的教训做证伪:那次「周末效应」在 1m 上显著,换到
|
|
7 年 5m/15m 样本就消失且符号反转。任何单一样本上的漂亮结果都要先过三关。
|
|
|
|
逐币 5 个币是不是都成立,还是被某一个币带起来的
|
|
分时段 前后半段样本各自是否成立(时间外样本)
|
|
与 B4 重叠 如果它只是换个名字的 B4,那就没有增量价值
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import warnings
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
warnings.filterwarnings("ignore")
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(HERE))
|
|
sys.path.insert(0, str(HERE.parent))
|
|
|
|
from lib.exit_model import fee_of, taker_notional # noqa: E402
|
|
|
|
SRC = HERE / "out" / "step55_bsp12.feather"
|
|
SL = 2.0
|
|
|
|
|
|
def stats(g: pd.DataFrame) -> dict:
|
|
"""反手口径的统计。f_* 是 step55 里用相反方向重跑出场得到的。"""
|
|
net = g.f_g.values - fee_of(g.f_r.values, g.f_c.values)
|
|
gR = g.f_g.values / (SL * g.atr_pct.values)
|
|
R = net / (SL * g.atr_pct.values)
|
|
tn = taker_notional(g.f_r.values, g.f_c.values)
|
|
w, o = net[net > 0].sum(), -net[net <= 0].sum()
|
|
return {
|
|
"笔数": len(g), "胜率": f"{(net > 0).mean()*100:.1f}%",
|
|
"毛R": round(gR.mean(), 3), "净均R": round(R.mean(), 3),
|
|
"PF": round(w / o, 2) if o > 0 else np.inf,
|
|
"余量bp": round(net.mean() / tn.mean() * 1e4, 2),
|
|
"t值": round(gR.mean() / (gR.std(ddof=1) / np.sqrt(len(g))), 2),
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
d = pd.read_feather(SRC)
|
|
if "f_g" not in d.columns:
|
|
print("step55 数据里没有反手列,先重跑 step55")
|
|
return
|
|
d["date_ext"] = pd.to_datetime(d["date_ext"])
|
|
one = d[d.type.isin(["B1", "S1"])].copy()
|
|
|
|
for tf, x in one.groupby("tf"):
|
|
print("\n" + "#" * 92)
|
|
print(f"########## {tf} · 一类反手 ##########")
|
|
|
|
print("\n【逐币】任何一个币独自撑起来的结果都不能要")
|
|
print(pd.DataFrame([{"币": s, **stats(g)}
|
|
for s, g in x.groupby("sym")]).to_string(index=False))
|
|
|
|
print("\n【分时段】按信号时间中位数切两半,后半段是时间外样本")
|
|
cut = x.date_ext.median()
|
|
for nm, g in (("前半", x[x.date_ext <= cut]), ("后半", x[x.date_ext > cut])):
|
|
r = stats(g)
|
|
print(f" {nm} {g.date_ext.min():%Y-%m-%d}~{g.date_ext.max():%Y-%m-%d}"
|
|
f" {r}")
|
|
|
|
print("\n【分方向】B1反手=做空,S1反手=做多。只有一边成立就是单边行情")
|
|
print(pd.DataFrame([{"原类型": t, **stats(g)}
|
|
for t, g in x.groupby("type")]).to_string(index=False))
|
|
|
|
print("\n【逐年】")
|
|
rows = []
|
|
for y, g in x.groupby(x.date_ext.dt.year):
|
|
if len(g) >= 40:
|
|
rows.append({"年": y, **stats(g)})
|
|
if rows:
|
|
print(pd.DataFrame(rows).to_string(index=False))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|