TCPDashboard/docs/components/charts/adding-new-indicators.md
Vasily.onl 476bd67f14 3.4 Implement user-defined indicator management system and enhance chart capabilities
- 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.
2025-06-04 13:01:57 +08:00

12 KiB

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)

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

# 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)

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)

# 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)

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

# 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)

# 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)

# 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

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

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

# 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

# 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

# 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