Files
AH-Oil-Trader/backtest_cme_volumen.py
T
Axel HocksandClaude Opus 5 abd5dc0cb8 Echtes CME-Volumen (CL=F) gemessen: traegt nicht mehr als die CFD-Tick-Zaehlung
User-Idee eines Volumen-Spike-Alerts. Berechtigte Frage dahinter: das Projekt
kennt nur tick_volume vom CFD - eine Zaehlung von Preisaenderungen bei einem
Market-Maker-Broker, kein gehandeltes Volumen. Echtes NYMEX-Volumen waere die
erste Datenquelle, die NICHT aus dem Preis abgeleitet ist.

Beide Quellen auf DEMSELBEN Fenster gemessen (28.05.-07.08., 13.855 CME- gegen
14.178 CFD-Bars), gleiche Methode, Ueberschuss ueber die Drift, zwei Haelften.
Bewusst nicht gegen den dokumentierten Wert gestellt - genau dieser Vergleich
(kurzes Fenster gegen Langfrist-Mittel) war am 07.08. der Messfehler.

  Volumen     CME H1/H2         CFD H1/H2
  1,5-2,0x    +0,116 / -0,203   -0,011 / +0,152
  2,0-2,5x    -0,221 / -0,282   -0,101 / +0,589
  >=2,5x      +0,102 / +0,096   -0,267 / -0,881

Trefferquote in ALLEN Zellen 44-53 % - Muenzwurf, in beiden Quellen. Kein
monotoner Zusammenhang, Vorzeichen kippen. Die einzige beidhaelftig konsistente
Zelle (CME >=2,5x) ist mit [-0,234 ... +0,428] bzw. [-0,265 ... +0,461] klar
Rauschen.

EHRLICHE GRENZE: meine CFD-Kontrolle reproduziert den alten Befund
(-0,117/-0,204) NICHT - Populations-Unterschied, kein Widerspruch.
backtest_candles.py mass bedingt auf ein Kerzen-MUSTER, hier laeuft es unbedingt
ueber alle Kerzen. Der alte Befund gilt weiter fuer seine Population.
60 Tage = ein Regime (yfinance-Deckel), also nur ein Ausschluss-Urteil moeglich.

Konsequenz: kein Volumen-Spike-Signal. Die Datenquelle funktioniert, sie traegt
nur keine Richtungsinformation.

Telegram-Kanaele als Eingabe (Bookmap/ATAS) ohne Messung verworfen: sie
transportieren Screenshots. Ein Orderbuch-Zustand ist vorbei, sobald ihn jemand
abfotografiert.

Nebenbefund zum eingereichten Skript: das .iloc[0]-Muster ist KORREKT
(MultiIndex-Spalten), nicht wie von mir zunaechst vermutet ein Fehler. Echte
Maengel: kein Dedup, kein Handelszeit-Gate, voller Tagesabruf im 60-s-Takt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 14:36:33 +02:00

175 lines
7.2 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
"""Verhält sich ECHTES CME-Volumen anders als die CFD-Tick-Zählung?
ANLASS (2026-08-08, User-Idee „Volume-Spike-Alert auf CL=F"). Das Projekt hat
„2× Volumen = echt" schon gemessen (`backtest_candles.py`): Überschuss
**0,117 / 0,204** — *monoton in die FALSCHE Richtung*. Mehr Volumen bedeutet
eine STÄRKERE Gegenbewegung; der Spike ist die Klimax-Kerze, und der Kurs läuft
danach GEGEN sie. Ein naiver „viel Volumen → mitgehen"-Alert stupst also
messbar falsch.
⚠ ABER: jene Messung lief auf `tick_volume` des **CFD** — das ist eine Zählung
von Preisänderungen, KEIN gehandeltes Volumen. Der Broker ist Market Maker; was
dort als „Volumen" erscheint, hat mit den Kontrakten an der NYMEX nur mittelbar
zu tun. Genau das ist die offene Frage: trägt ECHTES Börsenvolumen (CL=F, CME)
eine andere Information?
⚠⚠ DESHALB LAUFEN BEIDE QUELLEN AUF DEMSELBEN FENSTER UND MIT DERSELBEN METHODE.
Der dokumentierte Wert 0,117/0,204 stammt aus einem anderen Zeitraum und von
anderen Bars — ihn direkt danebenzustellen wäre derselbe Fehler wie am 07.08.,
als eine 6-Tage-Live-Stichprobe gegen ein 208-Tage-Mittel gehalten wurde.
Verglichen wird nur CME gegen CFD, Zeitpunkt für Zeitpunkt.
METHODE (wie `backtest_candles.py`):
· Volumen-Verhältnis = V[i] / Ø(V[i20 … i1])
· Forward-Return über 12 Bars (60 min), in ×ATR, VORZEICHEN = Kerzenrichtung
· gelesen als ÜBERSCHUSS über die Drift des Zeitraums — sonst liest man in
einem Trendfenster die Drift als Edge (die Lehre von `analyze_tageszeit.py`)
· zwei Hälften des Fensters
⚠⚠ GRENZE, DIE VORAB ZU NENNEN IST: yfinance liefert für 5-min-Bars maximal
~60 Tage. Beide Hälften liegen damit im SELBEN Regime — genau die Konstellation,
an der ORB und die Dead-Hour-Stunde 13 gescheitert sind. **Ein ✅ heißt hier
„nicht ausgeschlossen", NICHT „bestätigt".** Ein ❌ ist dagegen belastbar.
Aufruf: python backtest_cme_volumen.py
"""
from __future__ import annotations
import datetime as dt
import MetaTrader5 as mt5
_FWD = 12 # 12 M5-Bars = 60 min
_LOOK = 20 # Fenster fuer das Durchschnittsvolumen
_BUCKETS = ((0, 1.5), (1.5, 2.0), (2.0, 2.5), (2.5, 99))
_BROKER_OFF = 10800 # Brokerzeit = UTC+3
def atr_series(H, L, C, p=14):
tr = [0.0]
for i in range(1, len(C)):
tr.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
out = [None]
for i in range(1, len(C)):
seg = tr[max(1, i - p + 1):i + 1]
out.append(sum(seg) / len(seg))
return out
def hole_cme():
"""CL=F 5-min von yfinance. ⚠ MultiIndex-Spalten sind hier NORMAL — das
`.iloc[0]`-Muster im User-Skript ist deshalb korrekt, kein Fehler."""
import yfinance as yf
df = yf.download("CL=F", interval="5m", period="60d",
progress=False, auto_adjust=False)
if df is None or df.empty:
return None
reihen = []
for ts, r in df.iterrows():
def f(sp):
v = r[sp]
return float(v.iloc[0] if hasattr(v, "iloc") else v)
try:
reihen.append((int(ts.timestamp()), f("High"), f("Low"),
f("Close"), f("Volume")))
except Exception:
continue
return reihen
def hole_cfd(von_ts, bis_ts):
"""SpotCrude M5 aus MT5, auf dasselbe Zeitfenster beschnitten.
⚠ `time` ist Broker-Wallclock (UTC+3) — fuer den Abgleich in echte
UTC-Epoch umrechnen (die im Projekt mehrfach dokumentierte Falle)."""
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, 30000)
mt5.shutdown()
if bars is None:
return None, sym
out = []
for b in bars:
t = int(b["time"]) - _BROKER_OFF
if von_ts - 300 <= t <= bis_ts + 300:
out.append((t, float(b["high"]), float(b["low"]),
float(b["close"]), float(b["tick_volume"])))
return out, sym
def auswerten(reihen, name):
"""Ueberschuss ueber die Drift je Volumen-Bucket, in zwei Haelften."""
T = [r[0] for r in reihen]; H = [r[1] for r in reihen]
L = [r[2] for r in reihen]; C = [r[3] for r in reihen]
V = [r[4] for r in reihen]
AT = atr_series(H, L, C)
mid = len(C) // 2
print(f"\n ── {name} ── {len(C)} Bars, "
f"{dt.datetime.utcfromtimestamp(T[0]):%d.%m.} bis "
f"{dt.datetime.utcfromtimestamp(T[-1]):%d.%m.}")
for hn, lo, hi in (("H1", _LOOK + 15, mid), ("H2", mid, len(C) - _FWD - 1)):
# Drift des Fensters: mittlerer Forward-Return OHNE Richtungsvorzeichen
drift = []
for i in range(lo, hi):
a = AT[i]
if a and a > 0:
drift.append((C[i + _FWD] - C[i]) / a)
d_mean = sum(drift) / len(drift) if drift else 0.0
print(f" {hn} Drift des Fensters: {d_mean:+.3f}×ATR "
f"(darueber wird gelesen)")
print(f" {'Volumen':<14}{'n':>7}{'Ueberschuss':>14}{'Treffer':>10}")
for b_lo, b_hi in _BUCKETS:
werte = []
for i in range(lo, hi):
a = AT[i]
if not a or a <= 0:
continue
vs = V[max(0, i - _LOOK):i]
if len(vs) < _LOOK:
continue
m = sum(vs) / len(vs)
if m <= 0:
continue
q = V[i] / m
if not (b_lo <= q < b_hi):
continue
d = 1 if C[i] > (H[i] + L[i]) / 2 else -1 # Kerzenrichtung
r = (C[i + _FWD] - C[i]) * d / a
werte.append(r - d * d_mean) # Ueberschuss
if len(werte) < 25:
print(f" {f'{b_lo}{b_hi}x':<14}{len(werte):>7}{'zu wenig':>14}")
continue
m = sum(werte) / len(werte)
tr = 100 * sum(1 for x in werte if x > 0) / len(werte)
print(f" {f'{b_lo}{b_hi}x':<14}{len(werte):>7}{m:>+14.3f}{tr:>9.0f}%")
def main():
print("=" * 84)
print(" ECHTES CME-VOLUMEN gegen CFD-TICK-ZAEHLUNG — gleiches Fenster, gleiche Methode")
print("=" * 84)
cme = hole_cme()
if not cme or len(cme) < 500:
print(f" CL=F lieferte zu wenig ({0 if not cme else len(cme)} Bars)"); return
cfd, sym = hole_cfd(cme[0][0], cme[-1][0])
if not cfd or len(cfd) < 500:
print(f" {sym} lieferte zu wenig ({0 if not cfd else len(cfd)} Bars)"); return
print(f"\n Fenster: {dt.datetime.utcfromtimestamp(cme[0][0]):%d.%m.%Y} bis "
f"{dt.datetime.utcfromtimestamp(cme[-1][0]):%d.%m.%Y} (UTC)")
print(f" CL=F (CME, echte Kontrakte): {len(cme)} Bars")
print(f" {sym} (CFD, tick_volume): {len(cfd)} Bars")
print("\n ⚠ ~60 Tage = EIN Regime. Ein ✅ heisst 'nicht ausgeschlossen',")
print(" NICHT 'bestaetigt'. Ein ❌ ist dagegen belastbar.")
print("\n Erwartung aus der Vormessung (CFD, anderer Zeitraum): der Ueberschuss")
print(" wird mit steigendem Volumen NEGATIVER (Klimax, Kurs laeuft dagegen).")
auswerten(cme, "CL=F — echtes CME-Kontraktvolumen")
auswerten(cfd, f"{sym} — CFD-tick_volume (Kontrolle)")
if __name__ == "__main__":
main()