TCPDashboard/components/charts/config/indicator_defs.py
Vasily.onl c4ec3fac9f 3.4 Enhance logging and modular chart system for Crypto Trading Bot Dashboard
- Suppressed SQLAlchemy logging in `app.py` and `main.py` to reduce console verbosity.
- Introduced a new modular chart system in `components/charts/` with a `ChartBuilder` class for flexible chart creation.
- Added utility functions for data processing and validation in `components/charts/utils.py`.
- Implemented indicator definitions and configurations in `components/charts/config/indicator_defs.py`.
- Created a comprehensive documentation structure for the new chart system, ensuring clarity and maintainability.
- Added unit tests for the `ChartBuilder` class to verify functionality and robustness.
- Updated existing components to integrate with the new chart system, enhancing overall architecture and user experience.
2025-06-03 12:49:46 +08:00

266 lines
8.8 KiB
Python

"""
Indicator Definitions and Configuration
This module defines indicator configurations and provides integration
with the existing data/common/indicators.py technical indicators module.
"""
from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass
from datetime import datetime, timezone
from decimal import Decimal
from data.common.indicators import TechnicalIndicators, IndicatorResult, create_default_indicators_config, validate_indicator_config
from data.common.data_types import OHLCVCandle
from utils.logger import get_logger
# Initialize logger
logger = get_logger("indicator_defs")
@dataclass
class ChartIndicatorConfig:
"""
Configuration for chart indicators with display properties.
Extends the base indicator config with chart-specific properties
like colors, line styles, and subplot placement.
"""
name: str
indicator_type: str
parameters: Dict[str, Any]
display_type: str # 'overlay', 'subplot'
color: str
line_style: str = 'solid' # 'solid', 'dash', 'dot'
line_width: int = 2
opacity: float = 1.0
visible: bool = True
subplot_height_ratio: float = 0.3 # For subplot indicators
def to_indicator_config(self) -> Dict[str, Any]:
"""Convert to format expected by TechnicalIndicators."""
config = {'type': self.indicator_type}
config.update(self.parameters)
return config
# Built-in indicator definitions with chart display properties
INDICATOR_DEFINITIONS = {
'sma_20': ChartIndicatorConfig(
name='SMA (20)',
indicator_type='sma',
parameters={'period': 20, 'price_column': 'close'},
display_type='overlay',
color='#007bff',
line_width=2
),
'sma_50': ChartIndicatorConfig(
name='SMA (50)',
indicator_type='sma',
parameters={'period': 50, 'price_column': 'close'},
display_type='overlay',
color='#28a745',
line_width=2
),
'ema_12': ChartIndicatorConfig(
name='EMA (12)',
indicator_type='ema',
parameters={'period': 12, 'price_column': 'close'},
display_type='overlay',
color='#ff6b35',
line_width=2
),
'ema_26': ChartIndicatorConfig(
name='EMA (26)',
indicator_type='ema',
parameters={'period': 26, 'price_column': 'close'},
display_type='overlay',
color='#dc3545',
line_width=2
),
'rsi_14': ChartIndicatorConfig(
name='RSI (14)',
indicator_type='rsi',
parameters={'period': 14, 'price_column': 'close'},
display_type='subplot',
color='#20c997',
line_width=2,
subplot_height_ratio=0.25
),
'macd_default': ChartIndicatorConfig(
name='MACD',
indicator_type='macd',
parameters={'fast_period': 12, 'slow_period': 26, 'signal_period': 9, 'price_column': 'close'},
display_type='subplot',
color='#fd7e14',
line_width=2,
subplot_height_ratio=0.3
),
'bollinger_bands': ChartIndicatorConfig(
name='Bollinger Bands',
indicator_type='bollinger_bands',
parameters={'period': 20, 'std_dev': 2.0, 'price_column': 'close'},
display_type='overlay',
color='#6f42c1',
line_width=1,
opacity=0.7
)
}
def convert_database_candles_to_ohlcv(candles: List[Dict[str, Any]]) -> List[OHLCVCandle]:
"""
Convert database candle dictionaries to OHLCVCandle objects.
Args:
candles: List of candle dictionaries from database operations
Returns:
List of OHLCVCandle objects for technical indicators
"""
ohlcv_candles = []
for candle in candles:
try:
# Handle timestamp conversion
timestamp = candle['timestamp']
if isinstance(timestamp, str):
timestamp = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
elif timestamp.tzinfo is None:
timestamp = timestamp.replace(tzinfo=timezone.utc)
# For database candles, start_time and end_time are the same
# as we store right-aligned timestamps
ohlcv_candle = OHLCVCandle(
symbol=candle['symbol'],
timeframe=candle['timeframe'],
start_time=timestamp,
end_time=timestamp,
open=Decimal(str(candle['open'])),
high=Decimal(str(candle['high'])),
low=Decimal(str(candle['low'])),
close=Decimal(str(candle['close'])),
volume=Decimal(str(candle.get('volume', 0))),
trade_count=candle.get('trades_count', 0),
exchange=candle.get('exchange', 'okx'),
is_complete=True
)
ohlcv_candles.append(ohlcv_candle)
except Exception as e:
logger.error(f"Error converting candle to OHLCV: {e}")
continue
logger.debug(f"Converted {len(ohlcv_candles)} database candles to OHLCV format")
return ohlcv_candles
def calculate_indicators(candles: List[Dict[str, Any]],
indicator_configs: List[str],
custom_configs: Optional[Dict[str, ChartIndicatorConfig]] = None) -> Dict[str, List[IndicatorResult]]:
"""
Calculate technical indicators for chart display.
Args:
candles: List of candle dictionaries from database
indicator_configs: List of indicator names to calculate
custom_configs: Optional custom indicator configurations
Returns:
Dictionary mapping indicator names to their calculation results
"""
if not candles:
logger.warning("No candles provided for indicator calculation")
return {}
# Convert to OHLCV format
ohlcv_candles = convert_database_candles_to_ohlcv(candles)
if not ohlcv_candles:
logger.error("Failed to convert candles to OHLCV format")
return {}
# Initialize technical indicators calculator
indicators_calc = TechnicalIndicators(logger)
# Prepare configurations
configs_to_calculate = {}
all_configs = {**INDICATOR_DEFINITIONS}
if custom_configs:
all_configs.update(custom_configs)
for indicator_name in indicator_configs:
if indicator_name in all_configs:
chart_config = all_configs[indicator_name]
configs_to_calculate[indicator_name] = chart_config.to_indicator_config()
else:
logger.warning(f"Unknown indicator configuration: {indicator_name}")
if not configs_to_calculate:
logger.warning("No valid indicator configurations found")
return {}
# Calculate indicators
try:
results = indicators_calc.calculate_multiple_indicators(ohlcv_candles, configs_to_calculate)
logger.debug(f"Calculated {len(results)} indicators successfully")
return results
except Exception as e:
logger.error(f"Error calculating indicators: {e}")
return {}
def get_indicator_display_config(indicator_name: str) -> Optional[ChartIndicatorConfig]:
"""
Get display configuration for an indicator.
Args:
indicator_name: Name of the indicator
Returns:
Chart indicator configuration or None if not found
"""
return INDICATOR_DEFINITIONS.get(indicator_name)
def get_available_indicators() -> Dict[str, str]:
"""
Get list of available indicators with descriptions.
Returns:
Dictionary mapping indicator names to descriptions
"""
return {name: config.name for name, config in INDICATOR_DEFINITIONS.items()}
def get_overlay_indicators() -> List[str]:
"""Get list of indicators that display as overlays on the price chart."""
return [name for name, config in INDICATOR_DEFINITIONS.items()
if config.display_type == 'overlay']
def get_subplot_indicators() -> List[str]:
"""Get list of indicators that display in separate subplots."""
return [name for name, config in INDICATOR_DEFINITIONS.items()
if config.display_type == 'subplot']
def get_default_indicator_params(indicator_type: str) -> Dict[str, Any]:
"""
Get default parameters for an indicator type.
Args:
indicator_type: Type of indicator ('sma', 'ema', 'rsi', etc.)
Returns:
Dictionary of default parameters
"""
defaults = {
'sma': {'period': 20, 'price_column': 'close'},
'ema': {'period': 20, 'price_column': 'close'},
'rsi': {'period': 14, 'price_column': 'close'},
'macd': {'fast_period': 12, 'slow_period': 26, 'signal_period': 9, 'price_column': 'close'},
'bollinger_bands': {'period': 20, 'std_dev': 2.0, 'price_column': 'close'}
}
return defaults.get(indicator_type, {})