- 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.
236 lines
11 KiB
Python
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") |