Vasily.onl 132710a9a7 3.6 Enhance market statistics with comprehensive data analysis features
- Updated `register_chart_callbacks` to include enhanced market statistics.
- Implemented new data analysis callbacks in `dashboard/callbacks/data_analysis.py` for volume and price movement analysis.
- Created `VolumeAnalyzer` and `PriceMovementAnalyzer` classes for detailed statistical calculations.
- Integrated data analysis components into the market statistics layout, providing users with insights on volume trends and price movements.
- Improved error handling and logging for data analysis operations.
- Updated documentation to reflect the new features and usage guidelines.
2025-06-05 11:24:21 +08:00

236 lines
11 KiB
Python

"""
Chart-related callbacks for the dashboard.
"""
from dash import Output, Input
from datetime import datetime
from utils.logger import get_logger
from components.charts import (
create_strategy_chart,
create_chart_with_indicators,
create_error_chart,
get_market_statistics
)
from components.charts.config import get_all_example_strategies
from database.connection import DatabaseManager
from dash import html
logger = get_logger("default_logger")
def register_chart_callbacks(app):
"""Register chart-related callbacks."""
@app.callback(
Output('price-chart', 'figure'),
[Input('symbol-dropdown', 'value'),
Input('timeframe-dropdown', 'value'),
Input('overlay-indicators-checklist', 'value'),
Input('subplot-indicators-checklist', 'value'),
Input('strategy-dropdown', 'value'),
Input('interval-component', 'n_intervals')]
)
def update_price_chart(symbol, timeframe, overlay_indicators, subplot_indicators, selected_strategy, n_intervals):
"""Update the price chart with latest market data and selected indicators."""
try:
# If a strategy is selected, use strategy chart
if selected_strategy and selected_strategy != 'basic':
fig = create_strategy_chart(symbol, timeframe, selected_strategy)
logger.debug(f"Chart callback: Created strategy chart for {symbol} ({timeframe}) with strategy: {selected_strategy}")
else:
# Create chart with dynamically selected indicators
fig = create_chart_with_indicators(
symbol=symbol,
timeframe=timeframe,
overlay_indicators=overlay_indicators or [],
subplot_indicators=subplot_indicators or [],
days_back=7
)
indicator_count = len(overlay_indicators or []) + len(subplot_indicators or [])
logger.debug(f"Chart callback: Created dynamic chart for {symbol} ({timeframe}) with {indicator_count} indicators")
return fig
except Exception as e:
logger.error(f"Error updating price chart: {e}")
return create_error_chart(f"Error loading chart: {str(e)}")
# Strategy selection callback - automatically load strategy indicators
@app.callback(
[Output('overlay-indicators-checklist', 'value'),
Output('subplot-indicators-checklist', 'value')],
[Input('strategy-dropdown', 'value')]
)
def update_indicators_from_strategy(selected_strategy):
"""Update indicator selections when a strategy is chosen."""
if not selected_strategy or selected_strategy == 'basic':
return [], []
try:
# Get strategy configuration
all_strategies = get_all_example_strategies()
if selected_strategy in all_strategies:
strategy_example = all_strategies[selected_strategy]
config = strategy_example.config
# Extract overlay and subplot indicators from strategy
overlay_indicators = config.overlay_indicators or []
# Extract subplot indicators from subplot configs
subplot_indicators = []
for subplot_config in config.subplot_configs or []:
subplot_indicators.extend(subplot_config.indicators or [])
logger.debug(f"Chart callback: Loaded strategy {selected_strategy}: {len(overlay_indicators)} overlays, {len(subplot_indicators)} subplots")
return overlay_indicators, subplot_indicators
else:
logger.warning(f"Chart callback: Strategy {selected_strategy} not found")
return [], []
except Exception as e:
logger.error(f"Chart callback: Error loading strategy indicators: {e}")
return [], []
# Enhanced market statistics callback with comprehensive analysis
@app.callback(
Output('market-stats', 'children'),
[Input('symbol-dropdown', 'value'),
Input('timeframe-dropdown', 'value'),
Input('interval-component', 'n_intervals')]
)
def update_market_stats(symbol, timeframe, n_intervals):
"""Update comprehensive market statistics with analysis."""
try:
# Import analysis classes
from dashboard.components.data_analysis import VolumeAnalyzer, PriceMovementAnalyzer
# Get basic market statistics
basic_stats = get_market_statistics(symbol, timeframe)
# Create analyzers for comprehensive analysis
volume_analyzer = VolumeAnalyzer()
price_analyzer = PriceMovementAnalyzer()
# Get analysis for 7 days
volume_analysis = volume_analyzer.get_volume_statistics(symbol, timeframe, 7)
price_analysis = price_analyzer.get_price_movement_statistics(symbol, timeframe, 7)
# Create enhanced statistics layout
return html.Div([
html.H3("📊 Enhanced Market Statistics"),
# Basic Market Data
html.Div([
html.H4("💹 Current Market Data", style={'color': '#2c3e50', 'margin-bottom': '10px'}),
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',
'font-weight': 'bold'
})
], style={'margin': '5px 0'}) for key, value in basic_stats.items()
])
], style={'border': '1px solid #bdc3c7', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#f8f9fa'}),
# Volume Analysis Section
create_volume_analysis_section(volume_analysis),
# Price Movement Analysis Section
create_price_movement_section(price_analysis),
# Additional Market Insights
html.Div([
html.H4("🔍 Market Insights", style={'color': '#2c3e50', 'margin-bottom': '10px'}),
html.Div([
html.P(f"📈 Analysis Period: 7 days | Timeframe: {timeframe}", style={'margin': '5px 0'}),
html.P(f"🎯 Symbol: {symbol}", style={'margin': '5px 0'}),
html.P("💡 Statistics update automatically with chart changes", style={'margin': '5px 0', 'font-style': 'italic'})
])
], style={'border': '1px solid #3498db', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#ebf3fd'})
])
except Exception as e:
logger.error(f"Chart callback: Error updating enhanced market stats: {e}")
return html.Div([
html.H3("Market Statistics"),
html.P(f"Error loading statistics: {str(e)}", style={'color': '#e74c3c'})
])
def create_volume_analysis_section(volume_stats):
"""Create volume analysis section for market statistics."""
if not volume_stats or volume_stats.get('total_volume', 0) == 0:
return html.Div([
html.H4("📊 Volume Analysis", style={'color': '#2c3e50', 'margin-bottom': '10px'}),
html.P("No volume data available for analysis", style={'color': '#e74c3c'})
], style={'border': '1px solid #e74c3c', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#fdeded'})
return html.Div([
html.H4("📊 Volume Analysis (7 days)", style={'color': '#2c3e50', 'margin-bottom': '10px'}),
html.Div([
html.Div([
html.Strong("Total Volume: "),
html.Span(f"{volume_stats.get('total_volume', 0):,.2f}", style={'color': '#27ae60'})
], style={'margin': '5px 0'}),
html.Div([
html.Strong("Average Volume: "),
html.Span(f"{volume_stats.get('average_volume', 0):,.2f}", style={'color': '#2c3e50'})
], style={'margin': '5px 0'}),
html.Div([
html.Strong("Volume Trend: "),
html.Span(
volume_stats.get('volume_trend', 'Neutral'),
style={'color': '#27ae60' if volume_stats.get('volume_trend') == 'Increasing' else '#e74c3c' if volume_stats.get('volume_trend') == 'Decreasing' else '#f39c12'}
)
], style={'margin': '5px 0'}),
html.Div([
html.Strong("High Volume Periods: "),
html.Span(f"{volume_stats.get('high_volume_periods', 0)}", style={'color': '#2c3e50'})
], style={'margin': '5px 0'})
])
], style={'border': '1px solid #27ae60', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#eafaf1'})
def create_price_movement_section(price_stats):
"""Create price movement analysis section for market statistics."""
if not price_stats or price_stats.get('total_returns') is None:
return html.Div([
html.H4("📈 Price Movement Analysis", style={'color': '#2c3e50', 'margin-bottom': '10px'}),
html.P("No price movement data available for analysis", style={'color': '#e74c3c'})
], style={'border': '1px solid #e74c3c', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#fdeded'})
return html.Div([
html.H4("📈 Price Movement Analysis (7 days)", style={'color': '#2c3e50', 'margin-bottom': '10px'}),
html.Div([
html.Div([
html.Strong("Total Return: "),
html.Span(
f"{price_stats.get('total_returns', 0):+.2f}%",
style={'color': '#27ae60' if price_stats.get('total_returns', 0) >= 0 else '#e74c3c'}
)
], style={'margin': '5px 0'}),
html.Div([
html.Strong("Volatility: "),
html.Span(f"{price_stats.get('volatility', 0):.2f}%", style={'color': '#2c3e50'})
], style={'margin': '5px 0'}),
html.Div([
html.Strong("Bullish Periods: "),
html.Span(f"{price_stats.get('bullish_periods', 0)}", style={'color': '#27ae60'})
], style={'margin': '5px 0'}),
html.Div([
html.Strong("Bearish Periods: "),
html.Span(f"{price_stats.get('bearish_periods', 0)}", style={'color': '#e74c3c'})
], style={'margin': '5px 0'}),
html.Div([
html.Strong("Trend Strength: "),
html.Span(
price_stats.get('trend_strength', 'Neutral'),
style={'color': '#27ae60' if 'Strong' in str(price_stats.get('trend_strength', '')) else '#f39c12'}
)
], style={'margin': '5px 0'})
])
], style={'border': '1px solid #3498db', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#ebf3fd'})
logger.info("Chart callback: Chart callbacks registered successfully")