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!")
+381
View File
@@ -0,0 +1,381 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Predix Dashboard - COMPLETE Progress</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #eee;
min-height: 100vh;
padding: 20px;
}
.container { max-width: 1400px; margin: 0 auto; }
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
background: linear-gradient(90deg, #00d9ff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 25px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.card h2 {
font-size: 1.5em;
margin-bottom: 20px;
color: #00d9ff;
display: flex;
align-items: center;
gap: 10px;
}
.card h2 .icon { font-size: 1.2em; }
.metric {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.metric:last-child { border-bottom: none; }
.metric-label { color: #aaa; }
.metric-value {
font-weight: bold;
font-size: 1.2em;
}
.metric-value.positive { color: #00ff88; }
.metric-value.negative { color: #ff4757; }
.metric-value.neutral { color: #ffa502; }
.progress-bar {
width: 100%;
height: 30px;
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
overflow: hidden;
margin: 15px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00d9ff, #00ff88);
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #1a1a2e;
}
.status-badge {
display: inline-block;
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
font-size: 0.9em;
margin: 5px 0;
}
.status-badge.success { background: #00ff88; color: #1a1a2e; }
.status-badge.failed { background: #ff4757; color: white; }
.status-badge.running { background: #ffa502; color: #1a1a2e; }
.factor-list {
list-style: none;
margin-top: 15px;
}
.factor-list li {
padding: 8px 15px;
background: rgba(255, 255, 255, 0.05);
margin: 5px 0;
border-radius: 8px;
border-left: 3px solid #00d9ff;
}
.loading {
text-align: center;
padding: 50px;
font-size: 1.2em;
color: #aaa;
}
.refresh-btn {
background: linear-gradient(90deg, #00d9ff, #00ff88);
border: none;
padding: 12px 30px;
border-radius: 25px;
color: #1a1a2e;
font-weight: bold;
cursor: pointer;
font-size: 1em;
margin: 20px auto;
display: block;
transition: transform 0.2s;
}
.refresh-btn:hover { transform: scale(1.05); }
.session-info {
background: rgba(0, 217, 255, 0.1);
padding: 15px;
border-radius: 10px;
margin: 15px 0;
border-left: 4px solid #00d9ff;
}
.error-msg {
background: rgba(255, 71, 87, 0.2);
padding: 15px;
border-radius: 10px;
color: #ff4757;
margin: 15px 0;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.live-indicator {
display: inline-block;
width: 10px;
height: 10px;
background: #00ff88;
border-radius: 50%;
margin-right: 10px;
animation: pulse 2s infinite;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Predix Dashboard</h1>
<p style="text-align: center; color: #aaa; margin-bottom: 30px;">
COMPLETE Progress Visualisierung für EURUSD Trading-Agent
</p>
<div id="dashboard">
<div class="loading">Lade Dashboard-Daten...</div>
</div>
<button class="refresh-btn" onclick="loadDashboard()">🔄 Aktualisieren</button>
</div>
<script>
const API_BASE = window.location.origin;
async function loadDashboard() {
try {
const response = await fetch(`${API_BASE}/api/dashboard`);
const data = await response.json();
if (data.error) {
document.getElementById('dashboard').innerHTML = `
<div class="error-msg">❌ Fehler: ${data.error}</div>
`;
return;
}
const html = `
<div class="grid">
${renderProgressCard(data.progress)}
${renderMacroCard(data.macro)}
${renderSessionCard(data.session)}
${renderMemoryCard(data.memory)}
${renderConfigCard(data.config)}
</div>
`;
document.getElementById('dashboard').innerHTML = html;
// Auto-refresh alle 30 Sekunden
setTimeout(loadDashboard, 30000);
} catch (error) {
document.getElementById('dashboard').innerHTML = `
<div class="error-msg">❌ Verbindungsfehler: ${error.message}</div>
<p style="text-align: center; color: #aaa;">
Stelle sicher dass die API unter ${API_BASE} läuft.
</p>
`;
}
}
function renderProgressCard(progress) {
return `
<div class="card">
<h2><span class="icon">📊</span> Trading Progress</h2>
<div class="metric">
<span class="metric-label">Aktueller Loop:</span>
<span class="metric-value">${progress.current_loop}</span>
</div>
<div class="metric">
<span class="metric-label">Aktueller Step:</span>
<span class="metric-value">${progress.current_step}</span>
</div>
<div class="metric">
<span class="metric-label">Status:</span>
<span class="status-badge ${progress.last_status.toLowerCase()}">
${progress.last_status}
</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${progress.progress_percent}%">
${progress.progress_percent}%
</div>
</div>
${progress.recent_factors.length > 0 ? `
<h3 style="margin-top: 20px; color: #00d9ff; font-size: 1.1em;">Letzte Faktoren:</h3>
<ul class="factor-list">
${progress.recent_factors.map(f => `<li>📈 ${f}</li>`).join('')}
</ul>
` : ''}
<div class="metric" style="margin-top: 20px;">
<span class="metric-label">Log Größe:</span>
<span class="metric-value">${progress.log_size_mb || 'N/A'} MB</span>
</div>
</div>
`;
}
function renderMacroCard(macro) {
if (!macro || macro.error) {
return `
<div class="card">
<h2><span class="icon">🌍</span> Live Macro Daten</h2>
<div class="error-msg">Daten nicht verfügbar</div>
</div>
`;
}
const changeClass = macro.eurusd_24h_change >= 0 ? 'positive' : 'negative';
const changeSign = macro.eurusd_24h_change >= 0 ? '+' : '';
return `
<div class="card">
<h2><span class="icon">🌍</span> Live Macro Daten <span class="live-indicator"></span></h2>
<div class="metric">
<span class="metric-label">EURUSD:</span>
<span class="metric-value">${macro.eurusd_price ? macro.eurusd_price.toFixed(5) : 'N/A'}</span>
</div>
<div class="metric">
<span class="metric-label">24h Change:</span>
<span class="metric-value ${changeClass}">
${changeSign}${macro.eurusd_24h_change ? macro.eurusd_24h_change.toFixed(3) : '0'}%
</span>
</div>
<div class="metric">
<span class="metric-label">DXY (Dollar Index):</span>
<span class="metric-value">${macro.dxy_price ? macro.dxy_price.toFixed(2) : 'N/A'}</span>
</div>
<div class="metric">
<span class="metric-label">Volatility (24h):</span>
<span class="metric-value">${macro.realized_volatility ? macro.realized_volatility.toFixed(4) : 'N/A'}%</span>
</div>
</div>
`;
}
function renderSessionCard(session) {
if (!session) return '';
return `
<div class="card">
<h2><span class="icon">🕐</span> Aktuelle Session</h2>
<div class="session-info">
<strong>${session.name}</strong><br>
<span style="color: #aaa;">${session.hours} UTC</span>
</div>
<div class="metric">
<span class="metric-label">Charakteristika:</span>
</div>
<p style="color: #ccc; margin: 10px 0;">${session.characteristics}</p>
<div class="metric">
<span class="metric-label">Empfohlene Strategie:</span>
<span class="metric-value positive">${session.recommended_strategy}</span>
</div>
<div class="metric">
<span class="metric-label">Vermeiden:</span>
<span class="metric-value negative">${session.avoid}</span>
</div>
</div>
`;
}
function renderMemoryCard(memory) {
if (!memory || memory.error) {
return `
<div class="card">
<h2><span class="icon">💾</span> Memory Statistics</h2>
<div class="error-msg">Keine Trades gespeichert</div>
</div>
`;
}
const winRateClass = memory.win_rate >= 60 ? 'positive' : memory.win_rate >= 40 ? 'neutral' : 'negative';
return `
<div class="card">
<h2><span class="icon">💾</span> Memory Statistics</h2>
<div class="metric">
<span class="metric-label">Gespeicherte Trades:</span>
<span class="metric-value">${memory.total_trades}</span>
</div>
<div class="metric">
<span class="metric-label">Win-Rate:</span>
<span class="metric-value ${winRateClass}">${memory.win_rate}%</span>
</div>
<div class="metric">
<span class="metric-label">Ø Return:</span>
<span class="metric-value ${memory.avg_return >= 0 ? 'positive' : 'negative'}">
${memory.avg_return >= 0 ? '+' : ''}${memory.avg_return}%
</span>
</div>
<div class="metric">
<span class="metric-label">Sharpe Ratio:</span>
<span class="metric-value">${memory.sharpe_ratio || 'N/A'}</span>
</div>
</div>
`;
}
function renderConfigCard(config) {
if (!config) return '';
return `
<div class="card">
<h2><span class="icon">⚙️</span> Konfiguration</h2>
<div class="metric">
<span class="metric-label">Instrument:</span>
<span class="metric-value">${config.instrument}</span>
</div>
<div class="metric">
<span class="metric-label">Target ARR:</span>
<span class="metric-value positive">${config.target_arr}%</span>
</div>
<div class="metric">
<span class="metric-label">Max Drawdown:</span>
<span class="metric-value negative">${config.max_drawdown}%</span>
</div>
</div>
`;
}
// Initiales Laden
loadDashboard();
</script>
</body>
</html>
+317
View File
@@ -0,0 +1,317 @@
"""
Predix Dashboard API
Flask-Backend für das Web-Dashboard.
Zeigt COMPLETE Progress von EURUSD Trading-Agent.
Features:
- Live Trading Progress (Loop, Step, Faktor)
- Performance Metrics (Win-Rate, PnL, Sharpe)
- Live Macro Daten (EURUSD, DXY, Volatility)
- Session Info
- Memory Statistics
- Debate Status
"""
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from flask import Flask, jsonify
from flask_cors import CORS
# Parent-Directory zum Path hinzufügen
sys.path.insert(0, str(Path(__file__).parent.parent))
app = Flask(__name__)
CORS(app)
# Importiere unsere Module
try:
from rdagent.components.coder.factor_coder.fx_config import get_fx_config
from rdagent.components.coder.factor_coder.eurusd_macro import get_live_fx_data
from rdagent.components.coder.factor_coder.eurusd_debate import get_current_session_info
from rdagent.components.coder.factor_coder.eurusd_memory import EURUSDTradeMemory
MODULES_AVAILABLE = True
except ImportError as e:
print(f"⚠️ Module nicht verfügbar: {e}")
MODULES_AVAILABLE = False
def parse_fin_quant_log(log_path: str, lines: int = 100) -> dict:
"""
Parst die letzten N Zeilen der fin_quant.log.
Extrahiert:
- Aktueller Loop/Step
- Letzter Faktor
- Status (SUCCESS/FAILED/PENDING)
"""
result = {
"current_loop": "N/A",
"current_step": "N/A",
"progress_percent": 0,
"last_factor": "N/A",
"last_status": "N/A",
"recent_factors": []
}
try:
if not os.path.exists(log_path):
return result
with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
# Hole letzte N Zeilen
all_lines = f.readlines()
recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
log_content = ''.join(recent_lines)
# Extrahiere Loop/Step
import re
# Workflow Progress
progress_match = re.search(r'Workflow Progress:\s+(\d+)%.*loop_index=(\d+).*step_index=(\d+).*step_name=(\w+)', log_content)
if progress_match:
result["progress_percent"] = int(progress_match.group(1))
result["current_loop"] = int(progress_match.group(2))
result["current_step"] = f"{int(progress_match.group(3)) + 1}/4 ({progress_match.group(4)})"
# Extrahiere Faktor-Namen
factor_matches = re.findall(r'factor_name:\s*(\w+)', log_content)
if factor_matches:
result["last_factor"] = factor_matches[-1]
result["recent_factors"] = list(reversed(factor_matches[-5:]))
# Extrahiere Status
if "This implementation is SUCCESS" in log_content:
result["last_status"] = "SUCCESS"
elif "This implementation is FAIL" in log_content:
result["last_status"] = "FAILED"
elif "Execution succeeded" in log_content:
result["last_status"] = "RUNNING"
# Log-Aktivität
result["log_lines_total"] = len(all_lines)
result["log_size_mb"] = round(os.path.getsize(log_path) / (1024 * 1024), 2)
except Exception as e:
result["error"] = str(e)
return result
def get_memory_stats(memory_file: str) -> dict:
"""
Holt Statistics aus dem Trade-Memory.
"""
result = {
"total_trades": 0,
"win_rate": 0.0,
"avg_return": 0.0,
"total_pnl": 0.0
}
try:
if not os.path.exists(memory_file):
return result
memory = EURUSDTradeMemory(memory_file)
stats = memory.get_memory_stats()
result["total_trades"] = stats.get("total_trades", 0)
result["win_rate"] = round(stats.get("win_rate", 0) * 100, 1)
result["avg_return"] = round(stats.get("avg_return", 0) * 100, 2)
result["total_pnl"] = round(stats.get("total_pnl", 0) * 100, 2)
result["sharpe_ratio"] = round(stats.get("sharpe_ratio", 0), 2)
except Exception as e:
result["error"] = str(e)
return result
@app.route('/api/health', methods=['GET'])
def health_check():
"""Health Check Endpoint."""
return jsonify({
"status": "ok",
"timestamp": datetime.now(timezone.utc).isoformat(),
"modules_available": MODULES_AVAILABLE
})
@app.route('/api/progress', methods=['GET'])
def get_progress():
"""
Holt aktuellen Trading-Progress.
Returns:
- Aktueller Loop/Step
- Fortschritts-Prozent
- Letzter Faktor
- Status
"""
log_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'fin_quant.log')
progress = parse_fin_quant_log(log_path)
return jsonify(progress)
@app.route('/api/macro', methods=['GET'])
def get_macro_data():
"""
Holt Live-Macro-Daten.
Returns:
- EURUSD Preis
- DXY (Dollar Index)
- Realized Volatility
- 24h Change
"""
if not MODULES_AVAILABLE:
return jsonify({"error": "Modules not available"}), 500
live_data = get_live_fx_data()
return jsonify(live_data)
@app.route('/api/session', methods=['GET'])
def get_session_data():
"""
Holt aktuelle FX-Session Info.
Returns:
- Session Name
- Hours
- Characteristics
- Recommended Strategy
"""
if not MODULES_AVAILABLE:
return jsonify({"error": "Modules not available"}), 500
session = get_current_session_info()
return jsonify(session)
@app.route('/api/memory', methods=['GET'])
def get_memory_data():
"""
Holt Memory Statistics.
Returns:
- Total Trades
- Win-Rate
- Average Return
- Sharpe Ratio
"""
if not MODULES_AVAILABLE:
return jsonify({"error": "Modules not available"}), 500
config = get_fx_config()
stats = get_memory_stats(config.memory_file)
return jsonify(stats)
@app.route('/api/config', methods=['GET'])
def get_config_data():
"""
Holt FX-Konfiguration.
Returns:
- Instrument
- Target ARR
- Max Drawdown
- Spread
"""
if not MODULES_AVAILABLE:
return jsonify({"error": "Modules not available"}), 500
config = get_fx_config()
return jsonify({
"instrument": config.instrument,
"frequency": config.frequency,
"target_arr": config.target_arr,
"max_drawdown": config.max_drawdown,
"spread_bps": config.spread_bps,
"chat_model": config.chat_model
})
@app.route('/api/dashboard', methods=['GET'])
def get_full_dashboard():
"""
Holt alle Dashboard-Daten auf einmal.
Kombiniert:
- Progress
- Macro
- Session
- Memory
- Config
"""
dashboard = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"modules_available": MODULES_AVAILABLE
}
if MODULES_AVAILABLE:
# Progress
log_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'fin_quant.log')
dashboard["progress"] = parse_fin_quant_log(log_path)
# Macro
dashboard["macro"] = get_live_fx_data()
# Session
dashboard["session"] = get_current_session_info()
# Memory
config = get_fx_config()
dashboard["memory"] = get_memory_stats(config.memory_file)
# Config
dashboard["config"] = {
"instrument": config.instrument,
"target_arr": config.target_arr,
"max_drawdown": config.max_drawdown
}
else:
dashboard["error"] = "Modules not available"
return jsonify(dashboard)
@app.route('/', methods=['GET'])
def index():
"""Root Endpoint - zeigt API-Info."""
return jsonify({
"name": "Predix Dashboard API",
"version": "1.0.0",
"description": "COMPLETE Progress Visualisierung für EURUSD Trading-Agent",
"endpoints": {
"/api/health": "Health Check",
"/api/progress": "Trading Progress",
"/api/macro": "Live Macro Daten",
"/api/session": "FX Session Info",
"/api/memory": "Memory Statistics",
"/api/config": "FX Konfiguration",
"/api/dashboard": "Alle Daten kombiniert"
}
})
if __name__ == '__main__':
print("="*60)
print("Predix Dashboard API")
print("="*60)
print(f"Modules available: {MODULES_AVAILABLE}")
print(f"Starting server on http://localhost:5000")
print(f"API Docs: http://localhost:5000/")
print("="*60)
app.run(host='0.0.0.0', port=5000, debug=True)