Files
AH-Oil-Trader/backtest_rev_exit.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
3.7 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
"""Reversal-Setups mit ECHTER Exit-Simulation (SL 2,0×ATR, Breakeven 1,3,
Trailing HW∓1,5×ATR) — zeigt, ob der antizyklische Einstieg den Gegenlauf vor
der Wende überlebt oder vorher ausgestoppt wird. Vergleich zum normalen Signal.
"""
from __future__ import annotations
import sys
import MetaTrader5 as mt5
from core.analysis import calc_trend_angle
from core.wave_rec import (WaveRecommender, _atr, _ema_last, _EMA_FAST, _EMA_SLOW,
_N_BARS, _STRETCH_MAX, _HTF_DEADBAND, _ANGLE_DEAD)
from backtest_exit import simulate, _ATR_MIN, _MAXH, _ema_series, _atr_series
_LR = 14
class _NeutralTU:
def snapshot(self): return {"intervals": {}}
def _metrics(name, Rs):
if not Rs: print(f" {name:<24} -"); return
n=len(Rs); win=sum(1 for r in Rs if r>0)
g=sum(r for r in Rs if r>0); l=-sum(r for r in Rs if r<0)
pf=g/l if l>0 else 9.99
print(f" {name:<24} n={n:>4} Treffer={100*win/n:>3.0f}% Oe-R={sum(Rs)/n:+.3f} "
f"Summe={sum(Rs):+.1f} PF={pf:.2f} Worst={min(Rs):+.2f}")
def main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 12000
if not mt5.initialize(): print("init",mt5.last_error()); sys.exit(1)
sym=None
for c in ("SpotCrude","USOIL","WTI","XTIUSD"):
if mt5.symbol_info(c): sym=c; break
sym=sym or "SpotCrude"
bars=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_M5,0,n+_N_BARS+_MAXH+5)
m30b=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_M30,0,n//6+400)
mt5.shutdown()
if bars is None: print("Bars fehlen"); sys.exit(1)
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]
mT=[int(b["time"]) for b in m30b]; mc=[float(b["close"]) for b in m30b]
mh=[float(b["high"]) for b in m30b]; ml=[float(b["low"]) for b in m30b]
mEf=_ema_series(mc,_EMA_FAST); mEs=_ema_series(mc,_EMA_SLOW); mATR=_atr_series(mh,ml,mc)
def m30s(ts):
lo,hi,idx=0,len(mT)-1,-1
while lo<=hi:
md=(lo+hi)//2
if mT[md]<=ts: idx=md; lo=md+1
else: hi=md-1
if idx<_EMA_SLOW or mATR[idx] is None or mATR[idx]<=0: return 0
dd=mEf[idx]-mEs[idx]
return 0 if abs(dd)<_HTF_DEADBAND*mATR[idx] else (1 if dd>0 else -1)
w=WaveRecommender(_NeutralTU(),mt5.TIMEFRAME_M5)
rev=[]; rev_long=[]; rev_short=[]; trend=[]
for i in range(_N_BARS, len(C)-_MAXH-1):
wc=C[i-_N_BARS:i]; wh=H[i-_N_BARS:i]; wl=L[i-_N_BARS:i]
atr=_atr(wh,wl,wc)
if not atr or atr<=0: continue
ef=_ema_last(wc,_EMA_FAST); es=_ema_last(wc,_EMA_SLOW)
stretch=(C[i-1]-es)/atr; ang=calc_trend_angle(C[i-_LR-2:i], _LR); ad=ang-90.0
a=max(atr,_ATR_MIN)
# Reversal?
d=0
if stretch<=-_STRETCH_MAX and ad>=_ANGLE_DEAD: d=1
elif stretch>=_STRETCH_MAX and ad<=-_ANGLE_DEAD: d=-1
if d!=0:
R,_=simulate(C[i], d, a, 2.0, H, L, C, i+1, be=1.3)
rev.append(R); (rev_long if d>0 else rev_short).append(R)
continue
# normales Trendsignal (mit M30-Filter) zum Vergleich
rec,_=w._build(ef,es,C[i-1],atr,"M5",5,htf_trend=m30s(T[i]))
if rec["signal"]!="WARTEN":
dd=1 if rec["signal"]=="LONG" else -1
R,_=simulate(C[i], dd, a, 2.0, H, L, C, i+1, be=1.3)
trend.append(R)
print("="*72)
print(f" Reversal mit ECHTER Exit-Simulation — {sym} M5 (SL 2,0 · BE 1,3 · Trail)")
print("="*72)
_metrics("Reversal gesamt", rev)
_metrics(" Reversal LONG", rev_long)
_metrics(" Reversal SHORT", rev_short)
_metrics("Normales Trendsignal", trend)
print("\n Oe-R = Ø/Trade in ATR · Worst = größter Einzelverlust (Gegenlauf/SL)")
if __name__=="__main__":
main()