15-Min-Regel: Squeeze-Trades ausgenommen (gemessen schaedlich)
Analyse der negativen Autotrader-Trades. Von den drei heute per adverse15
geschlossenen Bot-Trades hat die Regel bei EINEM gerettet (Auto-Signal
11:44, -26,31 EUR; danach lief der Kurs 32 EUR weiter dagegen) und bei den
beiden SQUEEZE-Trades zu frueh gekappt (20:15 / 20:36, -13,77 / -28,09 EUR;
danach waeren +19,28 bzw. +17,60 EUR moeglich gewesen - aus candles_m1
nachgerechnet).
Verdacht bestaetigt: die Schwelle 0,5xATR wurde in backtest_auto_signal.py
auf WAVE-SIGNAL-Entries kalibriert und dann auf ALLE Bot-Trades angewandt.
Wieder ein Kalibrierungs-Bruch.
backtest_adverse15_squeeze.py (NEU, 80k M5, 2 Halbjahre, Echtkosten,
Exit-Kern): auf SQUEEZE-Entries schadet die Regel bei JEDER Schwelle in
BEIDEN Haelften:
0,3 -> -25/-38 · 0,5 -> -18/-34 · 0,8 -> -12/-39 · 1,2 -> -5/-18
Monoton, also ist die Regel selbst das Problem, nicht ihre Einstellung.
Ohne Regel H1 -54 / H2 +220, mit 0,5 H1 -72 / H2 +186.
Grund: der Squeeze ist runner-abhaengig (43 % Treffer, Ertrag im Tail) -
ein Schnitt nach 15 min trifft genau den Tail.
Umsetzung: _bot_open_source merkt beim Oeffnen, WOMIT eroeffnet wurde;
_check_adverse15 steigt bei auto_squeeze aus. Mit 7 Szenarien getestet.
Fuer den Auto-Signal-Pfad bleibt die Regel aktiv - dort gemessen ~neutral
(OeR +0,001 mit vs +0,026 ohne), also eine Versicherung mit kleiner Praemie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
be2c318745
commit
6cf2730dc3
@@ -990,6 +990,27 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
weil gemessen nicht tragfähig; zeigt die Schwelle an), neustart-fest via
|
||||
`runtime_state.json`. **B5-Pflicht:** Live-ØR/PF gegen die Backtest-Erwartung halten
|
||||
(H2-Regime +0,14 vs H1-Regime −0,05) — driftet es weg, **zurückbauen** (`=false`).
|
||||
- **⚠ 15-MINUTEN-REGEL: SQUEEZE-TRADES AUSGENOMMEN (gemessen
|
||||
`backtest_adverse15_squeeze.py`, 2026-07-31).** Die Schwelle 0,5×ATR wurde in
|
||||
`backtest_auto_signal.py` auf **WAVE-SIGNAL-Entries** kalibriert und danach auf ALLE
|
||||
Bot-Trades angewandt — wieder ein Kalibrierungs-Bruch (gemessen unter X, betrieben
|
||||
unter Y). Auf SQUEEZE-Entries schadet die Regel bei **JEDER** Schwelle in **BEIDEN**
|
||||
Hälften: Δ gegen „ohne Regel" 0,3 → −25/−38 · **0,5 → −18/−34** · 0,8 → −12/−39 ·
|
||||
1,2 → −5/−18. **Monoton** — also ist die Regel selbst das Problem, nicht ihre
|
||||
Einstellung. Grund: der Squeeze ist runner-abhängig (43 % Treffer, Ertrag im Tail),
|
||||
ein Schnitt nach 15 min trifft genau den Tail. Ohne Regel H1 −54 / H2 **+220**, mit
|
||||
0,5 H1 −72 / H2 +186.
|
||||
**Anlass (real, 2026-07-31):** drei Bot-Trades wurden per `adverse15` geschlossen —
|
||||
der **Auto-Signal**-Trade um 11:44 (−26,31 €) wurde damit **gerettet** (danach lief
|
||||
der Kurs 32 € weiter dagegen), die beiden **Squeeze**-Trades um 20:15/20:36
|
||||
(−13,77 / −28,09 €) wurden **zu früh gekappt** (danach +19,28 bzw. +17,60 € möglich,
|
||||
aus `candles_m1` nachgerechnet). Genau das Muster, das die Messung erklärt.
|
||||
**Umsetzung:** `_bot_open_source` merkt sich beim Öffnen, WOMIT der Bot eröffnet hat
|
||||
(`auto_squeeze` | `auto_signal`); `_check_adverse15` steigt bei `auto_squeeze` aus.
|
||||
Mit 7 Szenarien getestet (Signal/Squeeze/manuell/altes Ticket · zu früh · unter
|
||||
Schwelle · im Plus). **Für den Auto-Signal-Pfad bleibt sie aktiv** — dort ist sie
|
||||
gemessen ~neutral (ØR +0,001 mit vs +0,026 ohne), also eine **Versicherung mit
|
||||
kleiner Prämie**: hilft im schlechten Regime, kostet im guten. `=0` schaltet sie ganz aus.
|
||||
- **15-Minuten-Regel (`[trading] adverse_15min_atr=0.5`, AKTIV, User-Wunsch 2026-07-30
|
||||
„wenn der Trade nach 15 Minuten gegen die Richtung läuft schließe ihn"):** steht ein
|
||||
**vom BOT eröffneter** Trade 15 min nach Entry ≥ X×ATR_M5 gegen die Richtung →
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Schadet die 15-Minuten-Regel den SQUEEZE-Trades? (Anlass 2026-07-31)
|
||||
|
||||
BEOBACHTUNG: Von den drei heute per `adverse15` geschlossenen Bot-Trades hat die
|
||||
Regel bei EINEM gerettet (Auto-Signal, danach −32 € weiter dagegen) und bei den
|
||||
beiden SQUEEZE-Trades zu früh gekappt (danach +19,28 bzw. +17,60 € möglich).
|
||||
|
||||
VERDACHT — wieder ein Kalibrierungs-Bruch: die Schwelle `adverse_15min_atr=0.5`
|
||||
wurde in `backtest_auto_signal.py` auf **Wave-Signal-Entries** gemessen und wird
|
||||
live auf **ALLE** Bot-Trades angewandt, also auch auf den Squeeze. Der Squeeze ist
|
||||
aber runner-abhängig (nur 43 % Treffer, der Ertrag steckt im Tail) — eine Regel,
|
||||
die nach 15 Minuten kappt, trifft genau den Tail.
|
||||
|
||||
GEMESSEN: Squeeze-Entries (Regel exakt wie live), Exit aus `core/exit_model.py`,
|
||||
Echtkosten, 2 Halbjahre; ohne Regel vs. mit Regel bei mehreren Schwellen.
|
||||
Der Adverse-Check läuft über den `stop_when`-Hook — die Phasen-Mechanik wird nicht
|
||||
kopiert.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from core.exit_model import LIVE, simulate
|
||||
from core.wave_rec import _N_BARS
|
||||
|
||||
_ATRMIN = 0.06
|
||||
_BARS_15MIN = 3 # 15 min = 3 M5-Bars
|
||||
|
||||
|
||||
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])))
|
||||
out = []
|
||||
for i in range(len(C)):
|
||||
if not i:
|
||||
out.append(None); continue
|
||||
w = t[max(1, i - p + 1):i + 1]
|
||||
out.append(sum(w) / max(1, len(w)))
|
||||
return out
|
||||
|
||||
|
||||
def squeeze_signals(H, L, C, AT, lo, hi):
|
||||
"""Regel exakt wie live (`wave._squeeze_one`), Einstieg AM Ausbruchslevel."""
|
||||
from core.wave_rec import _SQ_N, _SQ_MULT, _SQ_K
|
||||
_W = 24; _COOL = 12
|
||||
out = []
|
||||
i = max(lo, _N_BARS, _SQ_N + 15)
|
||||
while i < min(hi, len(C) - LIVE.max_hold - 1):
|
||||
atr = AT[i]
|
||||
if not atr or atr < _ATRMIN:
|
||||
i += 1; continue
|
||||
bh = max(H[i - _SQ_N:i]); bl = min(L[i - _SQ_N:i])
|
||||
if (bh - bl) > _SQ_MULT * atr:
|
||||
i += 1; continue
|
||||
hit = None
|
||||
for j in range(i, min(i + _W, len(C) - LIVE.max_hold - 1)):
|
||||
up = bh + _SQ_K * atr; dn = bl - _SQ_K * atr
|
||||
if H[j] >= up: hit = (j, 1, up); break
|
||||
if L[j] <= dn: hit = (j, -1, dn); break
|
||||
if hit is None:
|
||||
i += 1; continue
|
||||
j, d, lvl = hit
|
||||
out.append((j, d, atr, lvl))
|
||||
i = j + _COOL
|
||||
return out
|
||||
|
||||
|
||||
def run(sig, H, L, C, SP, adverse=None):
|
||||
"""`adverse` = Schwelle in ×ATR (None = Regel aus)."""
|
||||
Rs = []
|
||||
for (j, d, atr, lvl) in sig:
|
||||
cost = (SP[j] if SP[j] > 0 else 0.0225) / atr
|
||||
hook = None
|
||||
if adverse is not None:
|
||||
j0 = j + 1
|
||||
|
||||
def hook(k, prof_atr, _j0=j0, _x=adverse):
|
||||
# 1× genau beim 15-Min-Zeitpunkt prüfen (wie live)
|
||||
return (k - _j0) == _BARS_15MIN - 1 and prof_atr <= -_x
|
||||
Rs.append(simulate(lvl, d, atr, H, L, C, j + 1, LIVE, stop_when=hook) - cost)
|
||||
return Rs
|
||||
|
||||
|
||||
def st(Rs):
|
||||
if not Rs:
|
||||
return " —"
|
||||
n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs)
|
||||
g = sum(x for x in Rs if x > 0); l = -sum(x for x in Rs if x < 0)
|
||||
return (f"n={n:>4} WR={100*w/n:>3.0f}% ØR={s/n:+.3f} PF={(g/l if l > 0 else 9.99):>4.2f} "
|
||||
f"ΣR={s:>+6.0f}")
|
||||
|
||||
|
||||
def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||
mt5.initialize(); sym = None
|
||||
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||||
if mt5.symbol_info(c):
|
||||
sym = c; break
|
||||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||
si = mt5.symbol_info(sym); point = si.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]
|
||||
AT = _atr_series(H, L, C)
|
||||
mid = len(C) // 2
|
||||
halves = [("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, len(C))]
|
||||
|
||||
print("=" * 92)
|
||||
print(f" 15-MIN-REGEL auf SQUEEZE-Entries — {sym} M5 ({len(C)} Bars), Echtkosten")
|
||||
print(f" Exit core/exit_model.py (Trail {LIVE.mult}) · live: adverse_15min_atr=0.5")
|
||||
print(" ⚠ Die 0,5 wurden auf WAVE-Signalen gemessen, nicht auf dem Squeeze.")
|
||||
print("=" * 92)
|
||||
SIG = {lbl: squeeze_signals(H, L, C, AT, a, b) for lbl, a, b in halves}
|
||||
for lbl, _, _ in halves:
|
||||
print(f" {lbl}: {len(SIG[lbl])} Squeeze-Entries")
|
||||
|
||||
base = {}
|
||||
for name, adv in (("OHNE Regel", None), ("0,3×ATR", 0.3),
|
||||
("0,5×ATR ← live", 0.5), ("0,8×ATR", 0.8), ("1,2×ATR", 1.2)):
|
||||
print(f"\n{name}")
|
||||
for lbl, _, _ in halves:
|
||||
Rs = run(SIG[lbl], H, L, C, SP, adverse=adv)
|
||||
s = sum(Rs)
|
||||
if adv is None:
|
||||
base[lbl] = s
|
||||
d = f" Δ ohne Regel {s - base.get(lbl, 0):+.0f}" if adv is not None else ""
|
||||
print(f" {lbl} {st(Rs)}{d}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -311,6 +311,9 @@ class TradingEngine:
|
||||
except (TypeError, ValueError):
|
||||
self._adverse15_atr = 0.5
|
||||
self._bot_open_ticket = None # Ticket, das der Bot selbst eröffnet hat
|
||||
self._bot_open_source = None # ... und WOMIT (auto_squeeze | auto_signal).
|
||||
# Nötig, weil die 15-Min-Regel nur für den
|
||||
# Auto-Signal-Pfad gemessen ist (s. unten).
|
||||
self._adverse15_done_ticket = None # 15-Min-Prüfung je Ticket nur 1×
|
||||
self._adverse15_count = 0
|
||||
self._sr_close_ts = 0.0 # Zeit + Richtung des letzten S/R-Closes
|
||||
@@ -1227,6 +1230,20 @@ class TradingEngine:
|
||||
return
|
||||
if ticket != self._bot_open_ticket: # nur Bot-eröffnete Trades
|
||||
return
|
||||
# ⚠ SQUEEZE-TRADES AUSGENOMMEN (gemessen `backtest_adverse15_squeeze.py`,
|
||||
# 2026-07-31). Die Schwelle 0,5×ATR wurde in `backtest_auto_signal.py` auf
|
||||
# WAVE-SIGNAL-Entries kalibriert und dann auf ALLE Bot-Trades angewandt —
|
||||
# wieder ein Kalibrierungs-Bruch. Auf SQUEEZE-Entries schadet die Regel bei
|
||||
# JEDER Schwelle in BEIDEN Hälften (Δ gegen „ohne Regel": 0,3 → −25/−38 ·
|
||||
# **0,5 → −18/−34** · 0,8 → −12/−39 · 1,2 → −5/−18) — monoton, d. h. die
|
||||
# Regel selbst ist das Problem, nicht ihre Einstellung. Grund: der Squeeze ist
|
||||
# runner-abhängig (43 % Treffer, Ertrag im Tail), und ein Schnitt nach 15 min
|
||||
# trifft genau den Tail. Ohne Regel H1 −54/H2 **+220**, mit 0,5 H1 −72/H2 +186.
|
||||
# Anlass war der 31.07.: zwei Squeeze-Trades wurden per adverse15 gekappt
|
||||
# (−13,77 / −28,09 €), danach wären +19,28 bzw. +17,60 € möglich gewesen —
|
||||
# während dieselbe Regel beim Auto-Signal-Trade desselben Tages 32 € rettete.
|
||||
if self._bot_open_source == "auto_squeeze":
|
||||
return
|
||||
if ticket == self._adverse15_done_ticket: # je Ticket nur 1× prüfen
|
||||
return
|
||||
ot = ps.get("open_time") or 0
|
||||
@@ -1981,6 +1998,7 @@ class TradingEngine:
|
||||
if source in ("auto_squeeze", "auto_signal"):
|
||||
try:
|
||||
self._bot_open_ticket = self.trader.snapshot().get("ticket")
|
||||
self._bot_open_source = source
|
||||
self._adverse15_done_ticket = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user