Gegen-Squeeze als Exit zum 3. Mal gemessen: der Mechanismus feuert praktisch nie
Anlass: Trade T=49423144 (-343 EUR) und der Wunsch, der Breakout haette frueher schliessen sollen - nachgeschaerft zu "bei einem Squeeze den SL sofort eng setzen". Beide Varianten gemessen, kanonischer Exit + stop_when-Hook (keine Exit-Kopie), Echtkosten, 2 Halbjahre, zwei Populationen. Der eigentliche Befund steht nicht in den Kennzahlen, sondern in den Ausloesungen: sofort-schliessen feuert 1x von 809 bzw. 1274 Squeeze-Trades, der enge SL 0x. Bei Wellen-Entries 8x bzw. 2-3x. Grund ist konstruktionsbedingt: ein Gegen-Ausbruch verlangt eine NEUE Kompression (Box ueber 10 M5-Bars = 50 min), waehrend die mittlere Haltedauer bei 12-18 min liegt. Der Gegen-Ausbruch trifft also in ~0,1 % (Squeeze) bzw. ~1 % (Welle) der Trades ein - der Fall des Users war einer davon. Damit sind die Kennzahl-Unterschiede Rauschen aus einer Handvoll Faelle, und keine Variante besteht auf beiden Populationen. Die abgeschwaechte Form (enger SL statt Close) adressiert zwar genau den alten Einwand (Gegen-Ausbrueche sind oft Fehlausbrueche), kann ihn aber nicht ausspielen, wenn sie 0-mal ausloest - und waere von einer Bar-Simulation ohnehin ueberschaetzt. Dritte Ablehnung, diesmal mit einer Begruendung, die nicht vom Exit-Modell abhaengt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c8c3e32926
commit
d8a243612a
@@ -1915,6 +1915,40 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
sich erholt hätten. Der 07:38-Fall (LONG lief zum SL während SHORT-Ausbruch) war Varianz.
|
sich erholt hätten. Der 07:38-Fall (LONG lief zum SL während SHORT-Ausbruch) war Varianz.
|
||||||
**Fazit: Squeeze taugt als ENTRY (validiert), NICHT als Gegen-EXIT.** Der einzige
|
**Fazit: Squeeze taugt als ENTRY (validiert), NICHT als Gegen-EXIT.** Der einzige
|
||||||
Speed-Hebel bleibt die schnellere Erkennung (Variante A, ~5 s).
|
Speed-Hebel bleibt die schnellere Erkennung (Variante A, ~5 s).
|
||||||
|
⚠⚠ **DRITTE MESSUNG 2026-08-05 (`backtest_squeeze_exit3.py`) — jetzt mit dem
|
||||||
|
KANONISCHEN Exit, und der Grund für das Nein ist ein anderer als gedacht.**
|
||||||
|
Anlass: Trade T=49423144 (manueller SELL 3,49 @ 75,012) lief in 14 min 1,135 $
|
||||||
|
ins Minus und endete am Initial-SL bei **−343 €**; User-Wunsch: „der Breakout
|
||||||
|
hätte den Trade früher schließen sollen", nachgeschärft zu „bei einem Squeeze den
|
||||||
|
SL sofort eng setzen".
|
||||||
|
⚠ **Was an dem Tag wirklich passierte:** beim Einstieg war der Squeeze **active
|
||||||
|
SHORT** — er stand FÜR den Trade. Die Empfehlung war WARTEN (`block=entry_room`),
|
||||||
|
der Einstieg manuell. Der Gegen-Ausbruch (armed LONG @ 75,729) wurde erst ~3–5 min
|
||||||
|
später aktiv; ein Close dort hätte ≈**−219 €** statt −343 € bedeutet (~124 €).
|
||||||
|
**Gemessen (beide Varianten, kanonischer Exit, Echtkosten, 2 Halbjahre, zwei
|
||||||
|
Populationen — 2.365 Squeeze- und 1.566 Wellen-Entries):**
|
||||||
|
| Variante | Auslösungen H1/H2 |
|
||||||
|
|---|---|
|
||||||
|
| sofort schließen (Squeeze-Entries) | **1 / 1** von 809 bzw. 1.274 |
|
||||||
|
| **enger SL 0,1–0,5×ATR** (Squeeze-Entries) | **0 / 0** |
|
||||||
|
| sofort schließen (Wellen-Entries) | 8 / 8 von 386 bzw. 835 |
|
||||||
|
| enger SL (Wellen-Entries) | 2–3 / 2–3 |
|
||||||
|
✅✅ **DAS IST DER EIGENTLICHE BEFUND: der Mechanismus feuert praktisch NIE.**
|
||||||
|
Nicht weil die Idee schlecht wäre, sondern weil sie **konstruktionsbedingt zu spät
|
||||||
|
kommt**: ein Gegen-Ausbruch verlangt eine NEUE Kompression (Box über 10 M5-Bars =
|
||||||
|
50 min), der Ausbruch selbst weitet die Box aber gerade erst. Die mittlere
|
||||||
|
Haltedauer liegt bei **12–18 min**. Der Gegen-Ausbruch trifft also in rund **0,1 %
|
||||||
|
(Squeeze) bzw. 1 % (Welle)** der Trades ein — der Fall des Users war einer davon.
|
||||||
|
Die Kennzahl-Unterschiede in der Tabelle sind entsprechend Rauschen aus einer
|
||||||
|
Handvoll Fällen; keine Variante besteht auf beiden Populationen.
|
||||||
|
⚠ **Auch die abgeschwächte Form („enger SL statt Close") ändert nichts** — sie
|
||||||
|
adressiert zwar genau den dokumentierten Einwand (Gegen-Ausbrüche sind oft
|
||||||
|
Fehlausbrüche), kann ihn aber nicht ausspielen, wenn sie 0-mal auslöst.
|
||||||
|
⚠ Und sie wäre von dieser Simulation ohnehin **überschätzt**: ein enger Stop wird
|
||||||
|
von einer Bar-Simulation systematisch zu gut gerechnet (s. Auflösungs-Artefakt,
|
||||||
|
`analyze_trail_resolution.py`, 05.08.).
|
||||||
|
**Damit zum DRITTEN Mal verworfen — und diesmal mit einer Begründung, die nicht
|
||||||
|
vom Exit-Modell abhängt.**
|
||||||
**Re-Test unter Vola-Regimen = ERNEUT VERWORFEN (gemessen
|
**Re-Test unter Vola-Regimen = ERNEUT VERWORFEN (gemessen
|
||||||
`backtest_squeeze_exit_vol.py`, 2026-07-24, User-Frage „Markt gerade sehr volatil —
|
`backtest_squeeze_exit_vol.py`, 2026-07-24, User-Frage „Markt gerade sehr volatil —
|
||||||
wirklich nicht profitabel?"):** Verschärfter Test mit echten SQUEEZE-Entries (statt
|
wirklich nicht profitabel?"):** Verschärfter Test mit echten SQUEEZE-Entries (statt
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Gegen-Squeeze als EXIT — dritte Messung, jetzt mit dem kanonischen Exit.
|
||||||
|
|
||||||
|
ANLASS (User, 2026-08-05): Trade T=49423144 (SELL 3,49 @ 75,012) lief in 14 min
|
||||||
|
1,135 $ ins Minus und endete am Initial-SL bei **−343 €**. Wunsch: „der Breakout
|
||||||
|
hätte den Trade viel früher schließen sollen."
|
||||||
|
|
||||||
|
⚠ WAS AN DEM TAG WIRKLICH PASSIERT IST (rekonstruiert):
|
||||||
|
* Beim Einstieg war der Squeeze **active SHORT** — er stand also FÜR den Trade,
|
||||||
|
nicht dagegen. Die Empfehlung war WARTEN (`block=entry_room`), der Einstieg
|
||||||
|
manuell.
|
||||||
|
* Der Gegen-Ausbruch (armed LONG @ 75,729 auf dem 17:05-Bar) wurde erst
|
||||||
|
~3–5 min SPÄTER aktiv. Ein Close dort hätte bei ≈75,73 stattgefunden:
|
||||||
|
(75,73 − 75,012) × 3,49 Lots × 87,6 ≈ **−219 €** statt −343 € → ~124 € gespart.
|
||||||
|
Das ist EIN Fall. Die Idee ist im Projekt bereits ZWEIMAL gemessen und verworfen
|
||||||
|
(`backtest_squeeze_exit.py` 22.07., `backtest_squeeze_exit_vol.py` 24.07.).
|
||||||
|
|
||||||
|
WARUM TROTZDEM EINE DRITTE MESSUNG: beide Vorläufer stammen von VOR der
|
||||||
|
Exit-Vereinheitlichung am 31.07. und ruhen damit auf einem Exit-Modell, das dem
|
||||||
|
Live-System nicht entspricht (Trail 1,5 statt 1,0, teils ohne Time-Stop). Genau
|
||||||
|
diese Konstellation hat heute schon mehrere alte Schlüsse gekippt.
|
||||||
|
|
||||||
|
MESSUNG: kanonischer Exit (`exit_model.simulate`) + der dokumentierte
|
||||||
|
**`stop_when`-Hook** — dadurch muss die Phasen-Mechanik NICHT kopiert werden
|
||||||
|
(genau diese Kopiererei hat im Projekt vier divergierende Exit-Modelle erzeugt).
|
||||||
|
Zwei Populationen (Squeeze-Ausbrüche, gegatete Wellensignale), feste Entry-Listen,
|
||||||
|
Echtkosten, 2 Halbjahre.
|
||||||
|
|
||||||
|
VORAB FIXIERTE ENTSCHEIDUNGSREGEL:
|
||||||
|
Einbauen nur, wenn eine Variante ΣR **und** ØR in BEIDEN Halbjahren und auf
|
||||||
|
BEIDEN Populationen verbessert. Sonst bleibt es beim Nein — dann zum dritten Mal.
|
||||||
|
|
||||||
|
Aufruf: python backtest_squeeze_exit3.py [bars]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
import core.wave_rec as wr
|
||||||
|
from backtest_cost_gate import _atr_series, kandidaten
|
||||||
|
from backtest_trail_be import _ema_series, _htf_sign, wellen_entries
|
||||||
|
from core.analysis import calc_trend_angle
|
||||||
|
from core.exit_model import LIVE, simulate
|
||||||
|
from core.wave_rec import (WaveRecommender, _EMA_FAST, _EMA_SLOW, _N_BARS,
|
||||||
|
_ANGLE_LR, _ANGLE_DEAD)
|
||||||
|
|
||||||
|
_ATRMIN = 0.06
|
||||||
|
_PIV_K, _PIV_LOOK = 3, 300
|
||||||
|
|
||||||
|
|
||||||
|
def squeeze_richtung(H, L, C, A):
|
||||||
|
"""Je Bar: +1 wenn ein LONG-Ausbruch aktiv ist, −1 bei SHORT, sonst 0.
|
||||||
|
Nutzt die ECHTE `_squeeze_one` — kein Nachbau."""
|
||||||
|
out = [0] * len(C)
|
||||||
|
for i in range(wr._SQ_N + 2, len(C)):
|
||||||
|
a = A[i]
|
||||||
|
if not a or a < _ATRMIN:
|
||||||
|
continue
|
||||||
|
w0 = i - wr._SQ_N - 2
|
||||||
|
st = WaveRecommender._squeeze_one(H[w0:i + 1], L[w0:i + 1], C[w0:i + 1], a)
|
||||||
|
if st.get("state") == "active":
|
||||||
|
out[i] = 1 if st.get("dir") == "LONG" else -1
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def lauf(K, H, L, C, SQ, lo, hi, modus, min_verlust=0.0):
|
||||||
|
"""modus: 'aus' | 'immer' | 'nur_minus' | 'eng_sl'.
|
||||||
|
|
||||||
|
⚠ `eng_sl` ist die User-Idee vom 05.08.: beim Gegen-Ausbruch NICHT sofort
|
||||||
|
schliessen, sondern den Stop ENG ans aktuelle Niveau ziehen. Der Unterschied
|
||||||
|
ist inhaltlich: die Sofort-Variante fiel gemessen durch, WEIL Gegen-Ausbrueche
|
||||||
|
oft Fehlausbrueche sind und die Position zurueckprallt. Ein enger Stop steigt
|
||||||
|
nur aus, wenn die Bewegung WEITERLAEUFT — genau der Einwand.
|
||||||
|
Modelliert ueber den Hook: Preis beim ersten Gegen-Ausbruch merken, schliessen
|
||||||
|
sobald es `min_verlust` xATR WEITER dagegen geht.
|
||||||
|
⚠ Bar-Aufloesung: ein enger Stop wird von dieser Simulation systematisch
|
||||||
|
UEBERSCHAETZT (s. analyze_trail_resolution.py, 05.08.) — die Zahlen unten sind
|
||||||
|
also die optimistische Obergrenze."""
|
||||||
|
rs = []
|
||||||
|
frei = lo
|
||||||
|
ausgeloest = 0
|
||||||
|
for k in K:
|
||||||
|
if not (lo <= k["i"] < hi) or k["i"] < frei:
|
||||||
|
continue
|
||||||
|
d = k["d"]
|
||||||
|
|
||||||
|
traf = []
|
||||||
|
ausl = [] # Profit-Stand beim ERSTEN Gegen-Ausbruch
|
||||||
|
|
||||||
|
def stop_when(j, profit, _d=d, _t=traf, _a=ausl):
|
||||||
|
if modus == "aus":
|
||||||
|
return False
|
||||||
|
if modus == "eng_sl":
|
||||||
|
if not _a:
|
||||||
|
if SQ[j] != -_d:
|
||||||
|
return False
|
||||||
|
_a.append(profit) # Stop ab hier eng nachziehen
|
||||||
|
return False
|
||||||
|
if profit <= _a[0] - min_verlust:
|
||||||
|
_t.append(j); return True
|
||||||
|
return False
|
||||||
|
if SQ[j] != -_d: # kein GEGEN-Ausbruch
|
||||||
|
return False
|
||||||
|
if modus == "nur_minus" and profit > -min_verlust:
|
||||||
|
return False
|
||||||
|
_t.append(j)
|
||||||
|
return True
|
||||||
|
|
||||||
|
r, xb = simulate(k["lvl"], d, k["atr"], H, L, C, k["i"] + 1, LIVE,
|
||||||
|
timestop=True, use_tp=True,
|
||||||
|
stop_when=(None if modus == "aus" else stop_when),
|
||||||
|
ret_bar=True)
|
||||||
|
rs.append(r - k["kosten"])
|
||||||
|
ausgeloest += 1 if traf else 0
|
||||||
|
frei = xb + 1
|
||||||
|
return rs, ausgeloest
|
||||||
|
|
||||||
|
|
||||||
|
def kenn(rs):
|
||||||
|
if not rs:
|
||||||
|
return None
|
||||||
|
n = len(rs); s = sum(rs)
|
||||||
|
g = sum(x for x in rs if x > 0); v = -sum(x for x in rs if x < 0)
|
||||||
|
return {"n": n, "wr": 100 * sum(1 for x in rs if x > 0) / n, "oer": s / n,
|
||||||
|
"sr": s, "pf": (g / v) if v > 0 else float("inf"), "worst": min(rs)}
|
||||||
|
|
||||||
|
|
||||||
|
def zeile(lbl, k1, k2, b1, b2):
|
||||||
|
def f(k):
|
||||||
|
if not k:
|
||||||
|
return f"{'—':>36}"
|
||||||
|
return (f"{k['n']:>5} {k['wr']:>5.1f}% {k['oer']:>+7.3f} "
|
||||||
|
f"{k['pf']:>5.2f} {k['sr']:>+8.1f}")
|
||||||
|
gut = bool(k1 and k2 and b1 and b2 and k1["oer"] > b1["oer"]
|
||||||
|
and k2["oer"] > b2["oer"] and k1["sr"] > b1["sr"] and k2["sr"] > b2["sr"])
|
||||||
|
print(f" {lbl:<26}{f(k1)} |{f(k2)}" + (" BESSER" if gut else ""))
|
||||||
|
return gut
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n_bars = 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_bars)
|
||||||
|
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]; T = [int(b["time"]) for b in bars]
|
||||||
|
SP = [float(b["spread"]) * point for b in bars]
|
||||||
|
A = _atr_series(H, L, C)
|
||||||
|
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
|
||||||
|
AD = [0.0] * len(C)
|
||||||
|
for i in range(_N_BARS, len(C)):
|
||||||
|
AD[i] = calc_trend_angle(C[i - _N_BARS:i + 1], _ANGLE_LR) - 90.0
|
||||||
|
if abs(AD[i]) < _ANGLE_DEAD:
|
||||||
|
AD[i] = 0.0
|
||||||
|
HTF = [_htf_sign(C, i, 6, A[i] or 0.1) for i in range(len(C))]
|
||||||
|
H1S = [_htf_sign(C, i, 12, A[i] or 0.1) for i in range(len(C))]
|
||||||
|
PH = [j for j in range(_PIV_K, len(C) - _PIV_K)
|
||||||
|
if H[j] == max(H[j - _PIV_K:j + _PIV_K + 1])]
|
||||||
|
PL = [j for j in range(_PIV_K, len(C) - _PIV_K)
|
||||||
|
if L[j] == min(L[j - _PIV_K:j + _PIV_K + 1])]
|
||||||
|
|
||||||
|
w = WaveRecommender(lambda: None)
|
||||||
|
w.set_reversal_enabled(False)
|
||||||
|
w.set_entry_room(0.6)
|
||||||
|
w.set_dead_hours("")
|
||||||
|
mid = len(C) // 2
|
||||||
|
|
||||||
|
print("=" * 100)
|
||||||
|
print(f" GEGEN-SQUEEZE ALS EXIT — 3. Messung · {sym} M5, {len(C)} Bars")
|
||||||
|
print(f" kanonischer Exit (Trail {LIVE.mult}) + stop_when-Hook · Echtkosten · 2 Halbjahre")
|
||||||
|
print(" ⚠ Idee bereits 2x verworfen (22.07. / 24.07.) — beide auf einem NICHT-Live-Exit.")
|
||||||
|
print("=" * 100)
|
||||||
|
SQ = squeeze_richtung(H, L, C, A)
|
||||||
|
print(f" Bars mit aktivem Ausbruch: {sum(1 for x in SQ if x)} von {len(SQ)}")
|
||||||
|
|
||||||
|
pops = [("SQUEEZE-Entries", kandidaten(H, L, C, T, SP, A)),
|
||||||
|
("WELLE (gegated)",
|
||||||
|
wellen_entries(w, H, L, C, A, SP, EF, ES, AD, HTF, H1S, PH, PL))]
|
||||||
|
treffer = {}
|
||||||
|
for name, K in pops:
|
||||||
|
print(f"\n {name} — {len(K)} Entries")
|
||||||
|
print(f" {'Variante':<26}{'H1 n WR OeR PF SigmaR':>36}"
|
||||||
|
f" |{'H2 n WR OeR PF SigmaR':>36}")
|
||||||
|
b1 = kenn(lauf(K, H, L, C, SQ, 0, mid, "aus")[0])
|
||||||
|
b2 = kenn(lauf(K, H, L, C, SQ, mid, len(C), "aus")[0])
|
||||||
|
zeile("BASIS (kein Gegen-Exit)", b1, b2, None, None)
|
||||||
|
for modus, mv, lbl in (("immer", 0.0, "sofort schliessen"),
|
||||||
|
("nur_minus", 0.0, "sofort, nur im Minus"),
|
||||||
|
("nur_minus", 0.5, "sofort, Verlust > 0,5xATR"),
|
||||||
|
("eng_sl", 0.1, "ENGER SL 0,1xATR"),
|
||||||
|
("eng_sl", 0.2, "ENGER SL 0,2xATR"),
|
||||||
|
("eng_sl", 0.3, "ENGER SL 0,3xATR"),
|
||||||
|
("eng_sl", 0.5, "ENGER SL 0,5xATR")):
|
||||||
|
r1, a1 = lauf(K, H, L, C, SQ, 0, mid, modus, mv)
|
||||||
|
r2, a2 = lauf(K, H, L, C, SQ, mid, len(C), modus, mv)
|
||||||
|
k1, k2 = kenn(r1), kenn(r2)
|
||||||
|
if zeile(lbl + f" [{a1}/{a2}x]", k1, k2, b1, b2):
|
||||||
|
treffer.setdefault(lbl, set()).add(name)
|
||||||
|
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print(" URTEIL (Regel VOR dem Lauf fixiert: OeR UND SigmaR besser, in BEIDEN")
|
||||||
|
print(" Haelften UND auf BEIDEN Populationen)")
|
||||||
|
print("=" * 100)
|
||||||
|
voll = [k for k, v in treffer.items() if len(v) == len(pops)]
|
||||||
|
if voll:
|
||||||
|
print(f" => BESTEHT: {voll}")
|
||||||
|
else:
|
||||||
|
print(" => KEINE Variante besteht auf beiden Populationen.")
|
||||||
|
print(" Der Gegen-Squeeze taugt als ENTRY (validiert), nicht als EXIT —")
|
||||||
|
print(" zum DRITTEN Mal, jetzt auch mit dem kanonischen Exit.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user