diff --git a/CLAUDE.md b/CLAUDE.md index 5b24f16..7f981f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2595,6 +2595,59 @@ konstant; (d) es gab **keine Telemetrie über die Entscheidung selbst**. in ~20 min gefunden; die Trefferquote spricht für weitere. `analyze_divergence.py` regelmäßig laufen lassen (Kandidat für den Wochenreport). +## ⚠⚠ News-Sentiment war GESÄTTIGT — Fix 2026-08-03 + +**Anlass:** User-Frage „können wir den X-Account anbinden, um den Copiloten mit mehr +Daten zu versorgen?" → erst prüfen, ob der vorhandene Textstrom überhaupt trägt. + +**Befund: er trug nichts, weil der Score kaputt war.** `calc_news_sentiment` bildete +ein **reines Verhältnis** `(bull−bear)/total` — **ohne jeden Bezug zur Beweismenge.** +Eine einzige bullische Schlagzeile bei null bearischen ergab damit **+1,00**. +| Diagnose | Wert | +|---|---| +| Log-Einträge auf ±1,00 gesättigt | **173 von 173 = 100 %** | +| davon mit einer Seite exakt 0 | **100 %** | +| Median-Beweismenge (bull+bear) | **2,8** gewichtete Headlines | +| DB gesamt (54.548 Zeilen) auf exakt +1,00 | **63,9 %** | +Real am 03.08.: Sentiment **+1,00** („bull=1.8, bear=0.0"), während WTI **−5,9 %** +stand. Der Wert trug faktisch **ein Bit** (das Vorzeichen) — und löste im Entry-Check +dauerhaft falsche „News-Konflikt"-Warnungen aus (Schwelle |s|≥0,5). +⚠ Die Keyword-Listen sind NICHT die Ursache (56 bullisch / 48 bärisch, keine +Überschneidung) — es war allein die Formel. + +**Fix:** Shrinkage `score = raw × total/(total+_EVIDENCE_K)`, `_EVIDENCE_K=4,0` +(gewählt, weil die reale Median-Beweismenge 2,8 beträgt → eine typische einseitige +3-Headline-Lage bleibt UNTER der 0,5-Warnschwelle; ~10 Headlines ergeben 0,71). +Zusätzlich liefert der Rückgabewert jetzt `raw_score` und `evidence`, damit im +UI/Copilot unterscheidbar ist, ob ein schwacher Score „ausgewogene Lage" oder „kaum +Daten" bedeutet. Der Leer-Fall gibt dieselben Felder zurück (sonst KeyError genau im +„keine News"-Fall). Mit 6 Szenarien getestet, live verifiziert: bei identischer +Beweislage (bull=1.8, bear=0.0) **+1,00 → +0,31**. + +⚠⚠ **EHRLICH: Das repariert die KALIBRIERUNG, nicht die Aussagekraft.** +Gemessen wurde parallel die Prädiktivität (Forward-Return 120 min, 38.213 Paare über +84 Tage, 2 Hälften, **gegen die Regime-Drift gelesen** — H1 +0,005 $, H2 −0,079 $): +| Bucket | H1 Überschuss | H2 Überschuss | +|---|---|---| +| bearisch ≤−0,5 | −0,029 ✓ | **+0,113 ✗ (Vorzeichen kippt)** | +| leicht bear | +0,118 ✗ | +0,031 ✗ | +| leicht bull | +0,106 ✓ | +0,044 ✓ | +| **bullisch ≥+0,5** (grösster Bucket) | −0,003 | **−0,023 ✗** | +Der stark-bullische Bucket (16.241 / 11.818 Zeilen) liegt in BEIDEN Hälften bei null +bis negativ, der bearische kippt das Vorzeichen. **News-Sentiment ist nicht robust +prädiktiv** — auch mit korrigierter Skala. Es bleibt Kontext-Chip ohne Verdict-Stimme. +⚠ **Methodik-Falle beim Messen selbst:** der erste Lauf nutzte `candles_m1` (deckt nur +~14 Tage), wodurch `bisect` für alle älteren Zeitstempel denselben Randwert lieferte → +alle Forward-Returns exakt 0,0000. Erst der Lauf mit MT5-M5-Bars (Jan–Aug) und einer +Deckungsprüfung (|Δt| ≤ 15 min) war gültig. + +**Konsequenz für die X-Anbindung:** einen ZWEITEN Textstrom in eine Pipeline zu legen, +deren erster gemessen nichts vorhersagt, ist nicht begründbar — zumal X seit 2023 +**keinen kostenlosen Lesezugang** mehr hat (Basic ≈ 200 $/Monat, Pro ≈ 5.000 $/Monat; +Scraping = ToS-Verstoß). Bei ~800 € Konto wären das ~25 % des Kontos pro Monat für +einen unvalidierten Input. Erst müsste der reparierte Score über ein paar Wochen +zeigen, dass Textdaten hier überhaupt etwas beitragen. + ## KI-Copilot: warum er neutral ist, und was 2026-08-02 repariert wurde **User-Frage:** „Der Bias ist fast immer neutral — können wir konkretere Empfehlungen diff --git a/core/analysis/news.py b/core/analysis/news.py index e4bbbc9..9ab9c11 100644 --- a/core/analysis/news.py +++ b/core/analysis/news.py @@ -39,6 +39,13 @@ NEWS_BEARISH_KW = { } +# Wie viele gewichtete Headlines es braucht, damit der Score seinen Rohwert +# annähernd erreicht (Shrinkage-Konstante, s. Begründung in `calc_news_sentiment`). +# 4,0 gewählt, weil die reale Median-Beweismenge bei 2,8 liegt: damit bleibt eine +# typische, einseitige 3-Headline-Lage UNTER der 0,5-Warnschwelle des Entry-Checks. +_EVIDENCE_K = 4.0 + + def calc_news_sentiment(headlines: list, half_life_hours: float = 8.0) -> dict: """ Bewertet Headlines per Keyword-Matching mit altersgewichtetem Decay. @@ -46,7 +53,10 @@ def calc_news_sentiment(headlines: list, half_life_hours: float = 8.0) -> dict: """ import time as _time if not headlines: - return {"score": 0.0, "n_bull": 0.0, "n_bear": 0.0, "samples": []} + # Felder identisch zum Normalfall halten — sonst wirft ein Konsument, der + # `raw_score`/`evidence` liest, ausgerechnet im „keine News"-Fall. + return {"score": 0.0, "n_bull": 0.0, "n_bear": 0.0, + "raw_score": 0.0, "evidence": 0.0, "samples": []} now = _time.time() bull_w = bear_w = 0.0 @@ -67,10 +77,37 @@ def calc_news_sentiment(headlines: list, half_life_hours: float = 8.0) -> dict: samples.append(("bear", h.get("title", "")[:70])) total = bull_w + bear_w - score = 0.0 if total < 0.1 else (bull_w - bear_w) / total + raw = 0.0 if total < 0.1 else (bull_w - bear_w) / total + # ── BEWEISMENGEN-GEWICHTUNG (Fix 2026-08-03) ───────────────────────────── + # ⚠ Vorher war der Score ein REINES VERHÄLTNIS: `(bull-bear)/total`. Eine + # einzige bullische Schlagzeile bei null bearischen ergab damit +1,00 — + # maximale Überzeugung aus EINEM Treffer. Gemessen an 173 Log-Einträgen: + # **100 % standen auf ±1,00**, und in **100 %** der Fälle war eine Seite + # exakt 0; die Median-Beweismenge betrug **2,8** gewichtete Headlines. + # Über die gesamte DB (54.548 Zeilen) klebten **63,9 % auf exakt +1,00**. + # Real am 03.08.: Sentiment +1,00 („bull=1.8, bear=0.0"), während WTI + # −5,9 % stand. Der Wert trug damit faktisch EIN Bit (das Vorzeichen) und + # löste im Entry-Check dauerhaft falsche „News-Konflikt"-Warnungen aus. + # Korrektur: zur Null schrumpfen, solange die Beweislage dünn ist + # (klassische Shrinkage). Bei der Median-Beweismenge 2,8 wird aus 1,00 + # noch 0,41 — unter der 0,5-Warnschwelle des Entry-Checks; erst ~10 + # gewichtete Headlines ergeben 0,71. + # ⚠ EHRLICH: Das repariert die KALIBRIERUNG, nicht die Aussagekraft. Der + # Score bleibt das Vorzeichen weniger Keyword-Treffer, und dessen + # Prädiktivität ist gemessen NICHT robust (Forward-Return über 38k Paare, + # 2 Hälften: der bearische Bucket kippt das Vorzeichen, der stark-bullische + # liegt in BEIDEN Hälften bei null bis negativ). `k` ist eine begründete + # Setzung, kein optimierter Wert — es gibt hier nichts zu optimieren, + # solange das Signal selbst nichts vorhersagt. + conf = total / (total + _EVIDENCE_K) if total > 0 else 0.0 + score = raw * conf return { "score": max(-1.0, min(1.0, score)), "n_bull": round(bull_w, 1), "n_bear": round(bear_w, 1), + # Rohwert + Beweismenge mitliefern: so ist im UI/Copilot erkennbar, ob + # ein schwacher Score „ausgewogene Nachrichtenlage" oder „kaum Daten" heißt. + "raw_score": round(max(-1.0, min(1.0, raw)), 3), + "evidence": round(total, 2), "samples": samples[:5], }