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

118 lines
5.4 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
"""Prüft die TRAILING-Logik und Optimierungen über 2 Zeiträume (Track B).
Bildet die ECHTE Phasen-Mechanik nach (Init→Trail→Lock, HW-Ratsche, Breakeven-Boden)
und variiert die Schlüsselparameter:
- trail_start (ab welchem Profit der SL zu ratchen beginnt; live 0,3×ATR)
- mult (Trail-Abstand HWmult×ATR; live M5=1,5)
plus Baseline 'Fix-Stop' (SL 2×ATR, kein Trailing) und 'Breakeven-only'.
Signal-Set = Reversal + Trend (wie backtest_hourly). Metrik: Ø-R/PF/WR/ΣR je Hälfte.
Robust nur, wenn eine Variante in BEIDEN Hälften besser ist.
"""
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; _TP_INIT=3.5; _LOCK_START=3.5; _LOCK_SCALE=0.6; _LOCK_MIN=1.2
_BE=1.3 # Breakeven-Boden (live)
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, trail_start, mult, breakeven=_BE, trail=True):
"""Echte Phasen-Trailing-Sim. trail=False → fixer Stop entrymult×ATR (kein Ratchen)."""
sl = entry - d*mult*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]
hit_sl = (lo<=sl) if d>0 else (hi>=sl)
hit_tp = (hi>=tp) if d>0 else (lo<=tp)
if hit_sl: return (sl-entry)*d/atr # pessimistisch: SL vor TP im selben Bar
if hit_tp: return (tp-entry)*d/atr
hw = max(hw,hi) if d>0 else min(hw,lo)
if not trail: continue
profit=(hw-entry)*d
ph = 0 if profit<trail_start*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>=breakeven*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 st(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)
return f"WR={100*w/n:>3.0f}% Ø-R={sum(Rs)/n:+.3f} PF={(g/ls if ls>0 else 99):>4.2f} Worst={min(Rs):+.2f} ΣR={sum(Rs):+.0f}"
def signals(H,L,C,ES,EF,AT,lo,hi,step):
TH=_REVERSAL_STRETCH; out=[]
for i in range(max(lo,_N_BARS), min(hi,len(C)-_MAXH-1), step):
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 d: out.append((i,d,atr))
return out
def main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 60000
step=int(sys.argv[2]) if len(sys.argv)>2 else 2
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("="*94)
print(f" TRAILING-Optimierung — {sym} M5 ({len(C)} Bars, step {step}) Signal=Trend+Reversal")
print(f" Live-Basis: trail_start=0,3 · mult=1,5 (M5) · Breakeven=1,3 · Lock ab 3,5×ATR")
print("="*94)
SIG={lbl: signals(H,L,C,ES,EF,AT,a,b,step) for lbl,a,b in halves}
for lbl,_,_ in halves: print(f" {lbl}: {len(SIG[lbl])} Signale")
variants = [("FIX-Stop 2,0 (kein Trail)", dict(trail_start=99, mult=2.0, trail=False)),
("Breakeven-only (mult=2,0)", dict(trail_start=_BE, mult=2.0, breakeven=_BE)),
("LIVE start0,3 mult1,5", dict(trail_start=0.3, mult=1.5)),
("start0,6 mult1,5", dict(trail_start=0.6, mult=1.5)),
("start1,0 mult1,5", dict(trail_start=1.0, mult=1.5)),
("start0,3 mult2,0", dict(trail_start=0.3, mult=2.0)),
("start1,0 mult2,0", dict(trail_start=1.0, mult=2.0)),
("start0,3 mult2,5", dict(trail_start=0.3, mult=2.5)),
("start1,3 mult2,5", dict(trail_start=1.3, mult=2.5))]
for name,kw in variants:
print(f"\n{name}")
for lbl,_,_ in halves:
Rs=[sim(C[i],d,atr,H,L,C,i+1, **kw) for (i,d,atr) in SIG[lbl]]
print(f" {lbl} {st(Rs)}")
if __name__=="__main__":
main()