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:
Vasily.onl
2025-06-07 01:32:21 +08:00
parent e7ede7f329
commit c8d8d980aa
9 changed files with 530 additions and 178 deletions

View 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'
]

View 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

View File

@@ -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

View 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