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

103 lines
4.6 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
"""Time-Stop-Messung (Track B, Exit-Hebel): Trade nach N Bars schließen, wenn er
bis dahin keinen Fortschritt gemacht hat (profit < thr×ATR) — Whipsaw-Opfer im Chop
früh raus, statt bis SL/Trailing zu bluten. Varianten N ∈ {6,12,24,48} M5-Bars ×
thr ∈ {0.0, 0.3}, gegen die Live-Exit-Baseline (SL 2×ATR + Trailing + BE), über
2 History-Hälften. Einbauen 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; _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
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, ts_n=None, ts_thr=0.0):
"""Live-Phasen-Exit + optionaler Time-Stop bei Bar j0+ts_n (kein Fortschritt)."""
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 (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
if ts_n is not None and j-j0>=ts_n and profit<ts_thr*atr:
return (C[j]-entry)*d/atr # Time-Stop: kein Fortschritt → raus
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 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"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
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; TH=_REVERSAL_STRETCH
SIG={}
for lbl,a,b in (("H1 (alt)",_N_BARS,mid),("H2 (neu)",mid,len(C))):
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
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))
SIG[lbl]=out
print("="*94)
print(f" Time-Stop — {sym} M5 ({len(C)} Bars) Exit ohne Fortschritt nach N Bars schließen")
print("="*94)
for lbl in SIG:
print(f"\n{lbl} ({len(SIG[lbl])} Signale):")
base=[sim(C[i],d,atr,H,L,C,i+1) for (i,d,atr) in SIG[lbl]]
print(f" Baseline (Live-Exit) {st(base)}")
for N in (6,12,24,48):
for thr in (0.0,0.3):
v=[sim(C[i],d,atr,H,L,C,i+1,ts_n=N,ts_thr=thr) for (i,d,atr) in SIG[lbl]]
print(f" N={N:>2} thr={thr:.1f}×ATR {st(v)}")
print("\n Einbauen nur, wenn eine Variante in BEIDEN Hälften ΣR UND Worst verbessert.")
if __name__=="__main__":
main()