Refactor technical indicators module and enhance structure
- Introduced a dedicated sub-package for technical indicators under `data/common/indicators/`, improving modularity and maintainability. - Moved `TechnicalIndicators` and `IndicatorResult` classes to their respective files, along with utility functions for configuration management. - Updated import paths throughout the codebase to reflect the new structure, ensuring compatibility. - Added comprehensive safety net tests for the indicators module to verify core functionality and prevent regressions during refactoring. - Enhanced documentation to provide clear usage examples and details on the new package structure. These changes improve the overall architecture of the technical indicators module, making it more scalable and easier to manage.
This commit is contained in:
26
data/common/indicators/__init__.py
Normal file
26
data/common/indicators/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Technical Indicators Package
|
||||
|
||||
This package provides technical indicator calculations optimized for sparse OHLCV data
|
||||
as produced by the TCP Trading Platform's aggregation strategy.
|
||||
|
||||
IMPORTANT: Handles Sparse Data
|
||||
- Missing candles (time gaps) are normal in this system
|
||||
- Indicators properly handle gaps without interpolation
|
||||
- Uses pandas for efficient vectorized calculations
|
||||
- Follows right-aligned timestamp convention
|
||||
"""
|
||||
|
||||
from .technical import TechnicalIndicators
|
||||
from .result import IndicatorResult
|
||||
from .utils import (
|
||||
create_default_indicators_config,
|
||||
validate_indicator_config
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'TechnicalIndicators',
|
||||
'IndicatorResult',
|
||||
'create_default_indicators_config',
|
||||
'validate_indicator_config'
|
||||
]
|
||||
29
data/common/indicators/result.py
Normal file
29
data/common/indicators/result.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Technical Indicator Result Container
|
||||
|
||||
This module provides the IndicatorResult dataclass for storing
|
||||
technical indicator calculation results in a standardized format.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class IndicatorResult:
|
||||
"""
|
||||
Container for technical indicator calculation results.
|
||||
|
||||
Attributes:
|
||||
timestamp: Candle timestamp (right-aligned)
|
||||
symbol: Trading symbol
|
||||
timeframe: Candle timeframe
|
||||
values: Dictionary of indicator values
|
||||
metadata: Additional calculation metadata
|
||||
"""
|
||||
timestamp: datetime
|
||||
symbol: str
|
||||
timeframe: str
|
||||
values: Dict[str, float]
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
@@ -18,33 +18,13 @@ Supported Indicators:
|
||||
- Bollinger Bands
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Any, Union, Tuple
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any, Union
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .data_types import OHLCVCandle
|
||||
|
||||
|
||||
@dataclass
|
||||
class IndicatorResult:
|
||||
"""
|
||||
Container for technical indicator calculation results.
|
||||
|
||||
Attributes:
|
||||
timestamp: Candle timestamp (right-aligned)
|
||||
symbol: Trading symbol
|
||||
timeframe: Candle timeframe
|
||||
values: Dictionary of indicator values
|
||||
metadata: Additional calculation metadata
|
||||
"""
|
||||
timestamp: datetime
|
||||
symbol: str
|
||||
timeframe: str
|
||||
values: Dict[str, float]
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
from .result import IndicatorResult
|
||||
from ..data_types import OHLCVCandle
|
||||
|
||||
|
||||
class TechnicalIndicators:
|
||||
@@ -112,7 +92,7 @@ class TechnicalIndicators:
|
||||
|
||||
return df
|
||||
|
||||
def sma(self, df: pd.DataFrame, period: int,
|
||||
def sma(self, df: pd.DataFrame, period: int,
|
||||
price_column: str = 'close') -> List[IndicatorResult]:
|
||||
"""
|
||||
Calculate Simple Moving Average (SMA).
|
||||
@@ -231,7 +211,7 @@ class TechnicalIndicators:
|
||||
|
||||
return results
|
||||
|
||||
def macd(self, df: pd.DataFrame,
|
||||
def macd(self, df: pd.DataFrame,
|
||||
fast_period: int = 12, slow_period: int = 26, signal_period: int = 9,
|
||||
price_column: str = 'close') -> List[IndicatorResult]:
|
||||
"""
|
||||
@@ -289,7 +269,7 @@ class TechnicalIndicators:
|
||||
|
||||
return results
|
||||
|
||||
def bollinger_bands(self, df: pd.DataFrame, period: int = 20,
|
||||
def bollinger_bands(self, df: pd.DataFrame, period: int = 20,
|
||||
std_dev: float = 2.0, price_column: str = 'close') -> List[IndicatorResult]:
|
||||
"""
|
||||
Calculate Bollinger Bands.
|
||||
@@ -345,13 +325,13 @@ class TechnicalIndicators:
|
||||
|
||||
return results
|
||||
|
||||
def calculate_multiple_indicators(self, candles: List[OHLCVCandle],
|
||||
def calculate_multiple_indicators(self, df: pd.DataFrame,
|
||||
indicators_config: Dict[str, Dict[str, Any]]) -> Dict[str, List[IndicatorResult]]:
|
||||
"""
|
||||
Calculate multiple indicators at once for efficiency.
|
||||
|
||||
Args:
|
||||
candles: List of OHLCV candles
|
||||
df: DataFrame with OHLCV data
|
||||
indicators_config: Configuration for indicators to calculate
|
||||
Example: {
|
||||
'sma_20': {'type': 'sma', 'period': 20},
|
||||
@@ -373,30 +353,30 @@ class TechnicalIndicators:
|
||||
if indicator_type == 'sma':
|
||||
period = config.get('period', 20)
|
||||
price_column = config.get('price_column', 'close')
|
||||
results[indicator_name] = self.sma(candles, period, price_column)
|
||||
results[indicator_name] = self.sma(df, period, price_column)
|
||||
|
||||
elif indicator_type == 'ema':
|
||||
period = config.get('period', 20)
|
||||
price_column = config.get('price_column', 'close')
|
||||
results[indicator_name] = self.ema(candles, period, price_column)
|
||||
results[indicator_name] = self.ema(df, period, price_column)
|
||||
|
||||
elif indicator_type == 'rsi':
|
||||
period = config.get('period', 14)
|
||||
price_column = config.get('price_column', 'close')
|
||||
results[indicator_name] = self.rsi(candles, period, price_column)
|
||||
results[indicator_name] = self.rsi(df, period, price_column)
|
||||
|
||||
elif indicator_type == 'macd':
|
||||
fast_period = config.get('fast_period', 12)
|
||||
slow_period = config.get('slow_period', 26)
|
||||
signal_period = config.get('signal_period', 9)
|
||||
price_column = config.get('price_column', 'close')
|
||||
results[indicator_name] = self.macd(candles, fast_period, slow_period, signal_period, price_column)
|
||||
results[indicator_name] = self.macd(df, fast_period, slow_period, signal_period, price_column)
|
||||
|
||||
elif indicator_type == 'bollinger_bands':
|
||||
period = config.get('period', 20)
|
||||
std_dev = config.get('std_dev', 2.0)
|
||||
price_column = config.get('price_column', 'close')
|
||||
results[indicator_name] = self.bollinger_bands(candles, period, std_dev, price_column)
|
||||
results[indicator_name] = self.bollinger_bands(df, period, std_dev, price_column)
|
||||
|
||||
else:
|
||||
if self.logger:
|
||||
@@ -410,13 +390,13 @@ class TechnicalIndicators:
|
||||
|
||||
return results
|
||||
|
||||
def calculate(self, indicator_type: str, candles: Union[pd.DataFrame, List[OHLCVCandle]], **kwargs) -> Optional[Dict[str, Any]]:
|
||||
def calculate(self, indicator_type: str, df: pd.DataFrame, **kwargs) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Calculate a single indicator with dynamic dispatch.
|
||||
|
||||
Args:
|
||||
indicator_type: Name of the indicator (e.g., 'sma', 'ema')
|
||||
candles: List of OHLCV candles or a pre-prepared DataFrame
|
||||
df: DataFrame with OHLCV data
|
||||
**kwargs: Indicator-specific parameters (e.g., period=20)
|
||||
|
||||
Returns:
|
||||
@@ -430,14 +410,6 @@ class TechnicalIndicators:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Prepare DataFrame if input is a list of candles
|
||||
if isinstance(candles, list):
|
||||
df = self._prepare_dataframe_from_list(candles)
|
||||
elif isinstance(candles, pd.DataFrame):
|
||||
df = candles
|
||||
else:
|
||||
raise TypeError("Input 'candles' must be a list of OHLCVCandle objects or a pandas DataFrame.")
|
||||
|
||||
if df.empty:
|
||||
return {'data': [], 'metadata': {}}
|
||||
|
||||
@@ -458,56 +430,4 @@ class TechnicalIndicators:
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.error(f"TechnicalIndicators: Error calculating {indicator_type}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def create_default_indicators_config() -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Create default configuration for common technical indicators.
|
||||
|
||||
Returns:
|
||||
Dictionary with default indicator configurations
|
||||
"""
|
||||
return {
|
||||
'sma_20': {'type': 'sma', 'period': 20},
|
||||
'sma_50': {'type': 'sma', 'period': 50},
|
||||
'ema_12': {'type': 'ema', 'period': 12},
|
||||
'ema_26': {'type': 'ema', 'period': 26},
|
||||
'rsi_14': {'type': 'rsi', 'period': 14},
|
||||
'macd_default': {'type': 'macd'},
|
||||
'bollinger_bands_20': {'type': 'bollinger_bands', 'period': 20}
|
||||
}
|
||||
|
||||
|
||||
def validate_indicator_config(config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Validate technical indicator configuration.
|
||||
|
||||
Args:
|
||||
config: Indicator configuration dictionary
|
||||
|
||||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
"""
|
||||
required_fields = ['type']
|
||||
|
||||
# Check required fields
|
||||
for field in required_fields:
|
||||
if field not in config:
|
||||
return False
|
||||
|
||||
# Validate indicator type
|
||||
valid_types = ['sma', 'ema', 'rsi', 'macd', 'bollinger_bands']
|
||||
if config['type'] not in valid_types:
|
||||
return False
|
||||
|
||||
# Validate period fields
|
||||
if 'period' in config and (not isinstance(config['period'], int) or config['period'] <= 0):
|
||||
return False
|
||||
|
||||
# Validate standard deviation for Bollinger Bands
|
||||
if config['type'] == 'bollinger_bands' and 'std_dev' in config:
|
||||
if not isinstance(config['std_dev'], (int, float)) or config['std_dev'] <= 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
return None
|
||||
60
data/common/indicators/utils.py
Normal file
60
data/common/indicators/utils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Technical Indicator Utilities
|
||||
|
||||
This module provides utility functions for managing technical indicator
|
||||
configurations and validation.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
def create_default_indicators_config() -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Create default configuration for common technical indicators.
|
||||
|
||||
Returns:
|
||||
Dictionary with default indicator configurations
|
||||
"""
|
||||
return {
|
||||
'sma_20': {'type': 'sma', 'period': 20},
|
||||
'sma_50': {'type': 'sma', 'period': 50},
|
||||
'ema_12': {'type': 'ema', 'period': 12},
|
||||
'ema_26': {'type': 'ema', 'period': 26},
|
||||
'rsi_14': {'type': 'rsi', 'period': 14},
|
||||
'macd_default': {'type': 'macd'},
|
||||
'bollinger_bands_20': {'type': 'bollinger_bands', 'period': 20}
|
||||
}
|
||||
|
||||
|
||||
def validate_indicator_config(config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Validate technical indicator configuration.
|
||||
|
||||
Args:
|
||||
config: Indicator configuration dictionary
|
||||
|
||||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
"""
|
||||
required_fields = ['type']
|
||||
|
||||
# Check required fields
|
||||
for field in required_fields:
|
||||
if field not in config:
|
||||
return False
|
||||
|
||||
# Validate indicator type
|
||||
valid_types = ['sma', 'ema', 'rsi', 'macd', 'bollinger_bands']
|
||||
if config['type'] not in valid_types:
|
||||
return False
|
||||
|
||||
# Validate period fields
|
||||
if 'period' in config and (not isinstance(config['period'], int) or config['period'] <= 0):
|
||||
return False
|
||||
|
||||
# Validate standard deviation for Bollinger Bands
|
||||
if config['type'] == 'bollinger_bands' and 'std_dev' in config:
|
||||
if not isinstance(config['std_dev'], (int, float)) or config['std_dev'] <= 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user