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

105 lines
4.6 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
"""ATR-Floor-Check (Track B): `trailing._ATR_MIN=0.12` stammt aus einer höheren
Vola-Phase — real ist ATR(M1/M5) inzwischen oft 0,070,12, der Floor bindet also
staendig und macht SL/Trail-Distanzen ~1,31,6x breiter als der echte ATR.
Misst die echte Phasen-Trailing-Sim (wie backtest_trailing) mit Floor NUR im Exit
(Signale identisch, raw ATR): floor ∈ {0.00, 0.06, 0.12, 0.25} über 2 Halbjahre.
Metrik in PREIS-Punkten (ΣPts), damit Floor-Varianten fair vergleichbar sind
(R-Normierung würde je Variante anders skalieren). Plus Bind-Quote je Hälfte.
"""
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; _TP_INIT=3.5; _LOCK_START=3.5; _LOCK_SCALE=0.6; _LOCK_MIN=1.2
_BE=1.3; _TRAIL_START=0.3; _MULT=1.5 # Live-Exit-Parameter (M5)
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_eff,H,L,C,j0):
"""Live-Phasen-Trailing mit effektivem (ggf. gefloortem) ATR."""
sl=entry-d*2.0*atr_eff; tp=entry+d*_TP_INIT*atr_eff
hw=entry; rank=0
end=min(j0+_MAXH,len(C)-1)
for j in range(j0,end+1):
hi,lo=H[j],L[j]
if (lo<=sl) if d>0 else (hi>=sl): return (sl-entry)*d
if (hi>=tp) if d>0 else (lo<=tp): return (tp-entry)*d
hw=max(hw,hi) if d>0 else min(hw,lo)
profit=(hw-entry)*d
ph=0 if profit<_TRAIL_START*atr_eff else (1 if profit<_LOCK_START*atr_eff else 2)
if ph<rank: ph=rank
rank=ph
if ph==1:
cand=hw-d*_MULT*atr_eff
if profit>=_BE*atr_eff:
cand=max(cand,entry) if d>0 else min(cand,entry)
sl=max(sl,cand) if d>0 else min(sl,cand)
elif ph==2:
tm=max(_LOCK_MIN,_MULT*_LOCK_SCALE)
cand=hw-d*tm*atr_eff
cand=max(cand,entry) if d>0 else min(cand,entry)
sl=max(sl,cand) if d>0 else min(sl,cand)
return (C[end]-entry)*d
def st(v):
if not v: return "n=0"
n=len(v); w=sum(1 for x in v if x>0)
g=sum(x for x in v if x>0); ls=-sum(x for x in v if x<0)
return (f"n={n:>5} WR={100*w/n:>3.0f}% ØPts={sum(v)/n:+.4f} "
f"PF={(g/ls if ls>0 else 99):>4.2f} Worst={min(v):+.3f} ΣPts={sum(v):+.1f}")
def main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 80000
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))]
# Signale (raw ATR, identisch für alle Floor-Varianten)
TH=_REVERSAL_STRETCH
SIG={}
for lbl,a,b in halves:
out=[]
for i in range(max(a,_N_BARS), min(b,len(C)-_MAXH-1)):
atr=AT[i]
if not atr or atr<=0: continue
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 d: out.append((i,d,atr))
SIG[lbl]=out
print("="*96)
print(f" ATR-Floor im Exit — {sym} M5 ({len(C)} Bars) Live-Exit (SL2 · start0,3 · mult1,5 · BE1,3)")
print("="*96)
for lbl,_,_ in halves:
sig=SIG[lbl]
bind12=100*sum(1 for _,_,a in sig if a<0.12)/max(1,len(sig))
bind06=100*sum(1 for _,_,a in sig if a<0.06)/max(1,len(sig))
print(f"\n{lbl}: {len(sig)} Signale · ATR<0,12 bei {bind12:.0f}% · ATR<0,06 bei {bind06:.0f}%")
for floor in (0.0, 0.06, 0.12, 0.25):
v=[sim(C[i],d,max(atr,floor),H,L,C,i+1) for (i,d,atr) in sig]
tag=" ← LIVE" if floor==0.12 else ""
print(f" Floor {floor:4.2f} {st(v)}{tag}")
if __name__=="__main__":
main()