Auto-Squeeze Zuverlaessigkeitspruefung + Wochen-Simulation
Befund 0 beantwortet die Frage vorweg: der Trader ist AUS.
runtime_state.json enthaelt "auto_squeeze": false und ueberschreibt
die ini (true) beim Start - dieselbe Falle wie am 31.07. bei
auto_signal, nur mit umgekehrtem Vorzeichen. Letzter Squeeze-Trade
06.08. 13:52, entry_count 0. Der Config-Waechter meldet es korrekt.
Befund 1: die Mechanik arbeitet. Die Simulation nutzt die echte
_squeeze_one und reproduziert 10 von 11 Live-Trades binnen 10 min
auf wenige Cent genau.
Befund 2: Woche n=47, Treffer 57 %, OeR +0,196, PF 1,37, SumR +9,20
(~+610 EUR bei 2,5 Lots) - im validierten Band. ABER: die Sim
ueberhandelt um das 2,4-fache (kein Live-Dedup/Cooldown), der Edge
stirbt bei 0,20xATR Fill-Slippage, und eine Woche ist Rauschen -
die Phase mit AUSgeschaltetem Trader war sogar die bessere.
Befund 3: auf denselben 10 Einstiegen war der LIVE-Exit 70 EUR
BESSER als der kanonische (-109,52 gegen -179,27). Das widerspricht
dem B4-Befund vom 01.08. ("die Luecke entsteht im Exit"). Der
S/R-Close rettete drei Trades. n=10, gemischt - kein Urteil, aber
ein Warnschild gegen den umgekehrten Schluss.
Befund 4: B5 bei 11/20 Trades, beide Bedingungen weit verletzt
(Verhaeltnis 0,17, PF 0,45 bei 73 % Trefferquote). Formal nicht
faellig.
Offen: zwei Live-BUYs am 06.08. 13:51/13:52 zum identischen Preis
auf einen Ausbruch - moeglicher Doppel-Einstieg, ungeprueft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
06ebb014be
commit
35e91474e8
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Haette der Auto-Squeeze in der letzten Woche Gewinn gemacht?
|
||||
|
||||
ANLASS (2026-08-11, User: „analysiere den Breakout-Auto-Trader, ob er zuverlaessig
|
||||
funktioniert; simuliere ueber die letzte Woche, ob er Gewinn erzielt haette").
|
||||
|
||||
⚠⚠ WAS DIESE SIMULATION IST UND WAS NICHT. **Eine Woche ist KEIN Edge-Nachweis.**
|
||||
Der Squeeze feuert 2–3×/Woche; das sind Stichproben von n≈2–6, und im Projekt sind
|
||||
Einzelzeitraeume schon dreimal faelschlich zum Beleg erhoben worden (15-Minuten-
|
||||
Regel 31.07., Nacht-Guard 05.08., das Veto im Wochentest 08.08.). Gemessen wird
|
||||
hier die **MECHANIK**: feuert die Regel ueberhaupt, zu welchen Preisen, und was
|
||||
haette der kanonische Exit daraus gemacht.
|
||||
|
||||
MODELLIERT WIRD DER LIVE-PFAD (Stand seit 05.08.):
|
||||
· echte Live-Funktion `WaveRecommender._squeeze_one` — kein Nachbau
|
||||
· **ruhende Stop-Order** an der Box-Grenze, solange `armed`; Fuellung bei
|
||||
BERUEHRUNG (nicht bei Bar-Schluss). Das ist der teurere und richtige Fall:
|
||||
(a) „Close bricht Level" ist Look-ahead und rund +0,32 R zu optimistisch
|
||||
(`backtest_squeeze_touchfill.py`).
|
||||
· Nacht-Sperre 3–6 Uhr Berlin (`auto_squeeze_night_hours`)
|
||||
· ATR-Floor, EIN Positions-Slot (sequentiell), Echtkosten aus dem Bar-Spread
|
||||
· **kanonischer Exit** `core.exit_model.LIVE` — ausdruecklich NICHT
|
||||
`squeeze_scan.exit_legacy` (Trail 1,5 · kein Lock · kein Time-Stop · kein TP),
|
||||
gegen den der B4-Monitor bis heute misst.
|
||||
|
||||
Aufruf: python backtest_squeeze_woche.py [tage] (Default 7)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from core.exit_model import LIVE, simulate
|
||||
from core.wave_rec import WaveRecommender
|
||||
|
||||
_BERLIN = ZoneInfo("Europe/Berlin")
|
||||
_BROKER = timezone(timedelta(hours=3))
|
||||
_NACHT = {3, 4, 5, 6}
|
||||
_ATR_MIN = 0.06
|
||||
_SLIPPAGE = (0.0, 0.05, 0.10, 0.20) # ×ATR Aufschlag auf den Fill
|
||||
|
||||
|
||||
def _berlin(ts):
|
||||
return datetime.fromtimestamp(ts, timezone.utc).replace(
|
||||
tzinfo=_BROKER).astimezone(_BERLIN)
|
||||
|
||||
|
||||
def atr_series(H, L, C, p=14):
|
||||
tr = [0.0]
|
||||
for i in range(1, len(C)):
|
||||
tr.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
|
||||
out = [None]
|
||||
for i in range(1, len(C)):
|
||||
seg = tr[max(1, i - p + 1):i + 1]
|
||||
out.append(sum(seg) / len(seg))
|
||||
return out
|
||||
|
||||
|
||||
def lauf(T, H, L, C, SP, A, lo_i, slip=0.0, nacht=True):
|
||||
"""Sequentiell EIN Slot, ruhende Stop-Order, Fuellung bei Beruehrung."""
|
||||
trades = []
|
||||
i = lo_i
|
||||
n = len(C)
|
||||
while i < n - LIVE.max_hold - 2:
|
||||
atr = A[i]
|
||||
if not atr or atr < _ATR_MIN:
|
||||
i += 1; continue
|
||||
if nacht and _berlin(T[i]).hour in _NACHT:
|
||||
i += 1; continue
|
||||
sq = WaveRecommender._squeeze_one(H[:i + 1], L[:i + 1], C[:i + 1], atr)
|
||||
if not sq or sq.get("box_hi") is None or sq.get("state") is None:
|
||||
i += 1; continue
|
||||
# ⚠ Die Box stammt aus den N bereits GESCHLOSSENEN Bars vor i — eine
|
||||
# ruhende Order darf waehrend Bar i also legitim gefuellt werden.
|
||||
up = sq["box_hi"] + 0.1 * atr
|
||||
dn = sq["box_lo"] - 0.1 * atr
|
||||
traf_up = H[i] >= up
|
||||
traf_dn = L[i] <= dn
|
||||
if not (traf_up or traf_dn):
|
||||
i += 1; continue
|
||||
if traf_up and traf_dn:
|
||||
# Beide Seiten in EINEM Bar — aus OHLC nicht aufloesbar. Die naeher
|
||||
# am Open liegende Seite wird zuerst beruehrt worden sein.
|
||||
traf_up = abs(up - C[i - 1]) <= abs(C[i - 1] - dn)
|
||||
traf_dn = not traf_up
|
||||
d = 1 if traf_up else -1
|
||||
fill = (up + slip * atr) if traf_up else (dn - slip * atr)
|
||||
kost = (SP[i] if SP[i] > 0 else 0.0225) / atr
|
||||
R, xb = simulate(fill, d, atr, H, L, C, i + 1, LIVE, ret_bar=True)
|
||||
trades.append({"i": i, "t": T[i], "d": d, "fill": fill, "atr": atr,
|
||||
"R": R - kost, "bars": xb - i,
|
||||
"box_atr": sq.get("box_atr")})
|
||||
i = xb + 1 # Slot war belegt
|
||||
return trades
|
||||
|
||||
|
||||
def kz(tr, lbl):
|
||||
if not tr:
|
||||
return f" {lbl:<34} keine Ausbrueche"
|
||||
n = len(tr); Rs = [x["R"] for x in tr]
|
||||
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)
|
||||
ow = g / w if w else 0.0
|
||||
ol = v / (n - w) if n - w else 0.0
|
||||
return (f" {lbl:<34} n={n:>3} Treffer {100*w/n:>3.0f} % OeR {sum(Rs)/n:>+6.3f} "
|
||||
f"PF {(g/v if v else 9.99):>4.2f} Verh {(ow/ol if ol else 9.99):>4.2f} "
|
||||
f"SumR {sum(Rs):>+6.2f}")
|
||||
|
||||
|
||||
def main():
|
||||
tage = int(sys.argv[1]) if len(sys.argv) > 1 else 7
|
||||
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, 20000)
|
||||
point = mt5.symbol_info(sym).point
|
||||
mt5.shutdown()
|
||||
T = [int(b["time"]) for b in bars]
|
||||
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)
|
||||
|
||||
grenze = T[-1] - tage * 86400
|
||||
lo_i = next(i for i, t in enumerate(T) if t >= grenze)
|
||||
lo_i = max(lo_i, 60)
|
||||
|
||||
print("=" * 100)
|
||||
print(f" AUTO-SQUEEZE — Simulation der letzten {tage} Tage ({sym})")
|
||||
print(f" {_berlin(T[lo_i]):%d.%m %H:%M} bis {_berlin(T[-1]):%d.%m %H:%M} "
|
||||
f"{len(C)-lo_i} M5-Bars")
|
||||
print(" Live-Pfad: ruhende Stop-Order an der Box, Fuellung bei BERUEHRUNG,")
|
||||
print(" Nacht 3-6 gesperrt, EIN Slot, kanonischer Exit, Echtkosten.")
|
||||
print("=" * 100)
|
||||
|
||||
basis = lauf(T, H, L, C, SP, A, lo_i)
|
||||
print("\n ── Fill-Slippage (eine Stop-Order fuellt zum Markt, nicht zum Ideal) ──")
|
||||
for s in _SLIPPAGE:
|
||||
print(kz(lauf(T, H, L, C, SP, A, lo_i, slip=s), f"Slippage +{s:.2f}xATR"))
|
||||
|
||||
print("\n ── Gegenprobe: ohne Nacht-Sperre ──")
|
||||
print(kz(lauf(T, H, L, C, SP, A, lo_i, nacht=False), "alle Stunden"))
|
||||
|
||||
print(f"\n ── die einzelnen Ausbrueche (Slippage 0) ──")
|
||||
if not basis:
|
||||
print(" KEINER — die Regel hat in diesem Fenster nicht gefeuert.")
|
||||
for x in basis:
|
||||
print(f' {_berlin(x["t"]):%d.%m %H:%M} {"LONG " if x["d"]>0 else "SHORT"} '
|
||||
f'@ {x["fill"]:.3f} Box {x["box_atr"]:.2f}xATR '
|
||||
f'{x["bars"]*5:>4} min R {x["R"]:>+6.2f}')
|
||||
|
||||
if basis:
|
||||
# EUR-Naeherung: R x ATR x Lots x ~87,6 EUR/$/Lot (robust geschaetzt, s. CLAUDE.md)
|
||||
lots = 2.5
|
||||
eur = sum(x["R"] * x["atr"] for x in basis) * lots * 87.6
|
||||
print(f"\n grob in EUR bei {lots} Lots: {eur:>+8.0f} EUR")
|
||||
print(" ⚠ Naeherung — die echte Lot-Groesse schwankt mit dem Konto.")
|
||||
|
||||
print("\n " + "-" * 96)
|
||||
print(" ⚠⚠ EINE WOCHE IST KEIN EDGE-NACHWEIS. Der Squeeze feuert 2-3x/Woche;")
|
||||
print(" n dieser Groesse ist Rauschen. Die 2-Stichproben-Regel gilt weiter.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user