Kanal-Pullback ueber alle Zeitebenen gemessen — 24/24 negativ
Anlass: User-Beobachtung im M5-Chart "der Kurs folgt dem Marktstrukturkanal und bricht die untere Linie nicht — koennen wir das nutzen?". Die vorhandene Messung (backtest_structure.py, 20.07.) lief NUR auf M30; M5 war ungetestet. Neues Skript backtest_structure_tf.py — bewusst mit dem KANONISCHEN Live-Exit (exit_model.LIVE: Trail 1,0 + Time-Stop + TP) statt der Exit-Kopie der alten Datei (Trail 1,5, kein Time-Stop). Die alte Datei bleibt unveraendert, weil die Migrations-Regel eine Umstellung nur bei identischen Zahlen erlaubt. Getestet: M5/M15/M30 x zwei Lesarten des M5-Kanals (60 Bars = 5 h "was man im Chart sieht" und 360 Bars = 30 h zeitgleich zu M30) x drei Regel-Varianten, 80k Bars je Zeitebene, 2 Haelften, Echtkosten. Ergebnis: 24 Zellen, ALLE negativ, PF 0,66-0,99. - Trefferquote 37-43 % ueber 1200-2400 Beruehrungen je Haelfte: der Kanalrand haelt in WENIGER als der Haelfte der Faelle. Die im Chart sichtbaren "Halter" sind Selektion. - M5 ist die SCHLECHTESTE Zeitebene (-0,201 vs -0,103 auf M30). - Keine Parameterfrage: 60-Bar- und 360-Bar-Kanal liefern identische Zahlen. Legacy-Recheck-Bonus: die M30-Zeile reproduziert den Befund vom 20.07. (-0,103/-0,019 gegen damals -0,05...-0,11) — der alte Schluss war NICHT durch das abweichende Exit-Modell verzerrt. Mechanismus: hinter einer Regressionslinie liegt nichts — keine Order, kein Pivot. Darin liegt der Unterschied zu P(break) auf einem echten S/R-Level. 19. verworfener Signal-Eingriff. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
00c78201fa
commit
5f94917014
@@ -1747,6 +1747,36 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
Continuation = breakeven-negativ (−0,011/−0,005, Momentum-Klasse). Damit ist auch
|
||||
die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie
|
||||
TF-Ampel/Squeeze), steuert nichts.
|
||||
- **NACHTEST über ALLE Zeitebenen = ERNEUT VERWORFEN (`backtest_structure_tf.py`,
|
||||
2026-08-03, User-Beobachtung im M5-Chart: „der Kurs folgt dem Kanal und bricht die
|
||||
untere Linie nicht — können wir das nutzen?"):** Die alte Messung lief NUR auf M30,
|
||||
M5 war streng genommen ungetestet. Jetzt mit dem **kanonischen Live-Exit**
|
||||
(`exit_model.LIVE`: Trail 1,0 + Time-Stop + TP — die alte Datei nutzt eine Exit-KOPIE
|
||||
mit Trail 1,5 OHNE Time-Stop) über M5/M15/M30, zwei Lesarten des M5-Kanals und drei
|
||||
Regel-Varianten, 80k Bars je Zeitebene, 2 Hälften, Echtkosten:
|
||||
| Zeitebene / Kanal | X=0,20 | X=0,30 | +Swing |
|
||||
|---|---|---|---|
|
||||
| **M5, 60 Bars** (=5 h, „was man im Chart sieht") | **−0,201/−0,017** | −0,170/−0,054 | −0,161/−0,082 |
|
||||
| M5, 360 Bars (=30 h, zeitgleich zu M30) | −0,206/−0,017 | −0,179/−0,054 | −0,159/−0,082 |
|
||||
| M15, 120 Bars | −0,032/−0,003 | −0,068/−0,034 | −0,043/−0,033 |
|
||||
| M30, 60 Bars (die ALTE Messung) | −0,103/−0,019 | −0,094/−0,013 | −0,090/−0,024 |
|
||||
**24 Zellen, ALLE negativ**, PF 0,66–0,99. Drei Befunde:
|
||||
(a) **Trefferquote 37–43 %** über 1.200–2.400 Berührungen je Hälfte — der Kanalrand
|
||||
hält in WENIGER als der Hälfte der Fälle. Die im Chart sichtbaren „Halter" sind
|
||||
Selektion: die Durchbrüche sehen hinterher nicht mehr nach Kanal aus.
|
||||
(b) **M5 ist die SCHLECHTESTE Zeitebene** (−0,201 vs. −0,103 auf M30) — feiner
|
||||
auflösen erzeugt mehr Berührungen, und jede kostet Spread.
|
||||
(c) **Keine Parameterfrage:** 60-Bar- und 360-Bar-Kanal liefern praktisch IDENTISCHE
|
||||
Zahlen. Zwei so verschiedene Fenster mit gleichem Ergebnis → es liegt am Mechanismus.
|
||||
✅ **Legacy-Recheck-Bonus:** die M30-Zeile **reproduziert** den Befund vom 20.07.
|
||||
(−0,103/−0,019 gegen damals −0,05…−0,11) — der alte Schluss war NICHT durch das
|
||||
abweichende Exit-Modell verzerrt.
|
||||
**Mechanismus:** Der Kanalrand ist eine Regressionslinie durch die letzten N Closes —
|
||||
dahinter liegt **nichts**: keine Order, kein Pivot, keine Struktur. Er hält nur,
|
||||
solange der Trend ohnehin läuft. Genau darin liegt der Unterschied zu **P(break) auf
|
||||
einem echten S/R-Level** (Pivot = Stelle, an der real gehandelt wurde), das
|
||||
out-of-sample kalibriert ist (AUC 0,65 bzw. 0,68–0,72). „Linie im Chart" ist nicht
|
||||
„Struktur im Markt". **19. verworfener Signal-Eingriff.**
|
||||
- **⚠⚠ P(break) LIVE-AUSWERTUNG (`analyze_pbreak_live.py`, 2026-07-31, 2275 ausgewertete
|
||||
Vorhersagen seit 23.07.): DAS MODELL HÄLT LIVE NICHT, WAS DER BACKTEST VERSPRACH —
|
||||
B5-Fall.** Fällige Messung aus dem Reminder, jetzt erledigt.
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Marktstruktur-Kanal als SIGNAL — über MEHRERE Zeitebenen (2026-08-03).
|
||||
|
||||
Anlass: User-Beobachtung im M5-Chart „der Kurs folgt dem Marktstrukturkanal und
|
||||
bricht die untere Linie nicht — können wir das nutzen?". Die vorhandene Messung
|
||||
(`backtest_structure.py`, 2026-07-20) lief ausschliesslich auf **M30** und fiel
|
||||
in beiden Hälften durch. M5 ist damit streng genommen eine UNGETESTETE Variante.
|
||||
|
||||
Unterschiede zu `backtest_structure.py` — bewusst, und beide relevant:
|
||||
|
||||
1. **Kanonischer Exit** (`core/exit_model.simulate`, LIVE) statt der dortigen
|
||||
Exit-KOPIE. Jene nutzt Trail **1,5** und kennt weder Time-Stop noch TP; live
|
||||
läuft seit 2026-07-31 Trail **1,0** MIT Time-Stop. Für eine NEUE Messung ist
|
||||
der kanonische Kern richtig (die alte Datei bleibt unverändert — sie umzu-
|
||||
stellen wäre laut Migrations-Regel nur erlaubt, wenn die Zahlen gleich blieben).
|
||||
2. **Zeitebene als Parameter**, plus zwei Lesarten des „M5-Kanals":
|
||||
· `bars` — gleiche BAR-Anzahl wie auf M30 (=schnellerer, kürzerer Kanal;
|
||||
das, was man im M5-Chart tatsächlich sieht)
|
||||
· `time` — gleiche ZEIT-Spanne (Fenster ×6 auf M5) = derselbe Kanal, nur
|
||||
feiner aufgelöst
|
||||
Beide zu testen ist Pflicht: kippt das Vorzeichen zwischen ihnen, ist es
|
||||
Rauschen (die ORB-/SMC-Lehre — Nachbar-Parameter müssen mithalten).
|
||||
|
||||
Maßstab wie immer: beidhälftig positiv UND parameter-robust. Vergleich:
|
||||
Squeeze ØR +0,14…+0,23 bei PF>1.
|
||||
|
||||
Aufruf: python backtest_structure_tf.py [bars]
|
||||
"""
|
||||
import sys
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from core.exit_model import LIVE, simulate
|
||||
from core.structure import _pivots, _classify, _channel
|
||||
|
||||
_ATRMIN = 0.06
|
||||
_COOL = 3
|
||||
|
||||
|
||||
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 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 line(lbl, s):
|
||||
if not s:
|
||||
return f" {lbl:<26} — (keine Trades)"
|
||||
return (f" {lbl:<26} 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 run(H, L, C, A, SP, lo, hi, rule, X, look, reg):
|
||||
"""Sequentielle 1-Positions-Sim mit dem KANONISCHEN Live-Exit."""
|
||||
def cost(i, atr):
|
||||
return (SP[i] if SP[i] > 0 else 0.0225) / atr
|
||||
|
||||
Rs = []
|
||||
i = max(lo, look)
|
||||
prev_pos = None
|
||||
while i < hi:
|
||||
atr = A[i]
|
||||
if not atr or atr < _ATRMIN:
|
||||
i += 1; prev_pos = None; continue
|
||||
ch = _channel(C[max(0, i - reg + 1):i + 1], atr)
|
||||
if not ch:
|
||||
i += 1; prev_pos = None; continue
|
||||
pos, cdir = ch["pos"], ch["dir"]
|
||||
d = 0
|
||||
if rule in ("A", "A2"):
|
||||
# frischer Eintritt ins untere (Aufwärtskanal) bzw. obere (Abwärts) Drittel
|
||||
if cdir == "up" and pos <= X and (prev_pos is None or prev_pos > X):
|
||||
d = 1
|
||||
elif cdir == "down" and pos >= 1 - X and (prev_pos is None or prev_pos < 1 - X):
|
||||
d = -1
|
||||
if d != 0 and rule == "A2":
|
||||
pl = _pivots(H[max(0, i - look):i + 1], L[max(0, i - look):i + 1], 3)
|
||||
lab = _classify(pl)
|
||||
recent = [x["type"] for x in lab[-4:]]
|
||||
ups = sum(1 for t in recent if t in ("HH", "HL"))
|
||||
dns = sum(1 for t in recent if t in ("LH", "LL"))
|
||||
strend = ("up" if ups >= 3 and ups > dns else
|
||||
"down" if dns >= 3 and dns > ups else "range")
|
||||
if (d > 0 and strend != "up") or (d < 0 and strend != "down"):
|
||||
d = 0
|
||||
prev_pos = pos
|
||||
if d == 0:
|
||||
i += 1; continue
|
||||
entry = C[i]
|
||||
a = max(atr, _ATRMIN)
|
||||
r, xj = simulate(entry, d, a, H, L, C, i + 1, LIVE,
|
||||
timestop=True, use_tp=True, ret_bar=True)
|
||||
Rs.append(r - cost(i, a))
|
||||
i = xj + _COOL
|
||||
prev_pos = None
|
||||
return Rs
|
||||
|
||||
|
||||
def main():
|
||||
n = 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)
|
||||
point = mt5.symbol_info(sym).point
|
||||
TFS = [("M5", mt5.TIMEFRAME_M5), ("M15", mt5.TIMEFRAME_M15),
|
||||
("M30", mt5.TIMEFRAME_M30)]
|
||||
data = {}
|
||||
for lbl, tf in TFS:
|
||||
b = mt5.copy_rates_from_pos(sym, tf, 0, n)
|
||||
if b is not None and len(b) > 5000:
|
||||
data[lbl] = ([float(x["high"]) for x in b], [float(x["low"]) for x in b],
|
||||
[float(x["close"]) for x in b],
|
||||
[float(x["spread"]) * point for x in b])
|
||||
mt5.shutdown()
|
||||
|
||||
print("=" * 92)
|
||||
print(f" Kanal-Pullback über Zeitebenen — {sym}, kanonischer Live-Exit "
|
||||
f"(SL {LIVE.sl_atr}× · Trail {LIVE.mult}× · BE {LIVE.be}× · Time-Stop), Echtkosten")
|
||||
print(" Regel: im Aufwärtskanal am UNTEREN Drittel long / im Abwärtskanal am OBEREN short")
|
||||
print(" Maßstab: beidhälftig ØR>0 UND PF>1 UND Nachbar-Varianten halten mit")
|
||||
print("=" * 92)
|
||||
|
||||
# (reg, look) je Lesart. M30-Referenz = 60/220 wie in backtest_structure.py.
|
||||
plans = [
|
||||
("M5 Kanal 60 Bars (=5 h, 'was man im M5-Chart sieht')", "M5", 60, 220),
|
||||
("M5 Kanal 360 Bars (=30 h, zeitgleich zum M30-Kanal)", "M5", 360, 1320),
|
||||
("M15 Kanal 120 Bars (=30 h, Nachbar-Zeitebene)", "M15", 120, 440),
|
||||
("M30 Kanal 60 Bars (=30 h, die ALTE Messung)", "M30", 60, 220),
|
||||
]
|
||||
for title, tfl, reg, look in plans:
|
||||
if tfl not in data:
|
||||
print(f"\n {title}: keine Daten"); continue
|
||||
H, L, C, SP = data[tfl]
|
||||
A = _atr_series(H, L, C)
|
||||
N = len(C); mid = N // 2
|
||||
print(f"\n {title} [{N} Bars]")
|
||||
for rl, X, sub in (("A", 0.20, "Kanal-Pullback X=0.20"),
|
||||
("A", 0.30, "Kanal-Pullback X=0.30"),
|
||||
("A2", 0.25, "+Swing-Filter X=0.25")):
|
||||
s1 = st(run(H, L, C, A, SP, 0, mid, rl, X, look, reg))
|
||||
s2 = st(run(H, L, C, A, SP, mid, N - LIVE.max_hold - 1, rl, X, look, reg))
|
||||
print(f" · {sub}")
|
||||
print(line("H1 (alt)", s1))
|
||||
print(line("H2 (neu)", s2))
|
||||
ok = (s1 and s2 and s1["oR"] > 0 and s2["oR"] > 0
|
||||
and s1["pf"] > 1 and s2["pf"] > 1)
|
||||
print(f" → {'ROBUST' if ok else 'fällt durch'}")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user