TCPDashboard/docs/modules/charts/adding-new-indicators.md
2025-06-06 20:33:29 +08:00

8.3 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, 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.

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.

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.

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.

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.

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

@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 Outputs for your parameter fields and logic to load values.

@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 Outputs for your parameter fields and their default values.

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

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.

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)