"""Setzt Tooltips (title=) auf alle Dashboard-Anzeigen. Idempotent."""
import pathlib
import re

P = pathlib.Path(r"c:\Users\ah\Downloads\tradingrobot\web\index.html")
t = P.read_text(encoding="utf-8")

KARTEN = {
 "trade-bar": "Der aktuell offene Trade. SL, TP und die Close-Schwellen sind hier direkt editierbar — Enter oder Verlassen des Feldes sendet.",
 "card-verdict": "Aggregation aller Module zu einer Empfehlung. Die HEADLINE (Welle) steuert die Order, alles darunter ist Konsens-Anzeige. Gemessen an 1.071 Live-Episoden trifft die Richtung zu 48–51 % — die Karte taugt als Veto (gegen das Signal zu handeln kostet gemessen 6,60 € je Lot), nicht als Prognose.",
 "card-pos": "Live-Meldungen zum laufenden Trade und zur Marktlage: Squeeze, Überdehnung, S/R-Nähe, News- und Kalender-Konflikte, Hyperliquid-Kontext.",
 "card-trail": "Zustand des Trailing-Stops (Phase, aktueller SL, High-Water). Ausgeblendet — das Trailing läuft im Hintergrund weiter.",
 "card-stats-live": "Kompakte Bilanz über Woche und Gesamt. Verhältnis = Ø-Gewinn geteilt durch Ø-Verlust; unter 1 heißt, die Verlierer sind größer als die Gewinner.",
 "card-agent": "Einschätzung des KI-Copiloten (LLM). Stimmt mit Gewicht 1,0 mit, wenn er eine Richtung nennt — bei NEUTRAL zählt er nicht. Als Prognose nicht belegt.",
 "card-patterns": "Mechanisch erkannte Chartmuster auf M30 mit Trigger und Kursziel. Gewicht 0,25. Die Ziel-Trefferquote liegt gemessen nur bei 13–38 % — Kontext, keine Vorhersage.",
 "card-structure": "Price-Action-Struktur auf M30: Swing-Folge, letzter Bruch (BOS) und Regressionskanal. Reine Anzeige — als Signal über alle Zeitebenen gemessen und verworfen.",
 "card-candles": "Kerzengröße, Dochte und Volumen der letzten M5-Kerzen. Reine Anzeige und bewusst neutral gefärbt: die Lehrbuch-Lesart ist hier gemessen INVERTIERT — der Kurs läuft nach einer Klimax-Kerze eher GEGEN sie.",
 "card-manual": "Deine eigenen manuellen Trades im Vergleich zum Bot, aufgeteilt nach Signal-Deckung. Rein deskriptiv.",
 "card-trades": "Die zuletzt geschlossenen Trades mit Ergebnis und Schließgrund.",
 "card-gaps": "Offene Kurslücken des Jahres. Vakuum = echte Lücke zwischen den Tagesspannen (fließt als S/R in die Konfidenz ein), Eröffnung = Schluss-zu-Eröffnung (reine Anzeige).",
}

HDR = {
 "BALANCE": "Kontostand ohne die offene Position.",
 "TAG P/L": "Realisiertes Ergebnis von heute. Brutto — die Quellensteuer wird größtenteils erstattet und ist hier nicht abgezogen.",
 "EQUITY": "Kontostand einschließlich der offenen Position.",
 "PEPPERSTONE KURS": "Der Kurs, zu dem tatsächlich gehandelt wird. Steht er länger als 3 Minuten still, gilt der Markt als geschlossen und das Label zeigt ZU.",
 "HYPERLIQUID KURS": "Roher Kurs des Hyperliquid-Öl-Perps — läuft 24/7, auch wenn Pepperstone geschlossen ist. Anderer Kontrakt, deshalb nicht identisch; der Tooltip am Wert nennt die umgerechnete Entsprechung.",
 "G/V": "Gewinn/Verlust der offenen Position, NETTO nach Quellensteuer. Bei geschlossenem Markt eine Schätzung auf Basis des Hyperliquid-Kurses (mit ≈ markiert).",
}

TB = {
 "Laufzeit": "Wie lange der Trade schon läuft. Ab 2 Stunden bernstein — dann greift der Time-Stop, falls der Trade nie in die Gewinnphase kam.",
 "Richtung": "LONG = auf steigende Kurse, SHORT = auf fallende.",
 "Lots": "Positionsgröße. Ergibt sich aus dem Einsatz-Feld in der Order-Leiste.",
 "Einsatz": "Die gebundene Margin dieser Position.",
 "Auto-Close": "Schließt am nächsten S/R-Level automatisch, wenn das Level wahrscheinlich hält. Derzeit AUS.",
 "SL": "Stop-Loss beim Broker. Ändern schaltet das Trailing ab, damit es die Handeingabe nicht zurückzieht.",
 "TP": "Take-Profit beim Broker. Ändern schaltet das Trailing ab.",
 "Gewinn-Close": "Mindestgewinn in Euro, unter dem der Auto-Close am Level NICHT schließt. Leer oder 0 = aus.",
 "Notfall-Close": "Harte Verlustgrenze in Euro — der Server schließt sofort, wenn sie erreicht ist. Wirkt auch bei gesperrtem Handy. ⚠ Ein gesetzter Wert wird auf den nächsten Trade übernommen, bis er gelöscht wird.",
}

ELEM = {
 "vd-signal": "Die eigentliche Empfehlung samt Zeitebene — sie steuert die Order. Bei WARTEN steht dahinter, was noch fehlt.",
 "vd-consensus": "Wie viele der stimmberechtigten Module dieselbe Richtung sehen, plus die aktuellen Handelskosten (Spread geteilt durch ATR).",
 "vd-modules": "Die Module mit Stimmrecht. Die Zahl im Chip ist das Gewicht, graue Chips schweigen gerade. Dauerhaft entmachtete Module werden hier nicht mehr gezeigt.",
 "vd-meter": "Bias-Nadel: gewichteter Konsens der Module zwischen SHORT und LONG. Reine Anzeige — die Order steuert die Headline.",
 "wave-turns": "EMA-Stand je Zeitebene: grün aufwärts, rot abwärts. Eine frische Kreuzung blinkt. Reine Anzeige.",
 "wave-squeeze": "Volatilitäts-Squeeze: der Kurs komprimiert und bricht aus. Das einzige 2-Stichproben-validierte Einstiegssignal des Projekts.",
 "wave-bounce": "Warnung bei Überdehnung — der Kurs ist weit von seiner EMA weg. KEIN Signal: die Richtungstreffer-Quote liegt gemessen bei ~50 %.",
 "cur-near": "Nächstes S/R-Level und Abstand dorthin.",
 "wave-reasons": "Die Begründungen, aus denen sich Richtung und Konfidenz zusammensetzen.",
 "pos-srhint": "Nähert sich der Kurs dem Ziel-Level, steht hier die Durchbruch-Wahrscheinlichkeit. Das Modell dahinter ist out-of-sample kalibriert (AUC 0,65).",
 "pos-stophint": "Dasselbe für das Level auf der Verlustseite — reine Anzeige, hier wird nichts automatisch geschlossen.",
 "news-conflict": "Warnt, wenn der News-Flow deutlich gegen die offene Position oder das aktive Signal steht. Kontext — als Prognose nicht belegt.",
 "cal-note": "Laufendes oder bevorstehendes High-Impact-Ereignis aus dem Wirtschaftskalender. Sperrt autonome Einstiege, nicht manuelle.",
 "flip-note": "Zustand des Auto-Flip-Close. Derzeit aus — in beiden Halbjahren gemessen negativ.",
 "agent-bias": "Richtungsaussage des Copiloten. NEUTRAL heißt, er zählt in der Empfehlung nicht mit.",
 "agent-reason": "Die Begründung des Copiloten im Klartext.",
 "pt-list": "Erkannte Muster mit Skizze, Trigger und Kursziel.",
 "ms-swings": "Swing-Folge: HH/HL = Aufwärtsstruktur, LH/LL = Abwärtsstruktur.",
 "ms-bos": "Letzter Break of Structure — welches Swing-Level zuletzt gebrochen wurde.",
 "ms-chan": "Position des Kurses im Regressionskanal: 0 % = unterer Rand, 100 % = oberer.",
 "ca-list": "Erkannte Kerzenformen der letzten M5-Bars mit Volumen-Vergleich.",
 "mt-grid": "Deine manuellen Trades über heute, 30 Tage und gesamt, dazu der Bot zum Vergleich.",
 "mt-split": "Aufgeteilt danach, ob der Trade MIT oder OHNE Deckung durch die Empfehlung lief. Gegen das Signal kostet gemessen 6,60 € je Lot.",
 "trades": "Zuletzt geschlossene Trades: Richtung, Ergebnis und Schließgrund.",
 "gaps-list": "Offene Kurslücken mit Zone, Fill-Level und Abstand zum aktuellen Kurs.",
 "stx-row": "Trades, Trefferquote, Profitfaktor und das Verhältnis von Ø-Gewinn zu Ø-Verlust.",
}


def setze(muster: str, tip: str, text: str) -> str:
    """Haengt title= an, wenn noch keins da ist (idempotent)."""
    def rep(m):
        attrs = m.group(2)
        if "title=" in attrs:
            return m.group(0)
        return m.group(1) + attrs + f' title="{tip}"' + m.group(3)
    return re.sub(muster, rep, text, count=1)


for i, tip in KARTEN.items():
    t = setze(rf'(<section class="card[^"]*"[^>]*id="{re.escape(i)}"[^>]*>\s*<h2)([^>]*)(>)',
              tip, t)
for lbl, tip in HDR.items():
    t = setze(rf'(<label(?: id="[^"]*")?)()(>{re.escape(lbl)}</label>)', tip, t)
for lbl, tip in TB.items():
    t = setze(rf'(<span class="tb-field[^"]*"><label)()(>{re.escape(lbl)}</label>)', tip, t)
for i, tip in ELEM.items():
    t = setze(rf'(<div class="[A-Za-z0-9 _-]+" id="{re.escape(i)}")([^>]*)(>)', tip, t)

P.write_text(t, encoding="utf-8")
print(f"  title-Attribute: {t.count('title=')}")
