Files
AH-Oil-Trader/analyze_tageszeit.py
T
Axel HocksandClaude Opus 5 d75789ac72 Tageszeit-Drift gemessen: Morgens-runter/Nachmittags-rauf widerlegt
analyze_tageszeit.py misst die ROHE Kursdrift je Berliner Stunde (nicht den
Signal-Edge). 80k M5-Bars, DST-korrekt, 2 Halften, Ueberschuss gegen den
Tagesschnitt.

Ergebnis: Vormittag 08-12 kippt zwischen den Halften, Nachmittag 14-18 ist
robust NEGATIV - beide Halften der Vermutung fallen durch. Groessenordnung
ohnehin unter dem Spread (staerkste robuste Stunde ~0,03 $/h vs 0,0225 $
Spread).

Korrektur am eigenen Skript: der erste Entwurf hatte einen ATR-Filter
(< 0,06 uebersprungen) plus ATR-Normierung. Der Filter verwarf ausgerechnet
die ruhigen Morgenstunden der aelteren Periode (n 138-391 statt 1740) und
erzeugte so den vermeintlichen Befund. Beides entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:08:22 +02:00

144 lines
6.6 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
"""Kursdrift nach Tageszeit — steigt WTI nachmittags und fällt morgens? (2026-08-04)
User-Beobachtung: „der Kurs sinkt morgens eher und steigt nachmittags."
⚠ Das ist eine ANDERE Frage als `backtest_hourly_split.py`. Dort wurde gemessen,
wie gut das SIGNAL je Stunde abschneidet (Edge nach Kosten). Hier geht es um die
rohe Richtungsdrift des Kurses selbst — unabhängig von jedem Signal.
Methodik und ihre Fallstricke, alle bewusst adressiert:
1. **Gesamt-Drift herausrechnen.** Fiel der Markt im Zeitraum, sehen ALLE Stunden
negativ aus. Deshalb wird zusätzlich der Überschuss gegen den Tagesmittelwert
ausgewiesen — nur der ist eine Tageszeit-Aussage.
2. **Zwei Stichproben.** 24 Stunden = 24 Tests; bei 5 % Fehlalarmquote sieht rund
eine Stunde zufällig „signifikant" aus. Nur was in BEIDEN Hälften dasselbe
Vorzeichen hat, zählt.
3. **KEIN ATR-Filter, KEINE ATR-Normierung** — der erste Entwurf hatte beides und
erzeugte damit genau den Befund, den er messen sollte (s. `auswerten`).
4. **Echte Zeitzone.** Broker-Zeit ist UTC+3 (ganzjährig), Berlin wechselt
zwischen UTC+1 und +2 — über eine lange Historie ist ein fester Versatz von
1 h falsch. Umrechnung daher über `zoneinfo`.
Aufruf: python analyze_tageszeit.py [bars]
"""
import sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
import MetaTrader5 as mt5
_BERLIN = ZoneInfo("Europe/Berlin")
_BROKER = timezone(timedelta(hours=3)) # Pepperstone: UTC+3 ganzjährig
_ATRMIN = 0.06
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 auswerten(bars, H, L, C, A, lo, hi):
"""→ {stunde: [Rendite je Bar in DOLLAR]}
⚠⚠ KEIN ATR-Filter und KEINE ATR-Normierung mehr (Korrektur 2026-08-04).
Der erste Entwurf übersprang Bars mit `atr < 0,06` und teilte durch den
rollierenden M5-ATR. Beides klang vernünftig, erzeugte aber eine massive
**Selektionsverzerrung**: in den ruhigen Nacht-/Morgenstunden der älteren
Periode liegt der ATR unter dem Floor, also blieben dort nur die
ungewöhnlich VOLATILEN Tage übrig — von 1.740 vorhandenen Bars je Stunde
kamen nur 138391 in die Auswertung. Genau die Stunden, um die es geht,
waren damit eine verzerrte Teilstichprobe.
Jetzt: rohe Dollar-Rendite je Bar, ALLE Bars. Die Regime-Unterschiede
zwischen den Hälften fängt der Abzug des jeweiligen Tagesmittels ab.
"""
out = defaultdict(list)
for i in range(max(lo, 1), hi):
# Broker-Epoch → echte Berliner Stunde (DST-korrekt)
t = datetime.fromtimestamp(int(bars[i]["time"]), _BROKER).astimezone(_BERLIN)
out[t.hour].append(C[i] - C[i - 1])
return out
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)
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]
A = _atr_series(H, L, C)
N = len(C); mid = N // 2
t0 = datetime.fromtimestamp(int(bars[0]["time"]), _BROKER).astimezone(_BERLIN)
t1 = datetime.fromtimestamp(int(bars[-1]["time"]), _BROKER).astimezone(_BERLIN)
h1 = auswerten(bars, H, L, C, A, 0, mid)
h2 = auswerten(bars, H, L, C, A, mid, N)
def mittel(d, s):
v = d.get(s) or []
return (sum(v) / len(v), len(v)) if v else (0.0, 0)
# Tagesmittel je Hälfte — dagegen wird der Überschuss gerechnet
alle1 = [x for v in h1.values() for x in v]
alle2 = [x for v in h2.values() for x in v]
m1 = sum(alle1) / len(alle1) if alle1 else 0.0
m2 = sum(alle2) / len(alle2) if alle2 else 0.0
print("=" * 86)
print(f" KURSDRIFT NACH TAGESZEIT — {sym} M5, {N} Bars")
print(f" {t0:%d.%m.%Y} bis {t1:%d.%m.%Y} · Berliner Zeit (DST-korrekt) · 2 Hälften")
print(f" Werte = Ø Rendite je 5-Min-Bar in US-Dollar (×1000 = Zehntel-Cent)")
print(f" Gesamt-Drift je Bar: H1 {m1*1000:+.3f} · H2 {m2*1000:+.3f} (wird abgezogen)")
print("=" * 86)
print(f" {'Std':>3} {'n H1':>6} {'H1 roh':>9} {'H1 Übersch.':>12} "
f"{'n H2':>6} {'H2 roh':>9} {'H2 Übersch.':>12} Urteil")
print("-" * 86)
robust_auf, robust_ab = [], []
for s in range(24):
a1, n1 = mittel(h1, s); a2, n2 = mittel(h2, s)
if n1 < 50 or n2 < 50:
continue
u1 = (a1 - m1) * 1000; u2 = (a2 - m2) * 1000
if u1 > 0 and u2 > 0:
urteil = "beide AUF"; robust_auf.append((s, u1, u2))
elif u1 < 0 and u2 < 0:
urteil = "beide AB"; robust_ab.append((s, u1, u2))
else:
urteil = "kippt"
print(f" {s:>3} {n1:>6} {a1*1000:>+9.3f} {u1:>+12.3f} "
f"{n2:>6} {a2*1000:>+9.3f} {u2:>+12.3f} {urteil}")
print("\n" + "=" * 86)
print(" ROBUST (gleiches Vorzeichen des Überschusses in BEIDEN Hälften)")
print("=" * 86)
print(" aufwärts: " + (", ".join(f"{s}:00 ({u1:+.2f}/{u2:+.2f})"
for s, u1, u2 in robust_auf) or "keine"))
print(" abwärts : " + (", ".join(f"{s}:00 ({u1:+.2f}/{u2:+.2f})"
for s, u1, u2 in robust_ab) or "keine"))
# Direkter Test der Behauptung: Vormittag gegen Nachmittag
def block(d, m, von, bis):
v = [x for s in range(von, bis) for x in (d.get(s) or [])]
return ((sum(v) / len(v) - m) * 1000, len(v)) if v else (0.0, 0)
print("\n Die Behauptung direkt geprüft (Überschuss gegen den Tagesschnitt):")
for lbl, von, bis in (("Vormittag 0812", 8, 12), ("Nachmittag 1418", 14, 18),
("Abend 1923", 19, 23), ("Nacht 0007", 0, 7)):
b1, c1 = block(h1, m1, von, bis); b2, c2 = block(h2, m2, von, bis)
ok = "robust" if (b1 > 0) == (b2 > 0) else "KIPPT"
print(f" {lbl:<18} H1 {b1:>+7.3f} (n={c1:>6}) "
f"H2 {b2:>+7.3f} (n={c2:>6}) {ok}")
print("\n ⚠ 24 Stunden = 24 Tests: bei 5 % Fehlalarmquote sieht rund EINE Stunde")
print(" zufällig auffällig aus. Nur beidhälftig gleiche Vorzeichen zählen —")
print(" und auch die sind ohne Kosten gerechnet (Spread ~0,265×ATR!).")
print()
if __name__ == "__main__":
main()