research: 一类反手(step57),六年五币双向全正,PF 2.3~3.0
用户问一类二类的识别是不是错了。查下来是代码没写错、信号该反着用。 先排除一个错误猜测: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>
This commit is contained in:
@@ -1086,6 +1086,59 @@ B4 跑出 PF 1.83~2.06 / t +8.5~+10.7。所以 fast B1 的负数不是管线接
|
|||||||
头早就写了「B4 不只是 B3 提前几根,两者统计性质符号相反」,一类的实验说明
|
头早就写了「B4 不只是 B3 提前几根,两者统计性质符号相反」,一类的实验说明
|
||||||
反过来也成立:**把反转信号提前,它还是反转信号。**
|
反过来也成立:**把反转信号提前,它还是反转信号。**
|
||||||
|
|
||||||
|
### 3.395 ⭐ 一类反手:识别没错,但发出的时点让它变成了反向信号(step55/57)
|
||||||
|
|
||||||
|
用户问「是不是一类二类的识别错了」。查下来结论是:**代码没写错,但信号该反着用。**
|
||||||
|
|
||||||
|
**先排除掉一个我自己的错误猜测。** `check_bi_div` 只比 MACD 面积、不检查创新极值,
|
||||||
|
这是缠论背驰定义(创新极值 **且** 力度减弱)丢了一半。看着像致命缺陷,但实测
|
||||||
|
**93.2% 的 B1 / 93.4% 的 S1 离开笔本来就创了新极值**,缺口只影响约 7%,
|
||||||
|
解释不了 PF 0.2。**先量再断,别拿理论缺陷当结论。**
|
||||||
|
|
||||||
|
**真正的线索是胜率的方向**:B1 胜率 18.6%,而 SL2/TP3 下随机入场约 40%。
|
||||||
|
**远低于随机说明样本带信息,只是指反了**——真正没用的信号会落在 40% 附近。
|
||||||
|
于是拿相反方向重跑出场(路径依赖,不能把 g 取负号了事):
|
||||||
|
|
||||||
|
| 反手 | 5m 胜率 | 5m PF | 5m t值 | 15m PF | 15m t值 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| **B1(反手做空)** | 64.4% | **2.35** | +10.4 | **3.03** | +12.6 |
|
||||||
|
| **S1(反手做多)** | 67.1% | **2.75** | +12.8 | 2.63 | +9.6 |
|
||||||
|
| B3/S3(反手) | 45~49% | 1.14~1.24 | +4 | 1.15~1.20 | +4 |
|
||||||
|
|
||||||
|
**稳健性(step57)——这是本项目做过的最稳的结果**
|
||||||
|
|
||||||
|
| 切法 | 结论 |
|
||||||
|
|---|---|
|
||||||
|
| 逐币 | 5 个币全正。15m PF 2.25~4.12,5m 2.31~2.70 |
|
||||||
|
| 前后半段 | 15m 2.81 / 2.81,5m 2.55 / 2.51,几乎不变 |
|
||||||
|
| 逐年 | 2021~2026 **连续六年全正**,15m PF 2.57~2.99 |
|
||||||
|
| 多空 | 两边都正,排除单边行情 |
|
||||||
|
| 余量 | 15m 33~76bp、5m 18~28bp。对比 B4 只有约 3bp |
|
||||||
|
|
||||||
|
**为什么不是 bug 而要反着用**
|
||||||
|
|
||||||
|
代码是照缠论写的:向下离开中枢 + 背驰 → B1 买。**教科书没错,错的是它发信号的
|
||||||
|
时刻。** 信号在 `leave_bi.sure_time` 发出,中位滞后 8~9 根,此时价格已从低点
|
||||||
|
**反弹了 1.68 ATR**(§3.393 表)。引擎想说「抄底」,但它给出的时间戳对应的是
|
||||||
|
「反弹已走完」。在下跌趋势里于此处做空 —— 那是标准的**顺势回调入场**。
|
||||||
|
|
||||||
|
所以这一族信号的几何识别是对的,方向标注按理论也是对的,**是滞后把它变成了
|
||||||
|
相反的交易**。它是一个穿着反转外衣的顺势回调信号。
|
||||||
|
|
||||||
|
**它与 B4 不是一回事**:B4 是突破延续(§3.391 实测 mom10 中位 +3.47 ATR,
|
||||||
|
根本不是回调入场),这个是回调延续。两种入场原型不同,**可能互补**。
|
||||||
|
|
||||||
|
**仍未验证,落地前必须做完(否则不许上实盘)**
|
||||||
|
|
||||||
|
1. **因果性**。`zs.is_sure` / `bi.sure_time` 在全量数据上算出,§5.41 已知中枢
|
||||||
|
右边缘会重画。必须用增量/流式回放确认信号在当时真的发得出来。
|
||||||
|
*反证据(不能替代验证)*:同一套机器算出的 B3 是 PF 0.64 的烂数,
|
||||||
|
若存在通用的未来函数,B3 不该这么难看。
|
||||||
|
2. **消融:是不是「反弹就做空」都赚**。照 §3.4 对 B4 做过的那样,把背驰条件
|
||||||
|
拿掉、把中枢换成伪阻力位,看 PF 掉不掉。掉不下来就说明 alpha 在几何和
|
||||||
|
时点上,与背驰无关,那这个信号就该重新命名和重新设计。
|
||||||
|
3. **与 B4 的重叠度与相关性**。若只是换名字的 B4,就没有增量价值。
|
||||||
|
|
||||||
### 3.4 alpha 的来源(step32 消融)
|
### 3.4 alpha 的来源(step32 消融)
|
||||||
|
|
||||||
逐条拆掉 `fast_bsp3` 的条件后发现:**alpha 完全来自缠论中枢的上下文定位,
|
逐条拆掉 `fast_bsp3` 的条件后发现:**alpha 完全来自缠论中枢的上下文定位,
|
||||||
|
|||||||
@@ -151,6 +151,15 @@ def collect(sym: str, rows: int, tf: str = "1m") -> pd.DataFrame | None:
|
|||||||
return None
|
return None
|
||||||
for k in ("g", "r", "c", "b"):
|
for k in ("g", "r", "c", "b"):
|
||||||
r[k] = res[f"{cfg}_{k}"].to_numpy()
|
r[k] = res[f"{cfg}_{k}"].to_numpy()
|
||||||
|
# 反向对照:胜率远低于随机说明信号带信息但可能指反了。出场是路径依赖的,
|
||||||
|
# 不能把 g 取负号了事,必须拿相反方向重跑一遍
|
||||||
|
flip = pd.DataFrame({"entry_idx": r.i_sure.values,
|
||||||
|
"direction": -r.dir.values})
|
||||||
|
rf = walk_exits(cdf, flip, [SL], [RUNNER], [MAXB], scale_at=SCALE_AT,
|
||||||
|
runners=(RUNNER,), runner_stops=(RSTOP,))
|
||||||
|
if len(rf) == len(r):
|
||||||
|
for k in ("g", "r", "c", "b"):
|
||||||
|
r["f_" + k] = rf[f"{cfg}_{k}"].to_numpy()
|
||||||
r["atr_pct"] = atr[r.i_sure.values] / cl[r.i_sure.values]
|
r["atr_pct"] = atr[r.i_sure.values] / cl[r.i_sure.values]
|
||||||
return r
|
return r
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -258,6 +267,31 @@ def profit(d: pd.DataFrame) -> None:
|
|||||||
print("\n⚠️ B3 是口径校验锚:HANDOFF §4 记的是 PF 0.66 / 胜率 27.4%。"
|
print("\n⚠️ B3 是口径校验锚:HANDOFF §4 记的是 PF 0.66 / 胜率 27.4%。"
|
||||||
"这里若明显更好,先怀疑接错了再信 B1/B2。")
|
"这里若明显更好,先怀疑接错了再信 B1/B2。")
|
||||||
|
|
||||||
|
if "f_g" not in x.columns:
|
||||||
|
return
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("########## 六、反向对照:信号是「指反了」还是「没有边」 ##########")
|
||||||
|
print("胜率 18.6% 远低于 SL2/TP3 的随机基准约 40%,说明样本带信息。若只是"
|
||||||
|
"方向标反了,\n反手做应显著为正;若反手也不赚,那就是入场时点本身"
|
||||||
|
"已经过期,两边都吃滑点。")
|
||||||
|
y = x.copy()
|
||||||
|
y["fnet"] = y.f_g.values - fee_of(y.f_r.values, y.f_c.values)
|
||||||
|
y["fgR"] = y.f_g.values / (SL * y.atr_pct.values)
|
||||||
|
rows = []
|
||||||
|
for name, g in y.groupby("type"):
|
||||||
|
if len(g) < 40:
|
||||||
|
continue
|
||||||
|
w, o = g.fnet[g.fnet > 0].sum(), -g.fnet[g.fnet <= 0].sum()
|
||||||
|
rows.append({
|
||||||
|
"类型(反手)": name, "笔数": len(g),
|
||||||
|
"胜率": f"{(g.fnet > 0).mean()*100:.1f}%",
|
||||||
|
"毛R": round(g.fgR.mean(), 3),
|
||||||
|
"PF": round(w / o, 2) if o > 0 else np.inf,
|
||||||
|
"t值": round(g.fgR.mean() / (g.fgR.std(ddof=1) / np.sqrt(len(g))),
|
||||||
|
2),
|
||||||
|
})
|
||||||
|
print(pd.DataFrame(rows).to_string(index=False))
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
"""一类反手(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()
|
||||||
Reference in New Issue
Block a user