indicators documentation
This commit is contained in:
parent
baf94b18da
commit
b0568f634d
@ -177,6 +177,7 @@ class TechnicalIndicators:
|
|||||||
def calculate_multiple_indicators(self, df: pd.DataFrame,
|
def calculate_multiple_indicators(self, df: pd.DataFrame,
|
||||||
indicators_config: Dict[str, Dict[str, Any]]) -> Dict[str, List[IndicatorResult]]:
|
indicators_config: Dict[str, Dict[str, Any]]) -> Dict[str, List[IndicatorResult]]:
|
||||||
"""
|
"""
|
||||||
|
TODO: need make more procedural without hardcoding indicators type and so on
|
||||||
Calculate multiple indicators at once for efficiency.
|
Calculate multiple indicators at once for efficiency.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
381
docs/guides/adding-new-indicators.md
Normal file
381
docs/guides/adding-new-indicators.md
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
# Adding New Indicators Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide provides comprehensive instructions for adding new technical indicators to the Crypto Trading Bot Dashboard. The system uses a modular approach where each indicator is implemented as a separate class inheriting from `BaseIndicator`.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
2. [Implementation Steps](#implementation-steps)
|
||||||
|
3. [Integration with Charts](#integration-with-charts)
|
||||||
|
4. [Best Practices](#best-practices)
|
||||||
|
5. [Testing Guidelines](#testing-guidelines)
|
||||||
|
6. [Common Pitfalls](#common-pitfalls)
|
||||||
|
7. [Example Implementation](#example-implementation)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Python knowledge with pandas/numpy
|
||||||
|
- Understanding of technical analysis concepts
|
||||||
|
- Familiarity with the project structure
|
||||||
|
- Knowledge of the indicator's mathematical formula
|
||||||
|
- Understanding of the dashboard's chart system
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### 1. Create Indicator Class
|
||||||
|
|
||||||
|
Create a new file in `data/common/indicators/implementations/` named after your indicator (e.g., `stochastic.py`):
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
import pandas as pd
|
||||||
|
from ..base import BaseIndicator
|
||||||
|
from ..result import IndicatorResult
|
||||||
|
|
||||||
|
class StochasticIndicator(BaseIndicator):
|
||||||
|
"""
|
||||||
|
Stochastic Oscillator implementation.
|
||||||
|
|
||||||
|
The Stochastic Oscillator is a momentum indicator comparing a particular closing price
|
||||||
|
of a security to a range of its prices over a certain period of time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, logger=None):
|
||||||
|
super().__init__(logger)
|
||||||
|
self.name = "stochastic"
|
||||||
|
|
||||||
|
def calculate(self, df: pd.DataFrame, k_period: int = 14,
|
||||||
|
d_period: int = 3, price_column: str = 'close') -> List[IndicatorResult]:
|
||||||
|
"""
|
||||||
|
Calculate Stochastic Oscillator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame with OHLCV data
|
||||||
|
k_period: The K period (default: 14)
|
||||||
|
d_period: The D period (default: 3)
|
||||||
|
price_column: Column to use for calculations (default: 'close')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of IndicatorResult objects containing %K and %D values
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Validate inputs
|
||||||
|
self._validate_dataframe(df)
|
||||||
|
self._validate_period(k_period, min_value=2)
|
||||||
|
self._validate_period(d_period, min_value=2)
|
||||||
|
|
||||||
|
# Calculate %K
|
||||||
|
lowest_low = df['low'].rolling(window=k_period).min()
|
||||||
|
highest_high = df['high'].rolling(window=k_period).max()
|
||||||
|
k_percent = 100 * ((df[price_column] - lowest_low) /
|
||||||
|
(highest_high - lowest_low))
|
||||||
|
|
||||||
|
# Calculate %D (signal line)
|
||||||
|
d_percent = k_percent.rolling(window=d_period).mean()
|
||||||
|
|
||||||
|
# Create results
|
||||||
|
results = []
|
||||||
|
for idx, row in df.iterrows():
|
||||||
|
if pd.notna(k_percent[idx]) and pd.notna(d_percent[idx]):
|
||||||
|
results.append(IndicatorResult(
|
||||||
|
timestamp=idx,
|
||||||
|
symbol=self._get_symbol(df),
|
||||||
|
timeframe=self._get_timeframe(df),
|
||||||
|
values={
|
||||||
|
'k_percent': float(k_percent[idx]),
|
||||||
|
'd_percent': float(d_percent[idx])
|
||||||
|
},
|
||||||
|
metadata={
|
||||||
|
'k_period': k_period,
|
||||||
|
'd_period': d_period
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_error(f"Error calculating Stochastic: {str(e)}")
|
||||||
|
return []
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Register the Indicator
|
||||||
|
|
||||||
|
Add your indicator to `data/common/indicators/implementations/__init__.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .stochastic import StochasticIndicator
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'SMAIndicator',
|
||||||
|
'EMAIndicator',
|
||||||
|
'RSIIndicator',
|
||||||
|
'MACDIndicator',
|
||||||
|
'BollingerBandsIndicator',
|
||||||
|
'StochasticIndicator'
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add to TechnicalIndicators Class
|
||||||
|
|
||||||
|
Update `data/common/indicators/technical.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class TechnicalIndicators:
|
||||||
|
def __init__(self, logger=None):
|
||||||
|
self.logger = logger
|
||||||
|
# ... existing indicators ...
|
||||||
|
self._stochastic = StochasticIndicator(logger)
|
||||||
|
|
||||||
|
def stochastic(self, df: pd.DataFrame, k_period: int = 14,
|
||||||
|
d_period: int = 3, price_column: str = 'close') -> List[IndicatorResult]:
|
||||||
|
"""
|
||||||
|
Calculate Stochastic Oscillator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame with OHLCV data
|
||||||
|
k_period: The K period (default: 14)
|
||||||
|
d_period: The D period (default: 3)
|
||||||
|
price_column: Column to use (default: 'close')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of indicator results with %K and %D values
|
||||||
|
"""
|
||||||
|
return self._stochastic.calculate(
|
||||||
|
df,
|
||||||
|
k_period=k_period,
|
||||||
|
d_period=d_period,
|
||||||
|
price_column=price_column
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Charts
|
||||||
|
|
||||||
|
### 1. Create Chart Layer
|
||||||
|
|
||||||
|
Create a new layer class in `components/charts/layers/indicators.py` (overlay) or `components/charts/layers/subplots.py` (subplot):
|
||||||
|
|
||||||
|
```python
|
||||||
|
class StochasticLayer(IndicatorLayer):
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
super().__init__(config)
|
||||||
|
self.name = "stochastic"
|
||||||
|
self.display_type = "subplot"
|
||||||
|
|
||||||
|
def create_traces(self, df: pd.DataFrame, values: Dict[str, pd.Series]) -> List[go.Scatter]:
|
||||||
|
traces = []
|
||||||
|
traces.append(go.Scatter(
|
||||||
|
x=df.index,
|
||||||
|
y=values['k_percent'],
|
||||||
|
mode='lines',
|
||||||
|
name=f"%K ({self.config.get('k_period', 14)})",
|
||||||
|
line=dict(
|
||||||
|
color=self.config.get('color', '#007bff'),
|
||||||
|
width=self.config.get('line_width', 2)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
traces.append(go.Scatter(
|
||||||
|
x=df.index,
|
||||||
|
y=values['d_percent'],
|
||||||
|
mode='lines',
|
||||||
|
name=f"%D ({self.config.get('d_period', 3)})",
|
||||||
|
line=dict(
|
||||||
|
color=self.config.get('secondary_color', '#ff6b35'),
|
||||||
|
width=self.config.get('line_width', 2)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
return traces
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Register in Layer Registry
|
||||||
|
|
||||||
|
Update `components/charts/layers/__init__.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
SUBPLOT_REGISTRY = {
|
||||||
|
'rsi': RSILayer,
|
||||||
|
'macd': MACDLayer,
|
||||||
|
'stochastic': StochasticLayer,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add UI Components
|
||||||
|
|
||||||
|
Update `dashboard/components/indicator_modal.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def create_parameter_fields():
|
||||||
|
return html.Div([
|
||||||
|
# ... existing fields ...
|
||||||
|
html.Div([
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col([
|
||||||
|
dbc.Label("%K Period:"),
|
||||||
|
dcc.Input(
|
||||||
|
id='stochastic-k-period-input',
|
||||||
|
type='number',
|
||||||
|
value=14
|
||||||
|
)
|
||||||
|
], width=6),
|
||||||
|
dbc.Col([
|
||||||
|
dbc.Label("%D Period:"),
|
||||||
|
dcc.Input(
|
||||||
|
id='stochastic-d-period-input',
|
||||||
|
type='number',
|
||||||
|
value=3
|
||||||
|
)
|
||||||
|
], width=6),
|
||||||
|
]),
|
||||||
|
dbc.FormText("Stochastic oscillator periods")
|
||||||
|
], id='stochastic-parameters', style={'display': 'none'})
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- Follow the project's coding style
|
||||||
|
- Add comprehensive docstrings
|
||||||
|
- Include type hints
|
||||||
|
- Handle edge cases gracefully
|
||||||
|
- Use vectorized operations where possible
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Validate all input parameters
|
||||||
|
- Check for sufficient data
|
||||||
|
- Handle NaN values appropriately
|
||||||
|
- Log errors with meaningful messages
|
||||||
|
- Return empty results for invalid inputs
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- Use vectorized operations
|
||||||
|
- Avoid unnecessary loops
|
||||||
|
- Clean up temporary calculations
|
||||||
|
- Consider memory usage
|
||||||
|
- Cache results when appropriate
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Document all public methods
|
||||||
|
- Include usage examples
|
||||||
|
- Explain parameter ranges
|
||||||
|
- Document any assumptions
|
||||||
|
- Keep documentation up-to-date
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
### Test File Structure
|
||||||
|
Create `tests/indicators/test_stochastic.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from data.common.indicators import TechnicalIndicators
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_data():
|
||||||
|
return pd.DataFrame({
|
||||||
|
'open': [10, 11, 12, 13, 14],
|
||||||
|
'high': [12, 13, 14, 15, 16],
|
||||||
|
'low': [8, 9, 10, 11, 12],
|
||||||
|
'close': [11, 12, 13, 14, 15],
|
||||||
|
'volume': [100, 110, 120, 130, 140]
|
||||||
|
}, index=pd.date_range('2023-01-01', periods=5))
|
||||||
|
|
||||||
|
def test_stochastic_calculation(sample_data):
|
||||||
|
indicators = TechnicalIndicators()
|
||||||
|
results = indicators.stochastic(sample_data, k_period=3, d_period=2)
|
||||||
|
|
||||||
|
assert len(results) > 0
|
||||||
|
for result in results:
|
||||||
|
assert 0 <= result.values['k_percent'] <= 100
|
||||||
|
assert 0 <= result.values['d_percent'] <= 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Checklist
|
||||||
|
- [ ] Basic functionality with ideal data
|
||||||
|
- [ ] Edge cases (insufficient data, NaN values)
|
||||||
|
- [ ] Performance with large datasets
|
||||||
|
- [ ] Error handling
|
||||||
|
- [ ] Parameter validation
|
||||||
|
- [ ] Integration with TechnicalIndicators class
|
||||||
|
- [ ] Chart layer rendering
|
||||||
|
- [ ] UI interaction
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
```bash
|
||||||
|
# Run all indicator tests
|
||||||
|
uv run pytest tests/indicators/
|
||||||
|
|
||||||
|
# Run specific indicator tests
|
||||||
|
uv run pytest tests/indicators/test_stochastic.py
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
uv run pytest tests/indicators/ --cov=data.common.indicators
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
1. **Insufficient Data Handling**
|
||||||
|
- Always check if enough data points are available
|
||||||
|
- Return empty results rather than partial calculations
|
||||||
|
- Consider the impact of NaN values
|
||||||
|
|
||||||
|
2. **NaN Handling**
|
||||||
|
- Use appropriate pandas NaN handling methods
|
||||||
|
- Don't propagate NaN values unnecessarily
|
||||||
|
- Document NaN handling behavior
|
||||||
|
|
||||||
|
3. **Memory Leaks**
|
||||||
|
- Clean up temporary DataFrames
|
||||||
|
- Avoid storing large datasets
|
||||||
|
- Use efficient data structures
|
||||||
|
|
||||||
|
4. **Performance Issues**
|
||||||
|
- Use vectorized operations instead of loops
|
||||||
|
- Profile code with large datasets
|
||||||
|
- Consider caching strategies
|
||||||
|
|
||||||
|
5. **UI Integration**
|
||||||
|
- Handle all parameter combinations
|
||||||
|
- Provide meaningful validation
|
||||||
|
- Give clear user feedback
|
||||||
|
|
||||||
|
## Example Implementation
|
||||||
|
|
||||||
|
See the complete Stochastic Oscillator implementation above as a reference. Key points:
|
||||||
|
|
||||||
|
1. **Modular Structure**
|
||||||
|
- Separate indicator class
|
||||||
|
- Clear inheritance hierarchy
|
||||||
|
- Focused responsibility
|
||||||
|
|
||||||
|
2. **Error Handling**
|
||||||
|
- Input validation
|
||||||
|
- Exception handling
|
||||||
|
- Meaningful error messages
|
||||||
|
|
||||||
|
3. **Performance**
|
||||||
|
- Vectorized calculations
|
||||||
|
- Efficient data structures
|
||||||
|
- Memory management
|
||||||
|
|
||||||
|
4. **Testing**
|
||||||
|
- Comprehensive test cases
|
||||||
|
- Edge case handling
|
||||||
|
- Performance verification
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For questions or issues:
|
||||||
|
1. Check existing documentation
|
||||||
|
2. Review test cases
|
||||||
|
3. Consult with team members
|
||||||
|
4. Create detailed bug reports if needed
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Technical Indicators Overview](../modules/technical-indicators.md)
|
||||||
|
- [Chart System Documentation](../modules/charts/README.md)
|
||||||
|
- [Data Types Documentation](../modules/data-types.md)
|
||||||
@ -1,249 +0,0 @@
|
|||||||
# Quick Guide: Adding New Indicators
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This guide provides a step-by-step checklist for adding new technical indicators to the Crypto Trading Bot Dashboard, updated for the new modular dashboard structure.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Understanding of Python and technical analysis
|
|
||||||
- Familiarity with the project structure and Dash callbacks
|
|
||||||
- Knowledge of the indicator type (overlay vs subplot)
|
|
||||||
|
|
||||||
## Step-by-Step Checklist
|
|
||||||
|
|
||||||
### ✅ Step 1: Plan Your Indicator
|
|
||||||
|
|
||||||
- [ ] Determine indicator type (overlay or subplot)
|
|
||||||
- [ ] Define required parameters
|
|
||||||
- [ ] Choose default styling
|
|
||||||
- [ ] Research calculation formula
|
|
||||||
|
|
||||||
### ✅ Step 2: Create Indicator Class
|
|
||||||
|
|
||||||
**File**: `components/charts/layers/indicators.py` (overlay) or `components/charts/layers/subplots.py` (subplot)
|
|
||||||
|
|
||||||
Create a class for your indicator that inherits from `IndicatorLayer`.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class StochasticLayer(IndicatorLayer):
|
|
||||||
def __init__(self, config: Dict[str, Any]):
|
|
||||||
super().__init__(config)
|
|
||||||
self.name = "stochastic"
|
|
||||||
self.display_type = "subplot"
|
|
||||||
|
|
||||||
def calculate_values(self, df: pd.DataFrame) -> Dict[str, pd.Series]:
|
|
||||||
k_period = self.config.get('k_period', 14)
|
|
||||||
d_period = self.config.get('d_period', 3)
|
|
||||||
lowest_low = df['low'].rolling(window=k_period).min()
|
|
||||||
highest_high = df['high'].rolling(window=k_period).max()
|
|
||||||
k_percent = 100 * ((df['close'] - lowest_low) / (highest_high - lowest_low))
|
|
||||||
d_percent = k_percent.rolling(window=d_period).mean()
|
|
||||||
return {'k_percent': k_percent, 'd_percent': d_percent}
|
|
||||||
|
|
||||||
def create_traces(self, df: pd.DataFrame, values: Dict[str, pd.Series]) -> List[go.Scatter]:
|
|
||||||
traces = []
|
|
||||||
traces.append(go.Scatter(x=df.index, y=values['k_percent'], mode='lines', name=f"%K ({self.config.get('k_period', 14)})", line=dict(color=self.config.get('color', '#007bff'), width=self.config.get('line_width', 2))))
|
|
||||||
traces.append(go.Scatter(x=df.index, y=values['d_percent'], mode='lines', name=f"%D ({self.config.get('d_period', 3)})", line=dict(color=self.config.get('secondary_color', '#ff6b35'), width=self.config.get('line_width', 2))))
|
|
||||||
return traces
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 3: Register Indicator
|
|
||||||
|
|
||||||
**File**: `components/charts/layers/__init__.py`
|
|
||||||
|
|
||||||
Register your new indicator class in the appropriate registry.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from .subplots import StochasticLayer
|
|
||||||
|
|
||||||
SUBPLOT_REGISTRY = {
|
|
||||||
'rsi': RSILayer,
|
|
||||||
'macd': MACDLayer,
|
|
||||||
'stochastic': StochasticLayer,
|
|
||||||
}
|
|
||||||
|
|
||||||
INDICATOR_REGISTRY = {
|
|
||||||
'sma': SMALayer,
|
|
||||||
'ema': EMALayer,
|
|
||||||
'bollinger_bands': BollingerBandsLayer,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 4: Add UI Dropdown Option
|
|
||||||
|
|
||||||
**File**: `dashboard/components/indicator_modal.py`
|
|
||||||
|
|
||||||
Add your new indicator to the `indicator-type-dropdown` options.
|
|
||||||
|
|
||||||
```python
|
|
||||||
dcc.Dropdown(
|
|
||||||
id='indicator-type-dropdown',
|
|
||||||
options=[
|
|
||||||
{'label': 'Simple Moving Average (SMA)', 'value': 'sma'},
|
|
||||||
{'label': 'Exponential Moving Average (EMA)', 'value': 'ema'},
|
|
||||||
{'label': 'Relative Strength Index (RSI)', 'value': 'rsi'},
|
|
||||||
{'label': 'MACD', 'value': 'macd'},
|
|
||||||
{'label': 'Bollinger Bands', 'value': 'bollinger_bands'},
|
|
||||||
{'label': 'Stochastic Oscillator', 'value': 'stochastic'},
|
|
||||||
],
|
|
||||||
placeholder='Select indicator type',
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 5: Add Parameter Fields to Modal
|
|
||||||
|
|
||||||
**File**: `dashboard/components/indicator_modal.py`
|
|
||||||
|
|
||||||
In `create_parameter_fields`, add the `dcc.Input` components for your indicator's parameters.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def create_parameter_fields():
|
|
||||||
return html.Div([
|
|
||||||
# ... existing parameter fields ...
|
|
||||||
html.Div([
|
|
||||||
dbc.Row([
|
|
||||||
dbc.Col([dbc.Label("%K Period:"), dcc.Input(id='stochastic-k-period-input', type='number', value=14)], width=6),
|
|
||||||
dbc.Col([dbc.Label("%D Period:"), dcc.Input(id='stochastic-d-period-input', type='number', value=3)], width=6),
|
|
||||||
]),
|
|
||||||
dbc.FormText("Stochastic oscillator periods for %K and %D lines")
|
|
||||||
], id='stochastic-parameters', style={'display': 'none'}, className="mb-3")
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 6: Update Parameter Visibility Callback
|
|
||||||
|
|
||||||
**File**: `dashboard/callbacks/indicators.py`
|
|
||||||
|
|
||||||
In `update_parameter_fields`, add an `Output` and logic to show/hide your new parameter fields.
|
|
||||||
|
|
||||||
```python
|
|
||||||
@app.callback(
|
|
||||||
[Output('indicator-parameters-message', 'style'),
|
|
||||||
Output('sma-parameters', 'style'),
|
|
||||||
Output('ema-parameters', 'style'),
|
|
||||||
Output('rsi-parameters', 'style'),
|
|
||||||
Output('macd-parameters', 'style'),
|
|
||||||
Output('bb-parameters', 'style'),
|
|
||||||
Output('stochastic-parameters', 'style')],
|
|
||||||
Input('indicator-type-dropdown', 'value'),
|
|
||||||
)
|
|
||||||
def update_parameter_fields(indicator_type):
|
|
||||||
styles = { 'sma': {'display': 'none'}, 'ema': {'display': 'none'}, 'rsi': {'display': 'none'}, 'macd': {'display': 'none'}, 'bb': {'display': 'none'}, 'stochastic': {'display': 'none'} }
|
|
||||||
message_style = {'display': 'block'} if not indicator_type else {'display': 'none'}
|
|
||||||
if indicator_type:
|
|
||||||
styles[indicator_type] = {'display': 'block'}
|
|
||||||
return [message_style] + list(styles.values())
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 7: Update Save Indicator Callback
|
|
||||||
|
|
||||||
**File**: `dashboard/callbacks/indicators.py`
|
|
||||||
|
|
||||||
In `save_new_indicator`, add `State` inputs for your parameters and logic to collect them.
|
|
||||||
|
|
||||||
```python
|
|
||||||
@app.callback(
|
|
||||||
# ... Outputs ...
|
|
||||||
Input('save-indicator-btn', 'n_clicks'),
|
|
||||||
[# ... States ...
|
|
||||||
State('stochastic-k-period-input', 'value'),
|
|
||||||
State('stochastic-d-period-input', 'value'),
|
|
||||||
State('edit-indicator-store', 'data')],
|
|
||||||
)
|
|
||||||
def save_new_indicator(n_clicks, name, indicator_type, ..., stochastic_k, stochastic_d, edit_data):
|
|
||||||
# ...
|
|
||||||
elif indicator_type == 'stochastic':
|
|
||||||
parameters = {'k_period': stochastic_k or 14, 'd_period': stochastic_d or 3}
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 8: Update Edit Callback Parameters
|
|
||||||
|
|
||||||
**File**: `dashboard/callbacks/indicators.py`
|
|
||||||
|
|
||||||
In `edit_indicator`, add `Output`s for your parameter fields and logic to load values.
|
|
||||||
|
|
||||||
```python
|
|
||||||
@app.callback(
|
|
||||||
[# ... Outputs ...
|
|
||||||
Output('stochastic-k-period-input', 'value'),
|
|
||||||
Output('stochastic-d-period-input', 'value')],
|
|
||||||
Input({'type': 'edit-indicator-btn', 'index': dash.ALL}, 'n_clicks'),
|
|
||||||
)
|
|
||||||
def edit_indicator(edit_clicks, button_ids):
|
|
||||||
# ...
|
|
||||||
stochastic_k, stochastic_d = 14, 3
|
|
||||||
if indicator:
|
|
||||||
# ...
|
|
||||||
elif indicator.type == 'stochastic':
|
|
||||||
stochastic_k = params.get('k_period', 14)
|
|
||||||
stochastic_d = params.get('d_period', 3)
|
|
||||||
return (..., stochastic_k, stochastic_d)
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 9: Update Reset Callback
|
|
||||||
|
|
||||||
**File**: `dashboard/callbacks/indicators.py`
|
|
||||||
|
|
||||||
In `reset_modal_form`, add `Output`s for your parameter fields and their default values.
|
|
||||||
|
|
||||||
```python
|
|
||||||
@app.callback(
|
|
||||||
[# ... Outputs ...
|
|
||||||
Output('stochastic-k-period-input', 'value', allow_duplicate=True),
|
|
||||||
Output('stochastic-d-period-input', 'value', allow_duplicate=True)],
|
|
||||||
Input('cancel-indicator-btn', 'n_clicks'),
|
|
||||||
)
|
|
||||||
def reset_modal_form(cancel_clicks):
|
|
||||||
# ...
|
|
||||||
return ..., 14, 3
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 10: Create Default Template
|
|
||||||
|
|
||||||
**File**: `components/charts/indicator_defaults.py`
|
|
||||||
|
|
||||||
Create a default template for your indicator.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def create_stochastic_template() -> UserIndicator:
|
|
||||||
return UserIndicator(
|
|
||||||
id=f"stochastic_{generate_short_id()}",
|
|
||||||
name="Stochastic 14,3",
|
|
||||||
type="stochastic",
|
|
||||||
display_type="subplot",
|
|
||||||
parameters={"k_period": 14, "d_period": 3},
|
|
||||||
styling=IndicatorStyling(color="#9c27b0", line_width=2)
|
|
||||||
)
|
|
||||||
|
|
||||||
DEFAULT_TEMPLATES = {
|
|
||||||
# ...
|
|
||||||
"stochastic": create_stochastic_template,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Step 11: Add Calculation Function (Optional)
|
|
||||||
|
|
||||||
**File**: `data/common/indicators.py`
|
|
||||||
|
|
||||||
Add a standalone calculation function.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def calculate_stochastic(df: pd.DataFrame, k_period: int = 14, d_period: int = 3) -> tuple:
|
|
||||||
lowest_low = df['low'].rolling(window=k_period).min()
|
|
||||||
highest_high = df['high'].rolling(window=k_period).max()
|
|
||||||
k_percent = 100 * ((df['close'] - lowest_low) / (highest_high - lowest_low))
|
|
||||||
d_percent = k_percent.rolling(window=d_period).mean()
|
|
||||||
return k_percent, d_percent
|
|
||||||
```
|
|
||||||
|
|
||||||
## File Change Summary
|
|
||||||
|
|
||||||
When adding a new indicator, you'll typically modify these files:
|
|
||||||
1. **`components/charts/layers/indicators.py`** or **`subplots.py`**
|
|
||||||
2. **`components/charts/layers/__init__.py`**
|
|
||||||
3. **`dashboard/components/indicator_modal.py`**
|
|
||||||
4. **`dashboard/callbacks/indicators.py`**
|
|
||||||
5. **`components/charts/indicator_defaults.py`**
|
|
||||||
6. **`data/common/indicators.py`** (optional)
|
|
||||||
@ -1,28 +1,228 @@
|
|||||||
# Technical Indicators Module
|
# Technical Indicators Module
|
||||||
|
|
||||||
The Technical Indicators module provides a suite of common technical analysis tools. It is designed to work efficiently with pandas DataFrames, which is the standard data structure for time-series analysis in the TCP Trading Platform.
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The module has been refactored into a dedicated package structure under `data/common/indicators/`. All calculation methods now expect a pandas DataFrame with a `DatetimeIndex` and the required OHLCV columns (`open`, `high`, `low`, `close`, `volume`). This change simplifies the data pipeline, improves performance through vectorization, and ensures consistency across the platform.
|
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.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
### Package Structure
|
### Package Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
data/common/indicators/
|
data/common/indicators/
|
||||||
├── __init__.py # Package exports
|
├── __init__.py # Package exports
|
||||||
├── technical.py # TechnicalIndicators class implementation
|
├── technical.py # Main facade class
|
||||||
├── result.py # IndicatorResult dataclass
|
├── base.py # Base indicator class
|
||||||
└── utils.py # Utility functions for configuration
|
├── result.py # Result container class
|
||||||
|
├── 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
|
||||||
```
|
```
|
||||||
|
|
||||||
The module implements five core technical indicators:
|
### Key Components
|
||||||
|
|
||||||
- **Simple Moving Average (SMA)**
|
#### 1. Base Classes
|
||||||
- **Exponential Moving Average (EMA)**
|
- **BaseIndicator**: Abstract base class providing common functionality
|
||||||
- **Relative Strength Index (RSI)**
|
- Data preparation
|
||||||
- **Moving Average Convergence Divergence (MACD)**
|
- Validation
|
||||||
- **Bollinger Bands**
|
- Error handling
|
||||||
|
- Logging
|
||||||
|
|
||||||
|
#### 2. Individual Indicators
|
||||||
|
Each indicator is implemented as a separate class inheriting from `BaseIndicator`:
|
||||||
|
- Focused responsibility
|
||||||
|
- Independent testing
|
||||||
|
- Easy maintenance
|
||||||
|
- Clear documentation
|
||||||
|
|
||||||
|
#### 3. TechnicalIndicators Facade
|
||||||
|
Main entry point providing:
|
||||||
|
- Unified interface
|
||||||
|
- Batch calculations
|
||||||
|
- Consistent error handling
|
||||||
|
- Data preparation
|
||||||
|
|
||||||
|
## Supported Indicators
|
||||||
|
|
||||||
|
### Simple Moving Average (SMA)
|
||||||
|
```python
|
||||||
|
from data.common.indicators import TechnicalIndicators
|
||||||
|
|
||||||
|
indicators = TechnicalIndicators()
|
||||||
|
results = indicators.sma(df, period=20, price_column='close')
|
||||||
|
```
|
||||||
|
- **Parameters**:
|
||||||
|
- `period`: Number of periods (default: 20)
|
||||||
|
- `price_column`: Column to average (default: 'close')
|
||||||
|
|
||||||
|
### Exponential Moving Average (EMA)
|
||||||
|
```python
|
||||||
|
results = indicators.ema(df, period=12, price_column='close')
|
||||||
|
```
|
||||||
|
- **Parameters**:
|
||||||
|
- `period`: Number of periods (default: 20)
|
||||||
|
- `price_column`: Column to average (default: 'close')
|
||||||
|
|
||||||
|
### Relative Strength Index (RSI)
|
||||||
|
```python
|
||||||
|
results = indicators.rsi(df, period=14, price_column='close')
|
||||||
|
```
|
||||||
|
- **Parameters**:
|
||||||
|
- `period`: Number of periods (default: 14)
|
||||||
|
- `price_column`: Column to analyze (default: 'close')
|
||||||
|
|
||||||
|
### Moving Average Convergence Divergence (MACD)
|
||||||
|
```python
|
||||||
|
results = indicators.macd(
|
||||||
|
df,
|
||||||
|
fast_period=12,
|
||||||
|
slow_period=26,
|
||||||
|
signal_period=9,
|
||||||
|
price_column='close'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
- **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')
|
||||||
|
|
||||||
|
### Bollinger Bands
|
||||||
|
```python
|
||||||
|
results = indicators.bollinger_bands(
|
||||||
|
df,
|
||||||
|
period=20,
|
||||||
|
std_dev=2.0,
|
||||||
|
price_column='close'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
- **Parameters**:
|
||||||
|
- `period`: SMA period (default: 20)
|
||||||
|
- `std_dev`: Standard deviation multiplier (default: 2.0)
|
||||||
|
- `price_column`: Column to analyze (default: 'close')
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
```python
|
||||||
|
from data.common.indicators import TechnicalIndicators
|
||||||
|
|
||||||
|
# Initialize calculator
|
||||||
|
indicators = TechnicalIndicators(logger=my_logger)
|
||||||
|
|
||||||
|
# Calculate single indicator
|
||||||
|
sma_results = indicators.sma(df, period=20)
|
||||||
|
|
||||||
|
# Access results
|
||||||
|
for result in sma_results:
|
||||||
|
print(f"Time: {result.timestamp}, SMA: {result.values['sma']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
results = indicators.calculate_multiple_indicators(df, config)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic Indicator Selection
|
||||||
|
```python
|
||||||
|
# Calculate any indicator by name
|
||||||
|
result = indicators.calculate(
|
||||||
|
'macd',
|
||||||
|
df,
|
||||||
|
fast_period=12,
|
||||||
|
slow_period=26,
|
||||||
|
signal_period=9
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Structures
|
||||||
|
|
||||||
|
### IndicatorResult
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The module provides comprehensive error handling:
|
||||||
|
- Input validation
|
||||||
|
- Data sufficiency checks
|
||||||
|
- Calculation error handling
|
||||||
|
- Detailed error logging
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
results = indicators.rsi(df, period=14)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"RSI calculation failed: {e}")
|
||||||
|
results = []
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Data Preparation**
|
||||||
|
- Uses pandas for vectorized calculations
|
||||||
|
- Handles sparse data efficiently
|
||||||
|
- Maintains timestamp alignment
|
||||||
|
|
||||||
|
2. **Memory Usage**
|
||||||
|
- Avoids unnecessary data copies
|
||||||
|
- Cleans up temporary calculations
|
||||||
|
- Uses efficient data structures
|
||||||
|
|
||||||
|
3. **Calculation Optimization**
|
||||||
|
- Vectorized operations where possible
|
||||||
|
- Minimal data transformations
|
||||||
|
- Efficient algorithm implementations
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The module includes comprehensive tests:
|
||||||
|
- Unit tests for each indicator
|
||||||
|
- Integration tests for the facade
|
||||||
|
- Edge case handling
|
||||||
|
- Performance benchmarks
|
||||||
|
|
||||||
|
Run tests with:
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/test_indicators.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
See [Adding New Indicators](./adding-new-indicators.md) for detailed instructions.
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
@ -136,153 +336,4 @@ The following details the parameters and the columns returned in the result Data
|
|||||||
### Bollinger Bands
|
### Bollinger Bands
|
||||||
|
|
||||||
- **Parameters**: `period` (int), `std_dev` (float), `price_column` (str, default: 'close')
|
- **Parameters**: `period` (int), `std_dev` (float), `price_column` (str, default: 'close')
|
||||||
- **Returned Columns**: `upper_band`, `middle_band`, `lower_band`
|
- **Returned Columns**: `upper_band`, `
|
||||||
|
|
||||||
## Data Structures
|
|
||||||
|
|
||||||
### IndicatorResult
|
|
||||||
|
|
||||||
The `IndicatorResult` class (from `data.common.indicators.result`) contains technical indicator calculation results:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@dataclass
|
|
||||||
class IndicatorResult:
|
|
||||||
timestamp: datetime # Right-aligned candle timestamp
|
|
||||||
symbol: str # Trading symbol (e.g., 'BTC-USDT')
|
|
||||||
timeframe: str # Candle timeframe (e.g., '1m', '5m')
|
|
||||||
values: Dict[str, float] # Indicator values
|
|
||||||
metadata: Optional[Dict[str, Any]] = None # Calculation metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration Management
|
|
||||||
|
|
||||||
The module provides utilities for managing indicator configurations (from `data.common.indicators.utils`):
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Create default configurations
|
|
||||||
config = create_default_indicators_config()
|
|
||||||
|
|
||||||
# Validate a configuration
|
|
||||||
is_valid = validate_indicator_config({
|
|
||||||
'type': 'sma',
|
|
||||||
'period': 20,
|
|
||||||
'price_column': 'close'
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration with TCP Platform
|
|
||||||
|
|
||||||
The indicators module is designed to work seamlessly with the platform's components:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from data.common.indicators import TechnicalIndicators
|
|
||||||
from data.common.data_types import OHLCVCandle
|
|
||||||
from components.charts.utils import prepare_chart_data
|
|
||||||
|
|
||||||
# Initialize calculator
|
|
||||||
indicators = TechnicalIndicators()
|
|
||||||
|
|
||||||
# Calculate indicators
|
|
||||||
results = indicators.calculate_multiple_indicators(df, {
|
|
||||||
'sma_20': {'type': 'sma', 'period': 20},
|
|
||||||
'rsi_14': {'type': 'rsi', 'period': 14}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Access results
|
|
||||||
for indicator_name, indicator_results in results.items():
|
|
||||||
for result in indicator_results:
|
|
||||||
print(f"{indicator_name}: {result.values}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration with the TCP Platform
|
|
||||||
|
|
||||||
The refactored `TechnicalIndicators` module is now tightly integrated with the `ChartBuilder`, which handles all data preparation and calculation automatically when indicators are added to a chart. For custom analysis or strategy development, you can use the class directly as shown in the examples above. The key is to always start with a properly prepared DataFrame using `prepare_chart_data`.
|
|
||||||
|
|
||||||
## Performance Considerations
|
|
||||||
|
|
||||||
### Memory Usage
|
|
||||||
- Process indicators in batches for large datasets
|
|
||||||
- Use appropriate period lengths to balance accuracy and performance
|
|
||||||
- Consider data retention policies for historical indicator values
|
|
||||||
|
|
||||||
### Calculation Frequency
|
|
||||||
- Calculate indicators only when new complete candles are available
|
|
||||||
- Cache recent indicator values to avoid recalculation
|
|
||||||
- Use incremental updates for real-time scenarios
|
|
||||||
|
|
||||||
### Optimization Tips
|
|
||||||
- Use `calculate_multiple_indicators()` for efficiency when computing multiple indicators
|
|
||||||
- Limit the number of historical candles to what's actually needed
|
|
||||||
- Consider using different timeframes for different indicators
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The module includes comprehensive error handling:
|
|
||||||
|
|
||||||
- **Insufficient Data**: Returns empty results when not enough data is available
|
|
||||||
- **Invalid Configuration**: Validates configuration parameters before calculation
|
|
||||||
- **Data Quality Issues**: Handles NaN values and missing data gracefully
|
|
||||||
- **Type Errors**: Converts data types safely with fallback values
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
The module includes comprehensive unit tests covering:
|
|
||||||
|
|
||||||
- All indicator calculations with known expected values
|
|
||||||
- Sparse data handling scenarios
|
|
||||||
- Edge cases (insufficient data, invalid parameters)
|
|
||||||
- Configuration validation
|
|
||||||
- Multiple indicator batch processing
|
|
||||||
|
|
||||||
Run tests with:
|
|
||||||
```bash
|
|
||||||
uv run pytest tests/test_indicators.py -v
|
|
||||||
```
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
Potential future additions to the indicators module:
|
|
||||||
|
|
||||||
- **Additional Indicators**: Stochastic, Williams %R, Commodity Channel Index
|
|
||||||
- **Custom Indicators**: Framework for user-defined indicators
|
|
||||||
- **Performance Metrics**: Calculation timing and memory usage statistics
|
|
||||||
- **Streaming Updates**: Incremental indicator updates for real-time scenarios
|
|
||||||
- **Parallel Processing**: Multi-threaded calculation for large datasets
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- [Aggregation Strategy Documentation](aggregation-strategy.md)
|
|
||||||
- [Data Types Documentation](data-types.md)
|
|
||||||
- [Database Schema Documentation](database-schema.md)
|
|
||||||
- [API Reference](api-reference.md)
|
|
||||||
|
|
||||||
## `TechnicalIndicators` Class
|
|
||||||
|
|
||||||
The main class for calculating technical indicators.
|
|
||||||
|
|
||||||
- **RSI**: `rsi(df, period=14, price_column='close')`
|
|
||||||
- **MACD**: `macd(df, fast_period=12, slow_period=26, signal_period=9, price_column='close')`
|
|
||||||
- **Bollinger Bands**: `bollinger_bands(df, period=20, std_dev=2.0, price_column='close')`
|
|
||||||
|
|
||||||
### `calculate_multiple_indicators`
|
|
||||||
|
|
||||||
Calculates multiple indicators in a single pass for efficiency.
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Configuration for multiple indicators
|
|
||||||
indicators_config = {
|
|
||||||
'sma_20': {'type': 'sma', 'period': 20},
|
|
||||||
'ema_50': {'type': 'ema', 'period': 50},
|
|
||||||
'rsi_14': {'type': 'rsi', 'period': 14}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Calculate all indicators
|
|
||||||
all_results = ti.calculate_multiple_indicators(candles, indicators_config)
|
|
||||||
|
|
||||||
print(f"SMA results: {len(all_results['sma_20'])}")
|
|
||||||
print(f"RSI results: {len(all_results['rsi_14'])}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sparse Data Handling
|
|
||||||
|
|
||||||
The `TechnicalIndicators` class is designed to handle sparse OHLCV data, which is a common scenario in real-time data collection.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user