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

109 lines
4.9 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
"""Gezielter Test: Konfidenz-STRAFE wenn der M30/H1-Regressionswinkel scharf GEGEN
die Signalrichtung steht (= Markt dreht, aber EMA lagt → falsche 'Konfluenz').
Borderline-Signale fallen dann unter das 55%-Gate (→WARTEN). Kein Pauschal-Filter.
Frage: Sind die so ENTFERNTEN Signale netto negativ (gut weg) und bleibt der
Gesamtertrag der behaltenen ≥ Baseline? Exit-Sim = SL 2,0×ATR + Trailing 1,5 (live).
"""
import sys, bisect
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, _HTF_DEADBAND, _ANGLE_LR, _MIN_CONF)
_MAXH=288; _ATRMIN=0.12
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 sim(entry,d,atr,H,L,C,j0, sl_atr=2.0, trail=1.5, trail_on=0.3, be_on=1.0):
eff=entry-d*sl_atr*atr; hw=entry
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): exit_px=eff; break
hw=max(hw,hi) if d>0 else min(hw,lo)
prof=(C[j]-entry)*d
if prof>=trail_on*atr:
cand=hw-d*trail*atr
if prof>=be_on*atr: cand=max(cand,entry) if d>0 else min(cand,entry)
eff=max(eff,cand) if d>0 else min(eff,cand)
return (exit_px-entry)*d/atr
def srep(name,r):
if not r: print(f" {name:<34} -"); return
n=len(r); w=sum(1 for x in r if x>0)
print(f" {name:<34} n={n:>5} Treffer={100*w/n:>3.0f}% ØR={sum(r)/n:+.3f} ΣR={sum(r):+.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)
m30=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_M30,0,n//6+500)
h1=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_H1,0,n//12+500)
mt5.shutdown()
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 m30]; mc=[float(b["close"]) for b in m30]
mh=[float(b["high"]) for b in m30]; ml=[float(b["low"]) for b in m30]
mEf=_ema_series(mc,_EMA_FAST); mEs=_ema_series(mc,_EMA_SLOW); mA=_atr_series(mh,ml,mc)
hT=[int(b["time"]) for b in h1]; hc=[float(b["close"]) for b in h1]
def m30sign(ts):
idx=bisect.bisect_right(mT,ts)-1
if idx<_EMA_SLOW or mA[idx] is None or mA[idx]<=0: return 0
dd=mEf[idx]-mEs[idx]
return 0 if abs(dd)<_HTF_DEADBAND*mA[idx] else (1 if dd>0 else -1)
def ang_at(ts,TT,CC):
idx=bisect.bisect_right(TT,ts)-1
if idx<_ANGLE_LR+2: return 90.0
return calc_trend_angle(CC[idx-_ANGLE_LR-1:idx+1],_ANGLE_LR)
class _TU:
def snapshot(self): return {"intervals": {}}
w=WaveRecommender(_TU(), mt5.TIMEFRAME_M5)
sigs=[] # (i,d,atr,conf,R,m30dev,h1dev)
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)
a5=calc_trend_angle(C[i-_ANGLE_LR-2:i],_ANGLE_LR)
rec,_=w._build(ef,es,C[i-1],atr,"M5",0,htf_trend=m30sign(T[i]),
h1_trend=0,angle=a5)
if rec["signal"]=="WARTEN": continue
d=1 if rec["signal"]=="LONG" else -1
atr=max(atr,_ATRMIN)
R=sim(C[i],d,atr,H,L,C,i+1)
sigs.append((d,rec["conf_pct"],R,ang_at(T[i],mT,mc)-90.0,ang_at(T[i],hT,hc)-90.0))
base=[R for (_,_,R,_,_) in sigs]
print("="*80)
print(f" Konfluenz-Winkel-Strafe — {sym} M5 Signale={len(sigs)} Exit SL2.0+Trail1.5")
print("="*80)
srep("BASELINE (alle Signale)", base)
print(" (Strafe greift, wenn M30- ODER H1-Winkel um >T gegen die Richtung steht)\n")
for T_ in (10, 20, 40):
def opp(d,m,h): return ((d<0 and (m>T_ or h>T_)) or (d>0 and (m<-T_ or h<-T_)))
for P in (15, 25):
kept=[]; removed=[]
for (d,conf,R,m,h) in sigs:
if opp(d,m,h) and (conf - P) < _MIN_CONF:
removed.append(R)
else:
kept.append(R)
print(f"T={T_}° Strafe={P}:")
srep(" entfernt (→WARTEN)", removed)
srep(" behalten (gehandelt)", kept)
db=sum(kept)-sum(base)
print(f" Δ Gesamtertrag vs Baseline: {db:+.0f} R ({'BESSER' if db>0 else 'schlechter'})"
f" · behalten {len(kept)}/{len(sigs)}\n")
if __name__=="__main__":
main()