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

95 lines
4.2 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
"""ER-Chop-Gate testen (Track B): Signal → WARTEN, wenn Efficiency Ratio der letzten
N Bars < X (choppy). Frage: haben die Chop-Signale (niedrige ER) wirklich schlechteren
Edge als die Trend-Signale (hohe ER)? Über 2 Zeiträume. Exit-Sim SL 2×ATR + Trailing.
Validiert nur, wenn in BEIDEN Hälften: behaltene (ER≥X) besser als alle, verworfene
(ER<X) klar schlechter — sonst schneidet der Gate nur Trades weg ohne Edge-Gewinn.
"""
import sys
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)
_MAXH=200; _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 st(Rs):
if not Rs: return "n=0"
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)
return f"n={n:>5} WR={100*w/n:>3.0f}% Ø-R={sum(Rs)/n:+.3f} PF={(g/ls if ls>0 else 99):>4.2f} ΣR={sum(Rs):+.0f}"
def er_at(C,i,N):
if i-N<0: return None
net=abs(C[i]-C[i-N]); path=sum(abs(C[j]-C[j-1]) for j in range(i-N+1,i+1))
return net/path if path>0 else 0.0
def collect(H,L,C,ES,EF,AT,lo,hi,N):
TH=_REVERSAL_STRETCH; out=[]
for i in range(max(lo,_N_BARS,N+2), min(hi,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
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
R=simulate(C[i],d,atr,C[i]-d*_SL_ATR*atr,H,L,C,i+1)
out.append((er_at(C,i,N), R))
return out
def main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 80000
N=int(sys.argv[2]) if len(sys.argv)>2 else 20 # ER-Lookback in Bars
mt5.initialize(); sym=None
for c in ("SpotCrude","USOIL","WTI","XTIUSD"):
if mt5.symbol_info(c): sym=c; break
bars=None
for req in (n,100000,80000,60000,40000):
bars=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_M5,0,req)
if bars is not None and len(bars)>2000: break
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]
EF=_ema_series(C,_EMA_FAST); ES=_ema_series(C,_EMA_SLOW); AT=_atr_series(H,L,C)
mid=len(C)//2
halves=[("H1 (alt)",_N_BARS,mid),("H2 (neu)",mid,len(C))]
print("="*90)
print(f" ER-Chop-Gate — {sym} M5 ({len(C)} Bars) ER-Lookback N={N} Bars Exit SL2+Trail")
print("="*90)
EV={lbl: collect(H,L,C,ES,EF,AT,a,b,N) for lbl,a,b in halves}
for lbl,_,_ in halves:
base=[R for _,R in EV[lbl]]
print(f"\n{lbl}: BASELINE (alle Signale) {st(base)}")
for X in (0.15,0.20,0.25,0.30):
keep=[R for er,R in EV[lbl] if er is not None and er>=X]
drop=[R for er,R in EV[lbl] if er is not None and er< X]
print(f" ER≥{X:.2f} behalten {st(keep)}")
print(f" ER<{X:.2f} verworfen {st(drop)}")
print("\n Gate lohnt NUR, wenn 'verworfen' in BEIDEN Hälften deutlich schlechteres")
print(" Ø-R hat als 'behalten' — sonst schneidet er nur Volumen ohne Edge-Gewinn.")
if __name__=="__main__":
main()