Die zwei geratenen M15-Schwellen gemessen: beide nicht kalibrierbar

Auftrag: Reichweite (0,5xATR) und Kosten-Schwelle (0,32, von M5) messen statt
raten. 2 Halbjahre, Level-Beruehrungen M15, Fill bei Beruehrung, kanonischer
Exit, Echtkosten, Episoden-entkoppelt, mit Bootstrap je Zelle.

ZUERST EIN ARTEFAKT IM EIGENEN CODE: der erste Lauf stieg sofort zum Level-Preis
ein, auch wenn der Kurs 0,9xATR entfernt stand - die Sim schenkte sich diesen
Abstand. Ergebnis war "je weiter weg, desto besser" (+0,293 im obersten Bucket,
KI ohne Null). Mit Fill bei Beruehrung faellt es auf +0,179. Derselbe Fehler wie
beim Momentum-Test, wo OeR +2,2 den Code entlarvte.

ERGEBNIS: keine der beiden Schwellen ist kalibrierbar.
  Abstand xATR   H1 OeR   H2 OeR
  0,00-0,10      -0,227   +0,100
  0,10-0,20      -0,130   -0,115
  0,20-0,30      -0,306   -0,129
  0,30-0,50      -0,112   +0,216
  0,50-0,75      -0,039   +0,042
  0,75-1,00      -0,040   +0,179
H1 ist ueber ALLE sechs Buckets negativ. Kein Bucket besteht die vorab fixierte
Regel. Beim Kosten-Gate dasselbe: nur H2 0,06-0,10 schliesst die Null aus, H1
liegt dort bei +0,016/-0,040.

Der eigentliche Befund ist nicht "welche Schwelle", sondern: es gibt nichts zu
optimieren. Level-Beruehrungen auf M15 in HTF-Richtung sind in H1 durchgehend
negativ. Eine Schwelle kann keinen Edge erzeugen, den die Grundgesamtheit nicht
hat.

Konsequenz: die 0,5xATR bleiben eine gesetzte Zahl (ehrlich so benannt), das
Kosten-Gate bleibt auf M15 dekorativ - beides jetzt gemessen statt vermutet.
Einzige schwach gestuetzte Richtung: teuer ist schlechter (H2 0,16-0,20 ->
-0,482, KI [-0,949 ... +0,008]).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-08 23:22:53 +02:00
co-authored by Claude Opus 5
parent 6a9b554d54
commit 0c81c4e651
2 changed files with 205 additions and 0 deletions
+41
View File
@@ -4942,6 +4942,47 @@ Modell schliesst keine Trades.
„die Rangfolge ist kaputt" ist **nicht** belegt; belegt ist nur die
Basisraten-/Kalibrierungslücke.
## ⚠⚠ DIE ZWEI GERATENEN M15-SCHWELLEN GEMESSEN — beide NICHT kalibrierbar (2026-08-08)
Auftrag: die Reichweite (0,5×ATR) und die Kosten-Schwelle (0,32, von M5) messen
statt raten. `backtest_m15_gates.py`, 2 Halbjahre, Level-Berührungen auf M15,
Einstieg AM Level in HTF-Richtung, **Fill bei Berührung**, kanonischer Exit,
Echtkosten, Episoden-entkoppelt, mit 95-%-Bootstrap je Zelle.
⚠⚠ **ZUERST EIN ARTEFAKT IM EIGENEN CODE — und es hätte zu einem absurden Rat
geführt.** Der erste Lauf stieg **sofort zum Level-Preis** ein, auch wenn der
Kurs 0,9×ATR entfernt stand — die Sim schenkte sich damit genau diesen Abstand.
Ergebnis: „je WEITER weg vom Level, desto besser" (oberster Bucket +0,293 in H2,
KI schloss die Null aus). Mit **Fill bei Berührung** verschwindet es:
| Abstand | vorher H2 | **mit Fill** |
|---|---|---|
| 0,300,50 | +0,142 | +0,216 |
| 0,751,00 | **+0,293** | **+0,179** |
Derselbe Fehler wie beim Momentum-Test (ØR +2,2 entlarvte den Code).
**② ERGEBNIS: KEINE der beiden Schwellen ist kalibrierbar.**
| Abstand ×ATR | H1 ØR | H2 ØR |
|---|---|---|
| 0,000,10 | 0,227 | +0,100 |
| 0,100,20 | 0,130 | 0,115 |
| 0,200,30 | 0,306 | 0,129 |
| 0,300,50 | 0,112 | **+0,216** |
| 0,500,75 | 0,039 | +0,042 |
| 0,751,00 | 0,040 | +0,179 |
**H1 ist über ALLE sechs Abstands-Buckets negativ.** Kein Bucket besteht die
vorab fixierte Regel (beidhälftig positiv + KI ohne Null). Beim Kosten-Gate
dasselbe: nur H2 0,060,10 schliesst die Null aus (+0,254/+0,307), H1 liegt dort
bei +0,016/0,040.
✅ **Der eigentliche Befund ist nicht „welche Schwelle", sondern: es gibt nichts
zu optimieren.** Die Population selbst trägt in H1 nicht — Level-Berührungen auf
M15 in HTF-Richtung sind dort durchgehend negativ. Eine Schwelle kann keinen
Edge erzeugen, den die Grundgesamtheit nicht hat.
⚠ Konsequenz für die Karte: die 0,5×ATR bleiben eine **gesetzte** Zahl (ehrlich
so benannt), und das Kosten-Gate bleibt auf M15 **dekorativ** — beides ist jetzt
gemessen, nicht mehr nur vermutet. Einzige schwach gestützte Richtung: teuer ist
schlechter (H2 0,160,20 → 0,482, KI [0,949 … +0,008]).
## ⚠⚠ M15-KARTE AN 7 TAGEN GETESTET — zwei eigene Fehler, einer davon fast veröffentlicht (2026-08-08)
**(1) BIAS WAR EIN RICHTUNGSGEBER — korrigiert zum VETO (v=155).** Der Tagestest
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Die zwei GERATENEN Schwellen der M15-Karte messen.
ANLASS (2026-08-08). Der Wochentest hat zwei Schwächen offengelegt:
(1) **Reichweite 0,5×ATR** — von mir GESETZT, nie gemessen. Sie ist zugleich
der EINZIGE Gate, der überhaupt diskriminiert (das Kosten-Gate blockte
0 von 644 Bars, das Veto trägt nur +0,04 R).
(2) **Kosten-Schwelle 0,32** — stammt von M5 (`backtest_realcosts.py`:
Ø 0,265×ATR, Nacht 0,320,50 = Kostenfalle). Auf M15 liegt Spread/ATR
bei ~0,10, die Schwelle bindet dort NIE.
Beides wird hier über 2 Halbjahre auf der ECHTEN Population gemessen:
Level-Berührungen auf M15, Einstieg AM Level in der vom HTF erlaubten Richtung,
kanonischer Exit, Echtkosten, Episoden-entkoppelt (nur die erste Bar je
zusammenhängendem Block — sonst zählt man dieselbe Marktlage vielfach, der
Fehler aus dem ersten Tagestest).
⚠⚠ ENTSCHEIDUNGSREGEL, VORAB:
· Eine Schwelle wird nur übernommen, wenn der Vorteil in BEIDEN Hälften
besteht UND die Nachbar-Schwellen nicht kippen.
· Zusätzlich das 95-%-Bootstrap-Intervall — bei den kleinen Effekten dieser
Population (ØR-Bereich ±0,1) ist ein Punktschätzer keine Aussage. Genau
daran ist heute schon die Slippage-Regel gescheitert.
Aufruf: python backtest_m15_gates.py [n_M15_bars] (Default 26000)
"""
from __future__ import annotations
import random
import sys
import MetaTrader5 as mt5
from core.exit_model import LIVE, simulate
from core.sr_levels import halten, linien, naechste
from test_m15_karte_tag import atr_series, htf_sign, m30_levels
_REICH_STUFEN = (0.1, 0.2, 0.3, 0.5, 0.75, 1.0)
_KOST_STUFEN = (0.06, 0.08, 0.10, 0.13, 0.16, 0.20)
def sammle(H, L, C, SP, A, S30, LV, lo, hi):
"""Alle Level-Berührungen als Episoden — mit gap und Kostenquote je Fall."""
out = []
prev_r = prev_s = None
vor = False
for i in range(lo, min(hi, len(C) - LIVE.max_hold - 2)):
a = A[i]
if not a or not LV[i]:
vor = False; continue
ph, pl = LV[i]
lv = linien(ph, pl, a)
r, s = naechste(lv, C[i])
r = halten(r, prev_r, C[i], a, True)
s = halten(s, prev_s, C[i], a, False)
prev_r, prev_s = r, s
gaps = [(abs(x - C[i]) / a, x) for x in (r, s) if x is not None]
if not gaps:
vor = False; continue
g, lvl = min(gaps)
# „am Level" im weitesten geprueften Sinn — die Schwelle wird UNTEN variiert
nah = g <= max(_REICH_STUFEN)
if nah and not vor and S30[i]:
d = 1 if S30[i] == 1 else -1
out.append({"gap": g, "kost": SP[i] / a, "i": i, "d": d,
"lvl": lvl, "atr": a})
vor = nah
return out
def rechne(eintraege, H, L, C, warte=8):
"""⚠⚠ FILL BEI BERUEHRUNG — nicht sofort zum Level-Preis.
Der erste Entwurf stieg direkt bei `lvl` ein, auch wenn der Kurs 0,9×ATR
entfernt stand. Das schenkt der Sim einen um genau diesen Abstand besseren
Einstieg und erzeugte das Scheinergebnis „weiter weg ist besser" (ØR +0,29
im obersten Abstands-Bucket). Derselbe Fehler wie beim Momentum-Test, wo
ein ØR von +2,2 den Code entlarvte: **eine Zahl, die zu gut ist, ist ein
Befund ueber den Code, nicht ueber den Markt.**
Jetzt liegt die Order `warte` Bars am Level; wird sie nicht beruehrt,
faellt der Fall raus (`R = None`)."""
for e in eintraege:
i, lvl, d = e["i"], e["lvl"], e["d"]
fill = None
for j in range(i + 1, min(i + 1 + warte, len(C) - LIVE.max_hold - 2)):
if L[j] <= lvl <= H[j]:
fill = j; break
e["R"] = (None if fill is None else
simulate(lvl, d, e["atr"], H, L, C, fill + 1, LIVE) - e["kost"])
return [e for e in eintraege if e["R"] is not None]
def ci(Rs, n=3000, seed=42):
if len(Rs) < 20:
return None, None
r = random.Random(seed); m = len(Rs)
b = [sum(Rs[r.randrange(m)] for _ in range(m)) / m for _ in range(n)]
b.sort(); return b[int(.025 * n)], b[int(.975 * n)]
def tabelle(titel, h1, h2, schluessel, stufen, kanten_text):
print(f"\n {titel}")
print(f" {kanten_text:<16}{'H1 n':>7}{'H1 ØR':>9}{'H1 95-%-KI':>22}"
f"{'H2 n':>7}{'H2 ØR':>9}{'H2 95-%-KI':>22}")
for lo_s, hi_s in zip((0,) + stufen[:-1], stufen):
z = []
for teil in (h1, h2):
grp = [e["R"] for e in teil if lo_s <= e[schluessel] < hi_s]
if len(grp) < 20:
z += [str(len(grp)), "", ""]; continue
m = sum(grp) / len(grp); a, b = ci(grp)
z += [str(len(grp)), f"{m:+.3f}",
f"[{a:+.3f}{b:+.3f}]" if a is not None else ""]
print(f" {f'{lo_s:.2f}{hi_s:.2f}':<16}{z[0]:>7}{z[1]:>9}{z[2]:>22}"
f"{z[3]:>7}{z[4]:>9}{z[5]:>22}")
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 26000
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_M15, 0, n)
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)
S30 = htf_sign(H, L, C, 2)
LV = m30_levels(H, L, C)
mid = len(C) // 2
r1 = sammle(H, L, C, SP, A, S30, LV, 60, mid)
r2 = sammle(H, L, C, SP, A, S30, LV, mid, len(C))
h1 = rechne(r1, H, L, C); h2 = rechne(r2, H, L, C)
print(f" Fill-Quote: H1 {len(h1)}/{len(r1)} = {100*len(h1)/max(1,len(r1)):.0f} %"
f" H2 {len(h2)}/{len(r2)} = {100*len(h2)/max(1,len(r2)):.0f} %")
print("=" * 100)
print(f" M15-GATES MESSEN — {sym}, {len(C)} Bars H1 n={len(h1)} H2 n={len(h2)}")
print(" Population: Level-Berührungen, Einstieg AM Level in HTF-Richtung,")
print(" kanonischer Exit, Echtkosten, Episoden-entkoppelt.")
print("=" * 100)
tabelle("① REICHWEITE — ab welchem Abstand trägt der Einstieg noch?",
h1, h2, "gap", _REICH_STUFEN, "Abstand ×ATR")
tabelle("② KOSTEN — ab welcher Quote wird es teuer? (M5-Schwelle 0,32 bindet nie)",
h1, h2, "kost", _KOST_STUFEN, "Spread/ATR")
ks = sorted(e["kost"] for e in h1 + h2)
gs = sorted(e["gap"] for e in h1 + h2)
print(f"\n Verteilungen: Spread/ATR Median {ks[len(ks)//2]:.3f} "
f"(90-Perz. {ks[int(0.9*len(ks))]:.3f}) · "
f"Abstand Median {gs[len(gs)//2]:.2f}×ATR")
print("\n ⚠ Übernommen wird eine Schwelle nur, wenn der Vorteil in BEIDEN")
print(" Hälften besteht, die Nachbarn nicht kippen UND das KI die Null")
print(" ausschliesst. Punktschätzer allein sind auf dieser Population keine Aussage.")
return 0
if __name__ == "__main__":
raise SystemExit(main())