TCPDashboard/docs/guides/adding-new-indicators.md
Vasily.onl 3e0e89b826 Refactor indicator management to a data-driven approach
- Introduced dynamic generation of parameter fields and callback handling for indicators, enhancing modularity and maintainability.
- Updated `config_utils.py` with new utility functions to load indicator templates and generate dynamic outputs and states for parameter fields.
- Refactored `indicators.py` to utilize these utilities, streamlining the callback logic and improving user experience by reducing hardcoded elements.
- Modified `indicator_modal.py` to create parameter fields dynamically based on JSON templates, eliminating the need for manual updates when adding new indicators.
- Added documentation outlining the new data-driven architecture for indicators, improving clarity and guidance for future development.

These changes significantly enhance the flexibility and scalability of the indicator system, aligning with project goals for maintainability and performance.
2025-06-11 19:09:52 +08:00

10 KiB

Adding New Indicators Guide

Overview

This guide provides comprehensive instructions for adding new technical indicators to the Crypto Trading Bot Dashboard. The system uses a modular approach where each indicator is implemented as a separate class inheriting from BaseIndicator.

Table of Contents

  1. Prerequisites
  2. Implementation Steps
  3. Integration with Charts
  4. Best Practices
  5. Testing Guidelines
  6. Common Pitfalls
  7. Example Implementation

Prerequisites

  • Python knowledge with pandas/numpy
  • Understanding of technical analysis concepts
  • Familiarity with the project structure
  • Knowledge of the indicator's mathematical formula
  • Understanding of the dashboard's chart system

Implementation Steps

1. Create Indicator Class

Create a new file in data/common/indicators/implementations/ named after your indicator (e.g., stochastic.py):

from typing import Dict, Any, List
import pandas as pd
from ..base import BaseIndicator
from ..result import IndicatorResult

class StochasticIndicator(BaseIndicator):
    """
    Stochastic Oscillator implementation.
    
    The Stochastic Oscillator is a momentum indicator comparing a particular closing price 
    of a security to a range of its prices over a certain period of time.
    """
    
    def __init__(self, logger=None):
        super().__init__(logger)
        self.name = "stochastic"
        
    def calculate(self, df: pd.DataFrame, k_period: int = 14, 
                 d_period: int = 3, price_column: str = 'close') -> List[IndicatorResult]:
        """
        Calculate Stochastic Oscillator.
        
        Args:
            df: DataFrame with OHLCV data
            k_period: The K period (default: 14)
            d_period: The D period (default: 3)
            price_column: Column to use for calculations (default: 'close')
            
        Returns:
            List of IndicatorResult objects containing %K and %D values
        """
        try:
            # Validate inputs
            self._validate_dataframe(df)
            self._validate_period(k_period, min_value=2)
            self._validate_period(d_period, min_value=2)
            
            # Calculate %K
            lowest_low = df['low'].rolling(window=k_period).min()
            highest_high = df['high'].rolling(window=k_period).max()
            k_percent = 100 * ((df[price_column] - lowest_low) / 
                             (highest_high - lowest_low))
            
            # Calculate %D (signal line)
            d_percent = k_percent.rolling(window=d_period).mean()
            
            # Create results
            results = []
            for idx, row in df.iterrows():
                if pd.notna(k_percent[idx]) and pd.notna(d_percent[idx]):
                    results.append(IndicatorResult(
                        timestamp=idx,
                        symbol=self._get_symbol(df),
                        timeframe=self._get_timeframe(df),
                        values={
                            'k_percent': float(k_percent[idx]),
                            'd_percent': float(d_percent[idx])
                        },
                        metadata={
                            'k_period': k_period,
                            'd_period': d_period
                        }
                    ))
            
            return results
            
        except Exception as e:
            self._handle_error(f"Error calculating Stochastic: {str(e)}")
            return []

2. Register the Indicator

Add your indicator to data/common/indicators/implementations/__init__.py:

from .stochastic import StochasticIndicator

__all__ = [
    'SMAIndicator',
    'EMAIndicator',
    'RSIIndicator',
    'MACDIndicator',
    'BollingerBandsIndicator',
    'StochasticIndicator'
]

3. Add to TechnicalIndicators Class

Update data/common/indicators/technical.py:

class TechnicalIndicators:
    def __init__(self, logger=None):
        self.logger = logger
        # ... existing indicators ...
        self._stochastic = StochasticIndicator(logger)
    
    def stochastic(self, df: pd.DataFrame, k_period: int = 14,
                  d_period: int = 3, price_column: str = 'close') -> List[IndicatorResult]:
        """
        Calculate Stochastic Oscillator.
        
        Args:
            df: DataFrame with OHLCV data
            k_period: The K period (default: 14)
            d_period: The D period (default: 3)
            price_column: Column to use (default: 'close')
            
        Returns:
            List of indicator results with %K and %D values
        """
        return self._stochastic.calculate(
            df,
            k_period=k_period,
            d_period=d_period,
            price_column=price_column
        )

Integration with Charts

1. Create Chart Layer

Create a new layer class in components/charts/layers/indicators.py (overlay) or components/charts/layers/subplots.py (subplot):

class StochasticLayer(IndicatorLayer):
    def __init__(self, config: Dict[str, Any]):
        super().__init__(config)
        self.name = "stochastic"
        self.display_type = "subplot"
    
    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

2. Register in Layer Registry

Update components/charts/layers/__init__.py:

SUBPLOT_REGISTRY = {
    'rsi': RSILayer,
    'macd': MACDLayer,
    'stochastic': StochasticLayer,
}

3. Add UI Components

(No longer needed - UI is dynamically generated from JSON templates)

Best Practices

Code Quality

  • Follow the project's coding style
  • Add comprehensive docstrings
  • Include type hints
  • Handle edge cases gracefully
  • Use vectorized operations where possible

Error Handling

  • Validate all input parameters
  • Check for sufficient data
  • Handle NaN values appropriately
  • Log errors with meaningful messages
  • Return empty results for invalid inputs

Performance

  • Use vectorized operations
  • Avoid unnecessary loops
  • Clean up temporary calculations
  • Consider memory usage
  • Cache results when appropriate

Documentation

  • Document all public methods
  • Include usage examples
  • Explain parameter ranges
  • Document any assumptions
  • Keep documentation up-to-date

Testing Guidelines

Test File Structure

Create tests/indicators/test_stochastic.py:

import pytest
import pandas as pd
import numpy as np
from data.common.indicators import TechnicalIndicators

@pytest.fixture
def sample_data():
    return pd.DataFrame({
        'open': [10, 11, 12, 13, 14],
        'high': [12, 13, 14, 15, 16],
        'low': [8, 9, 10, 11, 12],
        'close': [11, 12, 13, 14, 15],
        'volume': [100, 110, 120, 130, 140]
    }, index=pd.date_range('2023-01-01', periods=5))

def test_stochastic_calculation(sample_data):
    indicators = TechnicalIndicators()
    results = indicators.stochastic(sample_data, k_period=3, d_period=2)
    
    assert len(results) > 0
    for result in results:
        assert 0 <= result.values['k_percent'] <= 100
        assert 0 <= result.values['d_percent'] <= 100

Testing Checklist

  • Basic functionality with ideal data
  • Edge cases (insufficient data, NaN values)
  • Performance with large datasets
  • Error handling
  • Parameter validation
  • Integration with TechnicalIndicators class
  • Chart layer rendering
  • UI interaction

Running Tests

# Run all indicator tests
uv run pytest tests/indicators/

# Run specific indicator tests
uv run pytest tests/indicators/test_stochastic.py

# Run with coverage
uv run pytest tests/indicators/ --cov=data.common.indicators

Common Pitfalls

  1. Insufficient Data Handling

    • Always check if enough data points are available
    • Return empty results rather than partial calculations
    • Consider the impact of NaN values
  2. NaN Handling

    • Use appropriate pandas NaN handling methods
    • Don't propagate NaN values unnecessarily
    • Document NaN handling behavior
  3. Memory Leaks

    • Clean up temporary DataFrames
    • Avoid storing large datasets
    • Use efficient data structures
  4. Performance Issues

    • Use vectorized operations instead of loops
    • Profile code with large datasets
    • Consider caching strategies
  5. UI Integration

    • Handle all parameter combinations
    • Provide meaningful validation
    • Give clear user feedback

Example Implementation

See the complete Stochastic Oscillator implementation above as a reference. Key points:

  1. Modular Structure

    • Separate indicator class
    • Clear inheritance hierarchy
    • Focused responsibility
  2. Error Handling

    • Input validation
    • Exception handling
    • Meaningful error messages
  3. Performance

    • Vectorized calculations
    • Efficient data structures
    • Memory management
  4. Testing

    • Comprehensive test cases
    • Edge case handling
    • Performance verification

Support

For questions or issues:

  1. Check existing documentation
  2. Review test cases
  3. Consult with team members
  4. Create detailed bug reports if needed