Files
AH-Oil-Trader/backtest_deadhour.py
T
Axel HocksandClaude Opus 4.8 75d28827e8 Initial commit: Oil Trading Bot (MT5, WTI)
Headless FastAPI-Backend (server.py + core/engine.py) mit Mobile-PWA (web/),
Strategie-/Backtest-Suite und Doku. Secrets, DB, Logs und Laufzeit-State sind
via .gitignore ausgeschlossen; Config-Vorlage: oil_widget_config.ini.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 08:29:23 +02:00

104 lines
4.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
"""Soll der REVERSAL-Einstieg vom Dead-Hour-Gate (11-14 Berlin) ausgenommen werden?
Misst Reversal (überdehnt+Winkel gedreht) UND normalen Trend-Einstieg je in/außerhalb
der Dead-Hour, mit echter Exit-Sim (SL 2×ATR + Trailing-TP). Broker-Zeit (UTC+3) →
Berlin via zoneinfo.
Wenn Reversal in der Dead-Hour weiter positiv ist (≈ wie außerhalb), lohnt die Ausnahme;
wenn der Trend-Einstieg in der Dead-Hour negativ bleibt, bleibt das Gate für ihn richtig.
"""
import sys, datetime as dt
from zoneinfo import ZoneInfo
import MetaTrader5 as mt5
from core.analysis import calc_trend_angle
from core.wave_rec import (_atr, _EMA_FAST, _EMA_SLOW, _N_BARS, _ANGLE_LR, _ANGLE_DEAD,
_REVERSAL_STRETCH, _STRETCH_MAX, _DEAD_HOURS)
_MAXH=240; _TRAILON=0.3; _TPTRAIL=0.5; _ATRMIN=0.12; _SL_ATR=2.0
_BROKER_OFF=3*3600 # Broker = UTC+3
_BERLIN=ZoneInfo("Europe/Berlin")
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])))
return [(sum(t[max(1,i-p+1):i+1])/max(1,len(t[max(1,i-p+1):i+1]))) if i else None for i in range(len(C))]
def simulate(entry,d,atr,sl,H,L,C,j0):
eff=sl; hw=entry; trail=False
end=min(j0+_MAXH,len(C)-1); exit_px=C[end]
for j in range(j0,end+1):
hi,lo=H[j],L[j]
if (lo<=eff) if d>0 else (hi>=eff): return (eff-entry)*d/atr
hw=max(hw,hi) if d>0 else min(hw,lo)
if (C[j]-entry)*d>=_TRAILON*atr: trail=True
if trail:
lock=hw-d*_TPTRAIL*atr
eff=max(eff,lock) if d>0 else min(eff,lock)
return (exit_px-entry)*d/atr
def stats(Rs):
if not Rs: return " -"
n=len(Rs); w=sum(1 for r in Rs if r>0)
g=sum(r for r in Rs if r>0); ls=-sum(r for r in Rs if r<0)
pf=g/ls if ls>0 else 99.9
return f"n={n:>4} WR={100*w/n:>3.0f}% Ø-R={sum(Rs)/n:+.3f} PF={pf:>4.2f} ΣR={sum(Rs):+.0f}"
def berlin_hour(raw_time):
utc=int(raw_time)-_BROKER_OFF
return dt.datetime.fromtimestamp(utc, tz=dt.timezone.utc).astimezone(_BERLIN).hour
def main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 40000
TH=_REVERSAL_STRETCH
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+_N_BARS+_MAXH+5)
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]
EF=_ema_series(C,_EMA_FAST); ES=_ema_series(C,_EMA_SLOW); AT=_atr_series(H,L,C)
print("="*90)
print(f" Dead-Hour-Ausnahme für Reversal? — {sym} M5 Exit: SL {_SL_ATR}×ATR + Trailing")
print(f" Dead-Hours={_DEAD_HOURS} (Berlin) · Reversal-Schwelle TH={TH} · Trend nur |stretch|<{_STRETCH_MAX}")
print("="*90)
B={k:[] for k in ("rev_L_dead","rev_L_ok","rev_S_dead","rev_S_ok",
"rev_dead","rev_ok","trend_dead","trend_ok")}
for i in range(_N_BARS, len(C)-_MAXH-1):
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
dead = berlin_hour(T[i]) in _DEAD_HOURS
# Reversal
for d,cs,ca,lab in ((1, stretch<=-TH, ad>=_ANGLE_DEAD,"L"),
(-1, stretch>=TH, ad<=-_ANGLE_DEAD,"S")):
if cs and ca:
R=simulate(C[i],d,atr,C[i]-d*_SL_ATR*atr,H,L,C,i+1)
B[f"rev_{lab}_{'dead' if dead else 'ok'}"].append(R)
B[f"rev_{'dead' if dead else 'ok'}"].append(R)
# Trend-Einstieg (EMA-Richtung, NICHT überdehnt) — das, was das Gate blockt
if abs(stretch)<_STRETCH_MAX:
d = 1 if ef>es else -1 if ef<es else 0
if d:
R=simulate(C[i],d,atr,C[i]-d*_SL_ATR*atr,H,L,C,i+1)
B[f"trend_{'dead' if dead else 'ok'}"].append(R)
print("\nREVERSAL gesamt:")
print(f" außerhalb Dead-Hour {stats(B['rev_ok'])}")
print(f" IN Dead-Hour {stats(B['rev_dead'])} ← Kandidat für Ausnahme")
print("\nReversal-LONG (der Fall aus Trade ①):")
print(f" außerhalb Dead-Hour {stats(B['rev_L_ok'])}")
print(f" IN Dead-Hour {stats(B['rev_L_dead'])}")
print("\nReversal-SHORT:")
print(f" außerhalb Dead-Hour {stats(B['rev_S_ok'])}")
print(f" IN Dead-Hour {stats(B['rev_S_dead'])}")
print("\nNormaler TREND-Einstieg (das, was das Gate zu Recht blockt?):")
print(f" außerhalb Dead-Hour {stats(B['trend_ok'])}")
print(f" IN Dead-Hour {stats(B['trend_dead'])}")
if __name__=="__main__":
main()