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,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Misst den Effekt eines Higher-TF-Trendfilters auf die Empfehlung.
|
||||
|
||||
Vergleicht den Richtungs-Edge der echten _build-Logik OHNE Filter gegen
|
||||
MIT Filter: Signal wird verworfen, wenn der uebergeordnete Trend (H1 bzw. M30,
|
||||
EMA12 vs EMA50) klar dagegen steht (Totband in xATR der HTF).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
import MetaTrader5 as mt5
|
||||
from core.wave_rec import (WaveRecommender, _atr, _ema_last,
|
||||
_EMA_FAST, _EMA_SLOW, _N_BARS)
|
||||
|
||||
_TF = {"M1": mt5.TIMEFRAME_M1, "M5": mt5.TIMEFRAME_M5, "M15": mt5.TIMEFRAME_M15,
|
||||
"M30": mt5.TIMEFRAME_M30, "H1": mt5.TIMEFRAME_H1}
|
||||
|
||||
|
||||
class _NeutralTU:
|
||||
def snapshot(self):
|
||||
return {"intervals": {}}
|
||||
|
||||
|
||||
def _ema_series(vals, period):
|
||||
"""Komplette EMA-Reihe (gleiche Laenge wie vals)."""
|
||||
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.0 - k)
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
|
||||
def _rep(name, rets):
|
||||
if not rets:
|
||||
print(f" {name:<16} keine Signale"); return
|
||||
n = len(rets); win = sum(1 for x in rets if x > 0)
|
||||
print(f" {name:<16} n={n:>4} Treffer={100*win/n:>3.0f}% "
|
||||
f"Oe-Edge={sum(rets)/n:+.4f} Summe={sum(rets):+.2f}")
|
||||
|
||||
|
||||
def main():
|
||||
base_lbl = (sys.argv[1].upper() if len(sys.argv) > 1 else "M5")
|
||||
htf_lbl = (sys.argv[2].upper() if len(sys.argv) > 2 else "H1")
|
||||
n_bars = int(sys.argv[3]) if len(sys.argv) > 3 else 4000
|
||||
dead = float(sys.argv[4]) if len(sys.argv) > 4 else 0.10 # HTF-Totband xATR
|
||||
K = 10
|
||||
tf, htf = _TF[base_lbl], _TF[htf_lbl]
|
||||
|
||||
if not mt5.initialize():
|
||||
print("MT5-Init fehlgeschlagen:", mt5.last_error()); sys.exit(1)
|
||||
sym = None
|
||||
for cand in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||||
if mt5.symbol_info(cand):
|
||||
sym = cand; break
|
||||
sym = sym or "SpotCrude"
|
||||
bars = mt5.copy_rates_from_pos(sym, tf, 0, n_bars + _N_BARS + K + 5)
|
||||
hbars = mt5.copy_rates_from_pos(sym, htf, 0, n_bars // 2 + 300)
|
||||
mt5.shutdown()
|
||||
if bars is None or hbars is None:
|
||||
print("Zu wenige Bars."); 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]
|
||||
|
||||
hT = [int(b["time"]) for b in hbars]
|
||||
hC = [float(b["close"]) for b in hbars]
|
||||
hH = [float(b["high"]) for b in hbars]
|
||||
hL = [float(b["low"]) for b in hbars]
|
||||
hEf = _ema_series(hC, _EMA_FAST)
|
||||
hEs = _ema_series(hC, _EMA_SLOW)
|
||||
|
||||
def htf_trend(ts):
|
||||
"""Vorzeichen des HTF-Trends zum Zeitpunkt ts: +1 auf, -1 ab, 0 flach."""
|
||||
# letzte HTF-Bar mit time <= ts
|
||||
lo, hi = 0, len(hT) - 1
|
||||
idx = -1
|
||||
while lo <= hi:
|
||||
mid = (lo + hi) // 2
|
||||
if hT[mid] <= ts:
|
||||
idx = mid; lo = mid + 1
|
||||
else:
|
||||
hi = mid - 1
|
||||
if idx < _EMA_SLOW:
|
||||
return 0
|
||||
atr = _atr(hH[:idx+1], hL[:idx+1], hC[:idx+1])
|
||||
if not atr:
|
||||
return 0
|
||||
d = hEf[idx] - hEs[idx]
|
||||
if abs(d) < dead * atr:
|
||||
return 0
|
||||
return 1 if d > 0 else -1
|
||||
|
||||
w = WaveRecommender(_NeutralTU(), tf)
|
||||
raw = {"LONG": [], "SHORT": []}
|
||||
flt = {"LONG": [], "SHORT": []}
|
||||
dropped = 0
|
||||
for i in range(_N_BARS, len(C) - K):
|
||||
win_c = C[i-_N_BARS:i]; win_h = H[i-_N_BARS:i]; win_l = L[i-_N_BARS:i]
|
||||
atr = _atr(win_h, win_l, win_c)
|
||||
if not atr or atr <= 0:
|
||||
continue
|
||||
ef = _ema_last(win_c, _EMA_FAST); es = _ema_last(win_c, _EMA_SLOW)
|
||||
rec, _ = w._build(ef, es, C[i-1], atr, base_lbl, 5)
|
||||
sig = rec["signal"]
|
||||
if sig == "WARTEN":
|
||||
continue
|
||||
fwd = C[i+K] - C[i]
|
||||
r = fwd if sig == "LONG" else -fwd
|
||||
raw[sig].append(r)
|
||||
ht = htf_trend(T[i])
|
||||
d = 1 if sig == "LONG" else -1
|
||||
if ht != 0 and ht != d: # HTF steht klar dagegen → verwerfen
|
||||
dropped += 1
|
||||
continue
|
||||
flt[sig].append(r)
|
||||
|
||||
print("=" * 66)
|
||||
print(f" HTF-Filter — {sym} Basis={base_lbl} Filter={htf_lbl} "
|
||||
f"Totband={dead}xATR Vorlauf={K}")
|
||||
print("=" * 66)
|
||||
print("OHNE Filter:")
|
||||
_rep("LONG", raw["LONG"]); _rep("SHORT", raw["SHORT"])
|
||||
_rep("ALLE", raw["LONG"] + raw["SHORT"])
|
||||
print(f"MIT Filter (verworfen: {dropped}):")
|
||||
_rep("LONG", flt["LONG"]); _rep("SHORT", flt["SHORT"])
|
||||
_rep("ALLE", flt["LONG"] + flt["SHORT"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user