mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-28 16:07:46 +00:00
bb450f7740
- Updated QWEN.md with English-only comment policy - Translated all German comments in: * eurusd_regime.py * eurusd_llm.py * eurusd_reflection.py * eurusd_memory.py * eurusd_macro.py * eurusd_debate.py * predix_dashboard.py - All comments, docstrings, and print statements now in English - Ensures consistency with commit messages and documentation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
472 lines
17 KiB
Python
472 lines
17 KiB
Python
"""
|
|
BM25 Memory-System für EURUSD Trading-Setups
|
|
|
|
Speichert vergangene Trades mit:
|
|
- Marktsituation (Features, Regime, Indikatoren)
|
|
- Entscheidung (LONG/SHORT/NEUTRAL, Leverage, SL, TP)
|
|
- Ergebnis (PnL, Win/Loss)
|
|
- Reflection (Lessons Learned)
|
|
|
|
Vorteile gegenüber Vector-DBs:
|
|
- Keine API-Kosten (offline-fähig)
|
|
- Keine Token-Limits
|
|
- Lexikalische Ähnlichkeit (präzise für Trading-Setups)
|
|
- Schnell und einfach zu implementieren
|
|
"""
|
|
|
|
import json
|
|
import pickle
|
|
import re
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
import numpy as np
|
|
from rank_bm25 import BM25Okapi
|
|
|
|
|
|
def tokenize(text: str) -> List[str]:
|
|
"""
|
|
Tokenisiert Text für BM25-Verarbeitung.
|
|
|
|
- Entfernt Sonderzeichen
|
|
- Konvertiert zu Kleinbuchstaben
|
|
- Split auf Wörter und Zahlen
|
|
|
|
Parameters
|
|
----------
|
|
text : str
|
|
Eingabetext (Trading-Situation, Setup-Beschreibung)
|
|
|
|
Returns
|
|
-------
|
|
List[str]
|
|
Liste von Tokens
|
|
"""
|
|
# Konvertiere zu Kleinbuchstaben
|
|
text = text.lower()
|
|
|
|
# Extrahiere Wörter und Zahlen (inkl. Dezimalzahlen wie 1.0850)
|
|
tokens = re.findall(r'\b\w+(?:\.\d+)?\b', text)
|
|
|
|
# Filtere sehr kurze Tokens (< 2 Zeichen)
|
|
tokens = [t for t in tokens if len(t) >= 2]
|
|
|
|
return tokens
|
|
|
|
|
|
class EURUSDTradeMemory:
|
|
"""
|
|
BM25-basiertes Memory-System für vergangene EURUSD-Trading-Setups.
|
|
|
|
Speichert vergangene Trades mit:
|
|
- Marktsituation (Features, Regime, Indikatoren)
|
|
- Entscheidung (LONG/SHORT/NEUTRAL, Leverage, SL, TP)
|
|
- Ergebnis (PnL, Win/Loss)
|
|
- Reflection (Lessons Learned)
|
|
|
|
Bei neuer Situation: Findet ähnliche vergangene Setups und gibt
|
|
historische Win-Rate und durchschnittliche Rendite zurück.
|
|
|
|
Attributes
|
|
----------
|
|
memory_file : Path
|
|
Pfad zur persistenten Speicherdatei (JSON)
|
|
memories : List[dict]
|
|
Liste aller gespeicherten Trades
|
|
bm25 : BM25Okapi
|
|
BM25-Index für schnelle Ähnlichkeitssuche
|
|
|
|
Example
|
|
-------
|
|
>>> memory = EURUSDTradeMemory()
|
|
>>> memory.add_trade(
|
|
... situation="EURUSD 1.0850, RSI=28, Hurst=0.52 (MEAN_REVERSION), EZB hawkish",
|
|
... decision={"action": "LONG", "leverage": 20, "sl_pips": 25, "tp_pips": 15},
|
|
... outcome=0.023, # +2.3% Gewinn
|
|
... reflection="RSI < 30 in Mean-Reversion Regime war erfolgreich"
|
|
... )
|
|
>>> similar = memory.get_similar_setups("EURUSD 1.0820, RSI=25, Hurst=0.48")
|
|
>>> print(f"Historische Win-Rate: {similar['historical_win_rate']:.1%}")
|
|
"""
|
|
|
|
def __init__(self, memory_file: str = "git_ignore_folder/eurusd_trade_memory.json"):
|
|
"""
|
|
Initialisiert das Memory-System.
|
|
|
|
Parameters
|
|
----------
|
|
memory_file : str
|
|
Pfad zur JSON-Datei für persistente Speicherung
|
|
"""
|
|
self.memory_file = Path(memory_file)
|
|
self.memories: List[dict] = []
|
|
self.bm25: Optional[BM25Okapi] = None
|
|
self.tokenized_memories: List[List[str]] = []
|
|
|
|
# Lade existierende Memories von Datei
|
|
if self.memory_file.exists():
|
|
self.load()
|
|
|
|
def add_trade(
|
|
self,
|
|
situation: str,
|
|
decision: dict,
|
|
outcome: float,
|
|
reflection: Optional[str] = None
|
|
) -> None:
|
|
"""
|
|
Speichert einen vergangenen Trade im Memory.
|
|
|
|
Parameters
|
|
----------
|
|
situation : str
|
|
Beschreibung der Marktsituation zum Zeitpunkt des Trades.
|
|
Beispiel: "EURUSD 1.0850, RSI=28, Hurst=0.52 (MEAN_REVERSION),
|
|
London Session, EZB hawkish, DXY downtrend"
|
|
decision : dict
|
|
Trade-Entscheidung mit Details.
|
|
Beispiel: {"action": "LONG", "leverage": 20, "sl_pips": 25, "tp_pips": 15}
|
|
outcome : float
|
|
Ergebnis des Trades als Dezimalzahl.
|
|
Beispiel: 0.023 = +2.3% Gewinn, -0.015 = -1.5% Verlust
|
|
reflection : str, optional
|
|
Lessons Learned nach dem Trade (vom Reflection-System generiert).
|
|
"""
|
|
trade_record = {
|
|
"id": len(self.memories) + 1,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"situation": situation,
|
|
"decision": decision,
|
|
"outcome": outcome,
|
|
"reflection": reflection or "",
|
|
"tokens": tokenize(situation)
|
|
}
|
|
|
|
self.memories.append(trade_record)
|
|
self.tokenized_memories.append(trade_record["tokens"])
|
|
|
|
# Rebuild BM25 Index
|
|
self._rebuild_bm25()
|
|
|
|
# Speichere auf Festplatte
|
|
self.save()
|
|
|
|
def add_trades_batch(self, trades: List[dict]) -> None:
|
|
"""
|
|
Fügt mehrere Trades auf einmal hinzu (effizienter als einzelne add_trade Aufrufe).
|
|
|
|
Parameters
|
|
----------
|
|
trades : List[dict]
|
|
Liste von Trade-Records mit Keys: situation, decision, outcome, reflection
|
|
"""
|
|
for trade in trades:
|
|
trade_record = {
|
|
"id": len(self.memories) + len(trades),
|
|
"timestamp": datetime.now().isoformat(),
|
|
"situation": trade["situation"],
|
|
"decision": trade["decision"],
|
|
"outcome": trade["outcome"],
|
|
"reflection": trade.get("reflection", ""),
|
|
"tokens": tokenize(trade["situation"])
|
|
}
|
|
self.memories.append(trade_record)
|
|
self.tokenized_memories.append(trade_record["tokens"])
|
|
|
|
self._rebuild_bm25()
|
|
self.save()
|
|
|
|
def get_similar_setups(
|
|
self,
|
|
current_situation: str,
|
|
n: int = 5,
|
|
min_similarity: float = 0.0
|
|
) -> dict:
|
|
"""
|
|
Findet ähnliche vergangene Trading-Setups.
|
|
|
|
Parameters
|
|
----------
|
|
current_situation : str
|
|
Aktuelle Marktsituation (gleiche Formatierung wie bei add_trade)
|
|
n : int, default 5
|
|
Anzahl der zurückzugebenden ähnlichen Setups
|
|
min_similarity : float, default 0.0
|
|
Minimale BM25-Ähnlichkeit für Treffer
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
Ähnliche Setups mit Statistiken:
|
|
- similar_setups: Liste der Top-N ähnlichen Trades
|
|
- historical_win_rate: Win-Rate der ähnlichen Setups
|
|
- historical_avg_return: Durchschnittliche Rendite
|
|
- best_setup: Bestes historisches Setup
|
|
- recommendation: Handlungsempfehlung basierend auf History
|
|
"""
|
|
if len(self.memories) == 0:
|
|
return {
|
|
"similar_setups": [],
|
|
"historical_win_rate": 0.0,
|
|
"historical_avg_return": 0.0,
|
|
"message": "Keine historischen Trades gespeichert"
|
|
}
|
|
|
|
# Tokenisiere aktuelle Situation
|
|
query_tokens = tokenize(current_situation)
|
|
|
|
# Berechne BM25-Ähnlichkeiten
|
|
scores = self.bm25.get_scores(query_tokens)
|
|
|
|
# Finde Top-N Treffer
|
|
top_indices = np.argsort(scores)[::-1][:n]
|
|
|
|
# Filtere nach min_similarity
|
|
filtered_indices = [
|
|
i for i in top_indices
|
|
if scores[i] >= min_similarity
|
|
]
|
|
|
|
if len(filtered_indices) == 0:
|
|
return {
|
|
"similar_setups": [],
|
|
"historical_win_rate": 0.0,
|
|
"historical_avg_return": 0.0,
|
|
"message": f"Keine ähnlichen Setups gefunden (min_similarity={min_similarity})"
|
|
}
|
|
|
|
# Sammle ähnliche Setups
|
|
similar_setups = []
|
|
outcomes = []
|
|
|
|
for idx in filtered_indices:
|
|
memory = self.memories[idx]
|
|
similar_setups.append({
|
|
"id": memory["id"],
|
|
"situation": memory["situation"],
|
|
"decision": memory["decision"],
|
|
"outcome": memory["outcome"],
|
|
"reflection": memory["reflection"],
|
|
"similarity_score": float(scores[idx]),
|
|
"timestamp": memory["timestamp"]
|
|
})
|
|
outcomes.append(memory["outcome"])
|
|
|
|
# Berechne Statistiken
|
|
outcomes_array = np.array(outcomes)
|
|
win_rate = np.mean(outcomes_array > 0)
|
|
avg_return = np.mean(outcomes_array)
|
|
std_return = np.std(outcomes_array) if len(outcomes) > 1 else 0.0
|
|
|
|
# Finde bestes Setup
|
|
best_idx = np.argmax(outcomes_array)
|
|
best_setup = similar_setups[best_idx]
|
|
|
|
# Generiere Empfehlung
|
|
if win_rate > 0.7 and len(filtered_indices) >= 3:
|
|
recommendation = "STRONG_SIGNAL"
|
|
rec_text = f"Starke Historie: {win_rate:.0%} Win-Rate in {len(filtered_indices)} ähnlichen Situationen"
|
|
elif win_rate > 0.55:
|
|
recommendation = "MODERATE_SIGNAL"
|
|
rec_text = f"Moderate Historie: {win_rate:.0%} Win-Rate"
|
|
elif win_rate < 0.4 and len(filtered_indices) >= 3:
|
|
recommendation = "AVOID"
|
|
rec_text = f"Schwache Historie: Nur {win_rate:.0%} Win-Rate - Setup vermeiden!"
|
|
else:
|
|
recommendation = "NEUTRAL"
|
|
rec_text = f"Neutrale Historie: {win_rate:.0%} Win-Rate, zu wenig Daten für klare Empfehlung"
|
|
|
|
return {
|
|
"similar_setups": similar_setups,
|
|
"historical_win_rate": float(win_rate),
|
|
"historical_avg_return": float(avg_return),
|
|
"historical_std_return": float(std_return),
|
|
"n_similar_trades": len(filtered_indices),
|
|
"best_setup": best_setup,
|
|
"recommendation": recommendation,
|
|
"recommendation_text": rec_text
|
|
}
|
|
|
|
def get_memory_stats(self) -> dict:
|
|
"""
|
|
Gibt Statistiken über das gespeicherte Memory.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
Memory-Statistiken:
|
|
- total_trades: Gesamtanzahl Trades
|
|
- win_rate: Gesamte Win-Rate
|
|
- avg_return: Durchschnittliche Rendite
|
|
- best_trade: Bester Trade
|
|
- worst_trade: Schlechtester Trade
|
|
- recent_performance: Performance der letzten 10 Trades
|
|
"""
|
|
if len(self.memories) == 0:
|
|
return {"message": "Keine Trades gespeichert"}
|
|
|
|
outcomes = [m["outcome"] for m in self.memories]
|
|
outcomes_array = np.array(outcomes)
|
|
|
|
# Recent Performance (letzte 10 Trades)
|
|
recent_outcomes = outcomes_array[-10:] if len(outcomes) > 10 else outcomes_array
|
|
|
|
return {
|
|
"total_trades": len(self.memories),
|
|
"win_rate": float(np.mean(outcomes_array > 0)),
|
|
"avg_return": float(np.mean(outcomes_array)),
|
|
"std_return": float(np.std(outcomes_array)),
|
|
"sharpe_ratio": float(np.mean(outcomes_array) / np.std(outcomes_array)) if np.std(outcomes_array) > 0 else 0.0,
|
|
"best_trade": {
|
|
"id": self.memories[np.argmax(outcomes_array)]["id"],
|
|
"outcome": float(np.max(outcomes_array)),
|
|
"situation": self.memories[np.argmax(outcomes_array)]["situation"]
|
|
},
|
|
"worst_trade": {
|
|
"id": self.memories[np.argmin(outcomes_array)]["id"],
|
|
"outcome": float(np.min(outcomes_array)),
|
|
"situation": self.memories[np.argmin(outcomes_array)]["situation"]
|
|
},
|
|
"recent_performance": {
|
|
"n_trades": len(recent_outcomes),
|
|
"win_rate": float(np.mean(recent_outcomes > 0)),
|
|
"avg_return": float(np.mean(recent_outcomes))
|
|
}
|
|
}
|
|
|
|
def _rebuild_bm25(self) -> None:
|
|
"""
|
|
Baut den BM25-Index neu auf (nach Hinzufügen neuer Trades).
|
|
"""
|
|
if len(self.tokenized_memories) > 0:
|
|
self.bm25 = BM25Okapi(self.tokenized_memories)
|
|
|
|
def save(self) -> None:
|
|
"""
|
|
Speichert das Memory persistent auf die Festplatte.
|
|
"""
|
|
# Erstelle Verzeichnis falls nicht existent
|
|
self.memory_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Speichere als JSON (ohne BM25-Index, der wird beim Laden neu gebaut)
|
|
save_data = []
|
|
for memory in self.memories:
|
|
save_entry = {k: v for k, v in memory.items() if k != "tokens"}
|
|
save_data.append(save_entry)
|
|
|
|
with open(self.memory_file, 'w', encoding='utf-8') as f:
|
|
json.dump(save_data, f, indent=2, ensure_ascii=False)
|
|
|
|
def load(self) -> None:
|
|
"""
|
|
Lädt das Memory von der Festplatte.
|
|
"""
|
|
try:
|
|
with open(self.memory_file, 'r', encoding='utf-8') as f:
|
|
save_data = json.load(f)
|
|
|
|
self.memories = []
|
|
self.tokenized_memories = []
|
|
|
|
for entry in save_data:
|
|
entry["tokens"] = tokenize(entry["situation"])
|
|
self.memories.append(entry)
|
|
self.tokenized_memories.append(entry["tokens"])
|
|
|
|
self._rebuild_bm25()
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Error loading Memory: {e}")
|
|
self.memories = []
|
|
self.tokenized_memories = []
|
|
|
|
def clear(self) -> None:
|
|
"""
|
|
Löscht das gesamte Memory.
|
|
"""
|
|
self.memories = []
|
|
self.tokenized_memories = []
|
|
self.bm25 = None
|
|
|
|
if self.memory_file.exists():
|
|
self.memory_file.unlink()
|
|
|
|
|
|
# Test-Funktion für lokale Validierung
|
|
if __name__ == "__main__":
|
|
print("=== BM25 Memory Test ===\n")
|
|
|
|
# Erstelle Test-Memory
|
|
memory = EURUSDTradeMemory(memory_file="git_ignore_folder/test_trade_memory.json")
|
|
|
|
# Füge Beispiel-Trades hinzu
|
|
test_trades = [
|
|
{
|
|
"situation": "EURUSD 1.0850, RSI=28, Hurst=0.52 (MEAN_REVERSION), London Session, EZB hawkish, DXY downtrend",
|
|
"decision": {"action": "LONG", "leverage": 20, "sl_pips": 25, "tp_pips": 15},
|
|
"outcome": 0.023,
|
|
"reflection": "RSI < 30 in Mean-Reversion Regime war erfolgreich"
|
|
},
|
|
{
|
|
"situation": "EURUSD 1.0920, RSI=72, Hurst=0.58 (NEUTRAL), NY Session, Fed dovish, DXY weak",
|
|
"decision": {"action": "SHORT", "leverage": 15, "sl_pips": 30, "tp_pips": 20},
|
|
"outcome": 0.015,
|
|
"reflection": "RSI > 70 mit Mean-Reversion funktioniert gut"
|
|
},
|
|
{
|
|
"situation": "EURUSD 1.0780, RSI=25, Hurst=0.48 (MEAN_REVERSION), Asian Session, low volatility",
|
|
"decision": {"action": "LONG", "leverage": 10, "sl_pips": 20, "tp_pips": 12},
|
|
"outcome": -0.012,
|
|
"reflection": "Asian Session zu wenig Volumen für Mean-Reversion"
|
|
},
|
|
{
|
|
"situation": "EURUSD 1.0950, RSI=65, Hurst=0.72 (TRENDING), London-NY Overlap, strong momentum",
|
|
"decision": {"action": "LONG", "leverage": 25, "sl_pips": 20, "tp_pips": 35},
|
|
"outcome": 0.035,
|
|
"reflection": "Trending Regime mit Momentum war sehr profitabel"
|
|
},
|
|
{
|
|
"situation": "EURUSD 1.0880, RSI=45, Hurst=0.61 (NEUTRAL), no clear direction, choppy market",
|
|
"decision": {"action": "NEUTRAL", "leverage": 0, "sl_pips": 0, "tp_pips": 0},
|
|
"outcome": 0.0,
|
|
"reflection": "Abwarten war die beste Entscheidung in choppy Market"
|
|
},
|
|
]
|
|
|
|
memory.add_trades_batch(test_trades)
|
|
print(f"✅ {len(test_trades)} Trades zum Memory hinzugefügt\n")
|
|
|
|
# Teste Ähnlichkeitssuche
|
|
print("=== Test 1: Ähnliche Setups finden ===")
|
|
query = "EURUSD 1.0840, RSI=26, Hurst=0.50, MEAN_REVERSION, EZB hawkish"
|
|
similar = memory.get_similar_setups(query, n=3)
|
|
|
|
print(f"Query: {query}")
|
|
print(f"Gefundene ähnliche Setups: {similar.get('n_similar_trades', 0)}")
|
|
print(f"Historische Win-Rate: {similar.get('historical_win_rate', 0):.1%}")
|
|
print(f"Durchschnittliche Rendite: {similar.get('historical_avg_return', 0):.2%}")
|
|
print(f"Empfehlung: {similar.get('recommendation', 'N/A')} - {similar.get('recommendation_text', '')}")
|
|
|
|
# Teste Memory-Statistiken
|
|
print("\n=== Test 2: Memory Statistiken ===")
|
|
stats = memory.get_memory_stats()
|
|
print(f"Gesamte Trades: {stats.get('total_trades', 0)}")
|
|
print(f"Gesamte Win-Rate: {stats.get('win_rate', 0):.1%}")
|
|
print(f"Durchschnittliche Rendite: {stats.get('avg_return', 0):.2%}")
|
|
print(f"Sharpe Ratio: {stats.get('sharpe_ratio', 0):.2f}")
|
|
print(f"Bester Trade: {stats.get('best_trade', {}).get('outcome', 0):.2%}")
|
|
print(f"Schlechtester Trade: {stats.get('worst_trade', {}).get('outcome', 0):.2%}")
|
|
|
|
# Teste Persistenz
|
|
print("\n=== Test 3: Persistenz ===")
|
|
memory2 = EURUSDTradeMemory(memory_file="git_ignore_folder/test_trade_memory.json")
|
|
print(f"Memory nach Neuladen: {len(memory2.memories)} Trades")
|
|
|
|
# Cleanup
|
|
import os
|
|
if os.path.exists("git_ignore_folder/test_trade_memory.json"):
|
|
os.remove("git_ignore_folder/test_trade_memory.json")
|
|
|
|
print("\n✅ BM25 Memory Implementierung ist funktionsfähig!")
|