3.1 - 3.3 Add main Dash application for Crypto Trading Bot Dashboard

- Introduced `app.py` as the main entry point for the dashboard, providing real-time visualization and bot management interface.
- Implemented layout components including header, navigation tabs, and content areas for market data, bot management, performance analytics, and system health.
- Added callbacks for dynamic updates of market data charts and statistics, ensuring real-time interaction.
- Created reusable UI components in `components` directory for modularity and maintainability.
- Enhanced database operations for fetching market data and checking data availability.
- Updated `main.py` to start the dashboard application with improved user instructions and error handling.
- Documented components and functions for clarity and future reference.
This commit is contained in:
Vasily.onl 2025-06-03 12:09:37 +08:00
parent 74d7e1ab2c
commit 720002a441
7 changed files with 1190 additions and 21 deletions

358
app.py Normal file
View File

@ -0,0 +1,358 @@
#!/usr/bin/env python3
"""
Main Dash application for the Crypto Trading Bot Dashboard.
Provides real-time visualization and bot management interface.
"""
import sys
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
import dash
from dash import dcc, html, Input, Output, callback
import plotly.graph_objects as go
from datetime import datetime, timedelta
import pandas as pd
# Import project modules
from config.settings import app as app_settings, dashboard as dashboard_settings
from utils.logger import get_logger
from database.connection import DatabaseManager
from components.charts import (
create_candlestick_chart, get_market_statistics,
get_supported_symbols, get_supported_timeframes,
create_data_status_indicator, check_data_availability,
create_error_chart
)
# Initialize logger
logger = get_logger("dashboard_app")
def create_app():
"""Create and configure the Dash application."""
# Initialize Dash app
app = dash.Dash(
__name__,
title="Crypto Trading Bot Dashboard",
update_title="Loading...",
suppress_callback_exceptions=True
)
# Configure app
app.server.secret_key = "crypto-bot-dashboard-secret-key-2024"
logger.info("Initializing Crypto Trading Bot Dashboard")
# Define basic layout
app.layout = html.Div([
# Header
html.Div([
html.H1("🚀 Crypto Trading Bot Dashboard",
style={'margin': '0', 'color': '#2c3e50'}),
html.P("Real-time monitoring and bot management",
style={'margin': '5px 0 0 0', 'color': '#7f8c8d'})
], style={
'padding': '20px',
'background-color': '#ecf0f1',
'border-bottom': '2px solid #bdc3c7'
}),
# Navigation tabs
dcc.Tabs(id="main-tabs", value='market-data', children=[
dcc.Tab(label='📊 Market Data', value='market-data'),
dcc.Tab(label='🤖 Bot Management', value='bot-management'),
dcc.Tab(label='📈 Performance', value='performance'),
dcc.Tab(label='⚙️ System Health', value='system-health'),
], style={'margin': '10px 20px'}),
# Main content area
html.Div(id='tab-content', style={'padding': '20px'}),
# Auto-refresh interval for real-time updates
dcc.Interval(
id='interval-component',
interval=5000, # Update every 5 seconds
n_intervals=0
),
# Store components for data sharing between callbacks
dcc.Store(id='market-data-store'),
dcc.Store(id='bot-status-store'),
])
return app
def get_market_data_layout():
"""Create the market data visualization layout."""
# Get available symbols and timeframes from database
symbols = get_supported_symbols()
timeframes = get_supported_timeframes()
# Create dropdown options
symbol_options = [{'label': symbol, 'value': symbol} for symbol in symbols]
timeframe_options = [
{'label': '1 Minute', 'value': '1m'},
{'label': '5 Minutes', 'value': '5m'},
{'label': '15 Minutes', 'value': '15m'},
{'label': '1 Hour', 'value': '1h'},
{'label': '4 Hours', 'value': '4h'},
{'label': '1 Day', 'value': '1d'},
]
# Filter timeframe options to only show those available in database
available_timeframes = [tf for tf in ['1m', '5m', '15m', '1h', '4h', '1d'] if tf in timeframes]
if not available_timeframes:
available_timeframes = ['1h'] # Default fallback
timeframe_options = [opt for opt in timeframe_options if opt['value'] in available_timeframes]
return html.Div([
html.H2("📊 Real-time Market Data", style={'color': '#2c3e50'}),
# Symbol selector
html.Div([
html.Label("Select Trading Pair:", style={'font-weight': 'bold'}),
dcc.Dropdown(
id='symbol-dropdown',
options=symbol_options,
value=symbols[0] if symbols else 'BTC-USDT',
style={'margin': '10px 0'}
)
], style={'width': '300px', 'margin': '20px 0'}),
# Timeframe selector
html.Div([
html.Label("Timeframe:", style={'font-weight': 'bold'}),
dcc.Dropdown(
id='timeframe-dropdown',
options=timeframe_options,
value=available_timeframes[0] if available_timeframes else '1h',
style={'margin': '10px 0'}
)
], style={'width': '300px', 'margin': '20px 0'}),
# Price chart
dcc.Graph(
id='price-chart',
style={'height': '600px', 'margin': '20px 0'},
config={'displayModeBar': True, 'displaylogo': False}
),
# Market statistics
html.Div(id='market-stats', style={'margin': '20px 0'}),
# Data status indicator
html.Div(id='data-status', style={'margin': '20px 0'})
])
def get_bot_management_layout():
"""Create the bot management layout."""
return html.Div([
html.H2("🤖 Bot Management", style={'color': '#2c3e50'}),
html.P("Bot management interface will be implemented in Phase 4.0"),
# Placeholder for bot list
html.Div([
html.H3("Active Bots"),
html.Div(id='bot-list', children=[
html.P("No bots currently running", style={'color': '#7f8c8d'})
])
], style={'margin': '20px 0'})
])
def get_performance_layout():
"""Create the performance monitoring layout."""
return html.Div([
html.H2("📈 Performance Analytics", style={'color': '#2c3e50'}),
html.P("Performance analytics will be implemented in Phase 6.0"),
# Placeholder for performance metrics
html.Div([
html.H3("Portfolio Performance"),
html.P("Portfolio tracking coming soon", style={'color': '#7f8c8d'})
], style={'margin': '20px 0'})
])
def get_system_health_layout():
"""Create the system health monitoring layout."""
return html.Div([
html.H2("⚙️ System Health", style={'color': '#2c3e50'}),
# Database status
html.Div([
html.H3("Database Status"),
html.Div(id='database-status')
], style={'margin': '20px 0'}),
# Data collection status
html.Div([
html.H3("Data Collection Status"),
html.Div(id='collection-status')
], style={'margin': '20px 0'}),
# Redis status
html.Div([
html.H3("Redis Status"),
html.Div(id='redis-status')
], style={'margin': '20px 0'})
])
# Create the app instance
app = create_app()
# Tab switching callback
@callback(
Output('tab-content', 'children'),
Input('main-tabs', 'value')
)
def render_tab_content(active_tab):
"""Render content based on selected tab."""
if active_tab == 'market-data':
return get_market_data_layout()
elif active_tab == 'bot-management':
return get_bot_management_layout()
elif active_tab == 'performance':
return get_performance_layout()
elif active_tab == 'system-health':
return get_system_health_layout()
else:
return html.Div("Tab not found")
# Market data chart callback
@callback(
Output('price-chart', 'figure'),
[Input('symbol-dropdown', 'value'),
Input('timeframe-dropdown', 'value'),
Input('interval-component', 'n_intervals')]
)
def update_price_chart(symbol, timeframe, n_intervals):
"""Update the price chart with latest market data."""
try:
# Use the real chart component instead of sample data
fig = create_candlestick_chart(symbol, timeframe)
logger.debug(f"Updated chart for {symbol} ({timeframe}) - interval {n_intervals}")
return fig
except Exception as e:
logger.error(f"Error updating price chart: {e}")
# Return error chart on failure
return create_error_chart(f"Error loading chart: {str(e)}")
# Market statistics callback
@callback(
Output('market-stats', 'children'),
[Input('symbol-dropdown', 'value'),
Input('interval-component', 'n_intervals')]
)
def update_market_stats(symbol, n_intervals):
"""Update market statistics."""
try:
# Get real market statistics from database
stats = get_market_statistics(symbol)
return html.Div([
html.H3("Market Statistics"),
html.Div([
html.Div([
html.Strong(f"{key}: "),
html.Span(value, style={'color': '#27ae60' if '+' in str(value) else '#e74c3c' if '-' in str(value) else '#2c3e50'})
], style={'margin': '5px 0'}) for key, value in stats.items()
])
])
except Exception as e:
logger.error(f"Error updating market stats: {e}")
return html.Div("Error loading market statistics")
# System health callbacks
@callback(
Output('database-status', 'children'),
Input('interval-component', 'n_intervals')
)
def update_database_status(n_intervals):
"""Update database connection status."""
try:
db_manager = DatabaseManager()
# Test database connection
with db_manager.get_session() as session:
# Simple query to test connection
result = session.execute("SELECT 1").fetchone()
if result:
return html.Div([
html.Span("🟢 Connected", style={'color': '#27ae60', 'font-weight': 'bold'}),
html.P(f"Last checked: {datetime.now().strftime('%H:%M:%S')}",
style={'margin': '5px 0', 'color': '#7f8c8d'})
])
else:
return html.Div([
html.Span("🔴 Connection Error", style={'color': '#e74c3c', 'font-weight': 'bold'})
])
except Exception as e:
logger.error(f"Database status check failed: {e}")
return html.Div([
html.Span("🔴 Connection Failed", style={'color': '#e74c3c', 'font-weight': 'bold'}),
html.P(f"Error: {str(e)}", style={'color': '#7f8c8d', 'font-size': '12px'})
])
@callback(
Output('data-status', 'children'),
[Input('symbol-dropdown', 'value'),
Input('timeframe-dropdown', 'value'),
Input('interval-component', 'n_intervals')]
)
def update_data_status(symbol, timeframe, n_intervals):
"""Update data collection status."""
try:
# Check real data availability
status = check_data_availability(symbol, timeframe)
return html.Div([
html.H3("Data Collection Status"),
html.Div([
html.Div(
create_data_status_indicator(symbol, timeframe),
style={'margin': '10px 0'}
),
html.P(f"Checking data for {symbol} {timeframe}",
style={'color': '#7f8c8d', 'margin': '5px 0', 'font-style': 'italic'})
], style={'background-color': '#f8f9fa', 'padding': '15px', 'border-radius': '5px'})
])
except Exception as e:
logger.error(f"Error updating data status: {e}")
return html.Div([
html.H3("Data Collection Status"),
html.Div([
html.Span("🔴 Status Check Failed", style={'color': '#e74c3c', 'font-weight': 'bold'}),
html.P(f"Error: {str(e)}", style={'color': '#7f8c8d', 'margin': '5px 0'})
])
])
def main():
"""Main function to run the dashboard."""
try:
logger.info("Starting Crypto Trading Bot Dashboard")
logger.info(f"Dashboard will be available at: http://{dashboard_settings.host}:{dashboard_settings.port}")
# Run the app
app.run(
host=dashboard_settings.host,
port=dashboard_settings.port,
debug=dashboard_settings.debug
)
except Exception as e:
logger.error(f"Failed to start dashboard: {e}")
sys.exit(1)
if __name__ == '__main__':
main()

29
components/__init__.py Normal file
View File

@ -0,0 +1,29 @@
"""
Dashboard UI Components Package
This package contains reusable UI components for the Crypto Trading Bot Dashboard.
Components are designed to be modular and can be composed to create complex layouts.
"""
from pathlib import Path
# Package metadata
__version__ = "0.1.0"
__package_name__ = "components"
# Make components directory available
COMPONENTS_DIR = Path(__file__).parent
# Component registry for future component discovery
AVAILABLE_COMPONENTS = [
"dashboard", # Main dashboard layout components
"charts", # Chart and visualization components
]
def get_component_path(component_name: str) -> Path:
"""Get the file path for a specific component."""
return COMPONENTS_DIR / f"{component_name}.py"
def list_components() -> list:
"""List all available components."""
return AVAILABLE_COMPONENTS.copy()

455
components/charts.py Normal file
View File

@ -0,0 +1,455 @@
"""
Chart and Visualization Components
This module provides chart components for market data visualization,
including candlestick charts, technical indicators, and real-time updates.
"""
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import pandas as pd
from datetime import datetime, timedelta, timezone
from typing import List, Dict, Any, Optional
from decimal import Decimal
from database.operations import get_database_operations, DatabaseOperationError
from utils.logger import get_logger
# Initialize logger
logger = get_logger("charts_component")
def fetch_market_data(symbol: str, timeframe: str,
days_back: int = 7, exchange: str = "okx") -> List[Dict[str, Any]]:
"""
Fetch market data from the database for chart display.
Args:
symbol: Trading pair (e.g., 'BTC-USDT')
timeframe: Timeframe (e.g., '1h', '1d')
days_back: Number of days to look back
exchange: Exchange name
Returns:
List of candle data dictionaries
"""
try:
db = get_database_operations(logger)
# Calculate time range
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=days_back)
# Fetch candles from database using the proper API
candles = db.market_data.get_candles(
symbol=symbol,
timeframe=timeframe,
start_time=start_time,
end_time=end_time,
exchange=exchange
)
logger.debug(f"Fetched {len(candles)} candles for {symbol} {timeframe}")
return candles
except DatabaseOperationError as e:
logger.error(f"Database error fetching market data: {e}")
return []
except Exception as e:
logger.error(f"Unexpected error fetching market data: {e}")
return []
def create_candlestick_chart(symbol: str, timeframe: str,
candles: Optional[List[Dict[str, Any]]] = None) -> go.Figure:
"""
Create a candlestick chart with real market data.
Args:
symbol: Trading pair
timeframe: Timeframe
candles: Optional pre-fetched candle data
Returns:
Plotly Figure object
"""
try:
# Fetch data if not provided
if candles is None:
candles = fetch_market_data(symbol, timeframe)
# Handle empty data
if not candles:
logger.warning(f"No data available for {symbol} {timeframe}")
return create_empty_chart(f"No data available for {symbol} {timeframe}")
# Convert to DataFrame for easier manipulation
df = pd.DataFrame(candles)
# Ensure timestamp column is datetime
df['timestamp'] = pd.to_datetime(df['timestamp'])
# Sort by timestamp
df = df.sort_values('timestamp')
# Create candlestick chart
fig = go.Figure(data=go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name=symbol,
increasing_line_color='#26a69a',
decreasing_line_color='#ef5350'
))
# Update layout
fig.update_layout(
title=f"{symbol} - {timeframe} Chart",
xaxis_title="Time",
yaxis_title="Price (USDT)",
template="plotly_white",
showlegend=False,
height=600,
xaxis_rangeslider_visible=False,
hovermode='x unified'
)
# Add volume subplot if volume data exists
if 'volume' in df.columns and df['volume'].sum() > 0:
fig = create_candlestick_with_volume(df, symbol, timeframe)
logger.debug(f"Created candlestick chart for {symbol} {timeframe} with {len(df)} candles")
return fig
except Exception as e:
logger.error(f"Error creating candlestick chart for {symbol} {timeframe}: {e}")
return create_error_chart(f"Error loading chart: {str(e)}")
def create_candlestick_with_volume(df: pd.DataFrame, symbol: str, timeframe: str) -> go.Figure:
"""
Create a candlestick chart with volume subplot.
Args:
df: DataFrame with OHLCV data
symbol: Trading pair
timeframe: Timeframe
Returns:
Plotly Figure with candlestick and volume
"""
# Create subplots
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
subplot_titles=(f'{symbol} Price', 'Volume'),
row_width=[0.7, 0.3]
)
# Add candlestick chart
fig.add_trace(
go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name=symbol,
increasing_line_color='#26a69a',
decreasing_line_color='#ef5350'
),
row=1, col=1
)
# Add volume bars
colors = ['#26a69a' if close >= open else '#ef5350'
for close, open in zip(df['close'], df['open'])]
fig.add_trace(
go.Bar(
x=df['timestamp'],
y=df['volume'],
name='Volume',
marker_color=colors,
opacity=0.7
),
row=2, col=1
)
# Update layout
fig.update_layout(
title=f"{symbol} - {timeframe} Chart with Volume",
template="plotly_white",
showlegend=False,
height=700,
xaxis_rangeslider_visible=False,
hovermode='x unified'
)
# Update axes
fig.update_yaxes(title_text="Price (USDT)", row=1, col=1)
fig.update_yaxes(title_text="Volume", row=2, col=1)
fig.update_xaxes(title_text="Time", row=2, col=1)
return fig
def create_empty_chart(message: str = "No data available") -> go.Figure:
"""
Create an empty chart with a message.
Args:
message: Message to display
Returns:
Empty Plotly Figure
"""
fig = go.Figure()
fig.add_annotation(
text=message,
xref="paper", yref="paper",
x=0.5, y=0.5,
xanchor='center', yanchor='middle',
showarrow=False,
font=dict(size=16, color="#7f8c8d")
)
fig.update_layout(
template="plotly_white",
height=600,
showlegend=False,
xaxis=dict(visible=False),
yaxis=dict(visible=False)
)
return fig
def create_error_chart(error_message: str) -> go.Figure:
"""
Create an error chart with error message.
Args:
error_message: Error message to display
Returns:
Error Plotly Figure
"""
fig = go.Figure()
fig.add_annotation(
text=f"⚠️ {error_message}",
xref="paper", yref="paper",
x=0.5, y=0.5,
xanchor='center', yanchor='middle',
showarrow=False,
font=dict(size=16, color="#e74c3c")
)
fig.update_layout(
template="plotly_white",
height=600,
showlegend=False,
xaxis=dict(visible=False),
yaxis=dict(visible=False)
)
return fig
def get_market_statistics(symbol: str, timeframe: str = "1h") -> Dict[str, str]:
"""
Calculate market statistics from recent data.
Args:
symbol: Trading pair
timeframe: Timeframe for calculations
Returns:
Dictionary of market statistics
"""
try:
# Fetch recent data for statistics
candles = fetch_market_data(symbol, timeframe, days_back=1)
if not candles:
return {
'Price': 'N/A',
'24h Change': 'N/A',
'24h Volume': 'N/A',
'High 24h': 'N/A',
'Low 24h': 'N/A'
}
# Convert to DataFrame
df = pd.DataFrame(candles)
# Get latest and 24h ago prices
latest_candle = df.iloc[-1]
current_price = float(latest_candle['close'])
# Calculate 24h change
if len(df) > 1:
price_24h_ago = float(df.iloc[0]['open'])
change_24h = current_price - price_24h_ago
change_percent = (change_24h / price_24h_ago) * 100
else:
change_24h = 0
change_percent = 0
# Calculate volume and high/low
total_volume = df['volume'].sum()
high_24h = df['high'].max()
low_24h = df['low'].min()
# Format statistics
return {
'Price': f"${current_price:,.2f}",
'24h Change': f"{'+' if change_24h >= 0 else ''}{change_percent:.2f}%",
'24h Volume': f"{total_volume:,.2f}",
'High 24h': f"${float(high_24h):,.2f}",
'Low 24h': f"${float(low_24h):,.2f}"
}
except Exception as e:
logger.error(f"Error calculating market statistics for {symbol}: {e}")
return {
'Price': 'Error',
'24h Change': 'Error',
'24h Volume': 'Error',
'High 24h': 'Error',
'Low 24h': 'Error'
}
def check_data_availability(symbol: str, timeframe: str) -> Dict[str, Any]:
"""
Check data availability for a symbol and timeframe.
Args:
symbol: Trading pair
timeframe: Timeframe
Returns:
Dictionary with data availability information
"""
try:
db = get_database_operations(logger)
# Get latest candle using the proper API
latest_candle = db.market_data.get_latest_candle(symbol, timeframe)
if latest_candle:
latest_time = latest_candle['timestamp']
time_diff = datetime.now(timezone.utc) - latest_time.replace(tzinfo=timezone.utc)
return {
'has_data': True,
'latest_timestamp': latest_time,
'time_since_last': time_diff,
'is_recent': time_diff < timedelta(hours=1),
'message': f"Latest data: {latest_time.strftime('%Y-%m-%d %H:%M:%S UTC')}"
}
else:
return {
'has_data': False,
'latest_timestamp': None,
'time_since_last': None,
'is_recent': False,
'message': f"No data available for {symbol} {timeframe}"
}
except Exception as e:
logger.error(f"Error checking data availability for {symbol} {timeframe}: {e}")
return {
'has_data': False,
'latest_timestamp': None,
'time_since_last': None,
'is_recent': False,
'message': f"Error checking data: {str(e)}"
}
def create_data_status_indicator(symbol: str, timeframe: str) -> str:
"""
Create a data status indicator for the dashboard.
Args:
symbol: Trading pair
timeframe: Timeframe
Returns:
HTML string for status indicator
"""
status = check_data_availability(symbol, timeframe)
if status['has_data']:
if status['is_recent']:
icon = "🟢"
color = "#27ae60"
status_text = "Real-time Data"
else:
icon = "🟡"
color = "#f39c12"
status_text = "Delayed Data"
else:
icon = "🔴"
color = "#e74c3c"
status_text = "No Data"
return f'<span style="color: {color}; font-weight: bold;">{icon} {status_text}</span><br><small>{status["message"]}</small>'
def get_supported_symbols() -> List[str]:
"""
Get list of symbols that have data in the database.
Returns:
List of available trading pairs
"""
try:
db = get_database_operations(logger)
with db.market_data.get_session() as session:
# Query distinct symbols from market_data table
from sqlalchemy import text
result = session.execute(text("SELECT DISTINCT symbol FROM market_data ORDER BY symbol"))
symbols = [row[0] for row in result]
logger.debug(f"Found {len(symbols)} symbols in database: {symbols}")
return symbols
except Exception as e:
logger.error(f"Error fetching supported symbols: {e}")
# Return default symbols if database query fails
return ['BTC-USDT', 'ETH-USDT', 'LTC-USDT']
def get_supported_timeframes() -> List[str]:
"""
Get list of timeframes that have data in the database.
Returns:
List of available timeframes
"""
try:
db = get_database_operations(logger)
with db.market_data.get_session() as session:
# Query distinct timeframes from market_data table
from sqlalchemy import text
result = session.execute(text("SELECT DISTINCT timeframe FROM market_data ORDER BY timeframe"))
timeframes = [row[0] for row in result]
logger.debug(f"Found {len(timeframes)} timeframes in database: {timeframes}")
return timeframes
except Exception as e:
logger.error(f"Error fetching supported timeframes: {e}")
# Return default timeframes if database query fails
return ['1m', '5m', '15m', '1h', '4h', '1d']

323
components/dashboard.py Normal file
View File

@ -0,0 +1,323 @@
"""
Dashboard Layout Components
This module contains reusable layout components for the main dashboard interface.
These components handle the overall structure and navigation of the dashboard.
"""
from dash import html, dcc
from typing import List, Dict, Any, Optional
from datetime import datetime
def create_header(title: str = "Crypto Trading Bot Dashboard",
subtitle: str = "Real-time monitoring and bot management") -> html.Div:
"""
Create the main dashboard header component.
Args:
title: Main title text
subtitle: Subtitle text
Returns:
Dash HTML component for the header
"""
return html.Div([
html.H1(f"🚀 {title}",
style={'margin': '0', 'color': '#2c3e50', 'font-size': '28px'}),
html.P(subtitle,
style={'margin': '5px 0 0 0', 'color': '#7f8c8d', 'font-size': '14px'})
], style={
'padding': '20px',
'background-color': '#ecf0f1',
'border-bottom': '2px solid #bdc3c7',
'box-shadow': '0 2px 4px rgba(0,0,0,0.1)'
})
def create_navigation_tabs(active_tab: str = 'market-data') -> dcc.Tabs:
"""
Create the main navigation tabs component.
Args:
active_tab: Default active tab
Returns:
Dash Tabs component
"""
tab_style = {
'borderBottom': '1px solid #d6d6d6',
'padding': '6px',
'fontWeight': 'bold'
}
tab_selected_style = {
'borderTop': '1px solid #d6d6d6',
'borderBottom': '1px solid #d6d6d6',
'backgroundColor': '#119DFF',
'color': 'white',
'padding': '6px'
}
return dcc.Tabs(
id="main-tabs",
value=active_tab,
children=[
dcc.Tab(
label='📊 Market Data',
value='market-data',
style=tab_style,
selected_style=tab_selected_style
),
dcc.Tab(
label='🤖 Bot Management',
value='bot-management',
style=tab_style,
selected_style=tab_selected_style
),
dcc.Tab(
label='📈 Performance',
value='performance',
style=tab_style,
selected_style=tab_selected_style
),
dcc.Tab(
label='⚙️ System Health',
value='system-health',
style=tab_style,
selected_style=tab_selected_style
),
],
style={'margin': '10px 20px'}
)
def create_content_container(content_id: str = 'tab-content') -> html.Div:
"""
Create the main content container.
Args:
content_id: HTML element ID for the content area
Returns:
Dash HTML component for content container
"""
return html.Div(
id=content_id,
style={
'padding': '20px',
'min-height': '600px',
'background-color': '#ffffff'
}
)
def create_status_indicator(status: str, message: str,
timestamp: Optional[datetime] = None) -> html.Div:
"""
Create a status indicator component.
Args:
status: Status type ('connected', 'error', 'warning', 'info')
message: Status message
timestamp: Optional timestamp for the status
Returns:
Dash HTML component for status indicator
"""
status_colors = {
'connected': '#27ae60',
'error': '#e74c3c',
'warning': '#f39c12',
'info': '#3498db'
}
status_icons = {
'connected': '🟢',
'error': '🔴',
'warning': '🟡',
'info': '🔵'
}
color = status_colors.get(status, '#7f8c8d')
icon = status_icons.get(status, '')
components = [
html.Span(f"{icon} {message}",
style={'color': color, 'font-weight': 'bold'})
]
if timestamp:
components.append(
html.P(f"Last updated: {timestamp.strftime('%H:%M:%S')}",
style={'margin': '5px 0', 'color': '#7f8c8d', 'font-size': '12px'})
)
return html.Div(components)
def create_card(title: str, content: Any,
card_id: Optional[str] = None) -> html.Div:
"""
Create a card component for organizing content.
Args:
title: Card title
content: Card content (can be any Dash component)
card_id: Optional HTML element ID
Returns:
Dash HTML component for the card
"""
return html.Div([
html.H3(title, style={
'margin': '0 0 15px 0',
'color': '#2c3e50',
'border-bottom': '2px solid #ecf0f1',
'padding-bottom': '10px'
}),
content
], style={
'border': '1px solid #ddd',
'border-radius': '8px',
'padding': '20px',
'margin': '10px 0',
'background-color': '#ffffff',
'box-shadow': '0 2px 4px rgba(0,0,0,0.1)'
}, id=card_id)
def create_metric_display(metrics: Dict[str, str]) -> html.Div:
"""
Create a metrics display component.
Args:
metrics: Dictionary of metric names and values
Returns:
Dash HTML component for metrics display
"""
metric_components = []
for key, value in metrics.items():
# Color coding for percentage changes
color = '#27ae60' if '+' in str(value) else '#e74c3c' if '-' in str(value) else '#2c3e50'
metric_components.append(
html.Div([
html.Strong(f"{key}: ", style={'color': '#2c3e50'}),
html.Span(str(value), style={'color': color})
], style={
'margin': '8px 0',
'padding': '5px',
'background-color': '#f8f9fa',
'border-radius': '4px'
})
)
return html.Div(metric_components, style={
'display': 'grid',
'grid-template-columns': 'repeat(auto-fit, minmax(200px, 1fr))',
'gap': '10px'
})
def create_selector_group(selectors: List[Dict[str, Any]]) -> html.Div:
"""
Create a group of selector components (dropdowns, etc.).
Args:
selectors: List of selector configurations
Returns:
Dash HTML component for selector group
"""
selector_components = []
for selector in selectors:
selector_div = html.Div([
html.Label(
selector.get('label', ''),
style={'font-weight': 'bold', 'margin-bottom': '5px', 'display': 'block'}
),
dcc.Dropdown(
id=selector.get('id'),
options=selector.get('options', []),
value=selector.get('value'),
style={'margin-bottom': '15px'}
)
], style={'width': '250px', 'margin': '10px 20px 10px 0', 'display': 'inline-block'})
selector_components.append(selector_div)
return html.Div(selector_components, style={'margin': '20px 0'})
def create_loading_component(component_id: str, message: str = "Loading...") -> html.Div:
"""
Create a loading component for async operations.
Args:
component_id: ID for the component that will replace this loading screen
message: Loading message
Returns:
Dash HTML component for loading screen
"""
return html.Div([
html.Div([
html.Div(className="loading-spinner", style={
'border': '4px solid #f3f3f3',
'border-top': '4px solid #3498db',
'border-radius': '50%',
'width': '40px',
'height': '40px',
'animation': 'spin 2s linear infinite',
'margin': '0 auto 20px auto'
}),
html.P(message, style={'text-align': 'center', 'color': '#7f8c8d'})
], style={
'display': 'flex',
'flex-direction': 'column',
'align-items': 'center',
'justify-content': 'center',
'height': '200px'
})
], id=component_id)
def create_placeholder_content(title: str, description: str,
phase: str = "future implementation") -> html.Div:
"""
Create placeholder content for features not yet implemented.
Args:
title: Section title
description: Description of what will be implemented
phase: Implementation phase information
Returns:
Dash HTML component for placeholder content
"""
return html.Div([
html.H2(title, style={'color': '#2c3e50'}),
html.Div([
html.P(description, style={'color': '#7f8c8d', 'font-size': '16px'}),
html.P(f"🚧 Planned for {phase}",
style={'color': '#f39c12', 'font-weight': 'bold', 'font-style': 'italic'})
], style={
'background-color': '#f8f9fa',
'padding': '20px',
'border-radius': '8px',
'border-left': '4px solid #f39c12'
})
])
# CSS Styles for animation (to be included in assets or inline styles)
LOADING_CSS = """
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
"""

View File

@ -169,7 +169,7 @@ class MarketDataRepository(BaseRepository):
query = text("""
SELECT exchange, symbol, timeframe, timestamp,
open, high, low, close, volume, trades_count,
created_at, updated_at
created_at
FROM market_data
WHERE exchange = :exchange
AND symbol = :symbol
@ -200,15 +200,14 @@ class MarketDataRepository(BaseRepository):
'close': row.close,
'volume': row.volume,
'trades_count': row.trades_count,
'created_at': row.created_at,
'updated_at': row.updated_at
'created_at': row.created_at
})
self.log_info(f"Retrieved {len(candles)} candles for {symbol} {timeframe}")
self.log_debug(f"Retrieved {len(candles)} candles for {symbol} {timeframe}")
return candles
except Exception as e:
self.log_error(f"Error retrieving candles for {symbol} {timeframe}: {e}")
self.log_error(f"Error retrieving candles: {e}")
raise DatabaseOperationError(f"Failed to retrieve candles: {e}")
def get_latest_candle(self, symbol: str, timeframe: str, exchange: str = "okx") -> Optional[Dict[str, Any]]:
@ -228,7 +227,7 @@ class MarketDataRepository(BaseRepository):
query = text("""
SELECT exchange, symbol, timeframe, timestamp,
open, high, low, close, volume, trades_count,
created_at, updated_at
created_at
FROM market_data
WHERE exchange = :exchange
AND symbol = :symbol
@ -256,8 +255,7 @@ class MarketDataRepository(BaseRepository):
'close': row.close,
'volume': row.volume,
'trades_count': row.trades_count,
'created_at': row.created_at,
'updated_at': row.updated_at
'created_at': row.created_at
}
return None

26
main.py
View File

@ -23,23 +23,29 @@ def main():
if app.environment == "development":
print("\n🔧 Running in development mode")
print("To start the full application:")
print("1. Run: python scripts/dev.py setup")
print("2. Run: python scripts/dev.py start")
print("3. Update .env with your OKX API credentials")
print("4. Run: uv run python tests/test_setup.py")
print("Dashboard features available:")
print("✅ Basic Dash application framework")
print("✅ Real-time price charts (sample data)")
print("✅ System health monitoring")
print("🚧 Real data connection (coming in task 3.7)")
# TODO: Start the Dash application when ready
# from app import create_app
# app = create_app()
# app.run(host=dashboard.host, port=dashboard.port, debug=dashboard.debug)
# Start the Dash application
print(f"\n🌐 Starting dashboard at: http://{dashboard.host}:{dashboard.port}")
print("Press Ctrl+C to stop the application")
print(f"\n📝 Next: Implement Phase 1.0 - Database Infrastructure Setup")
from app import main as app_main
app_main()
except ImportError as e:
print(f"❌ Failed to import modules: {e}")
print("Run: uv sync")
sys.exit(1)
except KeyboardInterrupt:
print("\n\n👋 Dashboard stopped by user")
sys.exit(0)
except Exception as e:
print(f"❌ Failed to start dashboard: {e}")
sys.exit(1)
if __name__ == "__main__":

View File

@ -77,9 +77,9 @@
- [x] 2.9 Unit test data collection and aggregation logic
- [ ] 3.0 Basic Dashboard for Data Visualization and Analysis
- [ ] 3.1 Setup Dash application framework with Mantine UI components
- [ ] 3.2 Create basic layout and navigation structure
- [ ] 3.3 Implement real-time OHLCV price charts with Plotly (candlestick charts)
- [x] 3.1 Setup Dash application framework with Mantine UI components
- [x] 3.2 Create basic layout and navigation structure
- [x] 3.3 Implement real-time OHLCV price charts with Plotly (candlestick charts)
- [ ] 3.4 Add technical indicators overlay on price charts (SMA, EMA, RSI, MACD)
- [ ] 3.5 Create market data monitoring dashboard (real-time data feed status)
- [ ] 3.6 Build simple data analysis tools (volume analysis, price movement statistics)