TCPDashboard/docs/modules/technical-indicators.md

348 lines
12 KiB
Markdown
Raw Permalink Normal View History

# Technical Indicators Module
## Overview
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
2025-06-07 14:12:37 +08:00
## Architecture
2025-06-07 14:12:37 +08:00
### Package Structure
```
data/common/indicators/
2025-06-07 14:12:37 +08:00
├── __init__.py # Package exports
├── technical.py # Main facade class
├── base.py # Base indicator class
├── result.py # Result container class (legacy)
2025-06-07 14:12:37 +08:00
├── utils.py # Utility functions
└── implementations/ # Individual indicator implementations
├── __init__.py
├── sma.py # Simple Moving Average
├── ema.py # Exponential Moving Average
├── rsi.py # Relative Strength Index
├── macd.py # MACD
└── bollinger.py # Bollinger Bands
```
### Key Components
#### 1. Base Classes
- **BaseIndicator**: Abstract base class providing common functionality
- Data preparation with timestamp handling
- Validation and error handling
- Logging support
2025-06-07 14:12:37 +08:00
#### 2. Individual Indicators
Each indicator is implemented as a separate class inheriting from `BaseIndicator`:
- **Vectorized calculations** using pandas operations
- **Clean DataFrame output** with only relevant columns
- **Proper warm-up periods** for safe trading
- **Independent testing** and maintenance
2025-06-07 14:12:37 +08:00
#### 3. TechnicalIndicators Facade
Main entry point providing:
- Unified DataFrame-based interface
2025-06-07 14:12:37 +08:00
- Batch calculations
- Consistent error handling
- Data preparation utilities
2025-06-07 14:12:37 +08:00
## Supported Indicators
### Simple Moving Average (SMA)
```python
from data.common.indicators import TechnicalIndicators
indicators = TechnicalIndicators()
result_df = indicators.sma(df, period=20, price_column='close')
# Returns DataFrame with columns: ['sma'], indexed by timestamp
2025-06-07 14:12:37 +08:00
```
- **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
2025-06-07 14:12:37 +08:00
### Exponential Moving Average (EMA)
```python
result_df = indicators.ema(df, period=12, price_column='close')
# Returns DataFrame with columns: ['ema'], indexed by timestamp
2025-06-07 14:12:37 +08:00
```
- **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
2025-06-07 14:12:37 +08:00
### Relative Strength Index (RSI)
```python
result_df = indicators.rsi(df, period=14, price_column='close')
# Returns DataFrame with columns: ['rsi'], indexed by timestamp
2025-06-07 14:12:37 +08:00
```
- **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
2025-06-07 14:12:37 +08:00
### Moving Average Convergence Divergence (MACD)
```python
result_df = indicators.macd(
2025-06-07 14:12:37 +08:00
df,
fast_period=12,
slow_period=26,
signal_period=9,
price_column='close'
)
# Returns DataFrame with columns: ['macd', 'signal', 'histogram'], indexed by timestamp
2025-06-07 14:12:37 +08:00
```
- **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
2025-06-07 14:12:37 +08:00
### Bollinger Bands
```python
result_df = indicators.bollinger_bands(
2025-06-07 14:12:37 +08:00
df,
period=20,
std_dev=2.0,
price_column='close'
)
# Returns DataFrame with columns: ['upper_band', 'middle_band', 'lower_band'], indexed by timestamp
2025-06-07 14:12:37 +08:00
```
- **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
2025-06-07 14:12:37 +08:00
## Usage Examples
### Basic Usage with DataFrame Output
2025-06-07 14:12:37 +08:00
```python
from data.common.indicators import TechnicalIndicators
# Initialize calculator
indicators = TechnicalIndicators(logger=my_logger)
# Calculate single indicator - returns DataFrame
sma_df = indicators.sma(df, period=20)
2025-06-07 14:12:37 +08:00
# 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}")
2025-06-07 14:12:37 +08:00
```
### Batch Calculations
```python
# Configure multiple indicators
config = {
'sma_20': {'type': 'sma', 'period': 20},
'ema_12': {'type': 'ema', 'period': 12},
'rsi_14': {'type': 'rsi', 'period': 14},
'macd': {
'type': 'macd',
'fast_period': 12,
'slow_period': 26,
'signal_period': 9
}
}
# Calculate all at once - returns dict of DataFrames
2025-06-07 14:12:37 +08:00
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
2025-06-07 14:12:37 +08:00
```
### Working with Different Price Columns
2025-06-07 14:12:37 +08:00
```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_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
2025-06-07 14:12:37 +08:00
```
## Data Handling and Best Practices
2025-06-07 14:12:37 +08:00
### DataFrame Preparation
2025-06-07 14:12:37 +08:00
```python
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
2025-06-07 14:12:37 +08:00
```
### 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
2025-06-07 14:12:37 +08:00
```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
```
2025-06-07 14:12:37 +08:00
### 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
2025-06-07 14:12:37 +08:00
```python
try:
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")
2025-06-07 14:12:37 +08:00
except Exception as e:
logger.error(f"RSI calculation failed: {e}")
# Handle calculation errors
2025-06-07 14:12:37 +08:00
```
## Performance Considerations
1. **Vectorized Operations**
- Uses pandas rolling/ewm functions for maximum performance
- Minimal data copying and transformations
- Efficient memory usage
2025-06-07 14:12:37 +08:00
2. **DataFrame Alignment**
- Timestamp index ensures proper alignment with price data
- Easy integration with plotting libraries
- Consistent data structure across all indicators
2025-06-07 14:12:37 +08:00
3. **Memory Efficiency**
- Returns only necessary columns
- No metadata overhead in result DataFrames
- Clean, minimal output format
2025-06-07 14:12:37 +08:00
## Testing
The module includes comprehensive tests for the new DataFrame-based approach:
- Unit tests for each indicator's DataFrame output
2025-06-07 14:12:37 +08:00
- Integration tests for the facade
- Edge case handling (gaps, insufficient data)
2025-06-07 14:12:37 +08:00
- 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
When adding new indicators:
1. Create a new class in `implementations/`.
2. Inherit from `BaseIndicator`.
3. Implement the `calculate` method to return a DataFrame.
4. Ensure proper warm-up periods.
5. Add comprehensive tests.
6. Create a corresponding **JSON template file** in `config/indicators/templates/` to define its parameters, display properties, and styling for UI integration.
7. Update documentation in `docs/guides/adding-new-indicators.md`.
See [Adding New Indicators](./adding-new-indicators.md) for detailed instructions.
## API Reference
### TechnicalIndicators Class
```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]
def calculate_multiple_indicators(self, df: pd.DataFrame,
indicators_config: Dict[str, Dict[str, Any]]) -> Dict[str, pd.DataFrame]
```
### 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
# 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)
```