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