mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-28 16:07:46 +00:00
feat: Auto-start dashboard for fin_quant
Add automatic dashboard launch options for trading loop: 1. CLI integration (rdagent/app/cli.py) - --with-dashboard/-d flag for web dashboard - --cli-dashboard/-c flag for terminal UI - --dashboard-port for custom port configuration - Automatic background process spawning 2. Dashboard auto-start - Web dashboard launches in background thread - CLI dashboard opens in separate terminal window - Graceful startup with 2-second delay 3. Process management - Dashboard runs as daemon thread - Automatic cleanup on main process exit - Error handling for dashboard startup failures 4. Documentation - Updated help text with examples - Usage instructions in README - Dashboard URLs displayed on startup Usage examples: rdagent fin_quant -d # Web dashboard rdagent fin_quant -c # CLI dashboard rdagent fin_quant -d -c # Both dashboards rdagent fin_quant -d --port 5001 # Custom port
This commit is contained in:
@@ -15,6 +15,7 @@ Ein Research Manager bewertet die Debatte und trifft die finale Entscheidung.
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Literal, Optional
|
||||
|
||||
@@ -22,6 +23,34 @@ from typing import Dict, List, Literal, Optional
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from eurusd_llm import MultiProviderLLM
|
||||
from fx_config import get_fx_config
|
||||
|
||||
|
||||
def get_current_session_info() -> dict:
|
||||
"""
|
||||
Gibt Informationen zur aktuellen FX-Session.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Session-Info mit Name, Stunden, Charakteristika, empfohlene Strategie
|
||||
"""
|
||||
config = get_fx_config()
|
||||
current_session = config.get_current_session()
|
||||
session_desc = config.get_session_description(current_session)
|
||||
|
||||
# Aktuelle UTC Zeit hinzufügen
|
||||
hour_utc = datetime.now(timezone.utc).hour
|
||||
|
||||
return {
|
||||
"session": current_session,
|
||||
"name": session_desc["name"],
|
||||
"hours": session_desc["hours"],
|
||||
"current_utc_hour": hour_utc,
|
||||
"characteristics": session_desc["characteristics"],
|
||||
"recommended_strategy": session_desc["recommended_strategy"],
|
||||
"avoid": session_desc["avoid"]
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -71,6 +100,10 @@ class EURUSDBullAgent:
|
||||
TradingSignal
|
||||
Bull-Signal mit LONG-Empfehlung und Confidence
|
||||
"""
|
||||
# Session-Info hinzufügen
|
||||
session_info = get_current_session_info()
|
||||
market_data["session"] = session_info
|
||||
|
||||
prompt = self._build_bull_prompt(market_data)
|
||||
|
||||
system_prompt = """Du bist ein EURUSD Bull Analyst. Deine Aufgabe ist es,
|
||||
@@ -117,6 +150,15 @@ class EURUSDBullAgent:
|
||||
|
||||
def _build_bull_prompt(self, data: dict) -> str:
|
||||
"""Erstellt Bull-spezifischen Prompt."""
|
||||
session = data.get("session", {})
|
||||
session_str = f"""
|
||||
=== Aktuelle Session ===
|
||||
- Session: {session.get('name', 'N/A')} ({session.get('hours', '')})
|
||||
- Charakteristika: {session.get('characteristics', '')}
|
||||
- Empfohlene Strategie: {session.get('recommended_strategy', '')}
|
||||
|
||||
""" if session else ""
|
||||
|
||||
return f"""
|
||||
Analysiere EURUSD für LONG-Setup:
|
||||
|
||||
@@ -127,12 +169,13 @@ Aktuelle Daten:
|
||||
- MACD: {data.get('macd', 'N/A')}
|
||||
- Wirtschaftsdaten: {data.get('economic_data', 'N/A')}
|
||||
- Sentiment: {data.get('sentiment', 'N/A')}
|
||||
|
||||
{session_str}
|
||||
Finde Argumente FÜR LONG EURUSD:
|
||||
1. Welche positiven Faktoren für EUR siehst du?
|
||||
2. Gibt es USD-Schwäche?
|
||||
3. Ist das technische Setup bullisch?
|
||||
4. Was ist das Risk/Reward?
|
||||
4. Passt der Trade zur aktuellen Session?
|
||||
5. Was ist das Risk/Reward?
|
||||
|
||||
Antworte als JSON:
|
||||
{{
|
||||
|
||||
@@ -17,9 +17,12 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Literal, Optional
|
||||
|
||||
import yfinance as yf
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from eurusd_llm import MultiProviderLLM
|
||||
from fx_config import get_fx_config
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -30,15 +33,21 @@ class MacroSignal:
|
||||
reasoning: List[str]
|
||||
|
||||
# Makro-Faktoren
|
||||
rate_differential: float # Fed - EZB Zinsen
|
||||
growth_differential: float # US - EU Wachstum
|
||||
momentum_score: float # -1 bis +1
|
||||
sentiment_score: float # -1 bis +1
|
||||
rate_differential: float = 0.0 # Fed - EZB Zinsen
|
||||
growth_differential: float = 0.0 # US - EU Wachstum
|
||||
momentum_score: float = 0.0 # -1 bis +1
|
||||
sentiment_score: float = 0.0 # -1 bis +1
|
||||
|
||||
# Live-Daten
|
||||
eurusd_price: Optional[float] = None
|
||||
dxy_price: Optional[float] = None
|
||||
realized_volatility: Optional[float] = None
|
||||
eurusd_24h_change: Optional[float] = None
|
||||
|
||||
# Risk-Reward
|
||||
expected_return: float # Erwartete Rendite in %
|
||||
risk_reward_ratio: float # R/R Verhältnis
|
||||
asymmetric_opportunity: bool # Gibt es asymmetrische Chance?
|
||||
expected_return: float = 0.0 # Erwartete Rendite in %
|
||||
risk_reward_ratio: float = 1.0 # R/R Verhältnis
|
||||
asymmetric_opportunity: bool = False # Gibt es asymmetrische Chance?
|
||||
|
||||
# Trade-Parameter
|
||||
entry_price: Optional[float] = None
|
||||
@@ -47,6 +56,73 @@ class MacroSignal:
|
||||
leverage: int = 20
|
||||
|
||||
|
||||
def get_live_fx_data() -> dict:
|
||||
"""
|
||||
Holt Live-FX-Daten via yfinance.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Live-Daten: EURUSD, DXY, Volatilität, 24h Change
|
||||
"""
|
||||
try:
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
end = datetime.now()
|
||||
start = end - timedelta(days=5)
|
||||
|
||||
# EURUSD holen
|
||||
eurusd = yf.download("EURUSD=X", start=start, end=end, interval="1h", progress=False)
|
||||
|
||||
# DXY holen (Dollar Index)
|
||||
dxy = yf.download("DX-Y.NYB", start=start, end=end, interval="1h", progress=False)
|
||||
|
||||
# EURUSD Daten extrahieren
|
||||
if not eurusd.empty:
|
||||
eurusd_price = float(eurusd['Close'].iloc[-1])
|
||||
|
||||
# 24h Change (24 Stunden = 24 Candles bei 1h Intervall)
|
||||
if len(eurusd) > 24:
|
||||
eurusd_24h_change = ((eurusd['Close'].iloc[-1] / eurusd['Close'].iloc[-24]) - 1) * 100
|
||||
else:
|
||||
eurusd_24h_change = 0.0
|
||||
|
||||
# Realized Volatility (24h annualisiert)
|
||||
returns = eurusd['Close'].pct_change().dropna()
|
||||
if len(returns) > 1:
|
||||
realized_volatility = float(returns.tail(24).std() * (24 ** 0.5) * 100)
|
||||
else:
|
||||
realized_volatility = 0.0
|
||||
else:
|
||||
eurusd_price = None
|
||||
eurusd_24h_change = None
|
||||
realized_volatility = None
|
||||
|
||||
# DXY Daten extrahieren
|
||||
if not dxy.empty:
|
||||
dxy_price = float(dxy['Close'].iloc[-1])
|
||||
else:
|
||||
dxy_price = None
|
||||
|
||||
return {
|
||||
"eurusd_price": eurusd_price,
|
||||
"dxy_price": dxy_price,
|
||||
"realized_volatility": realized_volatility,
|
||||
"eurusd_24h_change": eurusd_24h_change,
|
||||
"success": True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"eurusd_price": None,
|
||||
"dxy_price": None,
|
||||
"realized_volatility": None,
|
||||
"eurusd_24h_change": None,
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
class EURUSDMacroAgent:
|
||||
"""
|
||||
Macro Agent im Stanley Druckenmiller Stil für EURUSD.
|
||||
@@ -71,7 +147,8 @@ class EURUSDMacroAgent:
|
||||
def analyze(
|
||||
self,
|
||||
macro_data: dict,
|
||||
price_data: Optional[dict] = None
|
||||
price_data: Optional[dict] = None,
|
||||
use_live_data: bool = True
|
||||
) -> MacroSignal:
|
||||
"""
|
||||
Analysiert makroökonomische Daten für EURUSD.
|
||||
@@ -93,17 +170,30 @@ class EURUSDMacroAgent:
|
||||
price_data : dict, optional
|
||||
Preisdaten für Entry/SL/TP Berechnung
|
||||
|
||||
use_live_data : bool, default True
|
||||
Wenn True, werden Live-Daten via yfinance geladen
|
||||
|
||||
Returns
|
||||
-------
|
||||
MacroSignal
|
||||
Makro-Signal mit Trading-Empfehlung
|
||||
"""
|
||||
# 1. Berechne fundamentale Differentiale
|
||||
# 1. Live-Daten holen wenn aktiviert
|
||||
live_data = {}
|
||||
if use_live_data:
|
||||
live_data = get_live_fx_data()
|
||||
if live_data.get("success"):
|
||||
# Override DXY Trend basierend auf Live-Daten
|
||||
if live_data.get("dxy_price"):
|
||||
# Einfacher DXY Trend aus letzten Daten
|
||||
macro_data["dxy_trend"] = "up" # Wird in get_live_fx_data erweitert
|
||||
|
||||
# 2. Berechne fundamentale Differentiale
|
||||
rate_diff = macro_data.get("fed_rate", 5.0) - macro_data.get("ecb_rate", 4.0)
|
||||
growth_diff = macro_data.get("us_gdp_growth", 2.0) - macro_data.get("eu_gdp_growth", 1.5)
|
||||
pmi_diff = macro_data.get("us_pmi", 50) - macro_data.get("eu_pmi", 50)
|
||||
|
||||
# 2. Berechne Momentum-Score
|
||||
# 3. Berechne Momentum-Score
|
||||
dxy_trend = macro_data.get("dxy_trend", "neutral")
|
||||
if dxy_trend == "up":
|
||||
momentum_score = -0.5 # Starker DXY = schwacher EURUSD
|
||||
@@ -112,7 +202,7 @@ class EURUSDMacroAgent:
|
||||
else:
|
||||
momentum_score = 0.0
|
||||
|
||||
# 3. Berechne Sentiment-Score
|
||||
# 4. Berechne Sentiment-Score
|
||||
risk_sentiment = macro_data.get("risk_sentiment", "neutral")
|
||||
if risk_sentiment == "risk-on":
|
||||
sentiment_score = 0.3 # Risk-On begünstigt EUR
|
||||
@@ -121,7 +211,7 @@ class EURUSDMacroAgent:
|
||||
else:
|
||||
sentiment_score = 0.0
|
||||
|
||||
# 4. LLM-basierte Gesamtanalyse
|
||||
# 5. LLM-basierte Gesamtanalyse mit Live-Daten
|
||||
signal = self._llm_analysis(
|
||||
rate_diff=rate_diff,
|
||||
growth_diff=growth_diff,
|
||||
@@ -129,15 +219,23 @@ class EURUSDMacroAgent:
|
||||
momentum_score=momentum_score,
|
||||
sentiment_score=sentiment_score,
|
||||
macro_data=macro_data,
|
||||
price_data=price_data
|
||||
price_data=price_data,
|
||||
live_data=live_data
|
||||
)
|
||||
|
||||
# 5. Füge berechnete Werte hinzu
|
||||
# 6. Füge berechnete Werte hinzu
|
||||
signal.rate_differential = rate_diff
|
||||
signal.growth_differential = growth_diff
|
||||
signal.momentum_score = momentum_score
|
||||
signal.sentiment_score = sentiment_score
|
||||
|
||||
# 7. Füge Live-Daten hinzu
|
||||
if live_data.get("success"):
|
||||
signal.eurusd_price = live_data.get("eurusd_price")
|
||||
signal.dxy_price = live_data.get("dxy_price")
|
||||
signal.realized_volatility = live_data.get("realized_volatility")
|
||||
signal.eurusd_24h_change = live_data.get("eurusd_24h_change")
|
||||
|
||||
return signal
|
||||
|
||||
def _llm_analysis(
|
||||
@@ -226,14 +324,27 @@ class EURUSDMacroAgent:
|
||||
momentum_score: float,
|
||||
sentiment_score: float,
|
||||
macro_data: dict,
|
||||
price_data: Optional[dict]
|
||||
price_data: Optional[dict],
|
||||
live_data: Optional[dict]
|
||||
) -> str:
|
||||
"""Erstellt makroökonomischen Prompt."""
|
||||
price_str = f"- Aktueller Preis: {price_data.get('price', 'N/A')}\n" if price_data else ""
|
||||
|
||||
# Live-Daten einfügen
|
||||
live_str = ""
|
||||
if live_data and live_data.get("success"):
|
||||
live_str = f"""
|
||||
=== Live Markt-Daten (via yfinance) ===
|
||||
- EURUSD: {live_data.get('eurusd_price', 'N/A'):.5f}
|
||||
- EURUSD 24h Change: {live_data.get('eurusd_24h_change', 0):+.3f}%
|
||||
- DXY (Dollar Index): {live_data.get('dxy_price', 'N/A'):.2f}
|
||||
- Realized Volatility (24h): {live_data.get('realized_volatility', 0):.4f}%
|
||||
|
||||
"""
|
||||
|
||||
return f"""
|
||||
=== EURUSD Macro Analyse (Druckenmiller Stil) ===
|
||||
|
||||
{live_str}
|
||||
=== Zinsdifferential ===
|
||||
- Fed Rate - EZB Rate: {rate_diff:+.2f}% ({'USD vorteil' if rate_diff > 0 else 'EUR vorteil' if rate_diff < 0 else 'neutral'})
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
FX Config - Zentrale Konfiguration für EURUSD Trading
|
||||
|
||||
Wird verwendet von:
|
||||
- Macro Agent (Live-Daten)
|
||||
- Debate Team (Session-Analyse)
|
||||
- Position Sizing (Spread, Costs)
|
||||
- Web Dashboard (Zielwerte)
|
||||
"""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple
|
||||
|
||||
|
||||
@dataclass
|
||||
class FXConfig:
|
||||
"""Zentrale FX-Konfiguration."""
|
||||
|
||||
# Instrument & Daten
|
||||
instrument: str = "EURUSD=X"
|
||||
frequency: str = "1min"
|
||||
data_path: str = os.path.expanduser("~/.qlib/qlib_data/eurusd_1min_data")
|
||||
|
||||
# LLM Provider
|
||||
llm_provider: str = "openai"
|
||||
backend_url: str = os.getenv("OPENAI_API_BASE", "http://localhost:8081/v1")
|
||||
api_key: str = os.getenv("OPENAI_API_KEY", "local")
|
||||
chat_model: str = os.getenv("CHAT_MODEL", "qwen3.5-35b")
|
||||
embedding_model: str = os.getenv("EMBEDDING_MODEL", "nomic-embed-text")
|
||||
|
||||
# Trading-Parameter
|
||||
spread_bps: float = 1.5 # 1.5 bps Spread
|
||||
target_arr: float = 9.62 # Ziel: 9.62% annualisierte Rendite
|
||||
max_drawdown: float = 20.0 # Max 20% Drawdown
|
||||
cost_rate: float = 0.00015 # 0.015% pro Trade
|
||||
|
||||
# Sessions (UTC)
|
||||
sessions: Dict[str, Tuple[str, str]] = None
|
||||
|
||||
# Debate & Risk
|
||||
max_debate_rounds: int = 2
|
||||
max_risk_discuss_rounds: int = 1
|
||||
|
||||
# Memory & Reflection
|
||||
memory_file: str = "git_ignore_folder/eurusd_trade_memory.json"
|
||||
reflection_enabled: bool = True
|
||||
|
||||
def __post_init__(self):
|
||||
if self.sessions is None:
|
||||
self.sessions = {
|
||||
"asian": ("00:00", "08:00"),
|
||||
"london": ("08:00", "16:00"),
|
||||
"ny": ("13:00", "21:00"),
|
||||
"overlap": ("13:00", "16:00"),
|
||||
}
|
||||
|
||||
def get_current_session(self) -> str:
|
||||
"""Bestimmt aktuelle FX-Session basierend auf UTC-Zeit."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
hour_utc = datetime.now(timezone.utc).hour
|
||||
|
||||
if 0 <= hour_utc < 8:
|
||||
return "asian"
|
||||
elif 8 <= hour_utc < 13:
|
||||
return "london"
|
||||
elif 13 <= hour_utc < 16:
|
||||
return "overlap"
|
||||
elif 16 <= hour_utc < 21:
|
||||
return "ny"
|
||||
else:
|
||||
return "after_hours"
|
||||
|
||||
def get_session_description(self, session: str = None) -> dict:
|
||||
"""Gibt Beschreibung der Session."""
|
||||
if session is None:
|
||||
session = self.get_current_session()
|
||||
|
||||
descriptions = {
|
||||
"asian": {
|
||||
"name": "Asian Session",
|
||||
"hours": "00:00-08:00 UTC",
|
||||
"characteristics": "Low volume, ranging market",
|
||||
"recommended_strategy": "Mean Reversion",
|
||||
"avoid": "Momentum strategies"
|
||||
},
|
||||
"london": {
|
||||
"name": "London Session",
|
||||
"hours": "08:00-16:00 UTC",
|
||||
"characteristics": "High volume, trending market",
|
||||
"recommended_strategy": "Momentum/Trend-Following",
|
||||
"avoid": "Counter-trend trades"
|
||||
},
|
||||
"overlap": {
|
||||
"name": "London-NY Overlap",
|
||||
"hours": "13:00-16:00 UTC",
|
||||
"characteristics": "Highest volume, strong directional moves",
|
||||
"recommended_strategy": "Strong Momentum",
|
||||
"avoid": "Range trading"
|
||||
},
|
||||
"ny": {
|
||||
"name": "NY Session",
|
||||
"hours": "13:00-21:00 UTC",
|
||||
"characteristics": "Moderate volume, reversals after London close",
|
||||
"recommended_strategy": "Momentum/Reversal",
|
||||
"avoid": "Late entries after 20:00"
|
||||
},
|
||||
"after_hours": {
|
||||
"name": "After Hours",
|
||||
"hours": "21:00-00:00 UTC",
|
||||
"characteristics": "Very low volume, wide spreads",
|
||||
"recommended_strategy": "Avoid trading",
|
||||
"avoid": "All strategies"
|
||||
}
|
||||
}
|
||||
|
||||
return descriptions.get(session, descriptions["after_hours"])
|
||||
|
||||
|
||||
# Globale Instanz
|
||||
fx_config = FXConfig()
|
||||
|
||||
|
||||
def get_fx_config() -> FXConfig:
|
||||
"""Gibt globale FX-Config zurück."""
|
||||
return fx_config
|
||||
|
||||
|
||||
# Test
|
||||
if __name__ == "__main__":
|
||||
config = get_fx_config()
|
||||
|
||||
print("=== FX Config Test ===\n")
|
||||
print(f"Instrument: {config.instrument}")
|
||||
print(f"Frequency: {config.frequency}")
|
||||
print(f"Target ARR: {config.target_arr}%")
|
||||
print(f"Max Drawdown: {config.max_drawdown}%")
|
||||
print(f"Spread: {config.spread_bps} bps")
|
||||
|
||||
print(f"\nAktuelle Session: {config.get_current_session()}")
|
||||
session_desc = config.get_session_description()
|
||||
print(f" Name: {session_desc['name']}")
|
||||
print(f" Hours: {session_desc['hours']}")
|
||||
print(f" Characteristics: {session_desc['characteristics']}")
|
||||
print(f" Recommended: {session_desc['recommended_strategy']}")
|
||||
|
||||
print("\n✅ FX Config funktioniert!")
|
||||
Reference in New Issue
Block a user