Files
AH-Oil-Trader/backtest_legacy_recheck.py
T
Axel HocksandClaude Opus 5 bb17ecff0a Beide offenen Vorschlaege umgesetzt: Trail 1,0 + Legacy-Recheck
VORSCHLAG 1 - Trail-Multiplikator 1,5 -> 1,0 (core/exit_model.py LIVE.mult),
live verifiziert ("AKTIVIERT ... mult=1.0x").
Voller Sweep mit FIXEM SL 2,0 (backtest_trailmult.py, 8 Werte 0,5-3,0):
enger ist MONOTON besser. Wave-Signal H1/H2 SigmaR:
    0,5 +1258/+2418 · 0,8 +852/+1668 · 1,0 +80/+1360
    1,5 -220/+1305 · 2,0 -394/+1198 · 2,5 -1072/+1327
Optimum am RAND = Warnsignal, deshalb Gegentest auf SQUEEZE-Entries mit
Echtkosten (--squeeze): das Setup lebt von Laeufern, muesste also
dagegenhalten - tut es NICHT (1,0: H1 -0,202/H2 +0,010 · 1,5:
-0,219/-0,032). Deshalb 1,0 (besser als 1,5 in beiden Haelften auf BEIDEN
Signalmengen), aber NICHT 0,5: Randwert, und die Sim modelliert keine
Exit-Slippage - ein engerer Trail loest viel haeufiger aus und ist davon
staerker betroffen (real bis 0,75xATR ueber den Stop).

VORSCHLAG 2 - backtest_legacy_recheck.py (NEU): die beiden Legacy-Befunde
nachgerechnet, die live ECHTE Gates steuern.
  EIA-Blackout BESTAETIGT: auch mit echtem Exit + Echtkosten in BEIDEN
  Haelften schlechter als der Rest (-0,097 / -0,119). Gate ist gedeckt.
  DEAD-HOURS reproduzieren NICHT als selektiver Befund: 19 von 22 Stunden
  sind in beiden Haelften negativ, keine robust positive Stunde. Die alte
  Auswahl (0-7, 12, 16) ist damit nicht mehr gestuetzt. Praktisch folgenlos
  (dead_hours ist leer), aber eine Reaktivierung auf der alten Begruendung
  waere nicht gedeckt.

Einschraenkung selbst benannt: der Recheck handelt die UNGEGATETE
EMA-Richtung (ohne min_conf, Breakout-Bestaetigung, HTF-Filter, Raum-Gate).
Fuer den Vergleich neutral, die absoluten Werte sind nicht das Live-Signal.

Konsequenz fuer den TF-Churn-Fix: "WARTEN Richtung 43 %" ist eine
Haeufigkeits-, keine Ertragsgroesse - mehr Signale sind nur dann besser,
wenn die freigegebenen Setups auch tragen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:09:32 +02:00

166 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Legacy-Befunde mit dem ECHTEN Exit gegenprüfen (2026-07-31, Stufe-3-Folgearbeit).
BEFUND BEIM STUFE-3-REFACTOR: sechs Backtests nutzen einen **stark vereinfachten
Exit** (`_TPTRAIL=0.5`, KEIN Breakeven, KEIN Lock, KEIN Time-Stop, `_MAXH=240`):
`backtest_hourly`, `_hourly_split`, `_bounce`, `_events`, `_deadhour`, `_chopgate`.
Auf ihnen ruhen aber Befunde, die live **echte Gates** steuern:
* **EIA-Blackout** (Mi 15:3016:30) — AKTIV, blockt Trades (`backtest_events.py`)
* **Dead-Hours** (07, 12, 16) — aktuell aus, aber jederzeit reaktivierbar
(`backtest_hourly_split.py`)
Wenn diese Befunde nur unter dem vereinfachten Exit gelten, gaten wir live auf einer
Grundlage, die es nicht mehr gibt. Genau das ist Deployment-Drift.
DIESES SKRIPT rechnet beide Aussagen mit `core/exit_model.simulate()` neu — dem
Exit, den das Live-System wirklich fährt (Phasen, Breakeven 1,3, Time-Stop,
`max_hold` 200) — plus ECHTKOSTEN (Bar-Spread/ATR) und dem 2-Hälften-Protokoll.
⚠ Es geht NICHT darum, die alten Zahlen zu reproduzieren (das kann es nicht — anderer
Exit), sondern um die FRAGE: hält die SCHLUSSFOLGERUNG? Also: ist das EIA-Fenster
auch mit dem echten Exit in beiden Hälften schlechter als der Rest, und sind die
Dead-Hours es auch?
"""
import sys
from datetime import datetime, timezone, timedelta
import MetaTrader5 as mt5
from core.analysis import calc_trend_angle
from core.exit_model import LIVE, simulate
from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _N_BARS, _ANGLE_LR, _ANGLE_DEAD,
_REVERSAL_STRETCH, _STRETCH_MAX)
_ATRMIN = 0.12
_BROKER_OFF_H = 3 # Brokerzeit = UTC+3
_BERLIN_OFF_H = 2 # Sommerzeit (die History liegt im Sommerhalbjahr)
def _ema_series(v, p):
k = 2.0 / (p + 1); o = []; e = v[0]
for i, x in enumerate(v):
e = x if i == 0 else x * k + e * (1 - k)
o.append(e)
return o
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 _berlin(ts_broker: int) -> datetime:
"""Broker-Epoch (UTC+3) → Berliner Wanduhr."""
return datetime.fromtimestamp(ts_broker, timezone.utc) \
- timedelta(hours=_BROKER_OFF_H) + timedelta(hours=_BERLIN_OFF_H)
def trades(T, H, L, C, EF, ES, AT, SP, lo, hi, step=2):
"""→ [(berlin_dt, R_netto)] mit dem ECHTEN Exit + Echtkosten."""
TH = _REVERSAL_STRETCH; out = []
for i in range(max(lo, _N_BARS), min(hi, len(C) - LIVE.max_hold - 1), step):
atr = AT[i]
if not atr or atr <= 0:
continue
atr = max(atr, _ATRMIN); es = ES[i]; ef = EF[i]
stretch = (C[i] - es) / atr
ang = calc_trend_angle(C[i - _ANGLE_LR - 2:i], _ANGLE_LR); ad = ang - 90.0
d = 0
if stretch <= -TH and ad >= _ANGLE_DEAD: d = 1
elif stretch >= TH and ad <= -_ANGLE_DEAD: d = -1
elif abs(stretch) < _STRETCH_MAX: d = 1 if ef > es else -1 if ef < es else 0
if not d:
continue
cost = (SP[i] if SP[i] > 0 else 0.0225) / atr
R = simulate(C[i], d, atr, H, L, C, i + 1, LIVE) - cost
out.append((_berlin(int(T[i])), R))
return out
def stat(rs):
if not rs:
return "n=0"
n = len(rs); 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:>6} ØR={s/n:+.3f} PF={(g/l if l > 0 else 9.99):>4.2f} ΣR={s:+.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()
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]
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C)
mid = len(C) // 2
print("=" * 90)
print(f" LEGACY-RECHECK mit dem ECHTEN Exit — {sym} M5 ({len(C)} Bars)")
print(f" Exit: core/exit_model.py (trail {LIVE.mult} · BE {LIVE.be} · "
f"Time-Stop {LIVE.timestop_bars}) · Echtkosten")
print(" Alt gemessen wurde mit einem Exit OHNE Breakeven/Lock/Time-Stop.")
print("=" * 90)
halves = [("H1", _N_BARS, mid), ("H2", mid, len(C))]
data = {lbl: trades(T, H, L, C, EF, ES, AT, SP, a, b) for lbl, a, b in halves}
for lbl, _, _ in halves:
print(f" {lbl}: {len(data[lbl])} Trades")
# ── 1) EIA-Blackout: Mi 15:3016:30 Berlin (LIVE AKTIVES GATE)
print("\n" + "=" * 90)
print(" 1) EIA-BLACKOUT (Mi 15:3016:30 Berlin) — blockt live echte Trades")
print("=" * 90)
for lbl, _, _ in halves:
inw = [r for t, r in data[lbl]
if t.weekday() == 2 and (15, 30) <= (t.hour, t.minute) < (16, 30)]
out = [r for t, r in data[lbl]
if not (t.weekday() == 2 and (15, 30) <= (t.hour, t.minute) < (16, 30))]
print(f" {lbl} im Fenster : {stat(inw)}")
print(f" {lbl} ausserhalb : {stat(out)}")
if inw and out:
delta = sum(inw) / len(inw) - sum(out) / len(out)
print(f" → Differenz ØR {delta:+.3f} "
f"{'Fenster schlechter (Gate bestätigt)' if delta < 0 else '⚠ Fenster BESSER — Gate nicht gedeckt'}")
# ── 2) Dead-Hours: Netto-R je Berliner Stunde
print("\n" + "=" * 90)
print(" 2) STUNDEN-PROFIL (Dead-Hours-Grundlage) — Netto-ØR je Berliner Stunde")
print("=" * 90)
print(f" {'Std':<5}{'H1 n':>7}{'H1 ØR':>9}{'H2 n':>7}{'H2 ØR':>9} Urteil")
robust_neg, robust_pos = [], []
for h in range(24):
a = [r for t, r in data["H1"] if t.hour == h]
b = [r for t, r in data["H2"] if t.hour == h]
if len(a) < 30 or len(b) < 30:
continue
ma, mb = sum(a) / len(a), sum(b) / len(b)
verdict = ""
if ma < 0 and mb < 0:
verdict = "beidseitig NEGATIV"; robust_neg.append(h)
elif ma > 0 and mb > 0:
verdict = "beidseitig positiv"; robust_pos.append(h)
print(f" {h:<5}{len(a):>7}{ma:>+9.3f}{len(b):>7}{mb:>+9.3f} {verdict}")
print(f"\n Robust NEGATIV: {robust_neg or '—'}")
print(f" Robust POSITIV: {robust_pos or '—'}")
print(" ⚠ Alt (vereinfachter Exit) waren 07 sowie 12 und 16 die Kandidaten.")
print(" Deckt sich das? Falls nicht, ruht das Dead-Hours-Gate auf dem alten Exit.")
if __name__ == "__main__":
main()