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:
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Misst vier Verbesserungs-Ideen in einem Lauf (Live-Konfig: M5 + M30-Filter):
|
||||
A) Multi-TF-Konfluenz: Edge wenn nur M30 vs M30+H1 einig
|
||||
B) Exit nach Qualitaet: MAE/MFE (max Gegen-/Mitlauf in ATR) je Pullback-Bucket
|
||||
C) Tageszeit-Edge: Edge je Berliner Stunde
|
||||
D) Vola-Gate: Edge je ATR-Band
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import MetaTrader5 as mt5
|
||||
from core.wave_rec import (WaveRecommender, _atr, _ema_last,
|
||||
_EMA_FAST, _EMA_SLOW, _N_BARS, _HTF_DEADBAND)
|
||||
|
||||
_TF = {"M1": mt5.TIMEFRAME_M1, "M5": mt5.TIMEFRAME_M5, "M15": mt5.TIMEFRAME_M15,
|
||||
"M30": mt5.TIMEFRAME_M30, "H1": mt5.TIMEFRAME_H1}
|
||||
_BERLIN = timezone(timedelta(hours=2)) # Juni = CEST (broker-epoch ist UTC+3 → -3+2)
|
||||
|
||||
|
||||
class _NeutralTU:
|
||||
def snapshot(self): return {"intervals": {}}
|
||||
|
||||
|
||||
def _ema_series(vals, period):
|
||||
k = 2.0/(period+1); out=[]; e=vals[0]
|
||||
for i,v in enumerate(vals):
|
||||
e = v if i==0 else v*k + e*(1-k); out.append(e)
|
||||
return out
|
||||
|
||||
|
||||
def _atr_series(H,L,C,period=14):
|
||||
trs=[0.0]
|
||||
for i in range(1,len(C)):
|
||||
trs.append(max(H[i]-L[i], abs(H[i]-C[i-1]), abs(L[i]-C[i-1])))
|
||||
out=[]
|
||||
for i in range(len(C)):
|
||||
w=trs[max(1,i-period+1):i+1]; out.append(sum(w)/len(w) if w else None)
|
||||
return out
|
||||
|
||||
|
||||
def _rep(name, rets):
|
||||
if not rets: print(f" {name:<20} -"); return
|
||||
n=len(rets); win=sum(1 for x in rets if x>0)
|
||||
print(f" {name:<20} n={n:>4} Treffer={100*win/n:>3.0f}% Oe-Edge={sum(rets)/n:+.4f}")
|
||||
|
||||
|
||||
def _sign_at(ts, T, Ef, Es, ATR, dead):
|
||||
lo,hi,idx=0,len(T)-1,-1
|
||||
while lo<=hi:
|
||||
m=(lo+hi)//2
|
||||
if T[m]<=ts: idx=m; lo=m+1
|
||||
else: hi=m-1
|
||||
if idx<_EMA_SLOW or ATR[idx] is None or ATR[idx]<=0: return 0
|
||||
d=Ef[idx]-Es[idx]
|
||||
return 0 if abs(d)<dead*ATR[idx] else (1 if d>0 else -1)
|
||||
|
||||
|
||||
def main():
|
||||
n_bars = int(sys.argv[1]) if len(sys.argv)>1 else 8000
|
||||
K = 10
|
||||
if not mt5.initialize(): print("init", mt5.last_error()); sys.exit(1)
|
||||
sym=None
|
||||
for c in ("SpotCrude","USOIL","WTI","XTIUSD"):
|
||||
if mt5.symbol_info(c): sym=c; break
|
||||
sym=sym or "SpotCrude"
|
||||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n_bars+_N_BARS+K+5)
|
||||
m30b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M30, 0, n_bars//6+400)
|
||||
h1b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_H1, 0, n_bars//12+400)
|
||||
mt5.shutdown()
|
||||
if bars is None or m30b is None or h1b is None: print("Bars fehlen"); sys.exit(1)
|
||||
|
||||
T=[int(b["time"]) for b in bars]
|
||||
H=[float(b["high"]) for b in bars]; L=[float(b["low"]) for b in bars]; C=[float(b["close"]) for b in bars]
|
||||
def prep(bb):
|
||||
t=[int(x["time"]) for x in bb]; h=[float(x["high"]) for x in bb]
|
||||
l=[float(x["low"]) for x in bb]; c=[float(x["close"]) for x in bb]
|
||||
return t, _ema_series(c,_EMA_FAST), _ema_series(c,_EMA_SLOW), _atr_series(h,l,c)
|
||||
m30 = prep(m30b); h1 = prep(h1b)
|
||||
|
||||
w=WaveRecommender(_NeutralTU(), mt5.TIMEFRAME_M5)
|
||||
confl={"nur M30":[], "M30+H1 einig":[]}
|
||||
mae={"near<0":[], "0-0.3":[], "0.3-1":[], "1-2":[], "2+":[]}
|
||||
mfe={k:[] for k in mae}
|
||||
by_hour={}; atr_vals=[]; rows=[]
|
||||
for i in range(_N_BARS, len(C)-K):
|
||||
wc=C[i-_N_BARS:i]; wh=H[i-_N_BARS:i]; wl=L[i-_N_BARS:i]
|
||||
atr=_atr(wh,wl,wc)
|
||||
if not atr or atr<=0: continue
|
||||
ef=_ema_last(wc,_EMA_FAST); es=_ema_last(wc,_EMA_SLOW)
|
||||
m=_sign_at(T[i], *m30, _HTF_DEADBAND)
|
||||
rec,_=w._build(ef,es,C[i-1],atr,"M5",5,htf_trend=m)
|
||||
if rec["signal"]=="WARTEN": continue
|
||||
d=1 if rec["signal"]=="LONG" else -1
|
||||
fwd=C[i+K]-C[i]; r=fwd*d
|
||||
rows.append((r,atr,T[i]))
|
||||
# A) Konfluenz
|
||||
h=_sign_at(T[i], *h1, _HTF_DEADBAND)
|
||||
(confl["M30+H1 einig"] if h==d else confl["nur M30"]).append(r)
|
||||
# B) MAE/MFE in ATR
|
||||
seg_h=H[i+1:i+K+1]; seg_l=L[i+1:i+K+1]
|
||||
if d>0: adv=(min(seg_l)-C[i])/atr; fav=(max(seg_h)-C[i])/atr
|
||||
else: adv=(C[i]-max(seg_h))/atr; fav=(C[i]-min(seg_l))/atr
|
||||
near=(C[i-1]-es)/atr*d
|
||||
b = "near<0" if near<0 else "0-0.3" if near<0.3 else "0.3-1" if near<1 else "1-2" if near<2 else "2+"
|
||||
mae[b].append(adv); mfe[b].append(fav)
|
||||
# C) Stunde
|
||||
hr=datetime.fromtimestamp(T[i]-10800, timezone.utc).astimezone(_BERLIN).hour
|
||||
by_hour.setdefault(hr,[]).append(r)
|
||||
atr_vals.append((atr,r))
|
||||
|
||||
print("="*60); print(f" {sym} M5+M30 n_bars={len(C)} Vorlauf={K}"); print("="*60)
|
||||
print("\nA) Multi-TF-Konfluenz:")
|
||||
_rep("nur M30", confl["nur M30"]); _rep("M30+H1 einig", confl["M30+H1 einig"])
|
||||
print("\nB) Exit: MAE/MFE (in ATR) je Pullback-Bucket [MAE=Gegenlauf, MFE=Mitlauf]:")
|
||||
for k in ["near<0","0-0.3","0.3-1","1-2","2+"]:
|
||||
a=mae[k]; f=mfe[k]
|
||||
if a: print(f" {k:<8} n={len(a):>4} Oe-MAE={sum(a)/len(a):+.2f} Oe-MFE={sum(f)/len(f):+.2f} MFE/|MAE|={ (sum(f)/len(f))/abs(sum(a)/len(a)+1e-9):.2f}")
|
||||
print("\nC) Tageszeit-Edge (Berliner Stunde):")
|
||||
for hr in sorted(by_hour):
|
||||
_rep(f"{hr:02d}:00", by_hour[hr])
|
||||
print("\nD) Vola-Gate (ATR-Terzile):")
|
||||
atr_vals.sort()
|
||||
n=len(atr_vals); t1=atr_vals[:n//3]; t2=atr_vals[n//3:2*n//3]; t3=atr_vals[2*n//3:]
|
||||
for nm,seg in [("niedrig ATR",t1),("mittel ATR",t2),("hoch ATR",t3)]:
|
||||
rr=[r for _,r in seg]; lo=seg[0][0]; hi=seg[-1][0]
|
||||
_rep(f"{nm} ({lo:.2f}-{hi:.2f})", rr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user