Messung: was bleibt vom BRK-Edge ohne Trailing? Ergebnis: kleiner Umbau faellt aus
User-Wunsch "BRK soll auch eroeffnen wenn M15 aktiv ist" + Entscheidung fuer
Variante 1+3 (erst messen, dann grosser Umbau in eigener Sitzung).
WARUM DIESE MESSUNG DIE BAUWEISE ENTSCHEIDET. Der KLEINE Umbau (Pendings nicht
mehr stornieren, open_* nicht mehr bei self.ticket abweisen) waere an einem
Abend gemacht - aber der zweite Trade haette dann nur den Broker-SL. `trader`
haelt EIN Ticket, und Trailing, Breakeven, Time-Stop, Notfall-Stop und
S/R-Close haengen alle daran (29 Stellen in engine.py). Die dokumentierten
Squeeze-Zahlen sind MIT dem kanonischen Exit gemessen; SL-only ist eine andere,
nie gemessene Konfiguration - Deployment-Drift per Konstruktion.
AUFBAU. Echte core.squeeze_scan-Regel, LIVE-Parameter (⚠ ATR-Floor 0,06, nicht
der Modul-Default 0,12), ruhende Stop-Order mit Fill bei BERUEHRUNG, Echtkosten,
80k M5, 2 Halbjahre, 95-%-Bootstrap, feste GETEILTE Einstiegsliste (alle
Varianten auf DENSELBEN Trades - sonst vergleicht man Populationen statt Exits).
⚠ KEINE Exit-Kopie: SL-only entsteht aus LIVE.with_(trail_start=999, be=999) -
der Trade bleibt in Phase "Init" und verlaesst sie nur ueber SL oder max_hold.
Es laeuft derselbe kanonische Code. Eine eigene Schleife waere das fuenfte
Exit-Modell gewesen.
ERGEBNIS (H1 / H2, OeR):
A) kanonisch (heute) -0,145 / +0,036 PF 0,76 / 1,07 WR 40 / 44 %
B) SL only -0,132 / +0,464 PF 0,92 / 1,27 WR 21 / 19 %
C) SL + Time-Stop -0,157 / +0,138 PF 0,87 / 1,12
D) SL + TP -0,188 / +0,073 PF 0,86 / 1,06
E) SL + Time + TP -0,212 / +0,064 PF 0,81 / 1,06
KEINE Variante erfuellt die vorab fixierte Regel (beidhaelftig OeR > 0 UND
PF > 1 UND 95-%-KI ohne die Null). H1 ist durchgehend negativ, und ALLE zehn
Intervalle enthalten die Null. Die vorab fixierte Antwort lautet damit:
GROSSER Umbau, nicht kleiner. Das deckt sich mit der Entscheidung des Users.
⚠⚠ GRENZE, die nicht ueberspielt werden darf: die KONTROLLE reproduziert NICHT.
Variante A liefert -0,145/+0,036, dokumentiert sind +0,124/+0,292
(backtest_squeeze_touchfill.py), und meine Einstiegszahl ist 362/559 gegen
1092/1807. Meine Auswahl ist also deutlich strenger. Damit sind die
ABSOLUTWERTE unbrauchbar; belastbar ist allein der INTERNE Vergleich, weil alle
Varianten auf derselben Entry-Liste laufen und sich nur im Exit unterscheiden.
Derselbe Vorbehalt wie am 13.08. bei backtest_squeeze_srclose2.py.
⚠⚠ UND DER INTERNE VERGLEICH IST KONTRAINTUITIV: SL-only sieht im OeR NICHT
schlechter aus (H1 -0,132 gegen -0,145, H2 +0,464 gegen +0,036). Das ist aber
kein Argument fuer den kleinen Umbau, sondern ein Varianz-Befund:
- Trefferquote faellt 40 -> 21 % bzw. 44 -> 19 %. Vier von fuenf Trades laufen
in den vollen -2R-Stop.
- Die KI-Breite explodiert: [-0,108 .. +1,115] gegen [-0,081 .. +0,148] beim
kanonischen Exit - rund das Vierfache.
Der Ertrag haengt an wenigen sehr langen Laeufern (max_hold 200 Bars = 16,7 h
ohne jede Absicherung). Unter 40-%-Margin-Sizing ist "81 % der Trades verlieren
volle 2R" eine ganz andere Risikoklasse als die gemessene - und statistisch
nicht von null zu unterscheiden.
FAZIT: nichts gebaut, nichts umgeschaltet. Der grosse Umbau (Trailing je Ticket)
bleibt der Weg, und er gehoert in eine eigene Sitzung mit FLACHEM Konto - ein
halb fertiger Ticket-Umbau laesst den Schutz-Stack an der falschen der beiden
Positionen haengen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a07ad942c1
commit
3e4a181c00
@@ -0,0 +1,164 @@
|
|||||||
|
"""Was bleibt vom Squeeze-Edge, wenn NUR der Broker-SL schuetzt? (2026-08-19)
|
||||||
|
|
||||||
|
FRAGE (User: "BRK soll auch eroeffnen wenn M15 aktiv ist"). Damit BRK parallel
|
||||||
|
zu einer anderen Position laufen kann, braucht es einen zweiten Positions-Slot.
|
||||||
|
Der kleine Umbau (Pendings nicht mehr stornieren, `open_*` nicht mehr bei
|
||||||
|
`self.ticket` abweisen) waere an einem Abend gemacht -- ABER der zweite Trade
|
||||||
|
haette dann nur den **Broker-SL**: `trader` haelt EIN Ticket (`self.ticket`), und
|
||||||
|
Trailing, Breakeven, Time-Stop, Notfall-Stop und S/R-Close haengen alle daran
|
||||||
|
(29 Stellen in `engine.py`).
|
||||||
|
|
||||||
|
⚠⚠ DAS IST NICHT NUR EIN SCHUTZ-, SONDERN EIN EDGE-PROBLEM. Die dokumentierten
|
||||||
|
Squeeze-Zahlen (OeR +0,14…+0,23) sind MIT dem kanonischen Exit gemessen. Der
|
||||||
|
Squeeze lebt von LAEUFERN (43 % Treffer, Ertrag im Tail) -- ein reiner SL-Exit
|
||||||
|
ist eine ANDERE, nie gemessene Konfiguration. Sie ohne Messung zu betreiben
|
||||||
|
waere Deployment-Drift per Konstruktion (neunmal dokumentiert).
|
||||||
|
|
||||||
|
DIESE MESSUNG ENTSCHEIDET DIE BAUWEISE:
|
||||||
|
traegt SL-only -> der kleine Umbau reicht, BRK kann sofort parallel laufen
|
||||||
|
traegt es nicht -> es braucht das Trailing JE TICKET, also den grossen Umbau
|
||||||
|
|
||||||
|
AUFBAU. Echte `core.squeeze_scan`-Regel (kein Nachbau), LIVE-Parameter, ruhende
|
||||||
|
Stop-Order am Level mit Fill bei BERUEHRUNG, Echtkosten aus dem Bar-Spread,
|
||||||
|
2 Halbjahre, 95-%-Bootstrap je Zelle.
|
||||||
|
|
||||||
|
⚠ KEINE Exit-Kopie: die SL-only-Variante entsteht aus `LIVE.with_(...)`, indem
|
||||||
|
Trail-Start und Breakeven unerreichbar gesetzt werden -- der Trade bleibt damit
|
||||||
|
in Phase "Init" und verlaesst sie nur ueber den SL oder `max_hold`. Es laeuft
|
||||||
|
also derselbe kanonische Code, nur ohne die Phasen. Eine eigene Schleife waere
|
||||||
|
das fuenfte Exit-Modell gewesen.
|
||||||
|
|
||||||
|
⚠ Zwischenstufen bewusst mitgemessen, weil die Entscheidung nicht binaer ist:
|
||||||
|
(b) SL + Time-Stop -- der Time-Stop haengt NICHT am Trailing-Zustand,
|
||||||
|
er koennte also billig nachgeruestet werden
|
||||||
|
(c) SL + Initial-TP -- `place_stop` koennte einen TP mitschicken
|
||||||
|
(d) SL + Time-Stop + TP -- was ohne echtes Trailing maximal erreichbar ist
|
||||||
|
|
||||||
|
REGEL VORAB FIXIERT: SL-only gilt als tragfaehig, wenn es in BEIDEN Haelften
|
||||||
|
OeR > 0 UND PF > 1 liefert und das 95-%-KI die Null ausschliesst. Alles darunter
|
||||||
|
heisst: grosser Umbau, nicht kleiner.
|
||||||
|
|
||||||
|
Aufruf: python backtest_brk_slonly.py [bars]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
from backtest_cost_gate import kennzahlen
|
||||||
|
from core.exit_model import LIVE, simulate
|
||||||
|
from core.squeeze_scan import atr_series, scan
|
||||||
|
|
||||||
|
# LIVE-Parameter -- ⚠ NICHT die Modul-Defaults. Der ATR-Floor wurde gemessen von
|
||||||
|
# 0,12 auf 0,06 gesenkt; ihn zu vergessen erzeugt eine ANDERE Population (real
|
||||||
|
# passiert am 13.08.: H1 127 gegen H2 483 Einstiege).
|
||||||
|
BOX_N, K, MULT, ATRMIN = 10, 0.1, 2.5, 0.06
|
||||||
|
|
||||||
|
# SL-only: Trail-Start und Breakeven unerreichbar -> der Trade bleibt in Phase
|
||||||
|
# "Init" und verlaesst sie nur ueber den Initial-SL oder `max_hold`.
|
||||||
|
SL_ONLY = LIVE.with_(trail_start=999.0, be=999.0)
|
||||||
|
|
||||||
|
VARIANTEN = [
|
||||||
|
("A) kanonisch (heute)", LIVE, True, True),
|
||||||
|
("B) SL only", SL_ONLY, False, False),
|
||||||
|
("C) SL + Time-Stop", SL_ONLY, True, False),
|
||||||
|
("D) SL + TP", SL_ONLY, False, True),
|
||||||
|
("E) SL + Time + TP", SL_ONLY, True, True),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def einstiege(H, L, C, A, SP, lo, hi):
|
||||||
|
"""Squeeze-Ausbrueche in [lo,hi) -- nur die KOMPRIMIERTEN."""
|
||||||
|
out = []
|
||||||
|
scan(H, L, C, A, SP, 1.0, lo, hi, MULT,
|
||||||
|
box_n=BOX_N, k=K, atr_min=ATRMIN,
|
||||||
|
exit_fn=lambda *a: 0.0,
|
||||||
|
on_entry=lambda j, d, lvl, atr, komp: (
|
||||||
|
out.append((j, d, lvl, atr)) if komp else None))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def lauf(ein, H, L, C, SP, ende, params, timestop, use_tp):
|
||||||
|
rs = []
|
||||||
|
for j, d, lvl, atr in ein:
|
||||||
|
# ruhende Stop-Order am Level: fuellt bei der ersten BERUEHRUNG
|
||||||
|
f = None
|
||||||
|
for x in range(j, min(j + 13, ende)):
|
||||||
|
if (H[x] >= lvl) if d > 0 else (L[x] <= lvl):
|
||||||
|
f = x
|
||||||
|
break
|
||||||
|
if f is None or f + 1 >= ende:
|
||||||
|
continue
|
||||||
|
r = simulate(lvl, d, atr, H, L, C, f + 1, params,
|
||||||
|
timestop=timestop, use_tp=use_tp)
|
||||||
|
rs.append(r - ((SP[f] if SP[f] > 0 else 0.0225) / atr))
|
||||||
|
return rs
|
||||||
|
|
||||||
|
|
||||||
|
def zeile(lbl, k1, k2):
|
||||||
|
def f(k):
|
||||||
|
if not k:
|
||||||
|
return " --"
|
||||||
|
ci = k.get("ci")
|
||||||
|
m = ""
|
||||||
|
if ci:
|
||||||
|
m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1])
|
||||||
|
+ ("" if ci[0] * ci[1] > 0 else " ~0"))
|
||||||
|
return ("n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f} SR={:+7.1f}{}"
|
||||||
|
.format(k["n"], k["wr"], k["oer"], k["pf"], k["sr"], m))
|
||||||
|
print(" {:<22} H1 {}".format(lbl, f(k1)))
|
||||||
|
print(" {:<22} H2 {}".format("", f(k2)))
|
||||||
|
ok = (k1 and k2 and k1["oer"] > 0 and k2["oer"] > 0
|
||||||
|
and k1["pf"] > 1 and k2["pf"] > 1
|
||||||
|
and (k1.get("ci") or (0, 0))[0] > 0 and (k2.get("ci") or (0, 0))[0] > 0)
|
||||||
|
print(" {:<22} {}".format("", "✅ Regel erfuellt" if ok
|
||||||
|
else "❌ Regel NICHT erfuellt"))
|
||||||
|
print("")
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1
|
||||||
|
|
||||||
|
print("=" * 96)
|
||||||
|
print(" BRK OHNE TRAILING -- was bleibt vom Edge? {} M5 ({} Bars)".format(sym, N))
|
||||||
|
print(" Fill bei BERUEHRUNG · Echtkosten · 2 Halbjahre · 95-%-KI")
|
||||||
|
print("=" * 96)
|
||||||
|
|
||||||
|
# ⚠ Feste, GETEILTE Einstiegslisten: alle Varianten laufen auf DENSELBEN
|
||||||
|
# Trades, sonst vergleicht man Populationen statt Exits (der Fehler, der
|
||||||
|
# am 31.07. in `backtest_exit_combo.py` korrigiert wurde).
|
||||||
|
e1 = einstiege(H, L, C, A, SP, 0, mid)
|
||||||
|
e2 = einstiege(H, L, C, A, SP, mid, ende)
|
||||||
|
print(" Einstiege: H1 {} H2 {}\n".format(len(e1), len(e2)))
|
||||||
|
|
||||||
|
erg = {}
|
||||||
|
for lbl, par, ts, tp in VARIANTEN:
|
||||||
|
k1 = kennzahlen(lauf(e1, H, L, C, SP, ende, par, ts, tp))
|
||||||
|
k2 = kennzahlen(lauf(e2, H, L, C, SP, ende, par, ts, tp))
|
||||||
|
erg[lbl] = (k1, k2, zeile(lbl, k1, k2))
|
||||||
|
|
||||||
|
print(" " + "=" * 92)
|
||||||
|
a1, a2, _ = erg["A) kanonisch (heute)"]
|
||||||
|
b1, b2, bok = erg["B) SL only"]
|
||||||
|
if a1 and b1:
|
||||||
|
print(" Preis des fehlenden Trailings: H1 {:+.3f} R H2 {:+.3f} R je Trade"
|
||||||
|
.format(b1["oer"] - a1["oer"], b2["oer"] - a2["oer"]))
|
||||||
|
print("")
|
||||||
|
print(" LESART: besteht B) die Regel, reicht der KLEINE Umbau (Pendings")
|
||||||
|
print(" nicht mehr stornieren). Sonst braucht es das Trailing JE TICKET.")
|
||||||
|
print(" " + "=" * 92)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user