# 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