Refactor technical indicators to return DataFrames and enhance documentation
- Updated all technical indicators to return pandas DataFrames instead of lists, improving consistency and usability. - Modified the `calculate` method in `TechnicalIndicators` to directly return DataFrames with relevant indicator values. - Enhanced the `data_integration.py` to utilize the new DataFrame outputs for better integration with charting. - Updated documentation to reflect the new DataFrame-centric approach, including usage examples and output structures. - Improved error handling to ensure empty DataFrames are returned when insufficient data is available. These changes streamline the indicator calculations and improve the overall architecture, aligning with project standards for maintainability and performance.
This commit is contained in:
@@ -4,6 +4,13 @@
|
||||
|
||||
The Crypto Trading Bot Dashboard features a comprehensive modular indicator system that allows users to create, customize, and manage technical indicators for chart analysis. The system supports both overlay indicators (displayed on the main price chart) and subplot indicators (displayed in separate panels below the main chart).
|
||||
|
||||
**Key Features:**
|
||||
- **Vectorized Calculations**: High-performance pandas-based indicator calculations
|
||||
- **Clean DataFrame Output**: Returns only relevant indicator columns with timestamp index
|
||||
- **Safe Trading**: Proper warm-up periods ensure no early/invalid values
|
||||
- **Gap Handling**: Maintains timestamp alignment without interpolation
|
||||
- **Real-time Integration**: Seamless integration with chart visualization
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [System Architecture](#system-architecture)
|
||||
@@ -23,6 +30,7 @@ The Crypto Trading Bot Dashboard features a comprehensive modular indicator syst
|
||||
components/charts/
|
||||
├── indicator_manager.py # Core indicator CRUD operations
|
||||
├── indicator_defaults.py # Default indicator templates
|
||||
├── data_integration.py # DataFrame-based indicator calculations
|
||||
├── layers/
|
||||
│ ├── indicators.py # Overlay indicator rendering
|
||||
│ └── subplots.py # Subplot indicator rendering
|
||||
@@ -34,6 +42,15 @@ config/indicators/
|
||||
├── sma_abc123.json
|
||||
├── ema_def456.json
|
||||
└── ...
|
||||
|
||||
data/common/indicators/
|
||||
├── technical.py # Vectorized indicator calculations
|
||||
├── implementations/ # Individual indicator implementations
|
||||
├── sma.py # Simple Moving Average
|
||||
├── ema.py # Exponential Moving Average
|
||||
├── rsi.py # Relative Strength Index
|
||||
├── macd.py # MACD
|
||||
└── bollinger.py # Bollinger Bands
|
||||
```
|
||||
|
||||
### Key Classes
|
||||
@@ -41,6 +58,7 @@ config/indicators/
|
||||
- **`IndicatorManager`**: Handles CRUD operations for user indicators
|
||||
- **`UserIndicator`**: Data structure for indicator configuration
|
||||
- **`IndicatorStyling`**: Appearance and styling configuration
|
||||
- **`TechnicalIndicators`**: Vectorized calculation engine
|
||||
- **Indicator Layers**: Rendering classes for different indicator types
|
||||
|
||||
## Current Indicators
|
||||
@@ -48,19 +66,19 @@ config/indicators/
|
||||
### Overlay Indicators
|
||||
These indicators are displayed directly on the price chart:
|
||||
|
||||
| Indicator | Type | Parameters | Description |
|
||||
|-----------|------|------------|-------------|
|
||||
| **Simple Moving Average (SMA)** | `sma` | `period` (1-200) | Average price over N periods |
|
||||
| **Exponential Moving Average (EMA)** | `ema` | `period` (1-200) | Weighted average giving more weight to recent prices |
|
||||
| **Bollinger Bands** | `bollinger_bands` | `period` (5-100), `std_dev` (0.5-5.0) | Price channels based on standard deviation |
|
||||
| Indicator | Type | Parameters | Description | Output Columns |
|
||||
|-----------|------|------------|-------------|----------------|
|
||||
| **Simple Moving Average (SMA)** | `sma` | `period` (1-200) | Average price over N periods | `['sma']` |
|
||||
| **Exponential Moving Average (EMA)** | `ema` | `period` (1-200) | Weighted average giving more weight to recent prices | `['ema']` |
|
||||
| **Bollinger Bands** | `bollinger_bands` | `period` (5-100), `std_dev` (0.5-5.0) | Price channels based on standard deviation | `['upper_band', 'middle_band', 'lower_band']` |
|
||||
|
||||
### Subplot Indicators
|
||||
These indicators are displayed in separate panels:
|
||||
|
||||
| Indicator | Type | Parameters | Description |
|
||||
|-----------|------|------------|-------------|
|
||||
| **Relative Strength Index (RSI)** | `rsi` | `period` (2-50) | Momentum oscillator (0-100 scale) |
|
||||
| **MACD** | `macd` | `fast_period` (2-50), `slow_period` (5-100), `signal_period` (2-30) | Moving average convergence divergence |
|
||||
| Indicator | Type | Parameters | Description | Output Columns |
|
||||
|-----------|------|------------|-------------|----------------|
|
||||
| **Relative Strength Index (RSI)** | `rsi` | `period` (2-50) | Momentum oscillator (0-100 scale) | `['rsi']` |
|
||||
| **MACD** | `macd` | `fast_period` (2-50), `slow_period` (5-100), `signal_period` (2-30) | Moving average convergence divergence | `['macd', 'signal', 'histogram']` |
|
||||
|
||||
## User Interface
|
||||
|
||||
@@ -212,6 +230,20 @@ class IndicatorManager:
|
||||
def get_indicators_by_type(self, display_type: str) -> List[UserIndicator]
|
||||
```
|
||||
|
||||
### TechnicalIndicators Class (Vectorized Calculations)
|
||||
|
||||
```python
|
||||
class TechnicalIndicators:
|
||||
def sma(self, df: pd.DataFrame, period: int, price_column: str = 'close') -> pd.DataFrame
|
||||
def ema(self, df: pd.DataFrame, period: int, price_column: str = 'close') -> pd.DataFrame
|
||||
def rsi(self, df: pd.DataFrame, period: int = 14, price_column: str = 'close') -> pd.DataFrame
|
||||
def macd(self, df: pd.DataFrame, fast_period: int = 12, slow_period: int = 26,
|
||||
signal_period: int = 9, price_column: str = 'close') -> pd.DataFrame
|
||||
def bollinger_bands(self, df: pd.DataFrame, period: int = 20, std_dev: float = 2.0,
|
||||
price_column: str = 'close') -> pd.DataFrame
|
||||
def calculate(self, indicator_type: str, df: pd.DataFrame, **kwargs) -> Optional[pd.DataFrame]
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```python
|
||||
@@ -246,6 +278,86 @@ all_indicators = manager.list_indicators()
|
||||
# Get by type
|
||||
overlay_indicators = manager.get_indicators_by_type("overlay")
|
||||
subplot_indicators = manager.get_indicators_by_type("subplot")
|
||||
|
||||
# Calculate indicators (vectorized)
|
||||
from data.common.indicators import TechnicalIndicators
|
||||
indicators = TechnicalIndicators()
|
||||
|
||||
# Calculate SMA - returns DataFrame with 'sma' column
|
||||
sma_df = indicators.sma(df, period=20)
|
||||
print(f"SMA values: {sma_df['sma'].tolist()}")
|
||||
|
||||
# Calculate MACD - returns DataFrame with 'macd', 'signal', 'histogram' columns
|
||||
macd_df = indicators.macd(df, fast_period=12, slow_period=26, signal_period=9)
|
||||
print(f"MACD signal: {macd_df['signal'].iloc[-1]}")
|
||||
```
|
||||
|
||||
## Data Integration and Chart Rendering
|
||||
|
||||
### DataFrame-Based Calculations
|
||||
|
||||
The system now uses vectorized DataFrame calculations for high performance:
|
||||
|
||||
```python
|
||||
# In data_integration.py
|
||||
def get_indicator_data(self, main_df: pd.DataFrame, indicator_configs: List[UserIndicator]) -> Dict[str, pd.DataFrame]:
|
||||
"""
|
||||
Calculate indicator data using vectorized operations.
|
||||
|
||||
Returns:
|
||||
Dict mapping indicator_id to DataFrame with indicator values
|
||||
"""
|
||||
indicator_data_map = {}
|
||||
|
||||
for config in indicator_configs:
|
||||
indicator = self.indicator_manager.load_indicator(config.id)
|
||||
if not indicator:
|
||||
continue
|
||||
|
||||
# Calculate using vectorized methods
|
||||
result_df = self.indicators.calculate(
|
||||
indicator.type,
|
||||
indicator_df,
|
||||
**indicator.parameters
|
||||
)
|
||||
|
||||
if result_df is not None and not result_df.empty:
|
||||
indicator_data_map[config.id] = result_df
|
||||
|
||||
return indicator_data_map
|
||||
```
|
||||
|
||||
### Chart Integration
|
||||
|
||||
Indicators are rendered using their clean DataFrame output:
|
||||
|
||||
```python
|
||||
# In layers/indicators.py
|
||||
def create_traces(self, indicator_data: pd.DataFrame, styling: IndicatorStyling) -> List[go.Scatter]:
|
||||
"""
|
||||
Create plotly traces from indicator DataFrame.
|
||||
"""
|
||||
traces = []
|
||||
|
||||
# For SMA/EMA - single line
|
||||
if 'sma' in indicator_data.columns:
|
||||
traces.append(go.Scatter(
|
||||
x=indicator_data.index,
|
||||
y=indicator_data['sma'],
|
||||
name=self.indicator.name,
|
||||
line=dict(color=styling.color, width=styling.line_width),
|
||||
opacity=styling.opacity
|
||||
))
|
||||
|
||||
# For MACD - multiple lines
|
||||
elif 'macd' in indicator_data.columns:
|
||||
traces.extend([
|
||||
go.Scatter(x=indicator_data.index, y=indicator_data['macd'], name='MACD'),
|
||||
go.Scatter(x=indicator_data.index, y=indicator_data['signal'], name='Signal'),
|
||||
go.Bar(x=indicator_data.index, y=indicator_data['histogram'], name='Histogram')
|
||||
])
|
||||
|
||||
return traces
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -263,8 +375,14 @@ subplot_indicators = manager.get_indicators_by_type("subplot")
|
||||
3. **Chart not updating**
|
||||
- Verify the indicator layer implements `calculate_values` and `create_traces`
|
||||
- Check if indicator is registered in the correct registry
|
||||
- Ensure DataFrame output is not empty
|
||||
|
||||
4. **File permission errors**
|
||||
4. **Empty indicator results**
|
||||
- Check if DataFrame has sufficient data (warm-up periods)
|
||||
- Verify timestamp column is present and properly formatted
|
||||
- Check for gaps in data that might affect calculations
|
||||
|
||||
5. **File permission errors**
|
||||
- Ensure `config/indicators/user_indicators/` directory is writable
|
||||
- Check file permissions on existing JSON files
|
||||
|
||||
@@ -274,14 +392,26 @@ subplot_indicators = manager.get_indicators_by_type("subplot")
|
||||
- Look at application logs for Python exceptions
|
||||
- Verify JSON file structure with a validator
|
||||
- Test indicator calculations with sample data
|
||||
- Check DataFrame structure and column names
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- **Vectorized calculations** provide significant performance improvements
|
||||
- Indicators with large periods may take longer to calculate
|
||||
- Consider data availability when setting parameter limits
|
||||
- Subplot indicators require additional chart space
|
||||
- Real-time updates may impact performance with many indicators
|
||||
|
||||
### Warm-up Periods
|
||||
|
||||
All indicators implement proper warm-up periods for safe trading:
|
||||
|
||||
- **SMA/EMA/RSI/BB**: First `period-1` values are excluded
|
||||
- **MACD**: First `max(slow_period, signal_period)-1` values are excluded
|
||||
- **Result**: Only reliable, fully-calculated values are returned
|
||||
|
||||
This ensures that no early/invalid values are used for trading decisions.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Naming Conventions**
|
||||
@@ -300,6 +430,7 @@ subplot_indicators = manager.get_indicators_by_type("subplot")
|
||||
- Log errors for debugging
|
||||
|
||||
4. **Performance**
|
||||
- Leverage vectorized calculations for speed
|
||||
- Cache calculated values when possible
|
||||
- Optimize calculation algorithms
|
||||
- Limit the number of active indicators
|
||||
@@ -307,7 +438,14 @@ subplot_indicators = manager.get_indicators_by_type("subplot")
|
||||
5. **User Experience**
|
||||
- Provide immediate visual feedback
|
||||
- Use intuitive color schemes
|
||||
- Group related indicators logically
|
||||
- Group related indicators logically
|
||||
|
||||
6. **Data Handling**
|
||||
- Respect warm-up periods for safe trading
|
||||
- Handle data gaps without interpolation
|
||||
- Maintain timestamp alignment
|
||||
- Use clean DataFrame output for plotting
|
||||
|
||||
---
|
||||
|
||||
*Back to [Chart System Documentation (`README.md`)]*
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The Technical Indicators module provides a modular, extensible system for calculating technical analysis indicators. It is designed to handle sparse OHLCV data efficiently, making it ideal for real-time trading applications.
|
||||
The Technical Indicators module provides a **vectorized, DataFrame-centric** system for calculating technical analysis indicators. It is designed to handle sparse OHLCV data efficiently using pandas for high-performance calculations, making it ideal for real-time trading applications and chart visualization.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **DataFrame-Centric Design**: All indicators return pandas DataFrames with timestamp index for easy alignment and plotting
|
||||
- **Vectorized Calculations**: Leverages pandas and numpy for high-speed computation
|
||||
- **Clean Output**: Returns only relevant indicator columns (e.g., `'sma'`, `'ema'`, `'rsi'`) with timestamp index
|
||||
- **Safe Trading**: Proper warm-up periods ensure no early/invalid values are returned
|
||||
- **Gap Handling**: Maintains timestamp alignment without interpolation for trading integrity
|
||||
- **Modular Architecture**: Clear separation between calculation logic and result formatting
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -12,7 +21,7 @@ data/common/indicators/
|
||||
├── __init__.py # Package exports
|
||||
├── technical.py # Main facade class
|
||||
├── base.py # Base indicator class
|
||||
├── result.py # Result container class
|
||||
├── result.py # Result container class (legacy)
|
||||
├── utils.py # Utility functions
|
||||
└── implementations/ # Individual indicator implementations
|
||||
├── __init__.py
|
||||
@@ -27,24 +36,23 @@ data/common/indicators/
|
||||
|
||||
#### 1. Base Classes
|
||||
- **BaseIndicator**: Abstract base class providing common functionality
|
||||
- Data preparation
|
||||
- Validation
|
||||
- Error handling
|
||||
- Logging
|
||||
- Data preparation with timestamp handling
|
||||
- Validation and error handling
|
||||
- Logging support
|
||||
|
||||
#### 2. Individual Indicators
|
||||
Each indicator is implemented as a separate class inheriting from `BaseIndicator`:
|
||||
- Focused responsibility
|
||||
- Independent testing
|
||||
- Easy maintenance
|
||||
- Clear documentation
|
||||
- **Vectorized calculations** using pandas operations
|
||||
- **Clean DataFrame output** with only relevant columns
|
||||
- **Proper warm-up periods** for safe trading
|
||||
- **Independent testing** and maintenance
|
||||
|
||||
#### 3. TechnicalIndicators Facade
|
||||
Main entry point providing:
|
||||
- Unified interface
|
||||
- Unified DataFrame-based interface
|
||||
- Batch calculations
|
||||
- Consistent error handling
|
||||
- Data preparation
|
||||
- Data preparation utilities
|
||||
|
||||
## Supported Indicators
|
||||
|
||||
@@ -53,73 +61,112 @@ Main entry point providing:
|
||||
from data.common.indicators import TechnicalIndicators
|
||||
|
||||
indicators = TechnicalIndicators()
|
||||
results = indicators.sma(df, period=20, price_column='close')
|
||||
result_df = indicators.sma(df, period=20, price_column='close')
|
||||
# Returns DataFrame with columns: ['sma'], indexed by timestamp
|
||||
```
|
||||
- **Parameters**:
|
||||
- `period`: Number of periods (default: 20)
|
||||
- `price_column`: Column to average (default: 'close')
|
||||
- **Returns**: DataFrame with `'sma'` column, indexed by timestamp
|
||||
- **Warm-up**: First `period-1` values are excluded for safety
|
||||
|
||||
### Exponential Moving Average (EMA)
|
||||
```python
|
||||
results = indicators.ema(df, period=12, price_column='close')
|
||||
result_df = indicators.ema(df, period=12, price_column='close')
|
||||
# Returns DataFrame with columns: ['ema'], indexed by timestamp
|
||||
```
|
||||
- **Parameters**:
|
||||
- `period`: Number of periods (default: 20)
|
||||
- `price_column`: Column to average (default: 'close')
|
||||
- **Returns**: DataFrame with `'ema'` column, indexed by timestamp
|
||||
- **Warm-up**: First `period-1` values are excluded for safety
|
||||
|
||||
### Relative Strength Index (RSI)
|
||||
```python
|
||||
results = indicators.rsi(df, period=14, price_column='close')
|
||||
result_df = indicators.rsi(df, period=14, price_column='close')
|
||||
# Returns DataFrame with columns: ['rsi'], indexed by timestamp
|
||||
```
|
||||
- **Parameters**:
|
||||
- `period`: Number of periods (default: 14)
|
||||
- `price_column`: Column to analyze (default: 'close')
|
||||
- **Returns**: DataFrame with `'rsi'` column, indexed by timestamp
|
||||
- **Warm-up**: First `period-1` values are excluded for safety
|
||||
|
||||
### Moving Average Convergence Divergence (MACD)
|
||||
```python
|
||||
results = indicators.macd(
|
||||
result_df = indicators.macd(
|
||||
df,
|
||||
fast_period=12,
|
||||
slow_period=26,
|
||||
signal_period=9,
|
||||
price_column='close'
|
||||
)
|
||||
# Returns DataFrame with columns: ['macd', 'signal', 'histogram'], indexed by timestamp
|
||||
```
|
||||
- **Parameters**:
|
||||
- `fast_period`: Fast EMA period (default: 12)
|
||||
- `slow_period`: Slow EMA period (default: 26)
|
||||
- `signal_period`: Signal line period (default: 9)
|
||||
- `price_column`: Column to analyze (default: 'close')
|
||||
- **Returns**: DataFrame with `'macd'`, `'signal'`, `'histogram'` columns, indexed by timestamp
|
||||
- **Warm-up**: First `max(slow_period, signal_period)-1` values are excluded for safety
|
||||
|
||||
### Bollinger Bands
|
||||
```python
|
||||
results = indicators.bollinger_bands(
|
||||
result_df = indicators.bollinger_bands(
|
||||
df,
|
||||
period=20,
|
||||
std_dev=2.0,
|
||||
price_column='close'
|
||||
)
|
||||
# Returns DataFrame with columns: ['upper_band', 'middle_band', 'lower_band'], indexed by timestamp
|
||||
```
|
||||
- **Parameters**:
|
||||
- `period`: SMA period (default: 20)
|
||||
- `std_dev`: Standard deviation multiplier (default: 2.0)
|
||||
- `price_column`: Column to analyze (default: 'close')
|
||||
- **Returns**: DataFrame with `'upper_band'`, `'middle_band'`, `'lower_band'` columns, indexed by timestamp
|
||||
- **Warm-up**: First `period-1` values are excluded for safety
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
### Basic Usage with DataFrame Output
|
||||
```python
|
||||
from data.common.indicators import TechnicalIndicators
|
||||
|
||||
# Initialize calculator
|
||||
indicators = TechnicalIndicators(logger=my_logger)
|
||||
|
||||
# Calculate single indicator
|
||||
sma_results = indicators.sma(df, period=20)
|
||||
# Calculate single indicator - returns DataFrame
|
||||
sma_df = indicators.sma(df, period=20)
|
||||
|
||||
# Access results
|
||||
for result in sma_results:
|
||||
print(f"Time: {result.timestamp}, SMA: {result.values['sma']}")
|
||||
# Access results using DataFrame operations
|
||||
print(f"First SMA value: {sma_df['sma'].iloc[0]}")
|
||||
print(f"Latest SMA value: {sma_df['sma'].iloc[-1]}")
|
||||
print(f"All SMA values: {sma_df['sma'].tolist()}")
|
||||
|
||||
# Plotting integration
|
||||
import plotly.graph_objects as go
|
||||
fig = go.Figure()
|
||||
fig.add_trace(go.Scatter(
|
||||
x=sma_df.index,
|
||||
y=sma_df['sma'],
|
||||
name='SMA 20',
|
||||
line=dict(color='blue')
|
||||
))
|
||||
```
|
||||
|
||||
### Using the Dynamic `calculate` Method
|
||||
```python
|
||||
# Calculate any indicator by type name
|
||||
rsi_df = indicators.calculate('rsi', df, period=14)
|
||||
if rsi_df is not None and not rsi_df.empty:
|
||||
print(f"RSI range: {rsi_df['rsi'].min():.2f} - {rsi_df['rsi'].max():.2f}")
|
||||
|
||||
# MACD with custom parameters
|
||||
macd_df = indicators.calculate('macd', df, fast_period=10, slow_period=30, signal_period=8)
|
||||
if macd_df is not None and not macd_df.empty:
|
||||
print(f"MACD signal line: {macd_df['signal'].iloc[-1]:.4f}")
|
||||
```
|
||||
|
||||
### Batch Calculations
|
||||
@@ -137,80 +184,121 @@ config = {
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate all at once
|
||||
# Calculate all at once - returns dict of DataFrames
|
||||
results = indicators.calculate_multiple_indicators(df, config)
|
||||
|
||||
# Access individual results
|
||||
sma_df = results['sma_20'] # DataFrame with 'sma' column
|
||||
ema_df = results['ema_12'] # DataFrame with 'ema' column
|
||||
rsi_df = results['rsi_14'] # DataFrame with 'rsi' column
|
||||
macd_df = results['macd'] # DataFrame with 'macd', 'signal', 'histogram' columns
|
||||
```
|
||||
|
||||
### Dynamic Indicator Selection
|
||||
### Working with Different Price Columns
|
||||
```python
|
||||
# Calculate any indicator by name
|
||||
result = indicators.calculate(
|
||||
'macd',
|
||||
df,
|
||||
fast_period=12,
|
||||
slow_period=26,
|
||||
signal_period=9
|
||||
)
|
||||
# Calculate SMA on the 'high' price
|
||||
sma_high_df = indicators.sma(df, period=20, price_column='high')
|
||||
|
||||
# Calculate RSI on the 'open' price
|
||||
rsi_open_df = indicators.calculate('rsi', df, period=14, price_column='open')
|
||||
|
||||
# All results are DataFrames with the same structure
|
||||
assert 'sma' in sma_high_df.columns
|
||||
assert 'rsi' in rsi_open_df.columns
|
||||
```
|
||||
|
||||
## Data Structures
|
||||
## Data Handling and Best Practices
|
||||
|
||||
### IndicatorResult
|
||||
### DataFrame Preparation
|
||||
```python
|
||||
@dataclass
|
||||
class IndicatorResult:
|
||||
timestamp: datetime # Right-aligned timestamp
|
||||
symbol: str # Trading symbol
|
||||
timeframe: str # Candle timeframe
|
||||
values: Dict[str, float] # Indicator values
|
||||
metadata: Optional[Dict[str, Any]] = None # Calculation metadata
|
||||
from components.charts.utils import prepare_chart_data
|
||||
|
||||
# Prepare DataFrame from candle data
|
||||
df = prepare_chart_data(candles)
|
||||
# df has columns: ['open', 'high', 'low', 'close', 'volume'] with DatetimeIndex
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
### Gap Handling
|
||||
The system handles data gaps naturally:
|
||||
- **No interpolation**: Gaps in timestamps are preserved
|
||||
- **Rolling calculations**: Use only available data points
|
||||
- **Safe trading**: No artificial data is introduced
|
||||
|
||||
The module provides comprehensive error handling:
|
||||
- Input validation
|
||||
- Data sufficiency checks
|
||||
- Calculation error handling
|
||||
- Detailed error logging
|
||||
```python
|
||||
# Example: If you have gaps in your data
|
||||
# 09:00, 09:01, 09:02, 09:04, 09:05 (missing 09:03)
|
||||
# The indicators will calculate correctly using available data
|
||||
# No interpolation or filling of gaps
|
||||
```
|
||||
|
||||
Example:
|
||||
### Warm-up Periods
|
||||
All indicators implement proper warm-up periods for safe trading:
|
||||
- **SMA/EMA/RSI/BB**: First `period-1` values excluded
|
||||
- **MACD**: First `max(slow_period, signal_period)-1` values excluded
|
||||
- **Result**: Only reliable, fully-calculated values are returned
|
||||
|
||||
### Error Handling
|
||||
```python
|
||||
try:
|
||||
results = indicators.rsi(df, period=14)
|
||||
result_df = indicators.rsi(df, period=14)
|
||||
if result_df is not None and not result_df.empty:
|
||||
# Process results
|
||||
pass
|
||||
else:
|
||||
# Handle insufficient data
|
||||
logger.warning("Insufficient data for RSI calculation")
|
||||
except Exception as e:
|
||||
logger.error(f"RSI calculation failed: {e}")
|
||||
results = []
|
||||
# Handle calculation errors
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Data Preparation**
|
||||
- Uses pandas for vectorized calculations
|
||||
- Handles sparse data efficiently
|
||||
- Maintains timestamp alignment
|
||||
1. **Vectorized Operations**
|
||||
- Uses pandas rolling/ewm functions for maximum performance
|
||||
- Minimal data copying and transformations
|
||||
- Efficient memory usage
|
||||
|
||||
2. **Memory Usage**
|
||||
- Avoids unnecessary data copies
|
||||
- Cleans up temporary calculations
|
||||
- Uses efficient data structures
|
||||
2. **DataFrame Alignment**
|
||||
- Timestamp index ensures proper alignment with price data
|
||||
- Easy integration with plotting libraries
|
||||
- Consistent data structure across all indicators
|
||||
|
||||
3. **Calculation Optimization**
|
||||
- Vectorized operations where possible
|
||||
- Minimal data transformations
|
||||
- Efficient algorithm implementations
|
||||
3. **Memory Efficiency**
|
||||
- Returns only necessary columns
|
||||
- No metadata overhead in result DataFrames
|
||||
- Clean, minimal output format
|
||||
|
||||
## Testing
|
||||
|
||||
The module includes comprehensive tests:
|
||||
- Unit tests for each indicator
|
||||
The module includes comprehensive tests for the new DataFrame-based approach:
|
||||
- Unit tests for each indicator's DataFrame output
|
||||
- Integration tests for the facade
|
||||
- Edge case handling
|
||||
- Edge case handling (gaps, insufficient data)
|
||||
- Performance benchmarks
|
||||
|
||||
Run tests with:
|
||||
```bash
|
||||
uv run pytest tests/test_indicators.py
|
||||
uv run pytest tests/test_indicators_safety.py
|
||||
```
|
||||
|
||||
## Migration from Legacy Format
|
||||
|
||||
If you were using the old `List[IndicatorResult]` format:
|
||||
|
||||
### Old Style:
|
||||
```python
|
||||
results = indicators.sma(df, period=20)
|
||||
for result in results:
|
||||
print(f"Time: {result.timestamp}, SMA: {result.values['sma']}")
|
||||
```
|
||||
|
||||
### New Style:
|
||||
```python
|
||||
result_df = indicators.sma(df, period=20)
|
||||
for timestamp, row in result_df.iterrows():
|
||||
print(f"Time: {timestamp}, SMA: {row['sma']}")
|
||||
```
|
||||
|
||||
## Contributing
|
||||
@@ -218,122 +306,42 @@ uv run pytest tests/test_indicators.py
|
||||
When adding new indicators:
|
||||
1. Create a new class in `implementations/`
|
||||
2. Inherit from `BaseIndicator`
|
||||
3. Implement the `calculate` method
|
||||
4. Add tests
|
||||
5. Update documentation
|
||||
3. Implement the `calculate` method to return a DataFrame
|
||||
4. Ensure proper warm-up periods
|
||||
5. Add comprehensive tests
|
||||
6. Update documentation
|
||||
|
||||
See [Adding New Indicators](./adding-new-indicators.md) for detailed instructions.
|
||||
|
||||
## Key Features
|
||||
## API Reference
|
||||
|
||||
- **DataFrame-Centric Design**: Operates directly on pandas DataFrames for performance and simplicity.
|
||||
- **Vectorized Calculations**: Leverages pandas and numpy for high-speed computation.
|
||||
- **Flexible `calculate` Method**: A single entry point for calculating any supported indicator by name.
|
||||
- **Standardized Output**: All methods return a DataFrame containing the calculated indicator values, indexed by timestamp.
|
||||
- **Modular Architecture**: Clear separation between calculation logic, result types, and utilities.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Importing the Required Components
|
||||
### TechnicalIndicators Class
|
||||
|
||||
```python
|
||||
from data.common.indicators import (
|
||||
TechnicalIndicators,
|
||||
IndicatorResult,
|
||||
create_default_indicators_config,
|
||||
validate_indicator_config
|
||||
)
|
||||
from data.common.data_types import OHLCVCandle
|
||||
class TechnicalIndicators:
|
||||
def sma(self, df: pd.DataFrame, period: int, price_column: str = 'close') -> pd.DataFrame
|
||||
def ema(self, df: pd.DataFrame, period: int, price_column: str = 'close') -> pd.DataFrame
|
||||
def rsi(self, df: pd.DataFrame, period: int = 14, price_column: str = 'close') -> pd.DataFrame
|
||||
def macd(self, df: pd.DataFrame, fast_period: int = 12, slow_period: int = 26,
|
||||
signal_period: int = 9, price_column: str = 'close') -> pd.DataFrame
|
||||
def bollinger_bands(self, df: pd.DataFrame, period: int = 20, std_dev: float = 2.0,
|
||||
price_column: str = 'close') -> pd.DataFrame
|
||||
def calculate(self, indicator_type: str, df: pd.DataFrame, **kwargs) -> Optional[pd.DataFrame]
|
||||
def calculate_multiple_indicators(self, df: pd.DataFrame,
|
||||
indicators_config: Dict[str, Dict[str, Any]]) -> Dict[str, pd.DataFrame]
|
||||
```
|
||||
|
||||
### Preparing the DataFrame
|
||||
|
||||
Before you can calculate indicators, you need a properly formatted pandas DataFrame. The `prepare_chart_data` utility is the recommended way to create one from a list of candle dictionaries.
|
||||
### Return Format
|
||||
All methods return:
|
||||
- **Success**: `pd.DataFrame` with indicator column(s) and DatetimeIndex
|
||||
- **Failure/Insufficient Data**: `pd.DataFrame()` (empty DataFrame)
|
||||
- **Error**: `None` (with logged error)
|
||||
|
||||
### DataFrame Structure
|
||||
```python
|
||||
from components.charts.utils import prepare_chart_data
|
||||
from data.common.indicators import TechnicalIndicators
|
||||
|
||||
# Assume 'candles' is a list of OHLCV dictionaries from the database
|
||||
# candles = fetch_market_data(...)
|
||||
|
||||
# Prepare the DataFrame
|
||||
df = prepare_chart_data(candles)
|
||||
|
||||
# df is now ready for indicator calculations
|
||||
# It has a DatetimeIndex and the necessary OHLCV columns.
|
||||
```
|
||||
|
||||
### Basic Indicator Calculation
|
||||
|
||||
Once you have a prepared DataFrame, you can calculate indicators directly.
|
||||
|
||||
```python
|
||||
# Initialize the calculator
|
||||
indicators = TechnicalIndicators()
|
||||
|
||||
# Calculate a Simple Moving Average
|
||||
sma_df = indicators.sma(df, period=20)
|
||||
|
||||
# Calculate an Exponential Moving Average
|
||||
ema_df = indicators.ema(df, period=12)
|
||||
|
||||
# sma_df and ema_df are pandas DataFrames containing the results.
|
||||
```
|
||||
|
||||
### Using the `calculate` Method
|
||||
|
||||
The most flexible way to compute an indicator is with the `calculate` method, which accepts the indicator type as a string.
|
||||
|
||||
```python
|
||||
# Calculate RSI using the generic method
|
||||
rsi_pkg = indicators.calculate('rsi', df, period=14)
|
||||
if rsi_pkg:
|
||||
rsi_df = rsi_pkg['data']
|
||||
|
||||
# Calculate MACD with custom parameters
|
||||
macd_pkg = indicators.calculate('macd', df, fast_period=10, slow_period=30, signal_period=8)
|
||||
if macd_pkg:
|
||||
macd_df = macd_pkg['data']
|
||||
```
|
||||
|
||||
### Using Different Price Columns
|
||||
|
||||
You can specify which price column (`open`, `high`, `low`, or `close`) to use for the calculation.
|
||||
|
||||
```python
|
||||
# Calculate SMA on the 'high' price
|
||||
sma_high_df = indicators.sma(df, period=20, price_column='high')
|
||||
|
||||
# Calculate RSI on the 'open' price
|
||||
rsi_open_pkg = indicators.calculate('rsi', df, period=14, price_column='open')
|
||||
```
|
||||
|
||||
## Indicator Details
|
||||
|
||||
The following details the parameters and the columns returned in the result DataFrame for each indicator.
|
||||
|
||||
### Simple Moving Average (SMA)
|
||||
|
||||
- **Parameters**: `period` (int), `price_column` (str, default: 'close')
|
||||
- **Returned Columns**: `sma`
|
||||
|
||||
### Exponential Moving Average (EMA)
|
||||
|
||||
- **Parameters**: `period` (int), `price_column` (str, default: 'close')
|
||||
- **Returned Columns**: `ema`
|
||||
|
||||
### Relative Strength Index (RSI)
|
||||
|
||||
- **Parameters**: `period` (int), `price_column` (str, default: 'close')
|
||||
- **Returned Columns**: `rsi`
|
||||
|
||||
### MACD (Moving Average Convergence Divergence)
|
||||
|
||||
- **Parameters**: `fast_period` (int), `slow_period` (int), `signal_period` (int), `price_column` (str, default: 'close')
|
||||
- **Returned Columns**: `macd`, `signal`, `histogram`
|
||||
|
||||
### Bollinger Bands
|
||||
|
||||
- **Parameters**: `period` (int), `std_dev` (float), `price_column` (str, default: 'close')
|
||||
- **Returned Columns**: `upper_band`, `
|
||||
# Example SMA result
|
||||
result_df = indicators.sma(df, period=20)
|
||||
# result_df.index: DatetimeIndex (timestamps)
|
||||
# result_df.columns: ['sma']
|
||||
# result_df.shape: (N, 1) where N = len(df) - period + 1 (after warm-up)
|
||||
```
|
||||
Reference in New Issue
Block a user