Files
gogo2/web/layout_manager.py
2025-07-20 18:08:37 +03:00

406 lines
18 KiB
Python

"""
Dashboard Layout Manager - Clean Trading Dashboard
Manages the layout and structure of the trading dashboard
"""
import dash
from dash import dcc, html
from datetime import datetime
class DashboardLayoutManager:
"""Manages dashboard layout and structure"""
def __init__(self, starting_balance: float = 100.0, trading_executor=None):
self.starting_balance = starting_balance
self.trading_executor = trading_executor
def create_main_layout(self):
"""Create the main dashboard layout"""
return html.Div([
self._create_header(),
self._create_interval_component(),
self._create_main_content()
], className="container-fluid")
def _create_header(self):
"""Create the dashboard header"""
trading_mode = "SIMULATION" if (not self.trading_executor or
getattr(self.trading_executor, 'simulation_mode', True)) else "LIVE"
return html.Div([
html.H2([
html.I(className="fas fa-chart-line me-2"),
"Clean Trading Dashboard"
], className="text-light mb-0"),
html.P(
f"Ultra-Fast Updates • Live Account Balance Sync • {trading_mode}",
className="text-light mb-0 opacity-75 small"
)
], className="bg-dark p-2 mb-2")
def _create_interval_component(self):
"""Create the auto-refresh interval component"""
return dcc.Interval(
id='interval-component',
interval=250, # Update every 250 ms (4 Hz)
n_intervals=0
)
def _create_main_content(self):
"""Create the main content area"""
return html.Div([
self._create_metrics_and_signals_row(),
self._create_charts_row(),
self._create_cob_and_trades_row()
])
def _create_metrics_and_signals_row(self):
"""Create the top row with key metrics, recent signals, and session controls"""
return html.Div([
# Left side - Key metrics (compact cards)
self._create_metrics_grid(),
# Middle - Recent Signals
self._create_signals_panel(),
# Right side - Session Controls
self._create_session_controls_panel()
], className="d-flex mb-3")
def _create_metrics_grid(self):
"""Create the metrics grid with compact cards"""
# Get exchange name dynamically
exchange_name = "Exchange"
if self.trading_executor:
if hasattr(self.trading_executor, 'primary_name'):
exchange_name = self.trading_executor.primary_name.upper()
elif hasattr(self.trading_executor, 'exchange') and self.trading_executor.exchange:
# Try to get exchange name from exchange interface
exchange_class_name = self.trading_executor.exchange.__class__.__name__
if 'Bybit' in exchange_class_name:
exchange_name = "BYBIT"
elif 'Mexc' in exchange_class_name or 'MEXC' in exchange_class_name:
exchange_name = "MEXC"
elif 'Binance' in exchange_class_name:
exchange_name = "BINANCE"
elif 'Deribit' in exchange_class_name:
exchange_name = "DERIBIT"
else:
exchange_name = "EXCHANGE"
metrics_cards = [
("current-price", "Live Price", "text-success"),
("session-pnl", "Session P&L", ""),
("current-position", "Position", "text-info"),
# ("leverage-info", "Leverage", "text-primary"),
("trade-count", "Trades", "text-warning"),
("portfolio-value", "Portfolio", "text-secondary"),
("profitability-multiplier", "Profit Boost", "text-primary"),
("mexc-status", f"{exchange_name} API", "text-info")
]
cards = []
for card_id, label, text_class in metrics_cards:
card = html.Div([
html.Div([
html.H5(id=card_id, className=f"{text_class} mb-0 small"),
html.P(label, className="text-muted mb-0 tiny")
], className="card-body text-center p-2")
], className="card bg-light", style={"height": "60px"})
cards.append(card)
return html.Div(
cards,
style={
"display": "grid",
"gridTemplateColumns": "repeat(4, 1fr)",
"gap": "8px",
"width": "60%"
}
)
def _create_signals_panel(self):
"""Create the signals panel"""
return html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-robot me-2"),
"Recent Trading Signals"
], className="card-title mb-2"),
html.Div(id="recent-decisions", style={"height": "160px", "overflowY": "auto"})
], className="card-body p-2")
], className="card")
], style={"width": "35%", "marginLeft": "2%"})
def _create_session_controls_panel(self):
"""Create the session controls panel"""
return html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-cog me-2"),
"Session Controls"
], className="card-title mb-2"),
# Leverage Control
html.Div([
html.Label([
html.I(className="fas fa-sliders-h me-1"),
"Leverage: ",
html.Span(id="leverage-display", children="x50", className="fw-bold text-primary")
], className="form-label small mb-1"),
dcc.Slider(
id='leverage-slider',
min=1,
max=100,
step=1,
value=50,
marks={
1: {'label': 'x1', 'style': {'fontSize': '8px'}},
25: {'label': 'x25', 'style': {'fontSize': '8px'}},
50: {'label': 'x50', 'style': {'fontSize': '8px'}},
75: {'label': 'x75', 'style': {'fontSize': '8px'}},
100: {'label': 'x100', 'style': {'fontSize': '8px'}}
},
tooltip={"placement": "bottom", "always_visible": False}
)
], className="mb-2"),
# Entry Aggressiveness Control
html.Div([
html.Label([
html.I(className="fas fa-bullseye me-1"),
"Entry Aggressiveness: ",
html.Span(id="entry-agg-display", children="0.5", className="fw-bold text-success")
], className="form-label small mb-1"),
dcc.Slider(
id='entry-aggressiveness-slider',
min=0.0,
max=1.0,
step=0.1,
value=0.5,
marks={
0.0: {'label': 'Conservative', 'style': {'fontSize': '7px'}},
0.5: {'label': 'Balanced', 'style': {'fontSize': '7px'}},
1.0: {'label': 'Aggressive', 'style': {'fontSize': '7px'}}
},
tooltip={"placement": "bottom", "always_visible": False}
)
], className="mb-2"),
# Exit Aggressiveness Control
html.Div([
html.Label([
html.I(className="fas fa-sign-out-alt me-1"),
"Exit Aggressiveness: ",
html.Span(id="exit-agg-display", children="0.5", className="fw-bold text-danger")
], className="form-label small mb-1"),
dcc.Slider(
id='exit-aggressiveness-slider',
min=0.0,
max=1.0,
step=0.1,
value=0.5,
marks={
0.0: {'label': 'Conservative', 'style': {'fontSize': '7px'}},
0.5: {'label': 'Balanced', 'style': {'fontSize': '7px'}},
1.0: {'label': 'Aggressive', 'style': {'fontSize': '7px'}}
},
tooltip={"placement": "bottom", "always_visible": False}
)
], className="mb-2"),
html.Button([
html.I(className="fas fa-trash me-1"),
"Clear Session"
], id="clear-session-btn", className="btn btn-warning btn-sm w-100"),
html.Button([
html.I(className="fas fa-save me-1"),
"Store All Models"
], id="store-models-btn", className="btn btn-info btn-sm w-100 mt-2"),
html.Button([
html.I(className="fas fa-arrows-rotate me-1"),
"Sync Positions/Orders"
], id="manual-sync-btn", className="btn btn-primary btn-sm w-100 mt-2"),
html.Hr(className="my-2"),
html.Small("System Status", className="text-muted d-block mb-1"),
html.Div([
html.Span("Trading: ", className="small"),
html.Span("SIMULATION", className="badge bg-info small")
], className="mb-1"),
html.Div([
html.Span("Data: ", className="small"),
html.Span("Active", className="badge bg-success small")
], className="mb-1"),
html.Div([
html.Span("WebSocket: ", className="small"),
html.Span("Connected", className="badge bg-success small")
])
], className="card-body p-2")
], className="card")
], style={"width": "23%", "marginLeft": "2%"})
def _create_charts_row(self):
"""Create the charts row with price chart and manual trading buttons"""
return html.Div([
html.Div([
html.Div([
# Chart header with manual trading buttons
html.Div([
html.H6([
html.I(className="fas fa-chart-candlestick me-2"),
"Live 1m Price Chart (3h) + 1s Mini Chart (5min) - Updated Every Second"
], className="card-title mb-0"),
html.Div([
html.Button([
html.I(className="fas fa-arrow-up me-1"),
"BUY"
], id="manual-buy-btn", className="btn btn-success btn-sm me-2",
style={"fontSize": "10px", "padding": "2px 8px"}),
html.Button([
html.I(className="fas fa-arrow-down me-1"),
"SELL"
], id="manual-sell-btn", className="btn btn-danger btn-sm",
style={"fontSize": "10px", "padding": "2px 8px"})
], className="d-flex")
], className="d-flex justify-content-between align-items-center mb-2"),
html.Div([
dcc.Graph(id="price-chart", style={"height": "500px"})
])
], className="card-body p-2")
], className="card")
])
def _create_cob_and_trades_row(self):
"""Creates the row for COB ladders, closed trades, pending orders, and model status"""
return html.Div([
# Top row: COB Ladders (left) and Models/Training (right)
html.Div([
# Left side: COB Ladders (60% width)
html.Div([
html.Div([
# ETH/USDT COB
html.Div([
html.Div(
id="eth-cob-content",
className="card-body p-2",
)
], className="card", style={"flex": "1"}),
# BTC/USDT COB
html.Div([
html.Div(
id="btc-cob-content",
className="card-body p-2",
)
], className="card", style={"flex": "1", "marginLeft": "1rem"}),
], className="d-flex")
], style={"width": "60%"}),
# Right side: Models & Training Progress (40% width)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-brain me-2"),
"Models & Training Progress",
], className="card-title mb-2"),
html.Div(
id="training-metrics",
style={"height": "300px", "overflowY": "auto"},
),
], className="card-body p-2")
], className="card")
], style={"width": "38%", "marginLeft": "2%"}),
], className="d-flex mb-3"),
# Second row: Pending Orders (left) and Closed Trades (right)
html.Div([
# Left side: Pending Orders (40% width)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-clock me-2"),
"Pending Orders & Position Sync",
], className="card-title mb-2"),
html.Div(
id="pending-orders-content",
style={"height": "200px", "overflowY": "auto"},
),
], className="card-body p-2")
], className="card")
], style={"width": "40%"}),
# Right side: Closed Trades (58% width)
html.Div([
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-history me-2"),
"Recent Closed Trades",
], className="card-title mb-2"),
html.Div(
id="closed-trades-table",
style={"height": "200px", "overflowY": "auto"},
),
], className="card-body p-2")
], className="card")
], style={"width": "58%", "marginLeft": "2%"}),
], className="d-flex")
])
def _create_analytics_and_performance_row(self):
"""Create the combined analytics and performance row with COB data, trades, and training progress"""
return html.Div([
# Left side - COB panels and trades (68% width)
html.Div([
# Top section - COB panels
html.Div([
# ETH/USDT COB
html.Div([
html.Div([
html.H6([
html.I(className="fab fa-ethereum me-2"),
"ETH/USDT COB"
], className="card-title mb-2"),
html.Div(id="eth-cob-content")
], className="card-body p-2")
], className="card", style={"width": "48%"}),
# BTC/USDT COB
html.Div([
html.Div([
html.H6([
html.I(className="fab fa-bitcoin me-2"),
"BTC/USDT COB"
], className="card-title mb-2"),
html.Div(id="btc-cob-content")
], className="card-body p-2")
], className="card", style={"width": "48%", "marginLeft": "4%"})
], className="d-flex mb-3"),
# Bottom section - Closed Trades
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-history me-2"),
"Closed Trades"
], className="card-title mb-2"),
html.Div(id="closed-trades-table", style={"height": "250px", "overflowY": "auto"})
], className="card-body p-2")
], className="card")
], style={"width": "68%"}),
# Right side - Training Progress & COB $1 Buckets (30% width, spans full height)
html.Div([
html.Div([
html.H6([
html.I(className="fas fa-brain me-2"),
"Models & Training Progress"
], className="card-title mb-2"),
html.Div(id="training-metrics", style={"height": "550px", "overflowY": "auto"})
], className="card-body p-2")
], className="card", style={"width": "30%", "marginLeft": "2%"})
], className="d-flex")