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

130 lines
5.7 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
"""Schlüssel-Messungen mit ECHTEN Kosten nachgerechnet (Track B):
Kosten je Trade = Bar-`spread`(Signal-Bar) / ATR(Signal-Bar) in R (Roundtrip = 1×Spread;
Kommission laut DB ~0). Bisher pauschal 0,1×ATR — real Ø 0,265, nachts bis 0,5.
(1) Netto-R je Berlin-Stunde (beide Hälften) — welche Stunden tragen echt?
(2) Gate-Politiken: kein Gate vs. Nacht-Blackout 07 vs. 07+12 — beide Hälften.
(3) breakout_k 0,3/0,5/1,0 mit echten Kosten (Kosten am Bestätigungs-Bar).
"""
import sys, datetime as dt
from zoneinfo import ZoneInfo
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; _TRAILON=0.3; _TPTRAIL=0.5; _ATRMIN=0.12; _SL_ATR=2.0; _CONFIRM_W=12
_BROKER_OFF=3*3600; _BERLIN=ZoneInfo("Europe/Berlin")
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 simulate(entry,d,atr,sl,H,L,C,j0):
eff=sl; hw=entry; trail=False
end=min(j0+_MAXH,len(C)-1); exit_px=C[end]
for j in range(j0,end+1):
hi,lo=H[j],L[j]
if (lo<=eff) if d>0 else (hi>=eff): return (eff-entry)*d/atr
hw=max(hw,hi) if d>0 else min(hw,lo)
if (C[j]-entry)*d>=_TRAILON*atr: trail=True
if trail:
lock=hw-d*_TPTRAIL*atr
eff=max(eff,lock) if d>0 else min(eff,lock)
return (exit_px-entry)*d/atr
def bhour(raw):
return dt.datetime.fromtimestamp(int(raw)-_BROKER_OFF,tz=dt.timezone.utc).astimezone(_BERLIN).hour
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]; T=[int(b["time"]) for b in bars]
SP=[float(b["spread"])*point for b in bars] # echter Spread je Bar
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
def cost(i,atr): return (SP[i] if SP[i]>0 else 0.0225)/atr # in R
def collect(a,b):
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 not d: continue
R=simulate(C[i],d,atr,C[i]-d*_SL_ATR*atr,H,L,C,i+1)
out.append((i,d,atr,bhour(T[i]),R-cost(i,atr)))
return out
halves=[("H1 (alt)",_N_BARS,mid),("H2 (neu)",mid,len(C))]
EV={lbl:collect(a,b) for lbl,a,b in halves}
print("="*92)
print(f" ECHTE Kosten (Spread je Signal-Bar) — {sym} M5 ({len(C)} Bars)")
print("="*92)
# (1) Netto je Stunde, beide Hälften nebeneinander
print("\n(1) Netto-R je Berlin-Stunde (echte Kosten):")
print(f" {'Std':>3} {'H1 netto':>10} {'H2 netto':>10} beide negativ?")
night_neg=[]
for h in range(24):
cells=[]; neg=True
for lbl,_,_ in halves:
v=[x[4] for x in EV[lbl] if x[3]==h]
m=sum(v)/len(v) if v else 0.0
cells.append(f"{m:+.3f}(n{len(v)})")
if m>=0: neg=False
if neg: night_neg.append(h)
print(f" {h:>3} {cells[0]:>12} {cells[1]:>12} {'← NEG in beiden' if neg else ''}")
print(f" → in BEIDEN Hälften netto negativ: {night_neg}")
# (2) Gate-Politiken
print("\n(2) Gesamt-Netto-R je Politik:")
POL=[("Kein Gate",set()),("Nacht 07",set(range(0,8))),
("Nacht 07 + 12",set(range(0,8))|{12}),
("nur robust-negative",set(night_neg))]
for name,blk in POL:
cells=[]
for lbl,_,_ in halves:
kept=[x[4] for x in EV[lbl] if x[3] not in blk]
cells.append(f"ΣR{sum(kept):+8.0f} (n{len(kept)})")
print(f" {name:<22} {cells[0]:>24} {cells[1]:>24}")
# (3) breakout_k mit echten Kosten
print("\n(3) breakout_k mit echten Kosten (Kosten am Bestätigungs-Bar):")
for lbl,a,b in halves:
sigs=[(i,d,atr) for (i,d,atr,_,_) in EV[lbl]]
line=f" {lbl}: "
for k in (0.3,0.5,1.0):
Rs=[]
for (i,d,atr) in sigs:
e0=C[i]; level=e0+d*k*atr; invalid=e0-d*k*atr; je=None
for j in range(i+1, min(i+1+_CONFIRM_W, len(C)-_MAXH-1)):
if d>0:
if L[j]<=invalid: break
if H[j]>=level: je=j; break
else:
if H[j]>=invalid: break
if L[j]<=level: je=j; break
if je is None: continue
Rs.append(simulate(level,d,atr,level-d*_SL_ATR*atr,H,L,C,je+1)-cost(je,atr))
line+=f"k={k}: ΣR{sum(Rs):+7.0f}(n{len(Rs)}{sum(Rs)/max(1,len(Rs)):+.3f}) "
print(line)
if __name__=="__main__":
main()