M30-Filter nach Vola-Regime geprueft — Hypothese invertiert, keine Aenderung
Anlass: Am 04.08. fiel WTI -4,12 $ (davon -4,16 $ in EINER Stunde), der Bot stand zu 93 % auf WARTEN, htf_counter blockte 160-mal. Hypothese: die nachlaufende M30-EMA sperrt in schnellen Umkehrungen das Richtige. Die Messung invertiert die Hypothese. Geblockte Signale, kanonischer Exit, Echtkosten, nach ATR-Terzil x 2 Halbjahre: Terzil geblockt H1 geblockt H2 durchgel. H1 durchgel. H2 niedrig -0,086 +0,107 -0,136 -0,134 mittel -0,172 +0,067 -0,198 -0,069 hoch -0,625 -0,329 -0,061 +0,014 Im HOHEN Terzil sind die geblockten Signale mit Abstand die schlechtesten des gesamten Datensatzes (PF 0,15 / 0,42) - der Filter ist dort am wertvollsten, nicht am schaedlichsten. Die durchgelassenen Hoch-Vola-Signale in H2 sind das einzige positive Feld der ganzen Tabelle. In den unteren Terzilen kippen die geblockten Signale zwischen den Haelften = Rauschen. Den Filter in schnellen Bewegungen zu lockern waere die schlechtestmoegliche Aenderung gewesen. 22. verworfener Eingriff. Auflösung des scheinbaren Widerspruchs: die 160 geblockten Signale und die 17 manuellen Gewinn-Trades desselben Tages sind VERSCHIEDENE Grundgesamtheiten. Die manuellen Trades liefen alle auf rec=WARTEN, stammen also gar nicht aus der Signal-Logik - der Filter hat den User nie gebremst. "Das Gate blockte X" und "der Mensch verdiente Y" gehoeren nur zusammen, wenn Y aus X stammt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
20137a3dd1
commit
7432be0e8c
@@ -310,6 +310,31 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
WARTEN-Treiber: M30-Gegen-Trend (~27 % der Bars, gewollt, Edge×2) + Überdehnung
|
WARTEN-Treiber: M30-Gegen-Trend (~27 % der Bars, gewollt, Edge×2) + Überdehnung
|
||||||
+ Totband. `_STRETCH_MAX` 2,5→**3,5** angehoben (`backtest_stretch.py`: +26 %
|
+ Totband. `_STRETCH_MAX` 2,5→**3,5** angehoben (`backtest_stretch.py`: +26 %
|
||||||
Signale, Gesamtertrag steigt; ab 3,5×ATR kippt der Edge negativ).
|
Signale, Gesamtertrag steigt; ab 3,5×ATR kippt der Edge negativ).
|
||||||
|
- **⚠ M30-FILTER NACH VOLA-REGIME GEPRÜFT = KEINE ÄNDERUNG (gemessen
|
||||||
|
`backtest_htf_vola.py`, 2026-08-04).** Anlass: Am 04.08. fiel WTI −4,12 $ (davon
|
||||||
|
−4,16 $ in EINER Stunde), der Bot stand zu 93 % auf WARTEN, `htf_counter` blockte
|
||||||
|
**160-mal** — scheinbar genau die Richtung, in die der Markt lief. Hypothese: die
|
||||||
|
nachlaufende M30-EMA sperrt in schnellen Umkehrungen das Richtige.
|
||||||
|
**Die Messung invertiert die Hypothese.** Geblockte Signale, kanonischer Exit,
|
||||||
|
Echtkosten, nach ATR-Terzil × 2 Halbjahre:
|
||||||
|
| ATR-Terzil | geblockt H1 | geblockt H2 | durchgelassen H1 | durchgelassen H2 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| niedrig | −0,086 | +0,107 | −0,136 | −0,134 |
|
||||||
|
| mittel | −0,172 | +0,067 | −0,198 | −0,069 |
|
||||||
|
| **hoch** | **−0,625** | **−0,329** | −0,061 | **+0,014** |
|
||||||
|
Im HOHEN Terzil sind die geblockten Signale **mit Abstand die schlechtesten des
|
||||||
|
gesamten Datensatzes** (PF 0,15 / 0,42) — der Filter ist dort am wertvollsten, nicht
|
||||||
|
am schädlichsten. Die durchgelassenen Hoch-Vola-Signale in H2 sind das **einzige
|
||||||
|
positive Feld** der ganzen Tabelle. In den unteren Terzilen kippen die geblockten
|
||||||
|
Signale zwischen den Hälften = Rauschen. **Den Filter in schnellen Bewegungen zu
|
||||||
|
lockern wäre die schlechtestmögliche Änderung gewesen. 22. verworfener Eingriff.**
|
||||||
|
⚠⚠ **Auflösung des scheinbaren Widerspruchs — und eine Lehre über Populationen:**
|
||||||
|
Die 160 geblockten Signale und die 17 manuellen Gewinn-Trades desselben Tages sind
|
||||||
|
**verschiedene Grundgesamtheiten**. Die manuellen Trades liefen ALLE auf
|
||||||
|
`rec=WARTEN`, stammen also gar nicht aus der Signal-Logik — der Filter hat den User
|
||||||
|
nie gebremst, er handelte unabhängig. „Das Gate blockte X" und „der Mensch verdiente
|
||||||
|
Y" gehören nur dann zusammen, wenn Y aus X stammt. Die Ausgangsvermutung hat genau
|
||||||
|
das verwechselt.
|
||||||
- **Higher-TF-Gegen-Trend-Filter:** Signal wird verworfen (→WARTEN), wenn der
|
- **Higher-TF-Gegen-Trend-Filter:** Signal wird verworfen (→WARTEN), wenn der
|
||||||
übergeordnete **M30**-Trend (EMA12/50, Totband `_HTF_DEADBAND=0,15×ATR`) klar
|
übergeordnete **M30**-Trend (EMA12/50, Totband `_HTF_DEADBAND=0,15×ATR`) klar
|
||||||
dagegen steht — kein Short im M30-Aufwärtstrend & umgekehrt. Nur für Basis-TF
|
dagegen steht — kein Short im M30-Aufwärtstrend & umgekehrt. Nur für Basis-TF
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Kostet der M30-Gegen-Trend-Filter in HOHER VOLATILITÄT Geld? (2026-08-04)
|
||||||
|
|
||||||
|
Anlass: Am 04.08. fiel WTI um −4,12 $ (davon −4,16 $ in EINER Stunde). Der Bot
|
||||||
|
stand zu 93 % auf WARTEN; `htf_counter` blockte dabei **160-mal** — also genau
|
||||||
|
die Richtung, in die der Markt lief. Die M30-EMA läuft nach und steht in einem
|
||||||
|
schnellen Absturz noch „auf".
|
||||||
|
|
||||||
|
Der Filter ist über die GESAMTstichprobe belegt (Ø-Edge ×2, `backtest_htf_filter.py`).
|
||||||
|
Die Frage ist enger: **trägt er auch im oberen Vola-Terzil, oder kommt sein Nutzen
|
||||||
|
allein aus ruhigen Phasen?**
|
||||||
|
|
||||||
|
Aufbau: gegatete Live-Population, aber der HTF-Filter wird NICHT in `_build`
|
||||||
|
angewandt (htf_trend=0) — stattdessen wird je Signal festgehalten, ob der Filter
|
||||||
|
es geblockt HÄTTE. Die geblockten Signale werden mit dem kanonischen Exit
|
||||||
|
simuliert. Auswertung nach ATR-Terzil × 2 Halbjahre.
|
||||||
|
|
||||||
|
⚠ Regel VORAB: Der Filter wird nur dann im Hoch-Vola-Bereich gelockert, wenn die
|
||||||
|
GEBLOCKTEN Signale dort in BEIDEN Hälften ØR>0 UND PF>1 liefern — und das
|
||||||
|
mittlere Terzil das nicht widerlegt. Ein einzelnes positives Feld zählt nicht.
|
||||||
|
⚠ Hohe Beweislast: 21 Signal-Eingriffe sind hier bereits gescheitert, und dieser
|
||||||
|
würde ein GEMESSEN POSITIVES Gate schwächen.
|
||||||
|
|
||||||
|
Aufruf: python backtest_htf_vola.py [bars]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
from core.analysis import calc_trend_angle
|
||||||
|
from core.exit_model import LIVE, simulate
|
||||||
|
from core.wave_rec import (WaveRecommender, _ANGLE_LR, _EMA_FAST, _EMA_SLOW,
|
||||||
|
_HTF_DEADBAND, _ema_series)
|
||||||
|
|
||||||
|
_ATRMIN = 0.06
|
||||||
|
_PIVK = 3
|
||||||
|
_PIV_LOOK = 300
|
||||||
|
|
||||||
|
|
||||||
|
class _TU:
|
||||||
|
def snapshot(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _atr_series(H, L, C, p=14):
|
||||||
|
t = [0.0]
|
||||||
|
for i in range(1, len(C)):
|
||||||
|
t.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
|
||||||
|
return [(sum(t[max(1, i - p + 1):i + 1]) / max(1, len(t[max(1, i - p + 1):i + 1])))
|
||||||
|
if i else None for i in range(len(C))]
|
||||||
|
|
||||||
|
|
||||||
|
def _htf_sign(C, i, n, atr):
|
||||||
|
lo = max(0, i - n * (_EMA_SLOW + 5))
|
||||||
|
sub = C[lo:i + 1:n]
|
||||||
|
if len(sub) < _EMA_SLOW + 2 or not atr:
|
||||||
|
return 0
|
||||||
|
d = (_ema_series(sub, _EMA_FAST)[-1] - _ema_series(sub, _EMA_SLOW)[-1]) / atr
|
||||||
|
return 0 if abs(d) < _HTF_DEADBAND else (1 if d > 0 else -1)
|
||||||
|
|
||||||
|
|
||||||
|
def st(Rs):
|
||||||
|
if not Rs:
|
||||||
|
return None
|
||||||
|
n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs)
|
||||||
|
up = sum(x for x in Rs if x > 0); dn = -sum(x for x in Rs if x < 0)
|
||||||
|
return dict(n=n, wr=100 * w / n, oR=s / n, pf=(up / dn if dn > 0 else 9.99), sum=s)
|
||||||
|
|
||||||
|
|
||||||
|
def zeile(lbl, s):
|
||||||
|
if not s:
|
||||||
|
return f" {lbl:<24} — (keine Trades)"
|
||||||
|
return (f" {lbl:<24} n={s['n']:>5} WR={s['wr']:>3.0f}% ØR={s['oR']:+.3f} "
|
||||||
|
f"PF={s['pf']:>4.2f} ΣR={s['sum']:>+7.0f}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n_req = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||||
|
mt5.initialize()
|
||||||
|
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD")
|
||||||
|
if mt5.symbol_info(c)), None)
|
||||||
|
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n_req)
|
||||||
|
point = mt5.symbol_info(sym).point
|
||||||
|
mt5.shutdown()
|
||||||
|
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
|
||||||
|
C = [float(b["close"]) for b in bars]; SP = [float(b["spread"]) * point for b in bars]
|
||||||
|
N = len(C); mid = N // 2
|
||||||
|
A = _atr_series(H, L, C)
|
||||||
|
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
|
||||||
|
|
||||||
|
print("Vorberechnung ...", flush=True)
|
||||||
|
AD = [90.0] * N
|
||||||
|
for i in range(_ANGLE_LR + 2, N):
|
||||||
|
AD[i] = calc_trend_angle(C[i - (_ANGLE_LR + 1):i + 1], _ANGLE_LR)
|
||||||
|
HTF = [0] * N; H1 = [0] * N
|
||||||
|
for i in range(N):
|
||||||
|
if A[i]:
|
||||||
|
HTF[i] = _htf_sign(C, i, 6, A[i]); H1[i] = _htf_sign(C, i, 12, A[i])
|
||||||
|
PH = [j for j in range(_PIVK, N - _PIVK) if H[j] == max(H[j - _PIVK:j + _PIVK + 1])]
|
||||||
|
PL = [j for j in range(_PIVK, N - _PIVK) if L[j] == min(L[j - _PIVK:j + _PIVK + 1])]
|
||||||
|
|
||||||
|
gueltig = sorted(A[i] for i in range(200, N) if A[i] and A[i] >= _ATRMIN)
|
||||||
|
t1 = gueltig[len(gueltig) // 3]; t2 = gueltig[2 * len(gueltig) // 3]
|
||||||
|
print(f"ATR-Terzil-Grenzen: niedrig < {t1:.3f} ≤ mittel < {t2:.3f} ≤ hoch\n")
|
||||||
|
|
||||||
|
w = WaveRecommender(_TU(), mt5.TIMEFRAME_M5)
|
||||||
|
w.set_reversal_enabled(False); w.set_entry_room(0.6)
|
||||||
|
w.set_dead_hours([]); w.set_breakout_k(0.3)
|
||||||
|
|
||||||
|
# Signale OHNE HTF-Filter erzeugen (htf_trend=0), Blockade separat feststellen
|
||||||
|
geblockt = {}; durchgelassen = {}
|
||||||
|
pend = None
|
||||||
|
i = _PIV_LOOK + _EMA_SLOW + 5
|
||||||
|
while i < N - LIVE.max_hold - 2:
|
||||||
|
atr = A[i]
|
||||||
|
if not atr or atr < _ATRMIN:
|
||||||
|
i += 1; continue
|
||||||
|
w._pb_feats = {"atr": max(atr, 0.12)}
|
||||||
|
w._pb_levels = {"ph": [C[j] for j in PH if i - _PIV_LOOK <= j <= i - _PIVK],
|
||||||
|
"pl": [C[j] for j in PL if i - _PIV_LOOK <= j <= i - _PIVK]}
|
||||||
|
rec, _ = w._build(EF[i], ES[i], C[i], atr, "M5", 5,
|
||||||
|
htf_trend=0, h1_trend=H1[i], angle=AD[i]) # Filter AUS
|
||||||
|
sig = rec.get("signal")
|
||||||
|
if sig in ("LONG", "SHORT"):
|
||||||
|
d = 1 if sig == "LONG" else -1
|
||||||
|
if pend is None or pend["dir"] != d:
|
||||||
|
pend = {"dir": d, "level": C[i] + d * 0.3 * atr,
|
||||||
|
"invalid": C[i] - d * 0.3 * atr, "confirmed": False, "bar": i}
|
||||||
|
if not pend["confirmed"]:
|
||||||
|
broke = (C[i] >= pend["level"]) if d > 0 else (C[i] <= pend["level"])
|
||||||
|
if broke:
|
||||||
|
pend["confirmed"] = True
|
||||||
|
else:
|
||||||
|
if ((C[i] <= pend["invalid"]) if d > 0 else (C[i] >= pend["invalid"])) \
|
||||||
|
or (i - pend["bar"]) > 12:
|
||||||
|
pend = {"dir": d, "level": C[i] + d * 0.3 * atr,
|
||||||
|
"invalid": C[i] - d * 0.3 * atr,
|
||||||
|
"confirmed": False, "bar": i}
|
||||||
|
i += 1; continue
|
||||||
|
rec = w._room_gate(rec, C[i])
|
||||||
|
if rec.get("signal") in ("LONG", "SHORT"):
|
||||||
|
r, xj = simulate(C[i], d, atr, H, L, C, i + 1, LIVE,
|
||||||
|
timestop=True, use_tp=True, ret_bar=True)
|
||||||
|
R = r - (SP[i] if SP[i] > 0 else 0.0225) / atr
|
||||||
|
terz = "niedrig" if atr < t1 else ("mittel" if atr < t2 else "hoch")
|
||||||
|
haelfte = "H1" if i < mid else "H2"
|
||||||
|
# Hätte der M30-Filter das Signal geblockt?
|
||||||
|
ziel = geblockt if (HTF[i] != 0 and HTF[i] != d) else durchgelassen
|
||||||
|
ziel.setdefault((terz, haelfte), []).append(R)
|
||||||
|
pend = None
|
||||||
|
i = xj + 3
|
||||||
|
continue
|
||||||
|
elif sig == "WARTEN":
|
||||||
|
pend = None
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
print("=" * 92)
|
||||||
|
print(f" M30-FILTER nach VOLA-REGIME — {sym} M5 ({N} Bars, kanonischer Exit, Echtkosten)")
|
||||||
|
print(" GEBLOCKT = Signale, die der Filter live verwirft. Ist ihr ØR positiv,")
|
||||||
|
print(" kostet der Filter in diesem Regime Geld.")
|
||||||
|
print("=" * 92)
|
||||||
|
for terz in ("niedrig", "mittel", "hoch"):
|
||||||
|
print(f"\n ATR-Terzil {terz.upper()}")
|
||||||
|
for h in ("H1", "H2"):
|
||||||
|
print(zeile(f"GEBLOCKT {h}", st(geblockt.get((terz, h)))))
|
||||||
|
for h in ("H1", "H2"):
|
||||||
|
print(zeile(f"durchgelassen {h}", st(durchgelassen.get((terz, h)))))
|
||||||
|
g1 = st(geblockt.get((terz, "H1"))); g2 = st(geblockt.get((terz, "H2")))
|
||||||
|
if g1 and g2:
|
||||||
|
ok = g1["oR"] > 0 and g2["oR"] > 0 and g1["pf"] > 1 and g2["pf"] > 1
|
||||||
|
print(f" → geblockte Signale beidhälftig positiv? "
|
||||||
|
f"{'JA — Filter kostet hier' if ok else 'nein — Filter zu Recht'}")
|
||||||
|
print("\n Regel (vorab): Lockerung nur, wenn die GEBLOCKTEN Signale im hohen Terzil")
|
||||||
|
print(" in BEIDEN Hälften ØR>0 und PF>1 liefern und das mittlere Terzil nicht")
|
||||||
|
print(" widerspricht. Hohe Beweislast: 21 Signal-Eingriffe sind hier gescheitert.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user