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>
This commit is contained in:
Axel Hocks
2026-07-24 08:29:23 +02:00
co-authored by Claude Opus 4.8
commit 75d28827e8
104 changed files with 21059 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Verlässlichkeit der Bounce-Anzeige ('aktiv'): Wenn _bounce_one(TF)=active feuert
(überdehnt + Winkel gedreht), wie oft bewegt sich der Kurs in Bounce-Richtung?
Aufgeschlüsselt nach TF (M1/M5/M15/M30) und nach H1-Regime (mit/gegen/flach zur
Bounce-Richtung). Nur ONSET (Übergang nicht-aktiv→aktiv) = unabhängige Ereignisse.
"""
import sys, bisect
import MetaTrader5 as mt5
from core.wave_rec import WaveRecommender, _atr, _ema_last, _EMA_FAST, _EMA_SLOW, _HTF_DEADBAND
K = 10 # Vorlauf in Bars der jeweiligen TF
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 main():
n=int(sys.argv[1]) if len(sys.argv)>1 else 40000
mt5.initialize()
sym=None
for c in ("SpotCrude","USOIL","WTI","XTIUSD"):
if mt5.symbol_info(c): sym=c; break
# H1-Regime vorbereiten
h1=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_H1,0,20000)
hT=[int(b["time"]) for b in h1]; hc=[float(b["close"]) for b in h1]
hh=[float(b["high"]) for b in h1]; hl=[float(b["low"]) for b in h1]
hEf=_ema_series(hc,_EMA_FAST); hEs=_ema_series(hc,_EMA_SLOW); hA=_atr_series(hh,hl,hc)
def h1_sign(ts):
idx=bisect.bisect_right(hT,ts)-1
if idx<_EMA_SLOW or hA[idx] is None or hA[idx]<=0: return 0
d=hEf[idx]-hEs[idx]
return 0 if abs(d)<_HTF_DEADBAND*hA[idx] else (1 if d>0 else -1)
tfs=[("M1",mt5.TIMEFRAME_M1),("M5",mt5.TIMEFRAME_M5),
("M15",mt5.TIMEFRAME_M15),("M30",mt5.TIMEFRAME_M30)]
print("="*86)
print(f" Bounce-Anzeige 'aktiv' — Verlässlichkeit je TF & H1-Regime ({sym}, Vorlauf {K} Bars)")
print(" Treffer = Kurs bewegt sich in Bounce-Richtung · Ø = Ø-Bewegung in ATR")
print("="*86)
for lbl,tf in tfs:
r=mt5.copy_rates_from_pos(sym,tf,0,n)
if r is None or len(r)<200: print(f"{lbl}: zu wenig Bars"); continue
T=[int(b["time"]) for b in r]; H=[float(b["high"]) for b in r]
L=[float(b["low"]) for b in r]; C=[float(b["close"]) for b in r]
buckets={"mit":[], "gegen":[], "flach":[], "alle":[]}
prev=False
for i in range(120, len(C)-K):
wc=C[i-119:i+1]; wh=H[i-119:i+1]; wl=L[i-119:i+1]
atr=_atr(wh,wl,wc)
if not atr or atr<=0: prev=False; continue
b=WaveRecommender._bounce_one(wc,atr)
active = bool(b and b["state"]=="active")
if active and not prev:
d=1 if b["dir"]=="LONG" else -1
fwd=(C[i+K]-C[i])*d/atr # in ATR, Bounce-Richtung
reg=h1_sign(T[i])
cls = "mit" if reg==d else "gegen" if reg==-d else "flach"
buckets[cls].append(fwd); buckets["alle"].append(fwd)
prev=active
print(f"\n{lbl} ({len(buckets['alle'])} aktive Bounce-Signale)")
for cls in ("alle","mit","gegen","flach"):
v=buckets[cls]
if not v: print(f" {cls:<6} -"); continue
hit=100*sum(1 for x in v if x>0)/len(v)
print(f" {cls:<6} n={len(v):>4} Treffer={hit:>3.0f}% Ø={sum(v)/len(v):+.2f}×ATR")
mt5.shutdown()
print("\n 'mit' = H1-Trend in Bounce-Richtung (Trend-Fortsetzung)")
print(" 'gegen' = H1-Trend GEGEN den Bounce (Gegen-Trend-Fade)")
print(" 'flach' = H1 neutral (reine Range/Mean-Reversion)")
if __name__=="__main__":
main()