Wiedereinstieg nach S/R-Close: 2-Halbjahres-Backtest faellt durch
Nachfolger des Live-Laufs (n=14, von einem Crash-Tag getragen). 80k M5-Bars, live-treu: Level ueber core.sr_levels.linien (ph/pl getrennt geclustert), P(break) mit den echten _PB_*-Gewichten aus core.engine, Bruch als Barrieren-Rennen, Wiedereinstieg als ruhende Stop-Order mit Fill bei Beruehrung, kanonischer Exit. Bruchquote 32-45 % - nahe der Basisrate ~38 %, die Plausibilitaetspruefung besteht. Ergebnis: die vorab fixierte Regel ist in keiner Zeile erfuellt. Bei 0,3xATR ist der OeR beidhaelftig positiv (+0,119/+0,196), aber H1s Intervall enthaelt die Null und der Nachbar 0,5 dreht H1 ins Negative. Keine Parameter-Robustheit. Fairerweise: zwei gleichgerichtete Haelften sind mehr als eine - "KI enthaelt 0" heisst nicht widerlegt, sondern nicht belegt. H1 ist duenn (285 Closes gegen 1.168), der ATR-Floor filtert die Niedrig-Vola- Periode staerker. Kohaerenter Nebenbefund: die ENGERE Bestaetigung ist besser als die weiteren - dieselbe Mechanik wie bei breakout_k (spaeterer Einstieg verschenkt den Anfang des Moves). Urteil: nicht bauen. Wiedervorlage, wenn 0,3 auch in H1 ein Intervall ueber null liefert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9e5ea22e08
commit
3ee2b4114a
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Wiedereinstieg nach einem „falschen" S/R-Close — ueber 2 Halbjahre.
|
||||
|
||||
ANLASS (2026-08-12, User). Der Live-Lauf (`analyze_srclose_reentry.py`, 43 echte
|
||||
Closes) sah gut aus — 71 % Treffer, ØR +0,859, ~+501 € — war aber **nicht
|
||||
entscheidbar**: das 95-%-KI enthielt die Null, und **drei Trades eines einzigen
|
||||
Crash-Tages (04.08.) trugen +530 €, die uebrigen elf −29 €**. Hier dieselbe Frage
|
||||
auf 80k M5-Bars in zwei Halbjahren.
|
||||
|
||||
DIE FRAGE: Das P(break)-Gate schliesst, wenn das Level wahrscheinlich HAELT.
|
||||
In ~38 % der Faelle bricht es doch. Lohnt es, dann wieder einzusteigen?
|
||||
|
||||
METHODE — bewusst LIVE-TREU, nicht nachgebaut:
|
||||
· Level via `build_levels_live` + `_cluster` + `pick_level` aus
|
||||
`backtest_pbreak_retrain` (dieselbe Hysterese wie `_draw_levels`).
|
||||
· P(break) mit den **echten Live-Gewichten** `_PB_MU/_SD/_W` aus `core.engine`
|
||||
— kein Nachbau, damit das Gate hier genau so entscheidet wie im Betrieb.
|
||||
· Schliess-Moment = „am Level" (≤0,15×ATR, wie live `at_level`) UND
|
||||
`p_break < sr_close_pbreak` (live 0,35).
|
||||
· Bruch = **Barrieren-Rennen** `outcome()` (Bestaetigung gegen Ablehnung, wer
|
||||
zuerst; Timeout = Abprall) — dieselbe Definition wie im Modell-TRAINING.
|
||||
⚠ Genau hier lag der Fehler im ersten Live-Lauf: nur EINE Barriere gefragt
|
||||
→ 93 % „Brueche" bei einer Basisrate von 38 %.
|
||||
· Wiedereinstieg = ruhende Stop-Order an der Bestaetigungsmarke, Fuellung bei
|
||||
BERUEHRUNG, **kanonischer Exit** (`core.exit_model.LIVE`), Echtkosten.
|
||||
|
||||
⚠⚠ ENTSCHEIDUNGSREGEL, VORAB FIXIERT:
|
||||
(1) ØR in BEIDEN Haelften > 0, (2) 95-%-Bootstrap-KI schliesst in beiden die
|
||||
Null aus, (3) die Nachbar-Schwellen (0,3 / 0,7×ATR Bestaetigung) kippen nicht.
|
||||
Alle drei muessen erfuellt sein.
|
||||
|
||||
Aufruf: python backtest_srclose_reentry.py [n_bars] (Default 80000)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import sys
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from backtest_pbreak_retrain import (_ATRMIN, _BRK_W, _M30, _TWIN, _ZONE,
|
||||
_atr_series, _ema_series,
|
||||
build_levels_live)
|
||||
from core.engine import _p_break
|
||||
from core.exit_model import LIVE, simulate
|
||||
# ⚠ Clusterung/Auswahl/Hysterese kommen aus dem LIVE-Modul, nicht aus dem
|
||||
# Backtest-Nachbau: `_cluster` dort laeuft ueber den GEMISCHTEN ph+pl-Pool und
|
||||
# erzeugt Ketten-Mega-Cluster — im Live-Code ist genau das als behobener Fehler
|
||||
# dokumentiert. `linien()` clustert ph und pl GETRENNT.
|
||||
from core.sr_levels import halten, linien, naechste
|
||||
|
||||
_SCHWELLE = 0.35 # live `sr_close_pbreak`
|
||||
_BRK_STUFEN = (0.3, 0.5, 0.7)
|
||||
|
||||
|
||||
def ci(v, n=4000, seed=42):
|
||||
if len(v) < 20:
|
||||
return None, None
|
||||
r = random.Random(seed); m = len(v)
|
||||
b = sorted(sum(v[r.randrange(m)] for _ in range(m)) / m for _ in range(n))
|
||||
return b[int(.025 * n)], b[int(.975 * n)]
|
||||
|
||||
|
||||
def lauf(lo, hi, H, L, C, SP, A, EF, ES, LV, brk):
|
||||
"""Alle Schliess-Momente durchgehen; bei bestaetigtem Bruch wieder einsteigen."""
|
||||
zu, gebrochen, Rs = 0, 0, []
|
||||
prev_r = prev_s = None
|
||||
letzt = {} # Dedup: (gerundetes Level, Seite) -> Bar
|
||||
for i in range(lo, min(hi, len(C) - LIVE.max_hold - _BRK_W - 2)):
|
||||
atr = A[i]
|
||||
if not atr or atr < _ATRMIN:
|
||||
continue
|
||||
# ⚠ `build_levels_live` ist nach M30-Bars indiziert, nicht nach M5.
|
||||
m = i // _M30
|
||||
if m >= len(LV):
|
||||
continue
|
||||
lvd = LV[m]
|
||||
px = C[i]
|
||||
lv = linien(lvd["ph"], lvd["pl"], atr) # ph/pl GETRENNT geclustert
|
||||
r_, s_ = naechste(lv, px)
|
||||
r_ = halten(r_, prev_r, px, atr, True)
|
||||
s_ = halten(s_, prev_s, px, atr, False)
|
||||
prev_r, prev_s = r_, s_
|
||||
mom6 = (C[i] - C[i - 6]) / atr
|
||||
mom3 = (C[i] - C[i - 3]) / atr
|
||||
ediff = EF[i] - ES[i]
|
||||
for d, lvl in ((1, r_), (-1, s_)):
|
||||
if lvl is None:
|
||||
continue
|
||||
gap = abs(lvl - px)
|
||||
if gap > _ZONE * atr:
|
||||
continue # nicht am Level
|
||||
k = (round(lvl, 2), d)
|
||||
if i - letzt.get(k, -999) < _TWIN:
|
||||
continue # dieselbe Beruehrung
|
||||
letzt[k] = i
|
||||
wt = 1.0 if ediff * d > 0 else 0.0
|
||||
p = _p_break(mom6 * d, mom3 * d, wt, gap / atr)
|
||||
if p >= _SCHWELLE:
|
||||
continue # Gate haette NICHT geschlossen
|
||||
zu += 1
|
||||
# Barrieren-Rennen mit der jeweils geprueften Bestaetigungsbreite
|
||||
up = lvl + d * brk * atr
|
||||
dn = lvl - d * brk * atr
|
||||
fill = None
|
||||
for j in range(i + 1, min(i + 1 + _BRK_W, len(C))):
|
||||
if (L[j] <= dn) if d > 0 else (H[j] >= dn):
|
||||
break # Level hielt
|
||||
if (H[j] >= up) if d > 0 else (L[j] <= up):
|
||||
fill = j; break
|
||||
if fill is None:
|
||||
continue
|
||||
gebrochen += 1
|
||||
kost = (SP[fill] if SP[fill] > 0 else 0.0225) / atr
|
||||
Rs.append(simulate(up, d, atr, H, L, C, fill + 1, LIVE) - kost)
|
||||
return zu, gebrochen, Rs
|
||||
|
||||
|
||||
def zeig(lbl, zu, gebr, Rs):
|
||||
if not Rs:
|
||||
return f" {lbl:<22}{zu:>7}{gebr:>9} keine Wiedereinstiege"
|
||||
n = len(Rs); w = sum(1 for x in Rs if x > 0)
|
||||
g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0)
|
||||
lo, hi = ci(Rs)
|
||||
cis = f"[{lo:+.3f} … {hi:+.3f}]" if lo is not None else "—"
|
||||
flag = "✅" if (lo is not None and lo > 0) else ("❌" if (hi is not None and hi < 0) else "⚠")
|
||||
return (f" {lbl:<22}{zu:>7}{gebr:>9}{100*gebr/max(1,zu):>7.0f} %"
|
||||
f"{n:>7}{100*w/n:>7.0f} %{sum(Rs)/n:>+9.3f}"
|
||||
f"{(g/v if v else 9.99):>7.2f}{sum(Rs):>+9.1f} {cis:>20} {flag}")
|
||||
|
||||
|
||||
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)
|
||||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 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)
|
||||
EF, ES = _ema_series(C, 12), _ema_series(C, 50)
|
||||
LV = build_levels_live(H, L)
|
||||
mid = len(C) // 2
|
||||
|
||||
print("=" * 118)
|
||||
print(f" WIEDEREINSTIEG NACH S/R-CLOSE — {sym}, {len(C)} M5-Bars, 2 Halbjahre")
|
||||
print(f" Schliess-Moment: am Level (≤{_ZONE}×ATR) UND p_break < {_SCHWELLE}")
|
||||
print(" Bruch = Barrieren-Rennen (Bestaetigung gegen Ablehnung, Timeout = Abprall)")
|
||||
print(" Wiedereinstieg: ruhende Stop-Order, Fill bei Beruehrung, kanonischer Exit")
|
||||
print("=" * 118)
|
||||
print(f"\n {'Bestaetigung':<22}{'Closes':>7}{'Brueche':>9}{'Quote':>8}"
|
||||
f"{'n':>7}{'Treffer':>8}{'ØR':>9}{'PF':>7}{'ΣR':>9} {'95-%-KI':>20}")
|
||||
for brk in _BRK_STUFEN:
|
||||
for lbl, (a, b) in (("H1", (60, mid)), ("H2", (mid, len(C)))):
|
||||
zu, gebr, Rs = lauf(a, b, H, L, C, SP, A, EF, ES, LV, brk)
|
||||
print(zeig(f"{brk:.1f}×ATR {lbl}", zu, gebr, Rs))
|
||||
print()
|
||||
|
||||
print(" " + "-" * 114)
|
||||
print(" ⚠ REGEL (vorab): ØR beidhaelftig > 0 UND beide KI ohne Null UND die")
|
||||
print(" Nachbar-Schwellen kippen nicht. Alle drei muessen erfuellt sein.")
|
||||
print(" ⚠ Nicht modelliert: der Positions-Slot ist waehrend des Wiedereinstiegs")
|
||||
print(" belegt — andere Setups koennen in der Zeit nicht feuern.")
|
||||
print(" ⚠ Die Bruchquote MUSS nahe der Basisrate (~38 %) liegen. Tut sie das")
|
||||
print(" nicht, ist die Bruch-Definition kaputt (so geschehen im Live-Lauf: 93 %).")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user