research: 一类线门槛量化——全局只有一个变量,精度需 67~73%
把 §3.3992 的天花板换成实时可算的 ext_run 选样配结构止损:5m 0.51、15m 0.48, 对比上界 2.29/4.11,兑现不了。但数字对不上(Q4 真端点浓度已 44%),拆开后 得到本轮最有价值的一张表: ext_run 在真端点**内部**毫无反向选样(5m 各档 2.53/1.80/2.61/2.22 持平, 15m 单调递增到 6.10),在非真端点内部也毫无区分力(恒在 0.11~0.18)。 全局只有「是不是真端点」这一个变量在起作用,其余特征都是它的噪声代理。 这也修正了 §3.399:ext_run 的符号翻转不是拟合,它确实提纯(28.7%→44%), 只是幅度远远不够。 于是 PF 退化为两组按精度 p 的混合,解出盈亏平衡精度:5m 72.6%、15m 67.1%。 即要求实时判「这是不是那个底」的准确率达七成,而现在是 28.7%。 顺带解释了为什么 B4 能做而一类不能:B4 是突破后的延续信号,不需要判断反转; 一类的全部难度集中在这一个二分类上。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-10
@@ -1484,10 +1484,52 @@ MFE/MAE 给出机制:真底那批**逆向行程中位 2.24 ATR(5m)/ 2.08
|
|||||||
它证明的是**失败原因可修(选样精度 + 止损位),而非这个级别没得做**。
|
它证明的是**失败原因可修(选样精度 + 止损位),而非这个级别没得做**。
|
||||||
|
|
||||||
**由此产生的两个待跑实验:**
|
**由此产生的两个待跑实验:**
|
||||||
1. **实时过滤器 + 结构止损**(唯一值得跑的组合)。step62 的 `ext_run` 实时可算、
|
1. ✅ **实时过滤器 + 结构止损** → 见 §3.3993,兑现不了,但换来一个确切门槛
|
||||||
已把精度做到 47.9%,但当时配 2 ATR 止损所以只到 0.54。天花板说这个配错了。
|
2. **超时从 48 根放宽**(仍未跑)。线段中位时长 **113 根**,48 根超时在行情走完
|
||||||
2. **超时从 48 根放宽**。线段中位时长 **113 根**,48 根超时在行情走完 42% 时就平仓;
|
42% 时就平仓;真底那批 MFE 中位仅 2.37 ATR,可能是被截断的(而非行情本身小)
|
||||||
真底那批 MFE 中位仅 2.37 ATR,很可能就是被截断的(而非行情本身小)。
|
|
||||||
|
### 3.3993 ⭐ 一类线的门槛量化:全局只有一个变量,精度要 67~73%(step67)
|
||||||
|
|
||||||
|
把 §3.3992 的天花板换成**实时可算**的 `ext_run` 选样,配结构止损:
|
||||||
|
|
||||||
|
| | 5m | 15m |
|
||||||
|
|---|---|---|
|
||||||
|
| 全体无过滤 | 0.34 | 0.32 |
|
||||||
|
| ext_run ≥ P75(实时) | 0.51 | 0.48 |
|
||||||
|
| ext_run≥P75 且 div≥中位 | 0.60 | 0.46 |
|
||||||
|
| ★ 真线段端点(未来函数) | **2.29** | **4.11** |
|
||||||
|
|
||||||
|
**兑现不了。** 但数字对不上——Q4 的真端点浓度已达 44%(基线 28.7%),
|
||||||
|
若真端点值 2.29,44% 浓度不该只有 0.51。拆开看:
|
||||||
|
|
||||||
|
| ext_run 档 | 真端点组 PF (5m / 15m) | 非真端点组 PF (5m / 15m) |
|
||||||
|
|---|---|---|
|
||||||
|
| Q1 最短 | 2.53 / 2.61 | 0.18 / 0.11 |
|
||||||
|
| Q4 最延伸 | 2.22 / **6.10** | 0.16 / 0.12 |
|
||||||
|
|
||||||
|
**这张表是本节的结论**:`ext_run` 在真端点**内部**毫无反向选样(5m 持平、
|
||||||
|
15m 单调递增到 6.10),在非真端点内部也毫无区分力(恒在 0.11~0.18)。
|
||||||
|
**全局只有「是不是真端点」这一个变量在起作用**,其余特征都是它的噪声代理。
|
||||||
|
|
||||||
|
这同时**修正了 §3.399 的判定**:当时把 `ext_run` 的符号翻转判为拟合。
|
||||||
|
现在看它不是拟合——它确实提纯(28.7% → 44%),只是提纯幅度远远不够。
|
||||||
|
|
||||||
|
于是 PF 退化成两组按精度 p 的混合,可解出门槛:
|
||||||
|
|
||||||
|
| 实时精度 | 28.7%(当前) | 44%(ext_run) | 60% | 70% | 100%(上界) |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| PF (5m) | 0.34 | 0.50 | 0.73 | 0.93 | 2.29 |
|
||||||
|
| PF (15m) | 0.33 | 0.51 | 0.81 | 1.10 | 4.11 |
|
||||||
|
|
||||||
|
**盈亏平衡精度:5m 需 72.6%,15m 需 67.1%。** 当前 28.7%,`ext_run` 做到 44%。
|
||||||
|
|
||||||
|
**判定:一类线的问题已完全定性,但门槛极高。** 要把「这是不是真线段底」的实时
|
||||||
|
判准精度做到 ~70%(现在 28.7%,最好的特征到 44%),本质上是要求实时抄底摸顶的
|
||||||
|
准确率达到七成。这是可以尝试的 ML 问题(标签、特征、评估口径本节都已给全),
|
||||||
|
但**先验上很难**,且 70% 只是毛平衡,还要再扣 §3.396 的 15 根因果滞后。
|
||||||
|
|
||||||
|
> **这解释了为什么 B4 能做而一类不能**:B4 是突破后的**延续**信号,
|
||||||
|
> 不需要判断反转;一类的全部难度集中在「这是不是那个底」这一个二分类上。
|
||||||
|
|
||||||
### 3.4 alpha 的来源(step32 消融)
|
### 3.4 alpha 的来源(step32 消融)
|
||||||
|
|
||||||
@@ -2700,12 +2742,14 @@ API 限流风险隔离三个好处。
|
|||||||
§3.396 证明 `sure_time` 对一类是后视产物(PF 2.35 → 0.92)。B4 用的是
|
§3.396 证明 `sure_time` 对一类是后视产物(PF 2.35 → 0.92)。B4 用的是
|
||||||
同一个 `sure_time` 字段,**在这个回放出结果前,所有 B4 的历史 PF 都待定**。
|
同一个 `sure_time` 字段,**在这个回放出结果前,所有 B4 的历史 PF 都待定**。
|
||||||
若 B4 也栽,整条线要重估;若过了,B4 是目前唯一活着的信号
|
若 B4 也栽,整条线要重估;若过了,B4 是目前唯一活着的信号
|
||||||
- [ ] ⭐ **一类线已重开,跑「实时过滤器 + 结构止损」**(§3.3992)。
|
- [ ] **一类线:问题已完全定性,等一个决策**(§3.3992 / §3.3993)。
|
||||||
§3.399 的关闭结论**已被 step66 推翻**:之前六次进攻每次只动一半,
|
全局只有一个变量——**「是不是真线段端点」**:是则 PF 2.29(5m)/4.11(15m),
|
||||||
完美选样配 2ATR 止损是 0.70,无选样配结构止损是 0.36,**两个一起是 2.29/4.11**。
|
否则恒为 0.13,其余特征都只是它的噪声代理。**盈亏平衡精度 5m 72.6%、
|
||||||
要跑的是 step62 的 `ext_run` 实时过滤器(精度 28.7% → 47.9%)配结构止损,
|
15m 67.1%**,当前 28.7%,最好的实时特征 `ext_run` 到 44%。
|
||||||
看能兑现多少上界。注意上界含两个未来函数,兑现后必须再过因果回放
|
要继续就是做一个「实时判真底」的分类器打到 ~70%,标签/特征/评估口径
|
||||||
- [ ] **一类超时从 48 根放宽**(§3.3992)。线段中位时长 113 根,
|
§3.3993 已给全,但先验很难,且 70% 只是毛平衡,还要再扣 15 根因果滞后。
|
||||||
|
**这条不阻塞任何实盘工作**
|
||||||
|
- [ ] **一类超时从 48 根放宽**(§3.3992,仍未跑,代价很低)。线段中位时长 113 根,
|
||||||
48 根在行情走完 42% 时平仓,真底那批 MFE 中位仅 2.37 ATR 疑似被截断
|
48 根在行情走完 42% 时平仓,真底那批 MFE 中位仅 2.37 ATR 疑似被截断
|
||||||
- [x] **二类关闭**(step64,§3.3991)。与一类不同,二类是**信号消失**而非信号指反:
|
- [x] **二类关闭**(step64,§3.3991)。与一类不同,二类是**信号消失**而非信号指反:
|
||||||
两个方向 t 都贴零,且入场离结构止损位 4.87 ATR(一类 2.90),
|
两个方向 t 都贴零,且入场离结构止损位 4.87 ATR(一类 2.90),
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
"""把 step66 的天花板换成实时可算的选样,看能兑现多少。
|
||||||
|
|
||||||
|
step66 测出上界:**未来函数选样 + 结构止损**在 5m 是 PF 2.29、15m 是 4.11。
|
||||||
|
但那个选样用了线段端点(事后才知道),不能交易。本脚本把它换成 step62 那个
|
||||||
|
**实时可算**的 `ext_run`(极值越过中枢边界几个 ATR,当根即可算),配上结构止损。
|
||||||
|
|
||||||
|
这是唯一同时处理两个已知约束的组合,也是 §3.3992 列出的第一个待跑实验:
|
||||||
|
|
||||||
|
step62 有实时过滤器(精度 28.7% -> 47.9%)但配了 2 ATR 止损 -> PF 0.54
|
||||||
|
step58 有结构止损但没有过滤器 -> PF 0.36
|
||||||
|
step66 两个都有,但选样是未来函数 -> PF 2.29 / 4.11
|
||||||
|
**本脚本:实时过滤器 + 结构止损** -> ?
|
||||||
|
|
||||||
|
判读(对照 step66 的上界):
|
||||||
|
> 1.3 兑现了相当部分,一类线值得继续,下一步做因果回放(sure_time 仍是未来函数)
|
||||||
|
~ 1.0 过滤器精度不够,需要更好的实时特征
|
||||||
|
< 0.8 实时特征抓不到那 28.7%,天花板兑现不了,一类线仍关闭
|
||||||
|
|
||||||
|
⚠️ 即使 > 1.3 也**不能算数**:入场仍在 `sure_time` 上,而 §3.396 证明实盘回放
|
||||||
|
还要再晚 15 根。这一步只决定「值不值得再花一次因果回放的机器时间」。
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
sys.path.insert(0, str(HERE))
|
||||||
|
sys.path.insert(0, str(HERE.parent))
|
||||||
|
|
||||||
|
F62 = HERE / "out" / "step62_trend_end.feather"
|
||||||
|
F64 = HERE / "out" / "step64_b2.feather"
|
||||||
|
KEY = ["sym", "tf", "type", "i_ext", "i_sure"]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--tf", default="5m")
|
||||||
|
ap.add_argument("--rows", type=int, default=200_000)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
from chanlun import TF_DF
|
||||||
|
from lib.data import fetch_ohlcv
|
||||||
|
from step58_struct_stop import simulate, stats
|
||||||
|
|
||||||
|
a = pd.read_feather(F62)[KEY + ["ext_run", "div", "ladder_n"]]
|
||||||
|
b = pd.read_feather(F64)[KEY + ["entry_px", "own_ext",
|
||||||
|
"atr_at_entry", "hit", "dir"]]
|
||||||
|
d = a.merge(b, on=KEY, how="inner")
|
||||||
|
d = d[d.tf == args.tf].reset_index(drop=True)
|
||||||
|
d["to_ext"] = (d.entry_px - d.own_ext) * d.dir / d.atr_at_entry
|
||||||
|
|
||||||
|
print(f"[实时过滤器 + 结构止损] {args.tf} · 一类 {len(d)} 笔 · "
|
||||||
|
f"真线段端点占比 {d.hit.mean()*100:.1f}%")
|
||||||
|
print(f"step66 上界(同止损、但用未来函数选样):"
|
||||||
|
f"{'PF 2.29' if args.tf == '5m' else 'PF 4.11'}\n")
|
||||||
|
|
||||||
|
cache = {}
|
||||||
|
for sym in sorted(d.sym.unique()):
|
||||||
|
df = fetch_ohlcv(f"{sym}/USDT:USDT", args.tf, args.rows)
|
||||||
|
cache[sym] = TF_DF(df, 1, args.tf).dataframe
|
||||||
|
|
||||||
|
def run(x: pd.DataFrame, margin: float | None) -> dict:
|
||||||
|
gs, rs, cs, ap_, sl_ = [], [], [], [], []
|
||||||
|
for sym, g in x.groupby("sym"):
|
||||||
|
g = g.reset_index(drop=True)
|
||||||
|
t = pd.DataFrame({"entry_idx": g.i_sure.values,
|
||||||
|
"direction": g.dir.values,
|
||||||
|
"atr_at_entry": g.atr_at_entry.values})
|
||||||
|
sl = (np.full(len(g), 2.0) if margin is None
|
||||||
|
else (g.to_ext.values + margin))
|
||||||
|
res = simulate(cache[sym], t, sl, r_scale=False)
|
||||||
|
ok = res.g.notna().values
|
||||||
|
gs.append(res.g[ok])
|
||||||
|
rs.append(res.r[ok])
|
||||||
|
cs.append(res.c[ok])
|
||||||
|
ap_.append((g.atr_at_entry.values / g.entry_px.values)[ok])
|
||||||
|
sl_.append(sl[ok])
|
||||||
|
return stats(pd.concat(gs, ignore_index=True),
|
||||||
|
pd.concat(rs, ignore_index=True),
|
||||||
|
pd.concat(cs, ignore_index=True),
|
||||||
|
np.concatenate(ap_), np.concatenate(sl_))
|
||||||
|
|
||||||
|
print("=" * 96)
|
||||||
|
print("【一】ext_run 分档 × 止损口径 —— 过滤器的效果依赖止损吗")
|
||||||
|
print("=" * 96)
|
||||||
|
q = d.ext_run.quantile([.25, .5, .75]).values
|
||||||
|
lab = ["Q1最短", "Q2", "Q3", "Q4最延伸"]
|
||||||
|
d["bucket"] = pd.cut(d.ext_run, [-np.inf, *q, np.inf], labels=lab)
|
||||||
|
rows = []
|
||||||
|
for bk, g in d.groupby("bucket", observed=True):
|
||||||
|
for name, m in [("固定2ATR", None), ("结构+1.0", 1.0)]:
|
||||||
|
rows.append({"ext_run档": bk, "止损": name, "真端点占比":
|
||||||
|
f"{g.hit.mean()*100:.0f}%", **run(g, m)})
|
||||||
|
print(pd.DataFrame(rows).to_string(index=False))
|
||||||
|
print("""
|
||||||
|
§3.399 判过「过滤器符号随入场时点翻转 -> 拟合」。这里换的是**止损**而非入场,
|
||||||
|
若 Q4 在两种止损下都最好,说明 ext_run 是真信号;若又翻转,则仍是拟合。""")
|
||||||
|
|
||||||
|
print("\n" + "=" * 96)
|
||||||
|
print("【二】实时可交易组合 vs step66 上界")
|
||||||
|
print("=" * 96)
|
||||||
|
rows = [{"选样": "全体(无过滤)", "笔数": len(d), **run(d, 1.0)}]
|
||||||
|
for p in (50, 75):
|
||||||
|
thr = np.percentile(d.ext_run, p)
|
||||||
|
g = d[d.ext_run >= thr]
|
||||||
|
rows.append({"选样": f"ext_run ≥ P{p}(实时)", "笔数": len(g),
|
||||||
|
**run(g, 1.0)})
|
||||||
|
g = d[(d.ext_run >= np.percentile(d.ext_run, 75))
|
||||||
|
& (d["div"] >= d["div"].median())]
|
||||||
|
rows.append({"选样": "ext_run≥P75 且 div≥中位(实时)",
|
||||||
|
"笔数": len(g), **run(g, 1.0)})
|
||||||
|
gh = d[d.hit]
|
||||||
|
rows.append({"选样": "★真线段端点(未来函数=上界)", "笔数": len(gh),
|
||||||
|
**run(gh, 1.0)})
|
||||||
|
print(pd.DataFrame(rows).to_string(index=False))
|
||||||
|
print("""
|
||||||
|
判读:实时行若接近 ★ 那行,说明 ext_run 抓到了同一批信号,一类线值得继续;
|
||||||
|
若仍贴近「全体」,说明实时特征抓不到那 28.7%,天花板兑现不了。
|
||||||
|
⚠️ 即便好也不算数——入场仍在 sure_time 上,必须再过一次因果回放。""")
|
||||||
|
|
||||||
|
print("\n" + "=" * 96)
|
||||||
|
print("【三】数字对不上,追一下:ext_run 挑出的真端点,质量还一样吗")
|
||||||
|
print("=" * 96)
|
||||||
|
print("Q4 的真端点浓度 44%(基线 28.7%),若真端点都值 PF 2.29,"
|
||||||
|
"Q4 不该只有 0.51。\n拆开看 ext_run 在真端点**内部**是帮忙还是帮倒忙——"
|
||||||
|
"这决定天花板是否可学:")
|
||||||
|
rows = []
|
||||||
|
for k, g in d.groupby(d.hit):
|
||||||
|
for bk, gg in g.groupby("bucket", observed=True):
|
||||||
|
if len(gg) < 25:
|
||||||
|
continue
|
||||||
|
rows.append({"真端点": "是" if k else "否", "ext_run档": bk,
|
||||||
|
"笔数": len(gg), **run(gg, 1.0)})
|
||||||
|
print(pd.DataFrame(rows).to_string(index=False))
|
||||||
|
print("""
|
||||||
|
「是」组内若 Q4 明显低于 Q1 -> ext_run 在真端点里**反向选样**,
|
||||||
|
它提高浓度的同时挑走了最差的那些,两个效应抵消,这解释了 0.51 vs 2.29。
|
||||||
|
若「是」组内各档持平 -> 浓度提升是真的,缺的只是更强的实时特征。""")
|
||||||
|
|
||||||
|
print("\n" + "=" * 96)
|
||||||
|
print("【四】那需要多高的实时精度才能翻正")
|
||||||
|
print("=" * 96)
|
||||||
|
print("【三】显示只有「是不是真端点」这一个变量在起作用(组内各档持平)。"
|
||||||
|
"\n于是 PF 只是两组按精度 p 的混合,可以直接解出盈亏平衡精度:")
|
||||||
|
net = {}
|
||||||
|
for k, g in d.groupby(d.hit):
|
||||||
|
ns = []
|
||||||
|
for sym, x in g.groupby("sym"):
|
||||||
|
x = x.reset_index(drop=True)
|
||||||
|
t = pd.DataFrame({"entry_idx": x.i_sure.values,
|
||||||
|
"direction": x.dir.values,
|
||||||
|
"atr_at_entry": x.atr_at_entry.values})
|
||||||
|
res = simulate(cache[sym], t, x.to_ext.values + 1.0,
|
||||||
|
r_scale=False)
|
||||||
|
ok = res.g.notna().values
|
||||||
|
from lib.exit_model import fee_of
|
||||||
|
ns.append(res.g[ok].values
|
||||||
|
- fee_of(res.r[ok].values, res.c[ok].values))
|
||||||
|
net[bool(k)] = np.concatenate(ns)
|
||||||
|
|
||||||
|
def mix_pf(p: float) -> float:
|
||||||
|
"""精度 p 时的 PF:两组按 p 加权(组内分布不变,只变权重)。"""
|
||||||
|
h, m = net[True], net[False]
|
||||||
|
w = (p * h[h > 0].sum() / len(h)
|
||||||
|
+ (1 - p) * m[m > 0].sum() / len(m))
|
||||||
|
l = (p * -h[h <= 0].sum() / len(h)
|
||||||
|
+ (1 - p) * -m[m <= 0].sum() / len(m))
|
||||||
|
return w / l if l > 0 else np.inf
|
||||||
|
|
||||||
|
grid = [0.287, 0.35, 0.44, 0.5, 0.6, 0.7, 0.8, 1.0]
|
||||||
|
print(pd.DataFrame([{
|
||||||
|
"实时精度": f"{p*100:.1f}%", "PF": round(mix_pf(p), 2),
|
||||||
|
"备注": {0.287: "← 当前基线", 0.44: "← ext_run Q4 已达到",
|
||||||
|
1.0: "← step66 上界"}.get(p, "")} for p in grid]
|
||||||
|
).to_string(index=False))
|
||||||
|
lo, hi = 0.287, 1.0
|
||||||
|
if mix_pf(hi) > 1 > mix_pf(lo):
|
||||||
|
for _ in range(40):
|
||||||
|
mid = (lo + hi) / 2
|
||||||
|
lo, hi = (mid, hi) if mix_pf(mid) < 1 else (lo, mid)
|
||||||
|
print(f"\n **盈亏平衡精度 ≈ {(lo+hi)/2*100:.1f}%** "
|
||||||
|
f"(当前 28.7%,ext_run Q4 已到 44.0%)")
|
||||||
|
print(" 这就是「改识别规则」要够到的具体门槛,且它只是毛平衡;"
|
||||||
|
"\n 还要再扣 §3.396 的因果滞后(实盘比 sure_time 再晚 15 根)。")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user