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
|
Continuation = breakeven-negativ (−0,011/−0,005, Momentum-Klasse). Damit ist auch
|
||||||
die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie
|
die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie
|
||||||
TF-Ampel/Squeeze), steuert nichts.
|
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
|
- **⚠⚠ 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 —
|
Vorhersagen seit 23.07.): DAS MODELL HÄLT LIVE NICHT, WAS DER BACKTEST VERSPRACH —
|
||||||
B5-Fall.** Fällige Messung aus dem Reminder, jetzt erledigt.
|
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