KI-Copilot: zwei Prompt-Fehler behoben + kalibrierte Groessen ergaenzt

Diagnose zur Frage "Bias fast immer neutral": es ist NICHT der Prompt, es ist
der Provider. Bruch exakt am 20.07. (DeepSeek-Wechsel) - bis 19.07. war der
Copilot zu 42-90 % gerichtet, ab 21.07. nur noch zu 1,5-9,3 %. Der Prompt war
unveraendert. Und er hedged aus KORREKTER Schlussfolgerung: ~90 % WARTEN-Signal,
negativer Track-Record, plus die Prompt-Anweisung, genau dann NEUTRAL zu sagen.

Gemessen, ob mehr Meinung ueberhaupt Wert haette: KI-LONG n=286 +0,29 $ / 76 %
Treffer, KI-SHORT n=93 -0,02 $ / 55 %. ABER die 286 sind 20 Episoden an 5 Tagen
(Minutentakt-Logger), die 93 sind 16 Episoden -> effektiv n~20 gegen n~16, ein
Regime. Kein Beleg. Deshalb BEWUSST nicht am Prompt gedreht, um Meinung zu
erzwingen; Verdict-Gewicht unveraendert.

Zwei echte Prompt-Fehler behoben (Korrektheit, keine Meinungsmache):
- _SYS beschrieb das Wellen-Signal als "ATR-ZigZag" - es ist EMA12 vs EMA50
- _SYS nannte "TradersUnion-Tachos" als Input - TU ist seit 19.07. raus

Kalibrierte Groessen ergaenzt: _tool_market gab dem Copiloten NUR Wellen-Signal
und Session. Neu: p_break_target / p_break_stop (AUC 0,65 bzw. 0,68-0,72) und
cone (80%-Baender mit der REAL gemessenen Abdeckung 77 %), plus Lese-Anleitung
im Prompt (45-55 % explizit als Muenzwurf markiert).
Zwei Fallen an Live-Daten korrigiert: p_break steht bereits in PROZENT (die
zuerst gebaute x100-Umrechnung haette 300 erzeugt), und die Engine-Felder plus
die engine-Referenz im Agent existierten gar nicht - der Fail-safe haette still
nichts geliefert. Snapshot weist die Werte jetzt nur zu, rechnet sie nicht neu.

DeepSeek max_tokens 4000 -> 8000: das JSON brach regelmaessig mitten im Text ab
("Unterminated string"), real 2x in ~14 Zyklen = ~14 % stille Ausfaelle.

Kerzen/Muster bewusst NICHT eingespeist - die Lehrbuch-Lesart ist hier gemessen
invertiert (langer Koerper im Trend -0,132/-0,042; 2x Volumen -0,117/-0,204;
Muster-Kursziel trifft nur 13-38 %). Roh eingespeist wuerden sie schaden.

Verifiziert: Stub-Test von _tool_market (Werte unveraendert durchgereicht, nur
80%-Baender, ohne Engine kein Absturz); Prozess juenger als beide Dateien.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-02 23:44:35 +02:00
co-authored by Claude Opus 5
parent 20cd04caa7
commit 00c78201fa
3 changed files with 158 additions and 11 deletions
+57
View File
@@ -2539,6 +2539,63 @@ konstant; (d) es gab **keine Telemetrie über die Entscheidung selbst**.
in ~20 min gefunden; die Trefferquote spricht für weitere. `analyze_divergence.py` in ~20 min gefunden; die Trefferquote spricht für weitere. `analyze_divergence.py`
regelmäßig laufen lassen (Kandidat für den Wochenreport). regelmäßig laufen lassen (Kandidat für den Wochenreport).
## 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
bekommen? Weitere Quellen (Kerzen, Muster)?"
**Diagnose — es ist NICHT der Prompt, es ist der Provider.** Der Bruch ist exakt
datierbar (`recommendations.ai_sentiment` je Tag): bis **19.07.** war der Copilot zu
**4290 %** gerichtet, ab **21.07.** nur noch zu **1,59,3 %**. Dazwischen liegt der
Wechsel auf **DeepSeek (2026-07-20)**. Der Prompt war die ganze Zeit unverändert.
⚠ Und er hedged **aus korrekter Schlussfolgerung**, nicht aus Schwäche: die
Begründungen sind konkret und zutreffend, und er bekommt zu ~90 % ein WARTEN-Signal,
einen negativen Track-Record UND die ausdrückliche Prompt-Anweisung, genau dann
NEUTRAL zu empfehlen. Er tut, was dort steht.
**Sind seine gerichteten Rufe etwas wert? Unentschieden — die Stichprobe trägt nicht.**
Forward-Return über `verdict_votes` × `candles_m1`: KI-LONG n=286 → **+0,29 $** in
Richtung / 76 % Treffer (60 min), KI-SHORT n=93 → 0,02 $ / 55 %. ⚠ **Das ist KEIN
Beleg:** die 286 Stimmen sind **20 Episoden an 5 Tagen** (Minutentakt-Logger), die 93
sind 16 Episoden — effektiv n≈20 gegen n≈16, alles in einem Regime. Dasselbe
Cluster-Muster, an dem ORB gescheitert ist. **Deshalb bewusst NICHT am Prompt gedreht,
um mehr Meinung zu erzwingen** — das würde eine korrekte Schlussfolgerung überschreiben
und einer unbelegten Stimme mehr Gewicht geben. Verdict-Gewicht bleibt 1,0 bei
gerichteter Aussage, 0 bei NEUTRAL.
**Zwei echte Prompt-FEHLER behoben (Korrektheit, keine Meinungsmache):**
1. `_SYS` beschrieb das Wellen-Signal als **„ATR-ZigZag"** — es ist seit Langem
**EMA12 vs. EMA50**; ZigZag wurde verworfen (~0 Edge). Das Modell missverstand also
die Natur des Hauptsignals.
2. `_SYS` nannte **„TradersUnion-Tachos"** als Input — TU ist seit 2026-07-19 gar nicht
mehr im Copilot-Kontext (`_tool_market`). Das Modell wurde auf Daten hingewiesen,
die es nie sieht.
**Die zwei kalibrierten Größen ergänzt.** Befund beim Nachsehen: `_tool_market` gab dem
Copiloten **nur Wellen-Signal + Session** — er war das schlechtest informierte Modul im
System und bekam ausgerechnet die einzigen beiden Komponenten nicht, deren Kalibrierung
out-of-sample nachgewiesen ist. Neu im Kontext: **`p_break_target` / `p_break_stop`**
(AUC 0,65 bzw. 0,680,72) und **`cone`** (80 %-Bänder, mit der REAL gemessenen Abdeckung
77 %, nicht dem Nennwert). Dazu eine Lese-Anleitung im Prompt — ohne sie liest ein LLM
„P(break) 30 %" als schwaches Signal statt als klare Aussage „Level hält mit 70 %";
4555 % ist ausdrücklich als Münzwurf markiert.
⚠ **Zwei Fallen beim Bau, beide an Live-Daten korrigiert:** (a) `p_break` steht im
Snapshot **bereits in Prozent** (3 = 3 %, deckungsgleich mit der Chart-Zeile „97 %
Abprall") — die zuerst gebaute ×100-Umrechnung hätte 300 erzeugt; (b) die Engine-Felder
`_last_sr_close_hint`/`_last_stop_approach`/`_last_cone` und die `engine`-Referenz im
Agent **existierten gar nicht** — der Fail-safe hätte still nichts geliefert. Beides
verdrahtet, der Snapshot rechnet die Werte ohnehin und weist sie jetzt nur zu (kein
zweites Rechnen, anders als beim Konsens-Pfeil). Mit Stub-Test verifiziert (Werte
unverändert durchgereicht, nur 80 %-Bänder, ohne Engine kein Absturz).
⚠ **Kerzen/Muster bewusst NICHT eingespeist.** Ein LLM wendet darauf die Lehrbuch-Lesart
an — und die ist hier gemessen **invertiert**: „langer Körper im Trend = Fortsetzung"
0,132/0,042 (Klimax, läuft dagegen); „2× Volumen = echt" → 0,117/0,204 (monoton
falsch herum); Muster-Kursziel trifft nur zu 1338 %. Roh eingespeist würden sie den
Copiloten aktiv verschlechtern. Offener Kandidat: sie MIT der gemessenen Lesart
mitgeben („langer Docht oben; gemessen läuft der Kurs danach eher GEGEN die Kerze") —
noch nicht gebaut.
## Gesamtprüfung 2026-08-02 (nach `docs/review-prompt.md`) ## Gesamtprüfung 2026-08-02 (nach `docs/review-prompt.md`)
Erster vollständiger Durchlauf des Review-Prompts. **Keine Strategie-Änderung** — alle Erster vollständiger Durchlauf des Review-Prompts. **Keine Strategie-Änderung** — alle
+83 -7
View File
@@ -50,8 +50,16 @@ _OPENAI_URL = "https://api.openai.com/v1/chat/completions"
_SYS = ( _SYS = (
"Du bist ein nüchterner Trading-Copilot für WTI-Rohöl (Intraday-Scalping). " "Du bist ein nüchterner Trading-Copilot für WTI-Rohöl (Intraday-Scalping). "
"Du bekommst den aktuellen Systemzustand eines automatischen Handels-Bots " "Du bekommst den aktuellen Systemzustand eines automatischen Handels-Bots "
"(Wellen-Signal als ATR-ZigZag, TradersUnion-Tachos, offene Position, " # ⚠ KORRIGIERT 2026-08-02: hier stand „Wellen-Signal als ATR-ZigZag,
"Konto, Track-Record, News). Beurteile die Lage knapp und ehrlich — keine " # TradersUnion-Tachos". BEIDES war falsch und hat das Modell in die Irre
# geführt: (a) die Richtung kommt seit Langem aus EMA12 vs. EMA50 — ZigZag
# wurde verworfen, weil es ~0 Edge hatte; (b) TradersUnion ist seit
# 2026-07-19 gar nicht mehr im Copilot-Kontext. Das Modell wurde also auf
# Daten hingewiesen, die es nie sieht, und hat die Natur des Hauptsignals
# missverstanden.
"(Wellen-Signal aus EMA12 gegen EMA50 mit Totband, Durchbruch-"
"Wahrscheinlichkeit am nächsten S/R-Level, erwartete Kursspanne, offene "
"Position, Konto, Track-Record, News). Beurteile die Lage knapp und ehrlich — keine "
"Garantien, keine Hype-Sprache. Wenn die Signale widersprüchlich oder dünn " "Garantien, keine Hype-Sprache. Wenn die Signale widersprüchlich oder dünn "
"sind, sag das klar und empfiehl NEUTRAL/abwarten.\n\n" "sind, sag das klar und empfiehl NEUTRAL/abwarten.\n\n"
"WICHTIG — nutze den Track-Record zur Kalibrierung: 'current_setup_history' " "WICHTIG — nutze den Track-Record zur Kalibrierung: 'current_setup_history' "
@@ -62,6 +70,28 @@ _SYS = (
"Live-Signal stark wirkt. Hat es sich bewährt, darf das deine Konfidenz " "Live-Signal stark wirkt. Hat es sich bewährt, darf das deine Konfidenz "
"stützen. Folge dem Signal nicht blind gegen eine klar negative Historie; " "stützen. Folge dem Signal nicht blind gegen eine klar negative Historie; "
"nenne den Bezug in 'reasoning' kurz (z.B. Setup-Trefferquote).\n\n" "nenne den Bezug in 'reasoning' kurz (z.B. Setup-Trefferquote).\n\n"
# ── Die zwei EINZIGEN kalibriert geprüften Größen des Projekts ────────────
# ⚠ Ergänzt 2026-08-02. Der Copilot sah bis dahin NUR das Wellen-Signal und
# die Session — er war damit das schlechtest informierte Modul im System und
# bekam ausgerechnet die beiden Komponenten nicht, deren Treffsicherheit
# out-of-sample nachgewiesen ist. Die Lese-Anleitung ist Pflicht: ohne sie
# deutet ein LLM „P(break) 30 %" als schwaches Signal statt als klare
# Aussage „das Level hält mit 70 %".
"ZWEI KALIBRIERTE GRÖSSEN — nutze sie bevorzugt, sie sind die einzigen "
"Zahlen hier mit nachgewiesener Treffsicherheit:\n"
"1) 'p_break_target' / 'p_break_stop' = Wahrscheinlichkeit in PROZENT, dass "
"der Kurs das jeweilige S/R-Level DURCHBRICHT (Ziel-Level in Trade-Richtung, "
"Stop-Level dagegen). Out-of-sample geprüft (AUC 0,65 bzw. 0,680,72). "
"Lies sie richtig: 30 % Durchbruch heißt 70 % ABPRALL — das ist eine starke "
"Aussage, kein schwaches Signal. Ein Abprall am Ziel-Level deckelt den "
"Ertrag (spricht gegen einen Einstieg dorthin); ein Durchbruch am "
"Stop-Level ist eine Warnung für eine offene Position. Werte zwischen 45 "
"und 55 % sind ein Münzwurf — dann NICHT darauf argumentieren.\n"
"2) 'cone' = erwartete Kursspanne in 30/60/120 Minuten (80 %-Band). "
"Gemessene Abdeckung real 77 %, nicht 80 % — das Band ist eher etwas ZU "
"ENG. Es sagt NICHTS über die Richtung, nur über die plausible Weite. "
"Nutze es, um Ziele/Stops auf Realismus zu prüfen: liegt ein Kursziel "
"außerhalb des 120-min-Bands, ist es für einen Intraday-Trade unrealistisch.\n\n"
"Wähle außerdem den Analyse-Timeframe 'timeframe' für die Wellen-Erkennung " "Wähle außerdem den Analyse-Timeframe 'timeframe' für die Wellen-Erkennung "
"(M1|M5|M15|M30|H1): M1/M5 bei enger Range und ruhigem Markt (Scalping, " "(M1|M5|M15|M30|H1): M1/M5 bei enger Range und ruhigem Markt (Scalping, "
"kleine Wellen); höhere TF (M15/M30/H1) bei klarem Trend, hoher Volatilität " "kleine Wellen); höhere TF (M15/M30/H1) bei klarem Trend, hoher Volatilität "
@@ -136,7 +166,11 @@ _SCHEMA = {
class TradingAgent: class TradingAgent:
def __init__(self, cfg, *, data, trader, trail, tu, wave, history, def __init__(self, cfg, *, data, trader, trail, tu, wave, history,
news, elliott=None): news, elliott=None, engine=None):
# `engine` (optional, 2026-08-02): Zugriff auf die kalibrierten Größen
# P(break) und Kegel, die die Engine ohnehin je Snapshot berechnet.
# Optional gehalten, damit Tests/Standalone-Nutzung ohne Engine laufen.
self.engine = engine
self.cfg = cfg self.cfg = cfg
self.data = data self.data = data
self.trader = trader self.trader = trader
@@ -221,7 +255,7 @@ class TradingAgent:
# TU entfernt (2026-07-08): war nach der TU-Entfernung aus Empfehlung/Verdict # TU entfernt (2026-07-08): war nach der TU-Entfernung aus Empfehlung/Verdict
# die letzte Hintertür — der Copilot ist eine Verdict-Stimme, TU floss so # die letzte Hintertür — der Copilot ist eine Verdict-Stimme, TU floss so
# indirekt wieder ein (lagging, nicht backtestbar). # indirekt wieder ein (lagging, nicht backtestbar).
return { out = {
"wave_signal": wsig.get("signal"), "wave_signal": wsig.get("signal"),
"wave_confidence": wsig.get("conf_pct"), "wave_confidence": wsig.get("conf_pct"),
"wave_setup": wsig.get("setup"), "wave_setup": wsig.get("setup"),
@@ -231,6 +265,42 @@ class TradingAgent:
"wave_reasons": wsig.get("reasons", [])[:4], "wave_reasons": wsig.get("reasons", [])[:4],
"session": _agent_session(), "session": _agent_session(),
} }
# ── Die kalibrierten Größen dazugeben (2026-08-02) ────────────────────
# ⚠ Bis hierher sah der Copilot NUR Wellen-Signal + Session. Er war damit
# das schlechtest informierte Modul im System — und ihm fehlten
# ausgerechnet die beiden Komponenten, deren Kalibrierung out-of-sample
# nachgewiesen ist (P(break) AUC 0,65 / Kegel 77 % reale Abdeckung).
# Fail-safe: fehlt die Engine oder wirft ein Teil, bleibt das Feld weg —
# ein fehlender Kontext darf die Analyse nie verhindern.
eng = getattr(self, "engine", None)
if eng is not None:
try:
# ⚠ `p_break` steht im Snapshot BEREITS in Prozent (3 = 3 %,
# deckungsgleich mit der Chart-Zeile „97 % Abprall") — NICHT
# nochmal ×100 rechnen. Beim Bau zuerst falsch angenommen und
# an den Live-Daten korrigiert.
hint = getattr(eng, "_last_sr_close_hint", None) or {}
if hint.get("p_break") is not None:
out["p_break_target"] = round(float(hint["p_break"]))
out["p_break_target_level"] = hint.get("level")
except Exception:
pass
try:
stop = getattr(eng, "_last_stop_approach", None) or {}
if stop.get("p_break") is not None:
out["p_break_stop"] = round(float(stop["p_break"]))
out["p_break_stop_level"] = stop.get("level")
except Exception:
pass
try:
cone = getattr(eng, "_last_cone", None) or {}
lv = [l for l in (cone.get("levels") or []) if l.get("band") == 80]
if lv:
out["cone"] = [{"minutes": l["minutes"], "lo": l["lo"], "hi": l["hi"],
"abdeckung_real_pct": l.get("coverage")} for l in lv]
except Exception:
pass
return out
def _tool_position(self) -> dict: def _tool_position(self) -> dict:
ps = self.trader.snapshot() ps = self.trader.snapshot()
@@ -461,8 +531,14 @@ class TradingAgent:
def _call_deepseek(self, prompt: str) -> str: def _call_deepseek(self, prompt: str) -> str:
# DeepSeek (OpenAI-kompatibel, api.deepseek.com). deepseek-v4-flash ist ein # DeepSeek (OpenAI-kompatibel, api.deepseek.com). deepseek-v4-flash ist ein
# Reasoning-Modell (content nach reasoning_content) → max_tokens großzügig # Reasoning-Modell (content nach reasoning_content) → max_tokens großzügig,
# (4000), sonst content leer. temperature 0.3 ok. CJK-Drift-Schutz im Aufrufer. # sonst content leer. temperature 0.3 ok. CJK-Drift-Schutz im Aufrufer.
# ⚠ 4000 → 8000 (2026-08-02): bei 4000 brach das JSON regelmäßig MITTEN im
# Text ab („Unterminated string …"), real 2× in ~14 Zyklen ≈ 14 % stille
# Ausfälle — das Reasoning frisst das Budget, bevor der content fertig ist.
# Ein Fehlschlag verwirft die Analyse und lässt die ALTE Empfehlung stehen,
# ist also von außen kaum sichtbar. Mit dem ergänzten P(break)/Kegel-Kontext
# wird die Antwort eher länger, nicht kürzer.
import requests import requests
dc = self.cfg["deepseek"] if self.cfg.has_section("deepseek") else {} dc = self.cfg["deepseek"] if self.cfg.has_section("deepseek") else {}
base = (dc.get("base_url") or "https://api.deepseek.com").rstrip("/") base = (dc.get("base_url") or "https://api.deepseek.com").rstrip("/")
@@ -474,7 +550,7 @@ class TradingAgent:
json={"model": model, json={"model": model,
"messages": [{"role": "system", "content": _SYS}, "messages": [{"role": "system", "content": _SYS},
{"role": "user", "content": prompt}], {"role": "user", "content": prompt}],
"temperature": 0.3, "max_tokens": 4000}, "temperature": 0.3, "max_tokens": 8000},
timeout=120) timeout=120)
resp.raise_for_status() resp.raise_for_status()
return (resp.json()["choices"][0]["message"].get("content") or "").strip() return (resp.json()["choices"][0]["message"].get("content") or "").strip()
+18 -4
View File
@@ -237,7 +237,14 @@ class TradingEngine:
self.agent = TradingAgent( self.agent = TradingAgent(
self.cfg, data=self.data, trader=self.trader, trail=self.trail, self.cfg, data=self.data, trader=self.trader, trail=self.trail,
tu=self.tu, wave=self.wave, history=self.history, tu=self.tu, wave=self.wave, history=self.history,
news=self.news, elliott=self.elliott) news=self.news, elliott=self.elliott, engine=self)
# Letzte kalibrierte Größen für den Copiloten (2026-08-02). Der Snapshot
# rechnet sie ohnehin je Zyklus — hier nur festhalten, damit `agent.
# _tool_market` sie mitgeben kann, OHNE sie ein zweites Mal zu rechnen
# (derselbe Fehler wie beim Konsens-Pfeil, der ein zweites `_verdict()` zog).
self._last_sr_close_hint: dict | None = None
self._last_stop_approach: dict | None = None
self._last_cone: dict | None = None
# Tägliche WTI-Intraday-Level per Web-Recherche (z.ai/GLM + web_search). # Tägliche WTI-Intraday-Level per Web-Recherche (z.ai/GLM + web_search).
# Nur Kontext → S/R + Anzeige, NICHT Handelsrichtung. # Nur Kontext → S/R + Anzeige, NICHT Handelsrichtung.
zc = self.cfg["zai"] if self.cfg.has_section("zai") else {} zc = self.cfg["zai"] if self.cfg.has_section("zai") else {}
@@ -3049,6 +3056,15 @@ class TradingEngine:
} }
sr_close_hint = self._sr_close_hint(market, position, wave_snap) sr_close_hint = self._sr_close_hint(market, position, wave_snap)
stop_approach = self._stop_approach_hint(market, position, wave_snap) stop_approach = self._stop_approach_hint(market, position, wave_snap)
_cone = _cone_build(
(market or {}).get("bid"),
float(((wave_snap or {}).get("pb_feats") or {}).get("atr") or 0.0))
# Für den KI-Copiloten festhalten (2026-08-02) — er bekam bis dahin nur
# das Wellen-Signal und sah ausgerechnet die beiden kalibrierten Größen
# des Projekts nicht. Hier nur ZUWEISEN, nicht neu rechnen.
self._last_sr_close_hint = sr_close_hint
self._last_stop_approach = stop_approach
self._last_cone = _cone
# einmal holen — Verdict-Stimme UND Snapshot-Feld nutzen dasselbe Objekt # einmal holen — Verdict-Stimme UND Snapshot-Feld nutzen dasselbe Objekt
_pat_snap = self.patterns.snapshot() _pat_snap = self.patterns.snapshot()
# ── Marktstatus + Hyperliquid-Kurs (User 2026-08-01) ───────────────── # ── Marktstatus + Hyperliquid-Kurs (User 2026-08-01) ─────────────────
@@ -3184,9 +3200,7 @@ class TradingEngine:
"candle_anat": self.candle_anat.snapshot(), "candle_anat": self.candle_anat.snapshot(),
# Wahrscheinlichkeits-Kegel (Streuung, NICHT Richtung) — out-of-sample # Wahrscheinlichkeits-Kegel (Streuung, NICHT Richtung) — out-of-sample
# kalibriert (`analyze_cone.py`). Reine Anzeige. # kalibriert (`analyze_cone.py`). Reine Anzeige.
"cone": _cone_build( "cone": _cone,
(market or {}).get("bid"),
float(((wave_snap or {}).get("pb_feats") or {}).get("atr") or 0.0)),
"stats_compact": {"week": self._compact_stats("week"), "stats_compact": {"week": self._compact_stats("week"),
"all": self._compact_stats("all")}, "all": self._compact_stats("all")},
"close_alert_count": self._close_alert_count, "close_alert_count": self._close_alert_count,