mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +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!")
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user