Files
AH-Oil-Trader/backtest_range.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
"""Range-Strategie messen (Track B, Echtkosten, 2 Hälften): Einstieg AM Level statt
mit dem Trend, Ausstieg am gegenüberliegenden Level — und braucht es dann noch Trailing?
Einstieg LONG: Kurs an Support S, P(Support bricht ABWÄRTS) < X (Level hält) → LONG.
Einstieg SHORT: Kurs an Resistance R, P(R bricht AUFWÄRTS) < X → SHORT.
P(break) aus dem kalibrierten Modell (engine._p_break), Merkmale in Bruchrichtung.
Ziel = gegenüberliegendes Level. SL = Level ∓ sl×ATR.
Exit-Varianten je Trade:
FIX nur festes Ziel (Level-zu-Level), Stop fix
FIX+TR Ziel + Trailing (nach Fortschritt Stop nachziehen)
TRAIL nur Trailing, KEIN festes Ziel (= brauchen wir das?)
Vergleich der ΣR (in ATR, Echtkosten) je Hälfte. Zeigt, ob Einstieg-am-Level trägt
UND ob Trailing dort noch etwas bringt.
"""
import sys, math
import MetaTrader5 as mt5
from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _N_BARS)
from core.engine import _p_break
_ATRMIN=0.12; _PIV_K=3; _LOOKBACK=300; _MAXH=200
_SL_ATR=1.5; _TRAILON=0.3; _MULT=1.5; _BE=1.0
_PB_X=0.60 # Level gilt als "hält", wenn P(break) < X
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, target=None, trail=False):
sl=entry-d*_SL_ATR*atr; hw=entry
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 target is not None and ((hi>=target) if d>0 else (lo<=target)):
return (target-entry)*d/atr
if trail:
hw=max(hw,hi) if d>0 else min(hw,lo)
profit=(hw-entry)*d
if profit>=_TRAILON*atr:
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)
return (C[end]-entry)*d/atr
def levels(H,L,i):
phis,plos=[],[]
for j in range(i-_LOOKBACK+_PIV_K, i-_PIV_K):
if H[j]==max(H[j-_PIV_K:j+_PIV_K+1]): phis.append(H[j])
if L[j]==min(L[j-_PIV_K:j+_PIV_K+1]): plos.append(L[j])
return phis,plos
def collect(a,b,H,L,C,EF,ES,AT,SP,point):
out={"FIX":[],"FIX+TR":[],"TRAIL":[]}; n_ent=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); cur=C[i]; cost=(SP[i] if SP[i]>0 else 0.0225)/atr
phis,plos=levels(H,L,i)
sup=max([p for p in plos if p<cur],default=None)
res=min([p for p in phis if p>cur],default=None)
# LONG an Support
for d,lvl,tgt in ((1,sup,res),(-1,res,sup)):
if lvl is None or tgt is None: continue
if abs(cur-lvl) > 0.15*atr: continue # nicht am Level
# P(Level bricht in Bruchrichtung bd) — bd = -d (Support bricht abwärts)
bd=-d
mom6=(C[i]-C[max(0,i-6)])/atr*bd
mom3=(C[i]-C[max(0,i-3)])/atr*bd
wt=1.0 if (EF[i]-ES[i])*bd>0 else 0.0
dist=abs(tgt-lvl)/atr
if _p_break(mom6,mom3,wt,dist) >= _PB_X: continue # Level bricht wahrscheinlich → kein Einstieg
n_ent+=1
out["FIX"].append(sim(cur,d,atr,H,L,C,i+1,target=tgt,trail=False)-cost)
out["FIX+TR"].append(sim(cur,d,atr,H,L,C,i+1,target=tgt,trail=True)-cost)
out["TRAIL"].append(sim(cur,d,atr,H,L,C,i+1,target=None,trail=True)-cost)
return out,n_ent
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}% Ø-R={sum(v)/n:+.3f} "
f"PF={(g/ls if ls>0 else 99):>4.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
print("="*88)
print(f" RANGE-Strategie (Einstieg AM Level, P(break)<{_PB_X}) — {sym} M5 Echtkosten, SL {_SL_ATR}×ATR")
print("="*88)
for lbl,a,b in (("H1 (alt)",_N_BARS,mid),("H2 (neu)",mid,len(C))):
d,ne=collect(a,b,H,L,C,EF,ES,AT,SP,point)
print(f"\n{lbl}: {ne} Einstiege am Level")
print(f" FIX (nur Level-Ziel) {st(d['FIX'])}")
print(f" FIX+TR (Ziel + Trailing) {st(d['FIX+TR'])}")
print(f" TRAIL (nur Trailing) {st(d['TRAIL'])}")
print("\n Trägt Einstieg-am-Level, wenn FIX/FIX+TR in BEIDEN Hälften positiv sind.")
print(" Trailing nötig? → nur wenn FIX+TR bzw. TRAIL klar über FIX liegt.")
if __name__=="__main__":
main()