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

105 lines
4.3 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
"""Opening Range Breakout (ORB) als Auto-Setup-Kandidat (2026-07-17, Recherche):
Box = Hoch/Tief der ersten K M5-Bars nach Session-Open; erster Ausbruch ±0,1×ATR
über/unter die Box (Fenster W Bars) wird gehandelt — one-shot je Open. Struktur-
Verwandter des validierten Squeeze (Box→Ausbruch), aber ZEIT-verankert.
Opens in BROKER-Zeit (UTC+3, US-DST-gekoppelt → US-Open konstant 16:30):
EU-Morgen 10:00 (≈ 09:00 Berlin) · US-Open 16:30 (≈ 15:30 Berlin).
Exit = Live-Modell (SL 2,0×ATR + Trailing 1,5 + BE 1,3). Kosten = Bar-Spread/ATR.
2 Halbjahre. Maßstab: Squeeze (ØR +0,14…+0,23 in BEIDEN Hälften).
"""
import sys, datetime as dt
import MetaTrader5 as mt5
_MAXH = 288; _ATRMIN = 0.12; _K_BRK = 0.1; _W = 48 # Breakout-Fenster 4 h
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 line(lbl, s):
if not s: return f" {lbl:<28} —"
return (f" {lbl:<28} n={s['n']:>4} WR={s['wr']:>3.0f}% ØR={s['oR']:+.3f} "
f"PF={s['pf']:>4.2f} ΣR={s['sum']:>+6.0f}")
def sim(entry, d, atr, H, L, C, j0):
eff = entry - d*2.0*atr; hw = entry
end = min(j0+_MAXH, len(C)-1); exit_px = C[end]
for j in range(j0, end+1):
hj, lj = H[j], L[j]
if (lj <= eff) if d > 0 else (hj >= eff): return None if False else ((eff-entry)*d/atr, j)
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)
return ((exit_px-entry)*d/atr, end)
def run(T, H, L, C, A, SP, lo, hi, open_h, open_m, K):
def cost(i, atr): return (SP[i] if SP[i] > 0 else 0.0225)/atr
Rs = []
i = lo
while i < hi:
t = dt.datetime.utcfromtimestamp(T[i])
if not (t.hour == open_h and t.minute == open_m):
i += 1; continue
if i+K+2 >= hi:
break
atr = max(A[i+K] or 0, _ATRMIN) # Floor wie live (Skip wäre Selektions-
# Artefakt: H1 hat 81 % Bars unter 0,12 → sonst fällt der EU-Morgen weg)
boxHi = max(H[i:i+K]); boxLo = min(L[i:i+K])
up = boxHi + _K_BRK*atr; dn = boxLo - _K_BRK*atr
hit = None
for j in range(i+K, min(i+K+_W, hi)):
if H[j] >= up: hit = (j, 1, up); break
if L[j] <= dn: hit = (j, -1, dn); break
if hit is None:
i += K; continue
j, d, lvl = hit
r, xj = sim(lvl, d, atr, H, L, C, j+1)
Rs.append(r - cost(j, atr))
i = xj + 1 # one-shot je Open, weiter nach Exit
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()
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]; SP = [float(b["spread"])*point for b in bars]
A = _atr_series(H, L, C); N = len(C); mid = N//2
print("="*88)
print(f" Opening Range Breakout — {sym} M5 (one-shot je Open · Exit live · Echtkosten)")
print(f" Box = erste K Bars nach Open (Brokerzeit) · Ausbruch ±{_K_BRK}×ATR · Fenster 4 h")
print("="*88)
for lbl, lo, hi in (("H1 (alt)", 0, mid), ("H2 (neu)", mid, N-_MAXH-1)):
print(f"\n{lbl}:")
for name, oh, om in (("EU-Open 10:00 Brk", 10, 0), ("US-Open 16:30 Brk", 16, 30)):
for K in (3, 6):
s = st(run(T, H, L, C, A, SP, lo, hi, oh, om, K))
print(line(f"{name} Box={K*5}min", s))
print(f"\n Maßstab: Squeeze ØR +0,14…+0,23 & PF>1 in BEIDEN Hälften.")
if __name__ == "__main__":
main()