Fibonacci-Retracements gemessen — verworfen (21. Signal-Eingriff)

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 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-04 13:10:57 +02:00
co-authored by Claude Opus 5
parent 3dc56e8571
commit a172353cee
3 changed files with 247 additions and 0 deletions
+2
View File
@@ -57,3 +57,5 @@ run_all.err
rerun_rejected.* rerun_rejected.*
gated.log gated.log
gated.err gated.err
fib.log
fib.err
+27
View File
@@ -1040,6 +1040,33 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
eine Aussage. **Kein Einbau in die Gesamtempfehlung. 16. verworfener Signal- eine Aussage. **Kein Einbau in die Gesamtempfehlung. 16. verworfener Signal-
Eingriff.** ⚠ Die DXY-Korrelations-Regel wurde bewusst NICHT erneut getestet — die Eingriff.** ⚠ Die DXY-Korrelations-Regel wurde bewusst NICHT erneut getestet — die
Inter-Market-Klasse ist 2026-07-19 gemessen durchgefallen. 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 - **„Erst messen, dann umbauen":** Strategie-Änderungen per Backtest belegen, nicht
raten. Gegen-Trend verliert; nicht bei WARTEN handeln. **Stichproben ~28 Tage** — raten. Gegen-Trend verliert; nicht bei WARTEN handeln. **Stichproben ~28 Tage** —
ein Trend-Regime; Befunde später auf mehr/andere History gegenprüfen. ein Trend-Regime; Befunde später auf mehr/andere History gegenprüfen.
+218
View File
@@ -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()