From a172353ceed263d563960d08c94d988d60b9d04f Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Tue, 4 Aug 2026 13:10:57 +0200 Subject: [PATCH] =?UTF-8?q?Fibonacci-Retracements=20gemessen=20=E2=80=94?= =?UTF-8?q?=20verworfen=20(21.=20Signal-Eingriff)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anlass: USOIL-Kommentar "ein Fibonacci-Retracement sollte den Kurs auf 79 $ druecken". Fib-Level waren im Projekt nie gemessen worden. Aufbau wie beim P(break)-Training: Schwuenge aus M15-Pivots, Beruehrung <=0,15xATR, Ausgang +/-0,5xATR in 12 M5-Bars, 80k Bars, 2 Halbjahre, ZWEI Kontrollgruppen. Haltequote: H1 H2 Fibonacci 54,3 % 54,5 % (a) Zufalls-Level 55,7 % 53,8 % (b) M30-Pivots 56,6 % 55,4 % Handelbarkeit (Bounce, kanonischer Exit, Echtkosten): H1 OR -0,168 / PF 0,71 H2 OR -0,082 / PF 0,85 Der aufschlussreichste Wert ist die Zufalls-Kontrolle: ~55 % Haltequote. Ein BELIEBIGES Level haelt in diesem Markt in gut der Haelfte der Faelle - das ist die Basisrate kurzfristiger Mean-Reversion, keine Eigenschaft von Fibonacci. Damit ist auch das Selbsterfuellungs-Argument entkraeftet: dann muesste Fib ueber der Zufallslinie liegen; es liegt darunter bzw. gleichauf. Bestaetigt umgekehrt die Struktur-These: M30-Pivots - Stellen, an denen real gehandelt wurde - halten in BEIDEN Haelften besser als beide gerechneten Linien. Dieselbe Trennlinie wie beim Regressionskanal und beim P(break)-Modell: "Linie im Chart" ist nicht "Struktur im Markt". Zur konkreten Aussage: 79,00 ist beim aktuellen M15-Schwung (78,808 -> 82,680) gar kein Fib-Level, sondern ein Retracement von 95 %. Die naechsten echten Marken sind 80,29 (61,8 %) und 79,64 (78,6 %). Der kalibrierte Kegel legt 79,00 in allen drei Horizonten ausserhalb des 80-%-Bandes. Co-Authored-By: Claude Opus 5 --- .gitignore | 2 + CLAUDE.md | 27 ++++++ backtest_fibonacci.py | 218 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 backtest_fibonacci.py diff --git a/.gitignore b/.gitignore index f4137c3..6e4da7e 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ run_all.err rerun_rejected.* gated.log gated.err +fib.log +fib.err diff --git a/CLAUDE.md b/CLAUDE.md index 5659a7a..3a42df2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1040,6 +1040,33 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: eine Aussage. **Kein Einbau in die Gesamtempfehlung. 16. verworfener Signal- Eingriff.** ⚠ Die DXY-Korrelations-Regel wurde bewusst NICHT erneut getestet — die Inter-Market-Klasse ist 2026-07-19 gemessen durchgefallen. +- **Fibonacci-Retracements = VERWORFEN (gemessen `backtest_fibonacci.py`, 2026-08-04, + User-Frage zu einem USOIL-Kommentar „ein Fibonacci-Retracement sollte den Kurs auf + 79 $ drücken"):** Fib-Level waren im Projekt bis dahin **nie gemessen** — weder als + S/R noch als Einstiegszone. Aufbau wie beim P(break)-Training: Schwünge aus + **M15**-Pivots (die genannte Zeitebene), Berührung ≤0,15×ATR, Ausgang ±0,5×ATR in + 12 M5-Bars, 80k Bars, 2 Halbjahre — mit ZWEI Kontrollgruppen. + | Haltequote | H1 | H2 | + |---|---|---| + | **Fibonacci** | 54,3 % | 54,5 % | + | (a) Zufalls-Level gleicher Distanz | **55,7 %** | 53,8 % | + | (b) M30-Pivots (Live-Quelle) | **56,6 %** | **55,4 %** | + **Handelbarkeit (Bounce am Level, kanonischer Exit, Echtkosten):** H1 ØR −0,168 / + PF 0,71 · H2 ØR −0,082 / PF 0,85 — beidhälftig negativ. + ⚠⚠ **Der aufschlussreichste Wert ist die Zufalls-Kontrolle: ~55 % Haltequote.** + Ein BELIEBIGES Level hält in diesem Markt in gut der Hälfte der Fälle — das ist die + Basisrate kurzfristiger Mean-Reversion, keine Eigenschaft von Fibonacci. Die Frage + war nie „hält das Level?", sondern „hält es BESSER als eine willkürliche Linie?" — + und Fib tut das nicht. **Damit ist auch das Selbsterfüllungs-Argument entkräftet** + („so viele schauen darauf, also wirkt es"): dann müsste Fib über der Zufallslinie + liegen; es liegt darunter bzw. gleichauf. + ✅ **Bestätigt umgekehrt die Struktur-These:** M30-Pivots — Stellen, an denen real + gehandelt wurde — halten in BEIDEN Hälften besser als beide gerechneten Linien. + Dieselbe Trennlinie wie beim Regressionskanal (`backtest_structure_tf.py`, 24/24 + negativ) und beim P(break)-Modell: **„Linie im Chart" ist nicht „Struktur im Markt".** + **21. verworfener Signal-Eingriff.** ⚠ Grenzen: eine Schwung-Definition (M15-Pivots + k=3, ≥2×ATR); die Pivot-Kontrolle ist dünner (n=173/224). Der Vergleich ist aber + design-intern, die relative Aussage damit robuster als die Absolutwerte. - **„Erst messen, dann umbauen":** Strategie-Änderungen per Backtest belegen, nicht raten. Gegen-Trend verliert; nicht bei WARTEN handeln. **Stichproben ~28 Tage** — ein Trend-Regime; Befunde später auf mehr/andere History gegenprüfen. diff --git a/backtest_fibonacci.py b/backtest_fibonacci.py new file mode 100644 index 0000000..e72d4c3 --- /dev/null +++ b/backtest_fibonacci.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +"""Halten Fibonacci-Retracements? (2026-08-04) + +Anlass: Kommentar zu USOIL — „Ein Fibonacci-Retracement sollte den Kurs vor dem +nächsten Aufwärtsimpuls auf 79 $ drücken." Fibonacci-Level sind in diesem Projekt +**nie gemessen worden**, weder als S/R noch als Einstiegszone. Diese Lücke wird +hier geschlossen. + +⚠ Ausgangslage ist zwiespältig, und das ist der Grund für die Kontrollgruppen: +`backtest_structure_tf.py` hat gezeigt, dass **gerechnete Linien** (Regressions- +kanal) NICHT halten — 24/24 Zellen negativ, weil hinter einer gerechneten Linie +keine Orders liegen. Ein Fib-Level ist ebenfalls gerechnet. **ABER** Fibonacci +wird von sehr vielen Marktteilnehmern beobachtet — das Selbsterfüllungs-Argument, +das beim Regressionskanal fehlt. Ob es trägt, entscheidet der Vergleich gegen: + (a) ZUFALLS-Level im selben Abstand → misst „ist Fib besser als irgendein Level?" + (b) M30-PIVOTS (was der Bot heute nutzt) → misst „ist Fib besser als Struktur?" + +Zwei Teile: + TEIL A — HALTEQUOTE: prallt der Kurs an einem Fib-Level häufiger ab als an den + Kontroll-Leveln? Barriere-Rennen wie beim P(break)-Training + (±0,5×ATR innerhalb von 12 M5-Bars, Timeout zählt als Abprall). + TEIL B — HANDELBARKEIT: Bounce-Einstieg am Level, kanonischer Live-Exit, + Echtkosten, 2 Halbjahre. + +Schwünge stammen aus **M15**-Pivots (die im Kommentar genannte Zeitebene), +Berührungen und Trades laufen auf **M5** (dort ist der Live-Exit kalibriert). + +⚠ Regel VORAB fixiert: Fib gilt nur als tragfähig, wenn die Haltequote in BEIDEN +Hälften über BEIDEN Kontrollen liegt UND Teil B in beiden Hälften ØR>0 mit PF>1 +liefert. Ein einzelner besserer Wert zählt nicht. + +Aufruf: python backtest_fibonacci.py [bars] +""" +import random +import sys + +import MetaTrader5 as mt5 + +from core.analysis import FIB_RATIOS +from core.exit_model import LIVE, simulate + +_ATRMIN = 0.06 +_TOUCH = 0.15 # Berührung: |Kurs − Level| ≤ x×ATR (wie P(break)-Training) +_BARRIER = 0.5 # Ausgang: ±x×ATR (wie P(break)-Training) +_WIN = 12 # Fenster in M5-Bars (= 60 min, wie P(break)-Training) +_PIVK = 3 # Pivot-Fenster +_MIN_SWING_ATR = 2.0 # Schwung muss mind. so groß sein, sonst kein Retracement +_COOL = 6 + + +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 _m15_swings(H, L, n): + """M15-Pivots (k=3) → Liste (bekannt_ab_m5_index, tief, hoch, richtung). + + Kausal: ein M15-Pivot bei Bar j ist erst bei j+_PIVK bestätigt; der + entsprechende M5-Index ist (j+_PIVK)*3. + """ + HH = [max(H[i:i + 3]) for i in range(0, n - 2, 3)] + LL = [min(L[i:i + 3]) for i in range(0, n - 2, 3)] + piv = [] + for j in range(_PIVK, len(HH) - _PIVK): + if HH[j] == max(HH[j - _PIVK:j + _PIVK + 1]): + piv.append((j, HH[j], "H")) + elif LL[j] == min(LL[j - _PIVK:j + _PIVK + 1]): + piv.append((j, LL[j], "L")) + swings = [] + for a, b in zip(piv, piv[1:]): + if a[2] == b[2]: + continue + bekannt = (b[0] + _PIVK) * 3 # erst dann im M5-Index nutzbar + if a[2] == "L": + swings.append((bekannt, a[1], b[1], +1)) # Aufwärts-Schwung + else: + swings.append((bekannt, b[1], a[1], -1)) # Abwärts-Schwung + return swings + + +def _aktiver_swing(swings, idx, i): + """Letzter Schwung, der bei M5-Bar i bereits bekannt ist.""" + while idx + 1 < len(swings) and swings[idx + 1][0] <= i: + idx += 1 + return idx + + +def _ausgang(H, L, i, level, d_app, atr): + """Barriere-Rennen. d_app = Richtung, aus der der Kurs kam (+1 = von oben). + → 'hold' (prallt zurück) oder 'break' (läuft durch). Timeout = hold.""" + zurueck = level + d_app * _BARRIER * atr + durch = level - d_app * _BARRIER * atr + for j in range(i + 1, min(i + 1 + _WIN, len(H))): + if d_app > 0: + if H[j] >= zurueck: + return "hold" + if L[j] <= durch: + return "break" + else: + if L[j] <= zurueck: + return "hold" + if H[j] >= durch: + return "break" + return "hold" + + +def _quote(res): + n = len(res) + return (n, 100.0 * sum(1 for x in res if x == "hold") / n) if n else (0, 0.0) + + +def main(): + n_req = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 + random.seed(20260804) + 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] + A = _atr_series(H, L, C) + N = len(C); mid = N // 2 + swings = _m15_swings(H, L, N) + # M30-Pivots als Kontrolle (b) — dieselbe Quelle, die der Bot live nutzt + HH30 = [max(H[i:i + 6]) for i in range(0, N - 5, 6)] + LL30 = [min(L[i:i + 6]) for i in range(0, N - 5, 6)] + p30 = [] + for j in range(_PIVK, len(HH30) - _PIVK): + if HH30[j] == max(HH30[j - _PIVK:j + _PIVK + 1]): + p30.append(((j + _PIVK) * 6, HH30[j])) + elif LL30[j] == min(LL30[j - _PIVK:j + _PIVK + 1]): + p30.append(((j + _PIVK) * 6, LL30[j])) + + print("=" * 94) + print(f" FIBONACCI-RETRACEMENTS — {sym} · Schwünge aus M15, Berührung/Trade auf M5") + print(f" {N} Bars · 2 Halbjahre · Berührung ≤{_TOUCH}×ATR · Ausgang ±{_BARRIER}×ATR " + f"in {_WIN} Bars") + print(f" Kontrollen: (a) Zufalls-Level gleicher Distanz (b) M30-Pivots (Live-Quelle)") + print("=" * 94) + + for lbl, lo_i, hi_i in (("H1 (alt)", 0, mid), ("H2 (neu)", mid, N - _WIN - 2)): + res_fib, res_rnd, res_piv = [], [], [] + trades = [] + letzte = {} + si = 0; pi = 0 + i = max(lo_i, 200) + while i < hi_i: + atr = A[i] + if not atr or atr < _ATRMIN: + i += 1; continue + si = _aktiver_swing(swings, si, i) + if si >= len(swings) or swings[si][0] > i: + i += 1; continue + _, slo, shi, sdir = swings[si] + if (shi - slo) < _MIN_SWING_ATR * atr: + i += 1; continue + while pi + 1 < len(p30) and p30[pi + 1][0] <= i: + pi += 1 + # --- Fib-Level des aktiven Schwungs + for r in FIB_RATIOS: + lvl = shi - (shi - slo) * r if sdir > 0 else slo + (shi - slo) * r + if abs(C[i] - lvl) > _TOUCH * atr: + continue + key = ("fib", round(lvl, 2)) + if i - letzte.get(key, -999) < _COOL: + continue + letzte[key] = i + d_app = 1 if C[i - 1] > lvl else -1 + res_fib.append(_ausgang(H, L, i, lvl, d_app, atr)) + # TEIL B: Bounce-Trade zurück in die Anlaufrichtung + d = d_app + cost = (SP[i] if SP[i] > 0 else 0.0225) / atr + r_, _xj = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, + timestop=True, use_tp=True, ret_bar=True) + trades.append(r_ - cost) + # --- Kontrolle (a): Zufalls-Level im selben Abstandsbereich + lvl_r = C[i] + random.uniform(-_TOUCH, _TOUCH) * atr + key = ("rnd", i // _COOL) + if key not in letzte: + letzte[key] = i + d_app = 1 if C[i - 1] > lvl_r else -1 + res_rnd.append(_ausgang(H, L, i, lvl_r, d_app, atr)) + # --- Kontrolle (b): M30-Pivot + if pi < len(p30): + lvl_p = p30[pi][1] + if abs(C[i] - lvl_p) <= _TOUCH * atr: + key = ("piv", round(lvl_p, 2)) + if i - letzte.get(key, -999) >= _COOL: + letzte[key] = i + d_app = 1 if C[i - 1] > lvl_p else -1 + res_piv.append(_ausgang(H, L, i, lvl_p, d_app, atr)) + i += 1 + + print(f"\n {lbl}") + for name, res in (("Fibonacci-Level", res_fib), + ("(a) Zufalls-Level", res_rnd), + ("(b) M30-Pivots", res_piv)): + c, q = _quote(res) + print(f" {name:<20} n={c:>6} Haltequote {q:5.1f} %") + if trades: + nt = len(trades); s = sum(trades) + up = sum(x for x in trades if x > 0); dn = -sum(x for x in trades if x < 0) + print(f" TEIL B Bounce-Trade n={nt:>6} WR " + f"{100*sum(1 for x in trades if x>0)/nt:4.1f} % ØR {s/nt:+.3f} " + f"PF {(up/dn if dn else 9.99):4.2f} ΣR {s:+.0f}") + print("\n Regel (vorab): tragfähig nur, wenn die Haltequote in BEIDEN Hälften über") + print(" BEIDEN Kontrollen liegt UND Teil B beidhälftig ØR>0 mit PF>1 liefert.") + print() + + +if __name__ == "__main__": + main()