Files
AH-Oil-Trader/core/hl_walls.py
T
Axel HocksandClaude Opus 4.8 03567adf6b MT5-Chart v1.18: Trade-Marker, Liquiditaetszonen, Kurs oben rechts + Pfeil-Bugfix
Pfeil-Bugfix (User: "liquiditaetspfeile werden immer noch nicht angezeigt"):
  (1) ANCHOR_LEFT ist fuer OBJ_ARROW UNGUELTIG (nur TOP/BOTTOM)
  (2) Pfeile sitzen rechts neben der letzten Kerze -> ohne CHART_SHIFT kein
      sichtbarer Bereich dort
Blink-Fix (User: "blinken nur manchmal auf"): hl_walls setzte den Cache bei jedem
Fehlschlag auf None -> LQ-Zeilen fehlten -> Indikator loeschte die Pfeile. Jetzt
letzter guter Stand 120s Grace.

Neu (alles reine Anzeige):
- TR;<O|C>;zeit  -> Kerze der Eroeffnung HELLGRUEN, des Close DUNKELGRUEN umrandet
- EQ;<H|L>;lo;hi;zeit -> Equal Highs/Lows aus M30-Pivots als goldenes Band +
  gestrichelte Kante (wie im User-Referenzbild). Als Signal gemessen verworfen.
- PX;bid -> aktueller Kurs fett oben rechts
- Beschriftung der Liquiditaets-Trendlinie per Default aus (v1.16)

Alles kompiliert und live in sr_levels.csv verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 18:36:22 +02:00

107 lines
5.5 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.
"""core/hl_walls.py — ECHTE Liquiditäts-Wände von Hyperliquid für den MT5-Chart.
Holt vom HL-Dashboard (Port 8001, `GET /api/walls`) das größte L2-Orderbuch-Level je
Seite — **basis-korrigiert auf das Pepperstone-Preisniveau** — und stellt es dem
Level-Export (`engine._write_levels_file` → `sr_levels.csv` → `SR_Levels.mq5`) bereit.
WARUM (User-Wunsch 2026-07-30): Alle S/R-Linien im Projekt sind aus **Pivots
geschätzt** („wo war mal ein Docht"). Das hier ist die einzige Quelle mit
**tatsächlich ruhenden Orders** — Pepperstone ist Market Maker und liefert kein
Orderbuch, Hyperliquid schon (20 Levels je Seite).
⚠ Zwei Dinge im Blick behalten:
1. **Basis-Differenz**: HL `xyz:CL` notiert ~0,45 $ unter dem CFD (anderer
Kontrakt/Contango). Die Umrechnung passiert SERVERSEITIG im HL-Projekt
(Median über die letzten 60 Snapshots) — hier kommt schon `bid_mt5`/`ask_mt5`
an. Fehlt die Basis (zu wenige Snapshots), wird NICHT gezeichnet.
2. **Anzeige, KEIN Signal.** Ob HL-Orderbuch-Signale auf den CFD übertragbar sind,
ist eine offene Frage und wird gerade gemessen (`analyze_bookflow.py` im
HL-Projekt). Bis dahin fließt hier nichts in Empfehlung/Trade ein.
Fail-safe: kurzer Timeout, Ergebnis gecacht; ist der HL-Server aus, liefert
`walls()` einfach None und der Chart bleibt unverändert.
"""
from __future__ import annotations
import json
import threading
import time
import urllib.request
from core.logger import get_logger
log = get_logger("hlwalls")
_TIMEOUT_S = 2.0 # der MT5-Loop darf nicht am HTTP hängen
_TTL_S = 10.0 # Cache — die Wände wandern langsamer als der Export-Takt (~5 s)
_MAX_AGE_S = 60.0 # älter = HL-Feed steht → nicht mehr zeichnen
_STALE_GRACE_S = 120.0 # nach einem Fehlschlag den letzten guten Stand so lange
# weiterreichen (Blink-Fix — s. Kommentar in `walls()`)
class HLWalls:
def __init__(self, url: str = "http://127.0.0.1:8001/api/walls",
enabled: bool = False):
self.url = url
self.enabled = bool(enabled)
self._lock = threading.Lock()
self._cache: dict | None = None
self._cache_ts = 0.0
self._fail_logged = False
def walls(self) -> dict | None:
"""{'bid','bid_sz','ask','ask_sz','basis','imb5'} auf MT5-Preisniveau —
oder None (aus / HL nicht erreichbar / Basis unbekannt / Daten zu alt)."""
if not self.enabled:
return None
now = time.time()
with self._lock:
if self._cache is not None and now - self._cache_ts < _TTL_S:
return self._cache
data = None
try:
with urllib.request.urlopen(self.url, timeout=_TIMEOUT_S) as r:
d = json.loads(r.read().decode())
if d.get("ok") and d.get("bid_mt5") and d.get("ask_mt5"):
# Feed-Alter prüfen: ein stehengebliebenes Buch darf nicht als
# aktuelle Liquidität gezeichnet werden.
age = now - float(d.get("ts") or 0)
if age <= _MAX_AGE_S:
data = {"bid": float(d["bid_mt5"]), "ask": float(d["ask_mt5"]),
"bid_sz": float(d.get("bid_sz") or 0),
"ask_sz": float(d.get("ask_sz") or 0),
"basis": d.get("basis"), "imb5": d.get("imb5"),
# Wirkrichtung je Wand (Pfeil im Chart) + größere Wand.
# ⚠ Lesart „die Wand hält" — INTERPRETATION, nicht gemessen.
"arrow_bid": d.get("arrow_bid") or "up",
"arrow_ask": d.get("arrow_ask") or "down",
"dominant": d.get("dominant"),
# Liquiditäts-Trendlinie (Regression über den volumen-
# gewichteten Wand-Schwerpunkt): {t1,p1,t2,p2,dir,slope_per_h}
# — Zeiten sind LOKALE Epoch, der Aufrufer rechnet sie in
# Broker-Zeit um (MT5-Charts brauchen Broker-Zeit).
"trend": d.get("trend")}
self._fail_logged = False
except Exception as e:
if not self._fail_logged: # nur 1× melden, nicht jeden Tick
log.info(f"HL-Wände nicht abrufbar ({e}) — Chart bleibt ohne "
f"Liquiditätslinien (HL-Dashboard auf 8001 läuft nicht?)")
self._fail_logged = True
with self._lock:
if data is not None:
self._last_good = data
self._last_good_ts = now
elif (getattr(self, "_last_good", None) is not None
and now - getattr(self, "_last_good_ts", 0.0) < _STALE_GRACE_S):
# ⚠ BLINK-FIX (2026-07-30, User „die Liquidationspfeile blinken nur
# manchmal auf"): ein einzelner Fehlschlag (HTTP-Timeout, HL-Server
# kurz busy, Snapshot minimal zu alt) darf die Pfeile NICHT löschen.
# Ohne den letzten guten Wert fehlen die LQ-Zeilen in der CSV → der
# Indikator löscht beim Redraw alle Objekte und zeichnet sie nicht neu
# → Pfeil weg, beim nächsten Erfolg wieder da = sichtbares Blinken.
# Innerhalb der Grace wird der letzte gute Stand weitergereicht;
# erst danach verschwinden die Pfeile wirklich (echter Ausfall).
data = self._last_good
self._cache = data
self._cache_ts = now
return data