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:
TPTBusiness
2026-03-30 21:11:07 +02:00
parent bab2107786
commit bc656e9b11
5 changed files with 1018 additions and 18 deletions
@@ -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!")