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

85 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
"""Misst antizyklische BOUNCE-Einstiege (gegen die EMA) bei verschiedenen
Überdehnungs-Schwellen + echter Exit-Sim (SL fix 2,0×ATR + Trailing-TP).
Bounce-LONG : Kurs ueberverkauft (stretch <= -TH unter EMA50) UND Winkel gedreht
(Momentum dreht hoch) -> LONG.
Bounce-SHORT : stretch >= +TH ueber EMA50 UND Winkel dreht runter -> SHORT.
Frage: Bei welcher Schwelle TH traegt der Bounce noch? (Tiefer = mehr Bounces
erwischt, wie der verpasste +1,3-Move; zu tief = Edge kippt.)
Vergleich gegen den reinen Trend-Edge (~+0,20 R) als Benchmark.
"""
import sys
import MetaTrader5 as mt5
from core.analysis import calc_trend_angle
from core.wave_rec import (_atr, _ema_last, _EMA_FAST, _EMA_SLOW, _N_BARS,
_ANGLE_LR, _ANGLE_DEAD)
_MAXH=240; _TRAILON=0.3; _TPTRAIL=0.5; _ATRMIN=0.12; _SL_ATR=2.0
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} Treffer={100*w/n:>3.0f}% Ø-R={sum(Rs)/n:+.3f} PF={pf:>4.2f} Worst={min(Rs):+.2f} ΣR={sum(Rs):+.0f}"
def main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 40000
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]
ES=_ema_series(C,_EMA_SLOW); AT=_atr_series(H,L,C)
print("="*92)
print(f" Bounce-Einstieg (antizyklisch) — {sym} M5 Exit: SL {_SL_ATR}×ATR + Trailing-TP pessimistisch")
print(f" Benchmark Trend-Edge ≈ +0,20 R (backtest_rev_exit/exit). Reversal-Schwelle aktuell 3,5×ATR.")
print("="*92)
for TH in (1.5, 2.0, 2.5, 3.0, 3.5):
with_ang=[]; no_ang=[]
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]
stretch=(C[i]-es)/atr
ang=calc_trend_angle(C[i-_ANGLE_LR-2:i],_ANGLE_LR); ad=ang-90.0
# Bounce-LONG: ueberverkauft; Bounce-SHORT: ueberkauft
for d,cond_stretch,cond_ang in ((1, stretch<=-TH, ad>=_ANGLE_DEAD),
(-1, stretch>=TH, ad<=-_ANGLE_DEAD)):
if not cond_stretch: continue
R=simulate(C[i],d,atr,C[i]-d*_SL_ATR*atr,H,L,C,i+1)
no_ang.append(R) # nur Überdehnung
if cond_ang: with_ang.append(R) # + Winkel gedreht (echtes Bounce-Signal)
print(f"\nSchwelle TH={TH}×ATR:")
print(f" nur überdehnt {stats(no_ang)}")
print(f" + Winkel gedreht (BOUNCE) {stats(with_ang)}")
if __name__=="__main__":
main()