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:
+104
@@ -0,0 +1,104 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user