Files
AH-Oil-Trader/backtest_momentum.py
T
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

93 lines
4.0 KiB
Python
Raw 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
"""Momentum-Continuation als AUTONOMER Setup-Kandidat (2026-07-17): Einstieg in
Richtung eines starken, frischen Momentum-Schubs (Kurs ≥ X×ATR über N Bars gelaufen)
— Trend-Persistenz-Wette, andere Klasse als der Squeeze (Kompression→Ausbruch).
Sequentielle 1-Positions-Sim (wie live), Exit = SL 2,0×ATR + Trailing 1,5 + BE 1,3.
Entry = FRISCHER Schub (mom_N kreuzt X, war die N Bars davor drunter → kein Einstieg
tief in einen ausgelaufenen Move). Kosten = Bar-Spread/ATR. 2 Halbjahre.
Robustheit über N (6/12/24) × X (1,0/1,5/2,0).
Automatisieren nur, wenn eine Kombi ØR & PF in BEIDEN Hälften klar positiv hält
(Maßstab: Squeeze ØR +0,14…+0,23).
"""
import sys
import MetaTrader5 as mt5
_MAXH = 288; _ATRMIN = 0.12; _COOL = 6
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 st(Rs):
if not Rs: return None
n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs)
up = sum(x for x in Rs if x > 0); dn = -sum(x for x in Rs if x < 0)
return dict(n=n, wr=100*w/n, oR=s/n, pf=(up/dn if dn > 0 else 9.99), sum=s)
def run(H, L, C, A, SP, lo, hi, N, X):
def cost(i, atr): return (SP[i] if SP[i] > 0 else 0.0225)/atr
Rs = []; i = max(lo, N+1)
while i < hi:
atr = A[i]
if not atr or atr < _ATRMIN or i-N < 0:
i += 1; continue
mom = (C[i]-C[i-N])/atr
momP = (C[i-1]-C[i-1-N])/atr if i-1-N >= 0 else 0.0
# frischer Schub: jetzt |mom|≥X, letzten Bar noch drunter
d = 0
if mom >= X and momP < X: d = 1
elif mom <= -X and momP > -X: d = -1
if d == 0:
i += 1; continue
atr = max(atr, _ATRMIN); entry = C[i]
eff = entry - d*2.0*atr; hw = entry
end = min(i+_MAXH, len(C)-1); exit_px = C[end]; exit_j = end
for j in range(i+1, end+1):
hj, lj = H[j], L[j]
if (lj <= eff) if d > 0 else (hj >= eff): exit_px = eff; exit_j = j; break
hw = max(hw, hj) if d > 0 else min(hw, lj)
prof = (C[j]-entry)*d
if prof >= 0.3*atr:
cand = hw - d*1.5*atr
if prof >= 1.3*atr: cand = max(cand, entry) if d > 0 else min(cand, entry)
eff = max(eff, cand) if d > 0 else min(eff, cand)
Rs.append((exit_px-entry)*d/atr - cost(i, atr))
i = exit_j + _COOL
return Rs
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
mt5.initialize()
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") if mt5.symbol_info(c)), None)
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n+_MAXH+30)
point = mt5.symbol_info(sym).point; 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]
A = _atr_series(H, L, C); N_ = len(C); mid = N_//2
print("="*84)
print(f" Momentum-Continuation — {sym} M5 (seq. Sim · Exit SL2/Trail1,5/BE1,3 · Echtkosten)")
print(f" Entry = frischer Momentum-Schub (mom_N kreuzt ±X×ATR). 2 Halbjahre.")
print("="*84)
print(f" {'N/X':>8} | {'H1: n ØR PF':>22} | {'H2: n ØR PF':>22}")
for N in (6, 12, 24):
for X in (1.0, 1.5, 2.0):
s1 = st(run(H, L, C, A, SP, 0, mid, N, X))
s2 = st(run(H, L, C, A, SP, mid, N_, N, X))
def f(s): return f"{s['n']:>4} {s['oR']:+.3f} {s['pf']:>5.2f}" if s else " "
ok = "OK" if (s1 and s2 and s1['oR'] > 0 and s2['oR'] > 0
and s1['pf'] > 1 and s2['pf'] > 1) else ""
print(f" N={N:>2} X={X:.1f} | {f(s1):>22} | {f(s2):>22} {ok}")
print(f"\n Automatisieren nur bei ØR>0 & PF>1 in BEIDEN Hälften (Squeeze-Maßstab +0,14…+0,23).")
if __name__ == "__main__":
main()