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

128 lines
5.8 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
"""S/R-Close-Empfehlung messen (Track B): Lohnt es, am nächsten gegenüberliegenden
S/R-Level zu SCHLIESSEN statt das Trailing laufen zu lassen?
S/R = Pivot-Hochs/-Tiefs (k Bars beidseitig) der letzten LOOKBACK Bars vor Entry.
Varianten (je LONG: nächster Pivot-High-Level ÜBER Entry als Ziel):
V1 Close am S/R-Ziel, sobald Level ≥0,3×ATR über Entry (hart)
V2 Close am S/R-Ziel nur wenn Level ≥1,0×ATR über Entry (nur „echte" Ziele)
BAS Live-Exit (SL 2,0 + Trailing 1,5 + BE 1,3 + Lock)
Kosten je Trade = echter Bar-Spread/ATR. 2 History-Hälften. Historie: Auto-Close an
S/R wurde früher schon einmal verworfen; hier Neu-Messung mit Echtkosten.
"""
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; _ATRMIN=0.12; _SL_ATR=2.0; _TRAILON=0.3; _MULT=1.5; _BE=1.3
_LOCK_START=3.5; _LOCK_SCALE=0.6; _LOCK_MIN=1.2; _TP_INIT=3.5
_PIV_K=3; _LOOKBACK=300
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 pivots(H,L,i0,i1):
"""Pivot-Hochs/-Tiefs in [i0,i1) (k Bars beidseitig bestätigt)."""
hi,lo=[],[]
for j in range(max(i0,_PIV_K), i1-_PIV_K):
if H[j]==max(H[j-_PIV_K:j+_PIV_K+1]): hi.append(H[j])
if L[j]==min(L[j-_PIV_K:j+_PIV_K+1]): lo.append(L[j])
return hi,lo
def sim(entry,d,atr,H,L,C,j0, sr_target=None):
"""Live-Exit; optional zusätzlich Voll-Close bei Berührung von sr_target."""
sl=entry-d*_SL_ATR*atr; tp=entry+d*_TP_INIT*atr
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/atr
if sr_target is not None and ((hi>=sr_target) if d>0 else (lo<=sr_target)):
return (sr_target-entry)*d/atr # Close am S/R-Ziel
if (hi>=tp) if d>0 else (lo<=tp): return (tp-entry)*d/atr
hw=max(hw,hi) if d>0 else min(hw,lo)
profit=(hw-entry)*d
ph=0 if profit<_TRAILON*atr else (1 if profit<_LOCK_START*atr else 2)
if ph<rank: ph=rank
rank=ph
if ph==1:
cand=hw-d*_MULT*atr
if profit>=_BE*atr:
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
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/atr
def stx(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}% Ø-R={sum(v)/n:+.3f} PF={(g/ls if ls>0 else 99):>4.2f} "
f"Worst={min(v):+.2f} ΣR={sum(v):+.0f}")
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
si=mt5.symbol_info(sym); point=si.point
bars=None
for req in (n,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]
SP=[float(b["spread"])*point 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; TH=_REVERSAL_STRETCH
print("="*94)
print(f" S/R-Close vs Trailing — {sym} M5 ({len(C)} Bars) Pivots k={_PIV_K}, Lookback {_LOOKBACK} ECHTE Kosten")
print("="*94)
for lbl,a,b in (("H1 (alt)",_N_BARS,mid),("H2 (neu)",mid,len(C))):
base=[]; v1=[]; v2=[]; n_t1=0; n_t2=0
for i in range(max(a,_N_BARS,_LOOKBACK), min(b,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
cost=(SP[i] if SP[i]>0 else 0.0225)/atr
entry=C[i]
phis,plos=pivots(H,L,i-_LOOKBACK,i)
if d>0:
cands=[p for p in phis if p>entry+0.3*atr]
tgt=min(cands) if cands else None
else:
cands=[p for p in plos if p<entry-0.3*atr]
tgt=max(cands) if cands else None
base.append(sim(entry,d,atr,H,L,C,i+1)-cost)
v1.append(sim(entry,d,atr,H,L,C,i+1,sr_target=tgt)-cost)
if tgt is not None: n_t1+=1
tgt2=tgt if (tgt is not None and abs(tgt-entry)>=1.0*atr) else None
v2.append(sim(entry,d,atr,H,L,C,i+1,sr_target=tgt2)-cost)
if tgt2 is not None: n_t2+=1
print(f"\n{lbl} ({len(base)} Signale · V1-Ziel bei {n_t1} · V2-Ziel bei {n_t2}):")
print(f" BASELINE Trailing {stx(base)}")
print(f" V1 Close@S/R (≥0,3×ATR) {stx(v1)}")
print(f" V2 Close@S/R (≥1,0×ATR) {stx(v2)}")
print("\n V gewinnt nur, wenn ΣR/PF in BEIDEN Hälften über der Baseline liegen.")
if __name__=="__main__":
main()