Remove deprecated app_new.py and consolidate main application logic into main.py

- Deleted `app_new.py`, which was previously the main entry point for the dashboard application, to streamline the codebase.
- Consolidated the application initialization and callback registration logic into `main.py`, enhancing modularity and maintainability.
- Updated the logging and error handling practices in `main.py` to ensure consistent application behavior and improved debugging capabilities.

These changes simplify the application structure, aligning with project standards for modularity and maintainability.
This commit is contained in:
Vasily.onl
2025-06-11 18:36:34 +08:00
parent 0a7e444206
commit dbe58e5cef
47 changed files with 1198 additions and 10784 deletions

View File

@@ -22,7 +22,7 @@ def create_app():
# Initialize Dash app
app = dash.Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.LUX])
# Define the main layout wrapped in MantineProvider
# Define the main layout
app.layout = html.Div([
html.Div([
# Page title

View File

@@ -6,13 +6,14 @@ from dash import Output, Input, html, dcc
import dash_bootstrap_components as dbc
from utils.logger import get_logger
from dashboard.components.data_analysis import (
VolumeAnalyzer,
PriceMovementAnalyzer,
create_volume_analysis_chart,
create_price_movement_chart,
create_volume_stats_display,
create_price_stats_display
create_price_stats_display,
get_market_statistics,
VolumeAnalyzer,
PriceMovementAnalyzer
)
from database.operations import get_database_operations
from datetime import datetime, timezone, timedelta
logger = get_logger("data_analysis_callbacks")
@@ -24,26 +25,38 @@ def register_data_analysis_callbacks(app):
# Initial callback to populate charts on load
@app.callback(
[Output('analysis-chart-container', 'children'),
Output('analysis-stats-container', 'children')],
[Input('analysis-type-selector', 'value'),
Input('analysis-period-selector', 'value')],
[Output('volume-analysis-chart', 'figure'),
Output('price-movement-chart', 'figure'),
Output('volume-stats-output', 'children'),
Output('price-stats-output', 'children'),
Output('market-statistics-output', 'children')],
[Input('data-analysis-symbol-dropdown', 'value'),
Input('data-analysis-timeframe-dropdown', 'value'),
Input('data-analysis-days-back-dropdown', 'value')],
prevent_initial_call=False
)
def update_data_analysis(analysis_type, period):
def update_data_analysis(symbol, timeframe, days_back):
"""Update data analysis with statistical cards only (no duplicate charts)."""
logger.info(f"🎯 DATA ANALYSIS CALLBACK TRIGGERED! Type: {analysis_type}, Period: {period}")
logger.info(f"🎯 DATA ANALYSIS CALLBACK TRIGGERED! Symbol: {symbol}, Timeframe: {timeframe}, Days Back: {days_back}")
db_ops = get_database_operations(logger)
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=days_back)
# Return placeholder message since we're moving to enhanced market stats
info_msg = dbc.Alert([
html.H4("📊 Statistical Analysis", className="alert-heading"),
html.P("Data analysis has been integrated into the Market Statistics section above."),
html.P("The enhanced statistics now include volume analysis, price movement analysis, and trend indicators."),
html.P("Change the symbol and timeframe in the main chart to see updated analysis."),
html.Hr(),
html.P("This section will be updated with additional analytical tools in future versions.", className="mb-0")
], color="info")
df = db_ops.market_data.get_candles_df(symbol, timeframe, start_time, end_time)
volume_analyzer = VolumeAnalyzer()
price_analyzer = PriceMovementAnalyzer()
return info_msg, html.Div()
volume_stats = volume_analyzer.get_volume_statistics(df)
price_stats = price_analyzer.get_price_movement_statistics(df)
volume_stats_display = create_volume_stats_display(volume_stats)
price_stats_display = create_price_stats_display(price_stats)
market_stats_display = get_market_statistics(df, symbol, timeframe)
# Return empty figures for charts, as they are no longer the primary display
# And the stats displays
return {}, {}, volume_stats_display, price_stats_display, market_stats_display
logger.info("✅ Data analysis callbacks registered successfully")

View File

@@ -5,10 +5,10 @@ Chart control components for the market data layout.
from dash import html, dcc
import dash_bootstrap_components as dbc
from utils.logger import get_logger
from utils.time_range_utils import load_time_range_options
logger = get_logger("default_logger")
def create_chart_config_panel(strategy_options, overlay_options, subplot_options):
"""Create the chart configuration panel with add/edit UI."""
return dbc.Card([
@@ -74,18 +74,7 @@ def create_time_range_controls():
html.Label("Quick Select:", className="form-label"),
dcc.Dropdown(
id='time-range-quick-select',
options=[
{'label': '🕐 Last 1 Hour', 'value': '1h'},
{'label': '🕐 Last 4 Hours', 'value': '4h'},
{'label': '🕐 Last 6 Hours', 'value': '6h'},
{'label': '🕐 Last 12 Hours', 'value': '12h'},
{'label': '📅 Last 1 Day', 'value': '1d'},
{'label': '📅 Last 3 Days', 'value': '3d'},
{'label': '📅 Last 7 Days', 'value': '7d'},
{'label': '📅 Last 30 Days', 'value': '30d'},
{'label': '📅 Custom Range', 'value': 'custom'},
{'label': '🔴 Real-time', 'value': 'realtime'}
],
options=load_time_range_options(),
value='7d',
placeholder="Select time range",
)

View File

@@ -4,9 +4,6 @@ Data analysis components for comprehensive market data analysis.
from dash import html, dcc
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np
from datetime import datetime, timezone, timedelta
@@ -14,7 +11,8 @@ from typing import Dict, Any, List, Optional
from utils.logger import get_logger
from database.connection import DatabaseManager
from database.operations import DatabaseOperationError
from database.operations import DatabaseOperationError, get_database_operations
from config.constants.chart_constants import CHART_COLORS, UI_TEXT
logger = get_logger("data_analysis")
@@ -23,8 +21,7 @@ class VolumeAnalyzer:
"""Analyze trading volume patterns and trends."""
def __init__(self):
self.db_manager = DatabaseManager()
self.db_manager.initialize()
pass
def get_volume_statistics(self, df: pd.DataFrame) -> Dict[str, Any]:
"""Calculate comprehensive volume statistics from a DataFrame."""
@@ -32,69 +29,81 @@ class VolumeAnalyzer:
if df.empty or 'volume' not in df.columns:
return {'error': 'DataFrame is empty or missing volume column'}
# Convert all relevant columns to float to avoid type errors with Decimal
df = df.copy()
numeric_cols = ['open', 'high', 'low', 'close', 'volume']
for col in numeric_cols:
if col in df.columns:
df[col] = df[col].astype(float)
if 'trades_count' in df.columns:
df['trades_count'] = df['trades_count'].astype(float)
df = self._ensure_numeric_cols(df)
# Calculate volume statistics
total_volume = df['volume'].sum()
avg_volume = df['volume'].mean()
volume_std = df['volume'].std()
stats = {}
stats.update(self._calculate_basic_volume_stats(df))
stats.update(self._analyze_volume_trend(df))
stats.update(self._identify_high_volume_periods(df, stats['avg_volume'], stats['volume_std']))
stats.update(self._calculate_volume_price_correlation(df))
stats.update(self._calculate_avg_trade_size(df))
stats.update(self._calculate_volume_percentiles(df))
# Volume trend analysis
recent_volume = df['volume'].tail(10).mean() # Last 10 periods
older_volume = df['volume'].head(10).mean() # First 10 periods
volume_trend = "Increasing" if recent_volume > older_volume else "Decreasing"
# High volume periods (above 2 standard deviations)
high_volume_threshold = avg_volume + (2 * volume_std)
high_volume_periods = len(df[df['volume'] > high_volume_threshold])
# Volume-Price correlation
price_change = df['close'] - df['open']
volume_price_corr = df['volume'].corr(price_change.abs())
# Average trade size (volume per trade)
if 'trades_count' in df.columns:
df['avg_trade_size'] = df['volume'] / df['trades_count'].replace(0, 1)
avg_trade_size = df['avg_trade_size'].mean()
else:
avg_trade_size = None # Not available
return {
'total_volume': total_volume,
'avg_volume': avg_volume,
'volume_std': volume_std,
'volume_trend': volume_trend,
'high_volume_periods': high_volume_periods,
'volume_price_correlation': volume_price_corr,
'avg_trade_size': avg_trade_size,
'max_volume': df['volume'].max(),
'min_volume': df['volume'].min(),
'volume_percentiles': {
'25th': df['volume'].quantile(0.25),
'50th': df['volume'].quantile(0.50),
'75th': df['volume'].quantile(0.75),
'95th': df['volume'].quantile(0.95)
}
}
return stats
except Exception as e:
logger.error(f"Volume analysis error: {e}")
return {'error': str(e)}
def _ensure_numeric_cols(self, df: pd.DataFrame) -> pd.DataFrame:
numeric_cols = ['open', 'high', 'low', 'close', 'volume']
for col in numeric_cols:
if col in df.columns:
df[col] = df[col].astype(float)
if 'trades_count' in df.columns:
df['trades_count'] = df['trades_count'].astype(float)
return df
def _calculate_basic_volume_stats(self, df: pd.DataFrame) -> Dict[str, Any]:
return {
'total_volume': df['volume'].sum(),
'avg_volume': df['volume'].mean(),
'volume_std': df['volume'].std(),
'max_volume': df['volume'].max(),
'min_volume': df['volume'].min()
}
def _analyze_volume_trend(self, df: pd.DataFrame) -> Dict[str, Any]:
recent_volume = df['volume'].tail(10).mean()
older_volume = df['volume'].head(10).mean()
volume_trend = "Increasing" if recent_volume > older_volume else "Decreasing"
return {'volume_trend': volume_trend}
def _identify_high_volume_periods(self, df: pd.DataFrame, avg_volume: float, volume_std: float) -> Dict[str, Any]:
high_volume_threshold = avg_volume + (2 * volume_std)
high_volume_periods = len(df[df['volume'] > high_volume_threshold])
return {'high_volume_periods': high_volume_periods}
def _calculate_volume_price_correlation(self, df: pd.DataFrame) -> Dict[str, Any]:
price_change = df['close'] - df['open']
volume_price_corr = df['volume'].corr(price_change.abs())
return {'volume_price_correlation': volume_price_corr}
def _calculate_avg_trade_size(self, df: pd.DataFrame) -> Dict[str, Any]:
if 'trades_count' in df.columns:
df['avg_trade_size'] = df['volume'] / df['trades_count'].replace(0, 1)
avg_trade_size = df['avg_trade_size'].mean()
else:
avg_trade_size = None
return {'avg_trade_size': avg_trade_size}
def _calculate_volume_percentiles(self, df: pd.DataFrame) -> Dict[str, Any]:
return {
'volume_percentiles': {
'25th': df['volume'].quantile(0.25),
'50th': df['volume'].quantile(0.50),
'75th': df['volume'].quantile(0.75),
'95th': df['volume'].quantile(0.95)
}
}
class PriceMovementAnalyzer:
"""Analyze price movement patterns and statistics."""
def __init__(self):
self.db_manager = DatabaseManager()
self.db_manager.initialize()
pass
def get_price_movement_statistics(self, df: pd.DataFrame) -> Dict[str, Any]:
"""Calculate comprehensive price movement statistics from a DataFrame."""
@@ -102,499 +111,317 @@ class PriceMovementAnalyzer:
if df.empty or not all(col in df.columns for col in ['open', 'high', 'low', 'close']):
return {'error': 'DataFrame is empty or missing required price columns'}
# Convert all relevant columns to float to avoid type errors with Decimal
df = df.copy()
numeric_cols = ['open', 'high', 'low', 'close', 'volume']
for col in numeric_cols:
if col in df.columns:
df[col] = df[col].astype(float)
# Basic price statistics
current_price = df['close'].iloc[-1]
period_start_price = df['open'].iloc[0]
period_return = ((current_price - period_start_price) / period_start_price) * 100
df = self._ensure_numeric_cols(df)
# Daily returns (percentage changes)
df['returns'] = df['close'].pct_change() * 100
df['returns'] = df['returns'].fillna(0)
# Volatility metrics
volatility = df['returns'].std()
avg_return = df['returns'].mean()
# Price range analysis
df['range'] = df['high'] - df['low']
df['range_pct'] = (df['range'] / df['open']) * 100
avg_range_pct = df['range_pct'].mean()
# Directional analysis
bullish_periods = len(df[df['close'] > df['open']])
bearish_periods = len(df[df['close'] < df['open']])
neutral_periods = len(df[df['close'] == df['open']])
total_periods = len(df)
bullish_ratio = (bullish_periods / total_periods) * 100 if total_periods > 0 else 0
# Price extremes
period_high = df['high'].max()
period_low = df['low'].min()
# Momentum indicators
# Simple momentum (current vs N periods ago)
momentum_periods = min(10, len(df) - 1)
if momentum_periods > 0:
momentum = ((current_price - df['close'].iloc[-momentum_periods-1]) / df['close'].iloc[-momentum_periods-1]) * 100
else:
momentum = 0
# Trend strength (linear regression slope)
if len(df) > 2:
x = np.arange(len(df))
slope, _ = np.polyfit(x, df['close'], 1)
trend_strength = slope / df['close'].mean() * 100 # Normalize by average price
else:
trend_strength = 0
return {
'current_price': current_price,
'period_return': period_return,
'volatility': volatility,
'avg_return': avg_return,
'avg_range_pct': avg_range_pct,
'bullish_periods': bullish_periods,
'bearish_periods': bearish_periods,
'neutral_periods': neutral_periods,
'bullish_ratio': bullish_ratio,
'period_high': period_high,
'period_low': period_low,
'momentum': momentum,
'trend_strength': trend_strength,
'return_percentiles': {
'5th': df['returns'].quantile(0.05),
'25th': df['returns'].quantile(0.25),
'75th': df['returns'].quantile(0.75),
'95th': df['returns'].quantile(0.95)
},
'max_gain': df['returns'].max(),
'max_loss': df['returns'].min(),
'positive_returns': len(df[df['returns'] > 0]),
'negative_returns': len(df[df['returns'] < 0])
}
stats = {}
stats.update(self._calculate_basic_price_stats(df))
stats.update(self._calculate_returns_and_volatility(df))
stats.update(self._analyze_price_range(df))
stats.update(self._analyze_directional_movement(df))
stats.update(self._calculate_price_extremes(df))
stats.update(self._calculate_momentum(df))
stats.update(self._calculate_trend_strength(df))
stats.update(self._calculate_return_percentiles(df))
return stats
except Exception as e:
logger.error(f"Price movement analysis error: {e}")
return {'error': str(e)}
def _ensure_numeric_cols(self, df: pd.DataFrame) -> pd.DataFrame:
numeric_cols = ['open', 'high', 'low', 'close', 'volume']
for col in numeric_cols:
if col in df.columns:
df[col] = df[col].astype(float)
return df
def create_volume_analysis_chart(symbol: str, timeframe: str = "1h", days_back: int = 7) -> go.Figure:
"""Create a comprehensive volume analysis chart."""
try:
analyzer = VolumeAnalyzer()
# Fetch market data for chart
db_manager = DatabaseManager()
db_manager.initialize()
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=days_back)
with db_manager.get_session() as session:
from sqlalchemy import text
query = text("""
SELECT timestamp, open, high, low, close, volume, trades_count
FROM market_data
WHERE symbol = :symbol
AND timeframe = :timeframe
AND timestamp >= :start_time
AND timestamp <= :end_time
ORDER BY timestamp ASC
""")
result = session.execute(query, {
'symbol': symbol,
'timeframe': timeframe,
'start_time': start_time,
'end_time': end_time
})
candles = []
for row in result:
candles.append({
'timestamp': row.timestamp,
'open': float(row.open),
'high': float(row.high),
'low': float(row.low),
'close': float(row.close),
'volume': float(row.volume),
'trades_count': int(row.trades_count) if row.trades_count else 0
})
if not candles:
fig = go.Figure()
fig.add_annotation(text="No data available", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
df = pd.DataFrame(candles)
# Calculate volume moving average
df['volume_ma'] = df['volume'].rolling(window=20, min_periods=1).mean()
# Create subplots
fig = make_subplots(
rows=3, cols=1,
subplot_titles=('Price Action', 'Volume Analysis', 'Volume vs Moving Average'),
vertical_spacing=0.08,
row_heights=[0.4, 0.3, 0.3]
)
# Price candlestick
fig.add_trace(
go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name='Price',
increasing_line_color='#26a69a',
decreasing_line_color='#ef5350'
),
row=1, col=1
)
# Volume bars with color coding
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
)
# Volume vs moving average
fig.add_trace(
go.Scatter(
x=df['timestamp'],
y=df['volume'],
mode='lines',
name='Volume',
line=dict(color='#2196f3', width=1)
),
row=3, col=1
)
fig.add_trace(
go.Scatter(
x=df['timestamp'],
y=df['volume_ma'],
mode='lines',
name='Volume MA(20)',
line=dict(color='#ff9800', width=2)
),
row=3, col=1
)
# Update layout
fig.update_layout(
title=f'{symbol} Volume Analysis ({timeframe})',
xaxis_rangeslider_visible=False,
height=800,
showlegend=True,
template='plotly_white'
)
# Update y-axes
fig.update_yaxes(title_text="Price", row=1, col=1)
fig.update_yaxes(title_text="Volume", row=2, col=1)
fig.update_yaxes(title_text="Volume", row=3, col=1)
return fig
except Exception as e:
logger.error(f"Volume chart creation error: {e}")
fig = go.Figure()
fig.add_annotation(text=f"Error: {str(e)}", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
def _calculate_basic_price_stats(self, df: pd.DataFrame) -> Dict[str, Any]:
current_price = df['close'].iloc[-1]
period_start_price = df['open'].iloc[0]
period_return = ((current_price - period_start_price) / period_start_price) * 100
return {'current_price': current_price, 'period_return': period_return}
def create_price_movement_chart(symbol: str, timeframe: str = "1h", days_back: int = 7) -> go.Figure:
"""Create a comprehensive price movement analysis chart."""
try:
# Fetch market data for chart
db_manager = DatabaseManager()
db_manager.initialize()
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=days_back)
with db_manager.get_session() as session:
from sqlalchemy import text
query = text("""
SELECT timestamp, open, high, low, close, volume
FROM market_data
WHERE symbol = :symbol
AND timeframe = :timeframe
AND timestamp >= :start_time
AND timestamp <= :end_time
ORDER BY timestamp ASC
""")
result = session.execute(query, {
'symbol': symbol,
'timeframe': timeframe,
'start_time': start_time,
'end_time': end_time
})
candles = []
for row in result:
candles.append({
'timestamp': row.timestamp,
'open': float(row.open),
'high': float(row.high),
'low': float(row.low),
'close': float(row.close),
'volume': float(row.volume)
})
if not candles:
fig = go.Figure()
fig.add_annotation(text="No data available", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
df = pd.DataFrame(candles)
# Calculate returns and statistics
def _calculate_returns_and_volatility(self, df: pd.DataFrame) -> Dict[str, Any]:
df['returns'] = df['close'].pct_change() * 100
df['returns'] = df['returns'].fillna(0)
df['range_pct'] = ((df['high'] - df['low']) / df['open']) * 100
df['cumulative_return'] = (1 + df['returns'] / 100).cumprod()
# Create subplots
fig = make_subplots(
rows=3, cols=1,
subplot_titles=('Cumulative Returns', 'Period Returns (%)', 'Price Range (%)'),
vertical_spacing=0.08,
row_heights=[0.4, 0.3, 0.3]
)
# Cumulative returns
fig.add_trace(
go.Scatter(
x=df['timestamp'],
y=df['cumulative_return'],
mode='lines',
name='Cumulative Return',
line=dict(color='#2196f3', width=2)
),
row=1, col=1
)
# Period returns with color coding
colors = ['#26a69a' if ret >= 0 else '#ef5350' for ret in df['returns']]
fig.add_trace(
go.Bar(
x=df['timestamp'],
y=df['returns'],
name='Returns (%)',
marker_color=colors,
opacity=0.7
),
row=2, col=1
)
# Price range percentage
fig.add_trace(
go.Scatter(
x=df['timestamp'],
y=df['range_pct'],
mode='lines+markers',
name='Range %',
line=dict(color='#ff9800', width=1),
marker=dict(size=4)
),
row=3, col=1
)
# Add zero line for returns
fig.add_hline(y=0, line_dash="dash", line_color="gray", row=2, col=1)
# Update layout
fig.update_layout(
title=f'{symbol} Price Movement Analysis ({timeframe})',
height=800,
showlegend=True,
template='plotly_white'
)
# Update y-axes
fig.update_yaxes(title_text="Cumulative Return", row=1, col=1)
fig.update_yaxes(title_text="Returns (%)", row=2, col=1)
fig.update_yaxes(title_text="Range (%)", row=3, col=1)
return fig
except Exception as e:
logger.error(f"Price movement chart creation error: {e}")
fig = go.Figure()
fig.add_annotation(text=f"Error: {str(e)}", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
volatility = df['returns'].std()
avg_return = df['returns'].mean()
return {'volatility': volatility, 'avg_return': avg_return, 'returns': df['returns']}
def _analyze_price_range(self, df: pd.DataFrame) -> Dict[str, Any]:
df['range'] = df['high'] - df['low']
df['range_pct'] = (df['range'] / df['open']) * 100
avg_range_pct = df['range_pct'].mean()
return {'avg_range_pct': avg_range_pct}
def create_data_analysis_panel():
"""Create the main data analysis panel with tabs for different analyses."""
return html.Div([
dcc.Tabs(
id="data-analysis-tabs",
value="volume-analysis",
children=[
dcc.Tab(label="Volume Analysis", value="volume-analysis", children=[
html.Div(id='volume-analysis-content', children=[
html.P("Content for Volume Analysis")
]),
html.Div(id='volume-stats-container', children=[
html.P("Stats container loaded - waiting for callback...")
])
]),
dcc.Tab(label="Price Movement", value="price-movement", children=[
html.Div(id='price-movement-content', children=[
dbc.Alert("Select a symbol and timeframe to view price movement analysis.", color="primary")
])
]),
],
)
], id='data-analysis-panel-wrapper')
def _analyze_directional_movement(self, df: pd.DataFrame) -> Dict[str, Any]:
bullish_periods = len(df[df['close'] > df['open']])
bearish_periods = len(df[df['close'] < df['open']])
neutral_periods = len(df[df['close'] == df['open']])
total_periods = len(df)
bullish_ratio = (bullish_periods / total_periods) * 100 if total_periods > 0 else 0
return {
'bullish_periods': bullish_periods,
'bearish_periods': bearish_periods,
'neutral_periods': neutral_periods,
'bullish_ratio': bullish_ratio,
'positive_returns': len(df[df['returns'] > 0]),
'negative_returns': len(df[df['returns'] < 0])
}
def _calculate_price_extremes(self, df: pd.DataFrame) -> Dict[str, Any]:
period_high = df['high'].max()
period_low = df['low'].min()
return {'period_high': period_high, 'period_low': period_low}
def _calculate_momentum(self, df: pd.DataFrame) -> Dict[str, Any]:
current_price = df['close'].iloc[-1]
momentum_periods = min(10, len(df) - 1)
if momentum_periods > 0:
momentum = ((current_price - df['close'].iloc[-momentum_periods-1]) / df['close'].iloc[-momentum_periods-1]) * 100
else:
momentum = 0
return {'momentum': momentum}
def _calculate_trend_strength(self, df: pd.DataFrame) -> Dict[str, Any]:
if len(df) > 2:
x = np.arange(len(df))
slope, _ = np.polyfit(x, df['close'], 1)
trend_strength = slope / df['close'].mean() * 100
else:
trend_strength = 0
return {'trend_strength': trend_strength}
def _calculate_return_percentiles(self, df: pd.DataFrame) -> Dict[str, Any]:
return {
'return_percentiles': {
'5th': df['returns'].quantile(0.05),
'25th': df['returns'].quantile(0.25),
'75th': df['returns'].quantile(0.75),
'95th': df['returns'].quantile(0.95)
},
'max_gain': df['returns'].max(),
'max_loss': df['returns'].min()
}
def format_number(value: float, decimals: int = 2) -> str:
"""Format number with appropriate decimals and units."""
if pd.isna(value):
"""Formats a number to a string with specified decimals."""
if value is None:
return "N/A"
if abs(value) >= 1e9:
return f"{value/1e9:.{decimals}f}B"
elif abs(value) >= 1e6:
return f"{value/1e6:.{decimals}f}M"
elif abs(value) >= 1e3:
return f"{value/1e3:.{decimals}f}K"
else:
return f"{value:.{decimals}f}"
return f"{value:,.{decimals}f}"
def _create_stat_card(icon, title, value, color="primary") -> dbc.Card: # Extracted helper
return dbc.Card(
dbc.CardBody(
[
html.H4(title, className="card-title"),
html.P(value, className="card-text"),
html.I(className=f"fas fa-{icon} text-{color}"),
]
),
className=f"text-center m-1 bg-light border-{color}"
)
def create_volume_stats_display(stats: Dict[str, Any]) -> html.Div:
"""Create volume statistics display."""
"""Creates a display for volume statistics."""
if 'error' in stats:
return dbc.Alert(
"Error loading volume statistics",
color="danger",
dismissable=True
)
def create_stat_card(icon, title, value, color="primary"):
return dbc.Col(dbc.Card(dbc.CardBody([
html.Div([
html.Div(icon, className="display-6"),
html.Div([
html.P(title, className="card-title mb-1 text-muted"),
html.H4(value, className=f"card-text fw-bold text-{color}")
], className="ms-3")
], className="d-flex align-items-center")
])), width=4, className="mb-3")
return html.Div(f"Error: {stats['error']}", className="alert alert-danger")
return dbc.Row([
create_stat_card("📊", "Total Volume", format_number(stats['total_volume'])),
create_stat_card("📈", "Average Volume", format_number(stats['avg_volume'])),
create_stat_card("🎯", "Volume Trend", stats['volume_trend'],
"success" if stats['volume_trend'] == "Increasing" else "danger"),
create_stat_card("", "High Volume Periods", str(stats['high_volume_periods'])),
create_stat_card("🔗", "Volume-Price Correlation", f"{stats['volume_price_correlation']:.3f}"),
create_stat_card("💱", "Avg Trade Size", format_number(stats['avg_trade_size']))
], className="mt-3")
return html.Div(
[
html.H3("Volume Statistics", className="mb-3 text-primary"),
dbc.Row([
dbc.Col(_create_stat_card("chart-bar", "Total Volume", format_number(stats.get('total_volume')), "success"), md=6),
dbc.Col(_create_stat_card("calculator", "Avg. Volume", format_number(stats.get('avg_volume')), "info"), md=6),
]),
dbc.Row([
dbc.Col(_create_stat_card("arrow-trend-up", "Volume Trend", stats.get('volume_trend'), "warning"), md=6),
dbc.Col(_create_stat_card("hand-holding-usd", "Avg. Trade Size", format_number(stats.get('avg_trade_size')), "secondary"), md=6),
]),
dbc.Row([
dbc.Col(_create_stat_card("ranking-star", "High Vol. Periods", stats.get('high_volume_periods')), md=6),
dbc.Col(_create_stat_card("arrows-left-right", "Vol-Price Corr.", format_number(stats.get('volume_price_correlation'), 4), "primary"), md=6),
]),
]
)
def create_price_stats_display(stats: Dict[str, Any]) -> html.Div:
"""Create price movement statistics display."""
"""Creates a display for price movement statistics."""
if 'error' in stats:
return dbc.Alert(
"Error loading price statistics",
color="danger",
dismissable=True
)
return html.Div(f"Error: {stats['error']}", className="alert alert-danger")
def create_stat_card(icon, title, value, color="primary"):
text_color = "text-dark"
if color == "success":
text_color = "text-success"
elif color == "danger":
text_color = "text-danger"
return dbc.Col(dbc.Card(dbc.CardBody([
html.Div([
html.Div(icon, className="display-6"),
html.Div([
html.P(title, className="card-title mb-1 text-muted"),
html.H4(value, className=f"card-text fw-bold {text_color}")
], className="ms-3")
], className="d-flex align-items-center")
])), width=4, className="mb-3")
return dbc.Row([
create_stat_card("💰", "Current Price", f"${stats['current_price']:.2f}"),
create_stat_card("📈", "Period Return", f"{stats['period_return']:+.2f}%",
"success" if stats['period_return'] >= 0 else "danger"),
create_stat_card("📊", "Volatility", f"{stats['volatility']:.2f}%", color="warning"),
create_stat_card("🎯", "Bullish Ratio", f"{stats['bullish_ratio']:.1f}%"),
create_stat_card("", "Momentum", f"{stats['momentum']:+.2f}%",
"success" if stats['momentum'] >= 0 else "danger"),
create_stat_card("📉", "Max Loss", f"{stats['max_loss']:.2f}%", "danger")
], className="mt-3")
return html.Div(
[
html.H3("Price Movement Statistics", className="mb-3 text-success"),
dbc.Row([
dbc.Col(_create_stat_card("dollar-sign", "Current Price", format_number(stats.get('current_price')), "success"), md=6),
dbc.Col(_create_stat_card("percent", "Period Return", f"{format_number(stats.get('period_return'))}%"), md=6),
]),
dbc.Row([
dbc.Col(_create_stat_card("wave-square", "Volatility", f"{format_number(stats.get('volatility'))}%"), md=6),
dbc.Col(_create_stat_card("chart-line", "Avg. Daily Return", f"{format_number(stats.get('avg_return'))}%"), md=6),
]),
dbc.Row([
dbc.Col(_create_stat_card("arrows-up-down-left-right", "Avg. Range %", f"{format_number(stats.get('avg_range_pct'))}%"), md=6),
dbc.Col(_create_stat_card("arrow-up", "Bullish Ratio", f"{format_number(stats.get('bullish_ratio'))}%"), md=6),
]),
]
)
def get_market_statistics(df: pd.DataFrame, symbol: str, timeframe: str) -> html.Div:
"""
Generate a comprehensive market statistics component from a DataFrame.
Generates a display of key market statistics from the provided DataFrame.
"""
if df.empty:
return html.Div("No data available for statistics.", className="text-center text-muted")
return html.Div([html.P("No market data available for statistics.")], className="alert alert-info mt-4")
try:
# Get statistics
price_analyzer = PriceMovementAnalyzer()
volume_analyzer = VolumeAnalyzer()
price_stats = price_analyzer.get_price_movement_statistics(df)
volume_stats = volume_analyzer.get_volume_statistics(df)
# Format key statistics for display
start_date = df.index.min().strftime('%Y-%m-%d %H:%M')
end_date = df.index.max().strftime('%Y-%m-%d %H:%M')
# Check for errors from analyzers
if 'error' in price_stats or 'error' in volume_stats:
error_msg = price_stats.get('error') or volume_stats.get('error')
return html.Div(f"Error generating statistics: {error_msg}", style={'color': 'red'})
# Time range for display
days_back = (df.index.max() - df.index.min()).days
time_status = f"📅 Analysis Range: {start_date} to {end_date} (~{days_back} days)"
return html.Div([
html.H3("📊 Enhanced Market Statistics", className="mb-3"),
html.P(
time_status,
className="lead text-center text-muted mb-4"
# Basic Market Overview
first_timestamp = df.index.min()
last_timestamp = df.index.max()
num_candles = len(df)
# Price Changes
first_close = df['close'].iloc[0]
last_close = df['close'].iloc[-1]
price_change_abs = last_close - first_close
price_change_pct = (price_change_abs / first_close) * 100 if first_close != 0 else 0
# Highs and Lows
period_high = df['high'].max()
period_low = df['low'].min()
# Average True Range (ATR) - A measure of volatility
# Requires TA-Lib or manual calculation. For simplicity, we'll use a basic range for now.
# Ideally, integrate a proper TA library.
df['tr'] = np.maximum(df['high'] - df['low'],
np.maximum(abs(df['high'] - df['close'].shift()),
abs(df['low'] - df['close'].shift())))
atr = df['tr'].mean() if not df['tr'].empty else 0
# Trading Volume Analysis
total_volume = df['volume'].sum()
average_volume = df['volume'].mean()
# Market Cap (placeholder - requires external data)
market_cap_info = "N/A (requires external API)"
# Order Book Depth (placeholder - requires real-time order book data)
order_book_depth = "N/A (requires real-time data)"
stats_content = html.Div([
html.H3(f"Market Statistics for {symbol} ({timeframe})", className="mb-3 text-info"),
_create_basic_market_overview(
first_timestamp, last_timestamp, num_candles,
first_close, last_close, price_change_abs, price_change_pct,
total_volume, average_volume, atr
),
html.Hr(className="my-4"),
_create_advanced_market_stats(
period_high, period_low, market_cap_info, order_book_depth
)
], className="mb-4")
return stats_content
def _create_basic_market_overview(
first_timestamp: datetime, last_timestamp: datetime, num_candles: int,
first_close: float, last_close: float, price_change_abs: float, price_change_pct: float,
total_volume: float, average_volume: float, atr: float
) -> dbc.Row:
return dbc.Row([
dbc.Col(
dbc.Card(
dbc.CardBody(
[
html.H4("Time Period", className="card-title"),
html.P(f"From: {first_timestamp.strftime('%Y-%m-%d %H:%M')}"),
html.P(f"To: {last_timestamp.strftime('%Y-%m-%d %H:%M')}"),
html.P(f"Candles: {num_candles}"),
]
),
className="text-center m-1 bg-light border-info"
),
create_price_stats_display(price_stats),
create_volume_stats_display(volume_stats)
])
except Exception as e:
logger.error(f"Error in get_market_statistics: {e}", exc_info=True)
return dbc.Alert(f"Error generating statistics display: {e}", color="danger")
md=4
),
dbc.Col(
dbc.Card(
dbc.CardBody(
[
html.H4("Price Movement", className="card-title"),
html.P(f"Initial Price: {format_number(first_close)}"),
html.P(f"Final Price: {format_number(last_close)}"),
html.P(f"Change: {format_number(price_change_abs)} ({format_number(price_change_pct)}%)",
style={'color': 'green' if price_change_pct >= 0 else 'red'}),
]
),
className="text-center m-1 bg-light border-info"
),
md=4
),
dbc.Col(
dbc.Card(
dbc.CardBody(
[
html.H4("Volume & Volatility", className="card-title"),
html.P(f"Total Volume: {format_number(total_volume)}"),
html.P(f"Average Volume: {format_number(average_volume)}"),
html.P(f"Average True Range: {format_number(atr, 4)}"),
]
),
className="text-center m-1 bg-light border-info"
),
md=4
),
])
def _create_advanced_market_stats(
period_high: float, period_low: float, market_cap_info: str, order_book_depth: str
) -> dbc.Row:
return dbc.Row([
dbc.Col(
dbc.Card(
dbc.CardBody(
[
html.H4("Period Extremes", className="card-title"),
html.P(f"Period High: {format_number(period_high)}"),
html.P(f"Period Low: {format_number(period_low)}"),
]
),
className="text-center m-1 bg-light border-warning"
),
md=4
),
dbc.Col(
dbc.Card(
dbc.CardBody(
[
html.H4("Liquidity/Depth", className="card-title"),
html.P(f"Market Cap: {market_cap_info}"),
html.P(f"Order Book Depth: {order_book_depth}"),
]
),
className="text-center m-1 bg-light border-warning"
),
md=4
),
dbc.Col(
dbc.Card(
dbc.CardBody(
[
html.H4("Custom Indicators", className="card-title"),
html.P("RSI: N/A"), # Placeholder
html.P("MACD: N/A"), # Placeholder
]
),
className="text-center m-1 bg-light border-warning"
),
md=4
)
])

View File

@@ -4,6 +4,7 @@ Indicator modal component for creating and editing indicators.
from dash import html, dcc
import dash_bootstrap_components as dbc
from utils.timeframe_utils import load_timeframe_options
def create_indicator_modal():
@@ -37,19 +38,7 @@ def create_indicator_modal():
dbc.Col(dbc.Label("Timeframe (Optional):"), width=12),
dbc.Col(dcc.Dropdown(
id='indicator-timeframe-dropdown',
options=[
{'label': 'Chart Timeframe', 'value': ''},
{'label': "1 Second", 'value': '1s'},
{'label': "5 Seconds", 'value': '5s'},
{'label': "15 Seconds", 'value': '15s'},
{'label': "30 Seconds", 'value': '30s'},
{'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'},
],
options=[{'label': 'Chart Timeframe', 'value': ''}] + load_timeframe_options(),
value='',
placeholder='Defaults to chart timeframe'
), width=12),

View File

@@ -13,81 +13,68 @@ from dashboard.components.chart_controls import (
create_time_range_controls,
create_export_controls
)
from utils.timeframe_utils import load_timeframe_options
logger = get_logger("default_logger")
def get_market_data_layout():
"""Create the market data visualization layout with indicator controls."""
# Get available symbols and timeframes from database
symbols = get_supported_symbols()
timeframes = get_supported_timeframes()
# Create dropdown options
def _create_dropdown_options(symbols, timeframes):
"""Creates symbol and timeframe dropdown options."""
symbol_options = [{'label': symbol, 'value': symbol} for symbol in symbols]
timeframe_options = [
{'label': "1 Second", 'value': '1s'},
{'label': "5 Seconds", 'value': '5s'},
{'label': "15 Seconds", 'value': '15s'},
{'label': "30 Seconds", 'value': '30s'},
{'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'},
]
all_timeframe_options = load_timeframe_options()
# Filter timeframe options to only show those available in database
available_timeframes = [tf for tf in ['1s', '5s', '15s', '30s', '1m', '5m', '15m', '1h', '4h', '1d'] if tf in timeframes]
if not available_timeframes:
available_timeframes = ['5m'] # Default fallback
available_timeframes_from_db = [tf for tf in [opt['value'] for opt in all_timeframe_options] if tf in timeframes]
if not available_timeframes_from_db:
available_timeframes_from_db = ['5m'] # Default fallback
timeframe_options = [opt for opt in timeframe_options if opt['value'] in available_timeframes]
timeframe_options = [opt for opt in all_timeframe_options if opt['value'] in available_timeframes_from_db]
# Get available strategies and indicators
return symbol_options, timeframe_options
def _load_strategy_and_indicator_options():
"""Loads strategy and indicator options for chart configuration."""
try:
strategy_names = get_available_strategy_names()
strategy_options = [{'label': name.replace('_', ' ').title(), 'value': name} for name in strategy_names]
# Get user indicators from the new indicator manager
indicator_manager = get_indicator_manager()
# Ensure default indicators exist
ensure_default_indicators()
# Get indicators by display type
overlay_indicators = indicator_manager.get_indicators_by_type('overlay')
subplot_indicators = indicator_manager.get_indicators_by_type('subplot')
# Create checkbox options for overlay indicators
overlay_options = []
for indicator in overlay_indicators:
display_name = f"{indicator.name} ({indicator.type.upper()})"
overlay_options.append({'label': display_name, 'value': indicator.id})
# Create checkbox options for subplot indicators
subplot_options = []
for indicator in subplot_indicators:
display_name = f"{indicator.name} ({indicator.type.upper()})"
subplot_options.append({'label': display_name, 'value': indicator.id})
return strategy_options, overlay_options, subplot_options
except Exception as e:
logger.warning(f"Market data layout: Error loading indicator options: {e}")
strategy_options = [{'label': 'Basic Chart', 'value': 'basic'}]
overlay_options = []
subplot_options = []
return [{'label': 'Basic Chart', 'value': 'basic'}], [], []
def get_market_data_layout():
"""Create the market data visualization layout with indicator controls."""
symbols = get_supported_symbols()
timeframes = get_supported_timeframes()
# Create components using the new modular functions
symbol_options, timeframe_options = _create_dropdown_options(symbols, timeframes)
strategy_options, overlay_options, subplot_options = _load_strategy_and_indicator_options()
chart_config_panel = create_chart_config_panel(strategy_options, overlay_options, subplot_options)
time_range_controls = create_time_range_controls()
export_controls = create_export_controls()
return html.Div([
# Title and basic controls
html.H3("💹 Market Data Visualization", style={'color': '#2c3e50', 'margin-bottom': '20px'}),
# Main chart controls
html.Div([
html.Div([
html.Label("Symbol:", style={'font-weight': 'bold'}),
@@ -111,21 +98,15 @@ def get_market_data_layout():
], style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
], style={'margin-bottom': '20px'}),
# Chart Configuration Panel
chart_config_panel,
# Time Range Controls (positioned under indicators, next to chart)
time_range_controls,
# Export Controls
export_controls,
# Chart
dcc.Graph(id='price-chart'),
# Hidden store for chart data
dcc.Store(id='chart-data-store'),
# Enhanced Market statistics with integrated data analysis
html.Div(id='market-stats', style={'margin-top': '20px'})
])

View File

@@ -5,127 +5,146 @@ System health monitoring layout for the dashboard.
from dash import html
import dash_bootstrap_components as dbc
def create_quick_status_card(title, component_id, icon):
"""Helper to create a quick status card."""
return dbc.Card(dbc.CardBody([
html.H5(f"{icon} {title}", className="card-title"),
html.Div(id=component_id, children=[
dbc.Badge("Checking...", color="warning", className="me-1")
])
]), className="text-center")
def _create_header_section():
"""Creates the header section for the system health layout."""
return html.Div([
html.H2("⚙️ System Health & Data Monitoring"),
html.P("Real-time monitoring of data collection services, database health, and system performance",
className="lead")
], className="p-5 mb-4 bg-light rounded-3")
def _create_quick_status_row():
"""Creates the quick status overview row."""
return dbc.Row([
dbc.Col(create_quick_status_card("Data Collection", "data-collection-quick-status", "📊"), width=3),
dbc.Col(create_quick_status_card("Database", "database-quick-status", "🗄️"), width=3),
dbc.Col(create_quick_status_card("Redis", "redis-quick-status", "🔗"), width=3),
dbc.Col(create_quick_status_card("Performance", "performance-quick-status", "📈"), width=3),
], className="mb-4")
def _create_data_collection_service_card():
"""Creates the data collection service status card."""
return dbc.Card([
dbc.CardHeader(html.H4("📡 Data Collection Service")),
dbc.CardBody([
html.H5("Service Status", className="card-title"),
html.Div(id='data-collection-service-status', className="mb-4"),
html.H5("Collection Metrics", className="card-title"),
html.Div(id='data-collection-metrics', className="mb-4"),
html.H5("Service Controls", className="card-title"),
dbc.ButtonGroup([
dbc.Button("🔄 Refresh Status", id="refresh-data-status-btn", color="primary", outline=True, size="sm"),
dbc.Button("📊 View Details", id="view-collection-details-btn", color="secondary", outline=True, size="sm"),
dbc.Button("📋 View Logs", id="view-collection-logs-btn", color="info", outline=True, size="sm")
])
])
], className="mb-4")
def _create_individual_collectors_card():
"""Creates the individual collectors health card."""
return dbc.Card([
dbc.CardHeader(html.H4("🔌 Individual Collectors")),
dbc.CardBody([
html.Div(id='individual-collectors-status'),
html.Div([
dbc.Alert(
"Collector health data will be displayed here when the data collection service is running.",
id="collectors-info-alert",
color="info",
is_open=True,
)
], id='collectors-placeholder')
])
], className="mb-4")
def _create_database_status_card():
"""Creates the database health status card."""
return dbc.Card([
dbc.CardHeader(html.H4("🗄️ Database Health")),
dbc.CardBody([
html.H5("Connection Status", className="card-title"),
html.Div(id='database-status', className="mb-3"),
html.Hr(),
html.H5("Database Statistics", className="card-title"),
html.Div(id='database-stats')
])
], className="mb-4")
def _create_redis_status_card():
"""Creates the Redis health status card."""
return dbc.Card([
dbc.CardHeader(html.H4("🔗 Redis Status")),
dbc.CardBody([
html.H5("Connection Status", className="card-title"),
html.Div(id='redis-status', className="mb-3"),
html.Hr(),
html.H5("Redis Statistics", className="card-title"),
html.Div(id='redis-stats')
])
], className="mb-4")
def _create_system_performance_card():
"""Creates the system performance metrics card."""
return dbc.Card([
dbc.CardHeader(html.H4("📈 System Performance")),
dbc.CardBody([
html.Div(id='system-performance-metrics')
])
], className="mb-4")
def _create_collection_details_modal():
"""Creates the data collection details modal."""
return dbc.Modal([
dbc.ModalHeader(dbc.ModalTitle("📊 Data Collection Details")),
dbc.ModalBody(id="collection-details-content")
], id="collection-details-modal", is_open=False, size="lg")
def _create_collection_logs_modal():
"""Creates the collection service logs modal."""
return dbc.Modal([
dbc.ModalHeader(dbc.ModalTitle("📋 Collection Service Logs")),
dbc.ModalBody(
html.Div(
html.Pre(id="collection-logs-content", style={'max-height': '400px', 'overflow-y': 'auto'}),
style={'white-space': 'pre-wrap', 'background-color': '#f8f9fa', 'padding': '15px', 'border-radius': '5px'}
)
),
dbc.ModalFooter([
dbc.Button("Refresh", id="refresh-logs-btn", color="primary"),
dbc.Button("Close", id="close-logs-modal", color="secondary", className="ms-auto")
])
], id="collection-logs-modal", is_open=False, size="xl")
def get_system_health_layout():
"""Create the enhanced system health monitoring layout with Bootstrap components."""
def create_quick_status_card(title, component_id, icon):
return dbc.Card(dbc.CardBody([
html.H5(f"{icon} {title}", className="card-title"),
html.Div(id=component_id, children=[
dbc.Badge("Checking...", color="warning", className="me-1")
])
]), className="text-center")
return html.Div([
# Header section
html.Div([
html.H2("⚙️ System Health & Data Monitoring"),
html.P("Real-time monitoring of data collection services, database health, and system performance",
className="lead")
], className="p-5 mb-4 bg-light rounded-3"),
_create_header_section(),
_create_quick_status_row(),
# Quick Status Overview Row
dbc.Row([
dbc.Col(create_quick_status_card("Data Collection", "data-collection-quick-status", "📊"), width=3),
dbc.Col(create_quick_status_card("Database", "database-quick-status", "🗄️"), width=3),
dbc.Col(create_quick_status_card("Redis", "redis-quick-status", "🔗"), width=3),
dbc.Col(create_quick_status_card("Performance", "performance-quick-status", "📈"), width=3),
], className="mb-4"),
# Detailed Monitoring Sections
dbc.Row([
# Left Column - Data Collection Service
dbc.Col([
# Data Collection Service Status
dbc.Card([
dbc.CardHeader(html.H4("📡 Data Collection Service")),
dbc.CardBody([
html.H5("Service Status", className="card-title"),
html.Div(id='data-collection-service-status', className="mb-4"),
html.H5("Collection Metrics", className="card-title"),
html.Div(id='data-collection-metrics', className="mb-4"),
html.H5("Service Controls", className="card-title"),
dbc.ButtonGroup([
dbc.Button("🔄 Refresh Status", id="refresh-data-status-btn", color="primary", outline=True, size="sm"),
dbc.Button("📊 View Details", id="view-collection-details-btn", color="secondary", outline=True, size="sm"),
dbc.Button("📋 View Logs", id="view-collection-logs-btn", color="info", outline=True, size="sm")
])
])
], className="mb-4"),
# Data Collector Health
dbc.Card([
dbc.CardHeader(html.H4("🔌 Individual Collectors")),
dbc.CardBody([
html.Div(id='individual-collectors-status'),
html.Div([
dbc.Alert(
"Collector health data will be displayed here when the data collection service is running.",
id="collectors-info-alert",
color="info",
is_open=True,
)
], id='collectors-placeholder')
])
], className="mb-4"),
_create_data_collection_service_card(),
_create_individual_collectors_card(),
], width=6),
# Right Column - System Health
dbc.Col([
# Database Status
dbc.Card([
dbc.CardHeader(html.H4("🗄️ Database Health")),
dbc.CardBody([
html.H5("Connection Status", className="card-title"),
html.Div(id='database-status', className="mb-3"),
html.Hr(),
html.H5("Database Statistics", className="card-title"),
html.Div(id='database-stats')
])
], className="mb-4"),
# Redis Status
dbc.Card([
dbc.CardHeader(html.H4("🔗 Redis Status")),
dbc.CardBody([
html.H5("Connection Status", className="card-title"),
html.Div(id='redis-status', className="mb-3"),
html.Hr(),
html.H5("Redis Statistics", className="card-title"),
html.Div(id='redis-stats')
])
], className="mb-4"),
# System Performance
dbc.Card([
dbc.CardHeader(html.H4("📈 System Performance")),
dbc.CardBody([
html.Div(id='system-performance-metrics')
])
], className="mb-4"),
_create_database_status_card(),
_create_redis_status_card(),
_create_system_performance_card(),
], width=6)
]),
# Data Collection Details Modal
dbc.Modal([
dbc.ModalHeader(dbc.ModalTitle("📊 Data Collection Details")),
dbc.ModalBody(id="collection-details-content")
], id="collection-details-modal", is_open=False, size="lg"),
# Collection Logs Modal
dbc.Modal([
dbc.ModalHeader(dbc.ModalTitle("📋 Collection Service Logs")),
dbc.ModalBody(
html.Div(
html.Pre(id="collection-logs-content", style={'max-height': '400px', 'overflow-y': 'auto'}),
style={'white-space': 'pre-wrap', 'background-color': '#f8f9fa', 'padding': '15px', 'border-radius': '5px'}
)
),
dbc.ModalFooter([
dbc.Button("Refresh", id="refresh-logs-btn", color="primary"),
dbc.Button("Close", id="close-logs-modal", color="secondary", className="ms-auto")
])
], id="collection-logs-modal", is_open=False, size="xl")
_create_collection_details_modal(),
_create_collection_logs_modal()
])