Files
AH-Oil-Trader/backtest_hourly.py
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

89 lines
4.0 KiB
Python
Raw Permalink 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
"""Macht das Dead-Hour-Gate (11-14 Berlin) Sinn? Stunde-für-Stunde die realisierte
R dessen, was der Bot REAL handeln würde: Reversal (überdehnt+Winkel) sonst Trend
(EMA-Richtung, nicht überdehnt). Exit-Sim SL 2×ATR + Trailing. Netto = Ø-R Kosten
(~0,1×ATR). Wenn 11-14 klar negativ/schlechtester Block → Gate berechtigt; wenn nur
mild schwächer & netto positiv → Gate wirft Gewinn weg.
"""
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 (_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; _COST=0.10
_BROKER_OFF=3*3600; _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 berlin_hour(raw):
return dt.datetime.fromtimestamp(int(raw)-_BROKER_OFF, 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)
by={h:[] for h in range(24)}
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
d=0
if stretch<=-TH and ad>=_ANGLE_DEAD: d=1 # Reversal-LONG
elif stretch>=TH and ad<=-_ANGLE_DEAD: d=-1 # Reversal-SHORT
elif abs(stretch)<_STRETCH_MAX: d = 1 if ef>es else -1 if ef<es else 0 # Trend
if not d: continue
R=simulate(C[i],d,atr,C[i]-d*_SL_ATR*atr,H,L,C,i+1)
by[berlin_hour(T[i])].append(R)
print("="*78)
print(f" Realisierte R je Berlin-Stunde — {sym} M5 (Trend+Reversal, Exit SL2+Trail)")
print(f" Netto = Ø-R Kosten {_COST}×ATR. Dead-Hours aktuell = {_DEAD_HOURS}")
print("="*78)
print(f" {'Std':>3} {'n':>6} {'WR':>4} {'Ø-R':>7} {'netto':>7} {'ΣR':>7} Bar")
allnet=0
for h in range(24):
v=by[h]
if not v: continue
wr=100*sum(1 for r in v if r>0)/len(v); avg=sum(v)/len(v); net=avg-_COST
allnet+=net*len(v)
mark=" « DEAD" if h in _DEAD_HOURS else ""
bar=('+'*int(net*60)) if net>0 else ('-'*int(-net*60))
print(f" {h:>3} {len(v):>6} {wr:>3.0f}% {avg:>+7.3f} {net:>+7.3f} {sum(v):>+7.0f} {bar}{mark}")
dead=[r for h in _DEAD_HOURS for r in by[h]]
ok=[r for h in range(24) if h not in _DEAD_HOURS for r in by[h]]
def s(v):
return f"n={len(v)} Ø-R={sum(v)/len(v):+.3f} netto={sum(v)/len(v)-_COST:+.3f} ΣR={sum(v):+.0f}"
print(f"\n DEAD (11-14): {s(dead)}")
print(f" Rest : {s(ok)}")
if __name__=="__main__":
main()