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
+83 -7
View File
@@ -50,8 +50,16 @@ _OPENAI_URL = "https://api.openai.com/v1/chat/completions"
_SYS = (
"Du bist ein nüchterner Trading-Copilot für WTI-Rohöl (Intraday-Scalping). "
"Du bekommst den aktuellen Systemzustand eines automatischen Handels-Bots "
"(Wellen-Signal als ATR-ZigZag, TradersUnion-Tachos, offene Position, "
"Konto, Track-Record, News). Beurteile die Lage knapp und ehrlich — keine "
# ⚠ KORRIGIERT 2026-08-02: hier stand „Wellen-Signal als ATR-ZigZag,
# 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 "
"sind, sag das klar und empfiehl NEUTRAL/abwarten.\n\n"
"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 "
"stützen. Folge dem Signal nicht blind gegen eine klar negative Historie; "
"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 "
"(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 "
@@ -136,7 +166,11 @@ _SCHEMA = {
class TradingAgent:
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.data = data
self.trader = trader
@@ -221,7 +255,7 @@ class TradingAgent:
# 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
# indirekt wieder ein (lagging, nicht backtestbar).
return {
out = {
"wave_signal": wsig.get("signal"),
"wave_confidence": wsig.get("conf_pct"),
"wave_setup": wsig.get("setup"),
@@ -231,6 +265,42 @@ class TradingAgent:
"wave_reasons": wsig.get("reasons", [])[:4],
"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:
ps = self.trader.snapshot()
@@ -461,8 +531,14 @@ class TradingAgent:
def _call_deepseek(self, prompt: str) -> str:
# DeepSeek (OpenAI-kompatibel, api.deepseek.com). deepseek-v4-flash ist ein
# Reasoning-Modell (content nach reasoning_content) → max_tokens großzügig
# (4000), sonst content leer. temperature 0.3 ok. CJK-Drift-Schutz im Aufrufer.
# Reasoning-Modell (content nach reasoning_content) → max_tokens großzügig,
# 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
dc = self.cfg["deepseek"] if self.cfg.has_section("deepseek") else {}
base = (dc.get("base_url") or "https://api.deepseek.com").rstrip("/")
@@ -474,7 +550,7 @@ class TradingAgent:
json={"model": model,
"messages": [{"role": "system", "content": _SYS},
{"role": "user", "content": prompt}],
"temperature": 0.3, "max_tokens": 4000},
"temperature": 0.3, "max_tokens": 8000},
timeout=120)
resp.raise_for_status()
return (resp.json()["choices"][0]["message"].get("content") or "").strip()