- Introduced a comprehensive user indicator management system in `components/charts/indicator_manager.py`, allowing users to create, edit, and manage custom indicators with JSON persistence. - Added new default indicators in `components/charts/indicator_defaults.py` to provide users with immediate options for technical analysis. - Enhanced the chart rendering capabilities by implementing the `create_chart_with_indicators` function in `components/charts/builder.py`, supporting both overlay and subplot indicators. - Updated the main application layout in `app.py` to include a modal for adding and editing indicators, improving user interaction. - Enhanced documentation to cover the new indicator system, including a quick guide for adding new indicators and detailed usage examples. - Added unit tests to ensure the reliability and functionality of the new indicator management features.
393 lines
12 KiB
Markdown
393 lines
12 KiB
Markdown
# 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.
|
|
|
|
## Prerequisites
|
|
|
|
- Understanding of Python and technical analysis
|
|
- Familiarity with the project structure
|
|
- 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)
|
|
|
|
```python
|
|
class StochasticLayer(IndicatorLayer):
|
|
"""Stochastic Oscillator indicator implementation."""
|
|
|
|
def __init__(self, config: Dict[str, Any]):
|
|
super().__init__(config)
|
|
self.name = "stochastic"
|
|
self.display_type = "subplot" # or "overlay"
|
|
|
|
def calculate_values(self, df: pd.DataFrame) -> Dict[str, pd.Series]:
|
|
"""Calculate stochastic oscillator values."""
|
|
k_period = self.config.get('k_period', 14)
|
|
d_period = self.config.get('d_period', 3)
|
|
|
|
# Calculate %K and %D lines
|
|
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]:
|
|
"""Create plotly traces for stochastic oscillator."""
|
|
traces = []
|
|
|
|
# %K line
|
|
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)
|
|
)
|
|
))
|
|
|
|
# %D line
|
|
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`
|
|
|
|
```python
|
|
# Import the new class
|
|
from .subplots import StochasticLayer
|
|
|
|
# Add to appropriate registry
|
|
SUBPLOT_REGISTRY = {
|
|
'rsi': RSILayer,
|
|
'macd': MACDLayer,
|
|
'stochastic': StochasticLayer, # Add this line
|
|
}
|
|
|
|
# For overlay indicators, add to INDICATOR_REGISTRY instead
|
|
INDICATOR_REGISTRY = {
|
|
'sma': SMALayer,
|
|
'ema': EMALayer,
|
|
'bollinger_bands': BollingerBandsLayer,
|
|
'stochastic': StochasticLayer, # Only if overlay
|
|
}
|
|
```
|
|
|
|
### ✅ Step 4: Add UI Dropdown Option
|
|
|
|
**File**: `app.py` (in the indicator type dropdown)
|
|
|
|
```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'}, # Add this
|
|
]
|
|
)
|
|
```
|
|
|
|
### ✅ Step 5: Add Parameter Fields to Modal
|
|
|
|
**File**: `app.py` (in the modal parameters section)
|
|
|
|
```python
|
|
# Add parameter section for stochastic
|
|
html.Div([
|
|
html.Div([
|
|
html.Label("%K Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}),
|
|
dcc.Input(
|
|
id='stochastic-k-period-input',
|
|
type='number',
|
|
value=14,
|
|
min=5, max=50,
|
|
style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'}
|
|
)
|
|
], style={'margin-bottom': '10px'}),
|
|
html.Div([
|
|
html.Label("%D Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}),
|
|
dcc.Input(
|
|
id='stochastic-d-period-input',
|
|
type='number',
|
|
value=3,
|
|
min=2, max=10,
|
|
style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'}
|
|
)
|
|
]),
|
|
html.P("Stochastic oscillator periods for %K and %D lines",
|
|
style={'color': '#7f8c8d', 'font-size': '12px', 'margin-top': '5px'})
|
|
], id='stochastic-parameters', style={'display': 'none', 'margin-bottom': '10px'})
|
|
```
|
|
|
|
### ✅ Step 6: Update Parameter Visibility Callback
|
|
|
|
**File**: `app.py` (in `update_parameter_fields` callback)
|
|
|
|
```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')], # Add this output
|
|
Input('indicator-type-dropdown', 'value'),
|
|
prevent_initial_call=True
|
|
)
|
|
def update_parameter_fields(indicator_type):
|
|
# ... existing code ...
|
|
|
|
# Add stochastic style
|
|
stochastic_style = hidden_style
|
|
|
|
# Show the relevant parameter section
|
|
if indicator_type == 'sma':
|
|
sma_style = visible_style
|
|
elif indicator_type == 'ema':
|
|
ema_style = visible_style
|
|
elif indicator_type == 'rsi':
|
|
rsi_style = visible_style
|
|
elif indicator_type == 'macd':
|
|
macd_style = visible_style
|
|
elif indicator_type == 'bollinger_bands':
|
|
bb_style = visible_style
|
|
elif indicator_type == 'stochastic': # Add this
|
|
stochastic_style = visible_style
|
|
|
|
return message_style, sma_style, ema_style, rsi_style, macd_style, bb_style, stochastic_style
|
|
```
|
|
|
|
### ✅ Step 7: Update Save Indicator Callback
|
|
|
|
**File**: `app.py` (in `save_new_indicator` callback)
|
|
|
|
```python
|
|
# Add stochastic parameters to State inputs
|
|
State('stochastic-k-period-input', 'value'),
|
|
State('stochastic-d-period-input', 'value'),
|
|
|
|
# Add to parameter collection logic
|
|
def save_new_indicator(n_clicks, name, indicator_type, description, color, line_width,
|
|
sma_period, ema_period, rsi_period,
|
|
macd_fast, macd_slow, macd_signal,
|
|
bb_period, bb_stddev,
|
|
stochastic_k, stochastic_d, # Add these
|
|
edit_data):
|
|
|
|
# ... existing code ...
|
|
|
|
elif indicator_type == 'stochastic':
|
|
parameters = {
|
|
'k_period': stochastic_k or 14,
|
|
'd_period': stochastic_d or 3
|
|
}
|
|
```
|
|
|
|
### ✅ Step 8: Update Edit Callback Parameters
|
|
|
|
**File**: `app.py` (in `edit_indicator` callback)
|
|
|
|
```python
|
|
# Add output for stochastic parameters
|
|
Output('stochastic-k-period-input', 'value'),
|
|
Output('stochastic-d-period-input', 'value'),
|
|
|
|
# Add parameter loading logic
|
|
elif indicator.type == 'stochastic':
|
|
stochastic_k = params.get('k_period', 14)
|
|
stochastic_d = params.get('d_period', 3)
|
|
|
|
# Add to return statement
|
|
return (
|
|
"✏️ Edit Indicator",
|
|
indicator.name,
|
|
indicator.type,
|
|
indicator.description,
|
|
indicator.styling.color,
|
|
edit_data,
|
|
sma_period,
|
|
ema_period,
|
|
rsi_period,
|
|
macd_fast,
|
|
macd_slow,
|
|
macd_signal,
|
|
bb_period,
|
|
bb_stddev,
|
|
stochastic_k, # Add these
|
|
stochastic_d
|
|
)
|
|
```
|
|
|
|
### ✅ Step 9: Update Reset Callback
|
|
|
|
**File**: `app.py` (in `reset_modal_form` callback)
|
|
|
|
```python
|
|
# Add outputs
|
|
Output('stochastic-k-period-input', 'value', allow_duplicate=True),
|
|
Output('stochastic-d-period-input', 'value', allow_duplicate=True),
|
|
|
|
# Add default values to return
|
|
return "", None, "", "#007bff", 2, "📊 Add New Indicator", None, 20, 12, 14, 12, 26, 9, 20, 2.0, 14, 3
|
|
```
|
|
|
|
### ✅ Step 10: Create Default Template
|
|
|
|
**File**: `components/charts/indicator_defaults.py`
|
|
|
|
```python
|
|
def create_stochastic_template() -> UserIndicator:
|
|
"""Create default Stochastic Oscillator template."""
|
|
return UserIndicator(
|
|
id=f"stochastic_{generate_short_id()}",
|
|
name="Stochastic 14,3",
|
|
description="14-period %K with 3-period %D smoothing",
|
|
type="stochastic",
|
|
display_type="subplot",
|
|
parameters={
|
|
"k_period": 14,
|
|
"d_period": 3
|
|
},
|
|
styling=IndicatorStyling(
|
|
color="#9c27b0",
|
|
line_width=2
|
|
)
|
|
)
|
|
|
|
# Add to DEFAULT_TEMPLATES
|
|
DEFAULT_TEMPLATES = {
|
|
"sma": create_sma_template,
|
|
"ema": create_ema_template,
|
|
"rsi": create_rsi_template,
|
|
"macd": create_macd_template,
|
|
"bollinger_bands": create_bollinger_bands_template,
|
|
"stochastic": create_stochastic_template, # Add this
|
|
}
|
|
```
|
|
|
|
### ✅ Step 11: Add Calculation Function (Optional)
|
|
|
|
**File**: `data/common/indicators.py`
|
|
|
|
```python
|
|
def calculate_stochastic(df: pd.DataFrame, k_period: int = 14, d_period: int = 3) -> tuple:
|
|
"""Calculate Stochastic Oscillator (%K and %D)."""
|
|
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
|
|
```
|
|
|
|
## Testing Checklist
|
|
|
|
- [ ] Indicator appears in dropdown
|
|
- [ ] Parameter fields show/hide correctly
|
|
- [ ] Default values are set properly
|
|
- [ ] Indicator saves and loads correctly
|
|
- [ ] Edit functionality works
|
|
- [ ] Chart updates with indicator
|
|
- [ ] Delete functionality works
|
|
- [ ] Error handling works with insufficient data
|
|
|
|
## Common Patterns
|
|
|
|
### Single Line Overlay
|
|
```python
|
|
# Simple indicators like SMA, EMA
|
|
def create_traces(self, df: pd.DataFrame, values: Dict[str, pd.Series]) -> List[go.Scatter]:
|
|
return [go.Scatter(
|
|
x=df.index,
|
|
y=values['indicator_name'],
|
|
mode='lines',
|
|
name=self.config.get('name', 'Indicator'),
|
|
line=dict(color=self.config.get('color', '#007bff'))
|
|
)]
|
|
```
|
|
|
|
### Multi-Line Subplot
|
|
```python
|
|
# Complex indicators like MACD, Stochastic
|
|
def create_traces(self, df: pd.DataFrame, values: Dict[str, pd.Series]) -> List[go.Scatter]:
|
|
traces = []
|
|
for key, series in values.items():
|
|
traces.append(go.Scatter(
|
|
x=df.index,
|
|
y=series,
|
|
mode='lines',
|
|
name=f"{key.title()}"
|
|
))
|
|
return traces
|
|
```
|
|
|
|
### Band Indicators
|
|
```python
|
|
# Indicators with bands like Bollinger Bands
|
|
def create_traces(self, df: pd.DataFrame, values: Dict[str, pd.Series]) -> List[go.Scatter]:
|
|
return [
|
|
# Upper band
|
|
go.Scatter(x=df.index, y=values['upper'], name='Upper'),
|
|
# Middle line
|
|
go.Scatter(x=df.index, y=values['middle'], name='Middle'),
|
|
# Lower band with fill
|
|
go.Scatter(x=df.index, y=values['lower'], name='Lower',
|
|
fill='tonexty', fillcolor='rgba(0,123,255,0.1)')
|
|
]
|
|
```
|
|
|
|
## File Change Summary
|
|
|
|
When adding a new indicator, you'll typically modify these files:
|
|
|
|
1. **`components/charts/layers/indicators.py`** or **`subplots.py`** - Indicator class
|
|
2. **`components/charts/layers/__init__.py`** - Registry registration
|
|
3. **`app.py`** - UI dropdown, parameter fields, callbacks
|
|
4. **`components/charts/indicator_defaults.py`** - Default template
|
|
5. **`data/common/indicators.py`** - Calculation function (optional)
|
|
|
|
## Tips
|
|
|
|
- Start with a simple single-line indicator first
|
|
- Test each step before moving to the next
|
|
- Use existing indicators as templates
|
|
- Check console/logs for errors
|
|
- Test with different parameter values
|
|
- Verify calculations with known data |