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.
This commit is contained in:
Vasily.onl 2025-06-04 13:01:57 +08:00
parent d71cb763bc
commit 476bd67f14
25 changed files with 3160 additions and 55 deletions

1237
app.py

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ Main Components:
"""
import plotly.graph_objects as go
from typing import List
from .builder import ChartBuilder
from .utils import (
validate_market_data,
@ -137,7 +138,8 @@ __all__ = [
# Convenience functions
"create_basic_chart",
"create_indicator_chart"
"create_indicator_chart",
"create_chart_with_indicators"
]
# Initialize logger
@ -447,4 +449,27 @@ def create_indicator_chart(symbol: str, data: list,
return create_basic_chart(symbol, data, indicators=[indicator_config])
except Exception as e:
return create_basic_chart(symbol, data, indicators=[]) # Fallback to basic chart
return create_basic_chart(symbol, data, indicators=[]) # Fallback to basic chart
def create_chart_with_indicators(symbol: str, timeframe: str,
overlay_indicators: List[str] = None,
subplot_indicators: List[str] = None,
days_back: int = 7, **kwargs) -> go.Figure:
"""
Create a chart with dynamically selected indicators.
Args:
symbol: Trading pair (e.g., 'BTC-USDT')
timeframe: Timeframe (e.g., '1h', '1d')
overlay_indicators: List of overlay indicator names
subplot_indicators: List of subplot indicator names
days_back: Number of days to look back
**kwargs: Additional chart parameters
Returns:
Plotly figure with selected indicators
"""
builder = ChartBuilder()
return builder.create_chart_with_indicators(
symbol, timeframe, overlay_indicators, subplot_indicators, days_back, **kwargs
)

View File

@ -349,4 +349,253 @@ class ChartBuilder:
'data_age_minutes': None,
'sufficient_for_indicators': False,
'message': f"Error checking data: {str(e)}"
}
}
def create_chart_with_indicators(self, symbol: str, timeframe: str,
overlay_indicators: List[str] = None,
subplot_indicators: List[str] = None,
days_back: int = 7, **kwargs) -> go.Figure:
"""
Create a chart with dynamically selected indicators.
Args:
symbol: Trading pair
timeframe: Timeframe
overlay_indicators: List of overlay indicator names
subplot_indicators: List of subplot indicator names
days_back: Number of days to look back
**kwargs: Additional chart parameters
Returns:
Plotly Figure object with selected indicators
"""
try:
# Fetch market data
candles = self.fetch_market_data_enhanced(symbol, timeframe, days_back)
if not candles:
self.logger.warning(f"No data available for {symbol} {timeframe}")
return self._create_empty_chart(f"No data available for {symbol} {timeframe}")
# Validate and prepare data
if not validate_market_data(candles):
self.logger.error(f"Invalid market data for {symbol} {timeframe}")
return self._create_error_chart("Invalid market data format")
df = prepare_chart_data(candles)
# Import layer classes
from .layers import (
LayerManager, CandlestickLayer, VolumeLayer,
SMALayer, EMALayer, BollingerBandsLayer,
RSILayer, MACDLayer, IndicatorLayerConfig
)
from .indicator_manager import get_indicator_manager
# Get user indicators instead of default configurations
indicator_manager = get_indicator_manager()
# Calculate subplot requirements
subplot_count = 0
volume_enabled = 'volume' in df.columns and df['volume'].sum() > 0
if volume_enabled:
subplot_count += 1
if subplot_indicators:
subplot_count += len(subplot_indicators)
# Create subplot structure if needed
if subplot_count > 0:
# Calculate height ratios
main_height = 0.7 # Main chart gets 70%
subplot_height = 0.3 / subplot_count if subplot_count > 0 else 0
# Create subplot specifications
subplot_specs = [[{"secondary_y": False}]] # Main chart
row_heights = [main_height]
if volume_enabled:
subplot_specs.append([{"secondary_y": False}])
row_heights.append(subplot_height)
if subplot_indicators:
for _ in subplot_indicators:
subplot_specs.append([{"secondary_y": False}])
row_heights.append(subplot_height)
# Create subplots figure
from plotly.subplots import make_subplots
fig = make_subplots(
rows=len(subplot_specs),
cols=1,
shared_xaxes=True,
vertical_spacing=0.02,
row_heights=row_heights,
specs=subplot_specs,
subplot_titles=[f"{symbol} - {timeframe}"] + [""] * (len(subplot_specs) - 1)
)
else:
# Create simple figure for main chart only
fig = go.Figure()
current_row = 1
# Add candlestick layer (always included)
candlestick_trace = go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name=symbol,
increasing_line_color=self.default_colors['bullish'],
decreasing_line_color=self.default_colors['bearish'],
showlegend=False
)
fig.add_trace(candlestick_trace, row=current_row, col=1)
# Add overlay indicators
if overlay_indicators:
for indicator_id in overlay_indicators:
try:
# Load user indicator
user_indicator = indicator_manager.load_indicator(indicator_id)
if user_indicator is None:
self.logger.warning(f"Overlay indicator {indicator_id} not found")
continue
# Create appropriate indicator layer using user configuration
if user_indicator.type == 'sma':
period = user_indicator.parameters.get('period', 20)
layer_config = IndicatorLayerConfig(
name=user_indicator.name,
indicator_type='sma',
color=user_indicator.styling.color,
parameters={'period': period},
line_width=user_indicator.styling.line_width
)
sma_layer = SMALayer(layer_config)
traces = sma_layer.create_traces(df.to_dict('records'))
for trace in traces:
fig.add_trace(trace, row=current_row, col=1)
elif user_indicator.type == 'ema':
period = user_indicator.parameters.get('period', 12)
layer_config = IndicatorLayerConfig(
name=user_indicator.name,
indicator_type='ema',
color=user_indicator.styling.color,
parameters={'period': period},
line_width=user_indicator.styling.line_width
)
ema_layer = EMALayer(layer_config)
traces = ema_layer.create_traces(df.to_dict('records'))
for trace in traces:
fig.add_trace(trace, row=current_row, col=1)
elif user_indicator.type == 'bollinger_bands':
period = user_indicator.parameters.get('period', 20)
std_dev = user_indicator.parameters.get('std_dev', 2.0)
layer_config = IndicatorLayerConfig(
name=user_indicator.name,
indicator_type='bollinger_bands',
color=user_indicator.styling.color,
parameters={'period': period, 'std_dev': std_dev},
line_width=user_indicator.styling.line_width,
show_middle_line=True
)
bb_layer = BollingerBandsLayer(layer_config)
traces = bb_layer.create_traces(df.to_dict('records'))
for trace in traces:
fig.add_trace(trace, row=current_row, col=1)
self.logger.debug(f"Added overlay indicator: {user_indicator.name}")
except Exception as e:
self.logger.error(f"Error adding overlay indicator {indicator_id}: {e}")
# Move to next row for volume if enabled
if volume_enabled:
current_row += 1
volume_colors = [self.default_colors['bullish'] if close >= open else self.default_colors['bearish']
for close, open in zip(df['close'], df['open'])]
volume_trace = go.Bar(
x=df['timestamp'],
y=df['volume'],
name='Volume',
marker_color=volume_colors,
opacity=0.7,
showlegend=False
)
fig.add_trace(volume_trace, row=current_row, col=1)
fig.update_yaxes(title_text="Volume", row=current_row, col=1)
# Add subplot indicators
if subplot_indicators:
for indicator_id in subplot_indicators:
current_row += 1
try:
# Load user indicator
user_indicator = indicator_manager.load_indicator(indicator_id)
if user_indicator is None:
self.logger.warning(f"Subplot indicator {indicator_id} not found")
continue
# Create appropriate subplot indicator layer
if user_indicator.type == 'rsi':
period = user_indicator.parameters.get('period', 14)
rsi_layer = RSILayer(period=period, color=user_indicator.styling.color, name=user_indicator.name)
# Use the render method
fig = rsi_layer.render(fig, df, row=current_row, col=1)
# Add RSI reference lines
fig.add_hline(y=70, line_dash="dash", line_color="red", opacity=0.5, row=current_row, col=1)
fig.add_hline(y=30, line_dash="dash", line_color="green", opacity=0.5, row=current_row, col=1)
fig.update_yaxes(title_text="RSI", range=[0, 100], row=current_row, col=1)
elif user_indicator.type == 'macd':
fast_period = user_indicator.parameters.get('fast_period', 12)
slow_period = user_indicator.parameters.get('slow_period', 26)
signal_period = user_indicator.parameters.get('signal_period', 9)
macd_layer = MACDLayer(fast_period=fast_period, slow_period=slow_period,
signal_period=signal_period, color=user_indicator.styling.color, name=user_indicator.name)
# Use the render method
fig = macd_layer.render(fig, df, row=current_row, col=1)
# Add zero line for MACD
fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5, row=current_row, col=1)
fig.update_yaxes(title_text="MACD", row=current_row, col=1)
self.logger.debug(f"Added subplot indicator: {user_indicator.name}")
except Exception as e:
self.logger.error(f"Error adding subplot indicator {indicator_id}: {e}")
# Update layout
height = kwargs.get('height', self.default_height)
template = kwargs.get('template', self.default_template)
fig.update_layout(
title=f"{symbol} - {timeframe} Chart",
template=template,
height=height,
showlegend=True,
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
xaxis_rangeslider_visible=False,
hovermode='x unified'
)
# Update x-axis for all subplots
fig.update_xaxes(title_text="Time", row=current_row, col=1)
fig.update_yaxes(title_text="Price (USDT)", row=1, col=1)
indicator_count = len(overlay_indicators or []) + len(subplot_indicators or [])
self.logger.debug(f"Created chart for {symbol} {timeframe} with {indicator_count} indicators")
return fig
except Exception as e:
self.logger.error(f"Error creating chart with indicators: {e}")
return self._create_error_chart(f"Chart creation failed: {str(e)}")

View File

@ -0,0 +1,133 @@
"""
Default Indicator Creation
This module creates a set of default indicators that users can start with.
These are common indicator configurations that are immediately useful.
"""
from .indicator_manager import get_indicator_manager, IndicatorType, DisplayType
def create_default_indicators():
"""Create default indicators if they don't exist."""
manager = get_indicator_manager()
# Check if we already have indicators
existing_indicators = manager.list_indicators()
if existing_indicators:
manager.logger.info(f"Found {len(existing_indicators)} existing indicators, skipping defaults creation")
return
# Define default indicators
default_indicators = [
# Moving Averages
{
"name": "SMA 20",
"description": "20-period Simple Moving Average for short-term trend",
"type": IndicatorType.SMA.value,
"parameters": {"period": 20},
"color": "#007bff"
},
{
"name": "SMA 50",
"description": "50-period Simple Moving Average for medium-term trend",
"type": IndicatorType.SMA.value,
"parameters": {"period": 50},
"color": "#6c757d"
},
{
"name": "EMA 12",
"description": "12-period Exponential Moving Average for fast signals",
"type": IndicatorType.EMA.value,
"parameters": {"period": 12},
"color": "#ff6b35"
},
{
"name": "EMA 26",
"description": "26-period Exponential Moving Average for slower signals",
"type": IndicatorType.EMA.value,
"parameters": {"period": 26},
"color": "#28a745"
},
# Oscillators
{
"name": "RSI 14",
"description": "14-period RSI for momentum analysis",
"type": IndicatorType.RSI.value,
"parameters": {"period": 14},
"color": "#20c997"
},
{
"name": "RSI 21",
"description": "21-period RSI for less sensitive momentum signals",
"type": IndicatorType.RSI.value,
"parameters": {"period": 21},
"color": "#17a2b8"
},
# MACD Variants
{
"name": "MACD Standard",
"description": "Standard MACD (12, 26, 9) for trend changes",
"type": IndicatorType.MACD.value,
"parameters": {"fast_period": 12, "slow_period": 26, "signal_period": 9},
"color": "#fd7e14"
},
{
"name": "MACD Fast",
"description": "Fast MACD (5, 13, 4) for quick signals",
"type": IndicatorType.MACD.value,
"parameters": {"fast_period": 5, "slow_period": 13, "signal_period": 4},
"color": "#dc3545"
},
# Bollinger Bands
{
"name": "Bollinger Bands",
"description": "Standard Bollinger Bands (20, 2) for volatility analysis",
"type": IndicatorType.BOLLINGER_BANDS.value,
"parameters": {"period": 20, "std_dev": 2.0},
"color": "#6f42c1"
},
{
"name": "Bollinger Tight",
"description": "Tight Bollinger Bands (20, 1.5) for sensitive volatility",
"type": IndicatorType.BOLLINGER_BANDS.value,
"parameters": {"period": 20, "std_dev": 1.5},
"color": "#e83e8c"
}
]
# Create indicators
created_count = 0
for indicator_config in default_indicators:
indicator = manager.create_indicator(
name=indicator_config["name"],
indicator_type=indicator_config["type"],
parameters=indicator_config["parameters"],
description=indicator_config["description"],
color=indicator_config["color"]
)
if indicator:
created_count += 1
manager.logger.info(f"Created default indicator: {indicator.name}")
else:
manager.logger.error(f"Failed to create indicator: {indicator_config['name']}")
manager.logger.info(f"Created {created_count} default indicators")
def ensure_default_indicators():
"""Ensure default indicators exist (called during app startup)."""
try:
create_default_indicators()
except Exception as e:
manager = get_indicator_manager()
manager.logger.error(f"Error creating default indicators: {e}")
if __name__ == "__main__":
# Create defaults when run directly
create_default_indicators()

View File

@ -0,0 +1,446 @@
"""
Indicator Management System
This module provides functionality to manage user-defined indicators with
file-based storage. Each indicator is saved as a separate JSON file for
portability and easy sharing.
"""
import json
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
from utils.logger import get_logger
# Initialize logger
logger = get_logger("indicator_manager")
# Base directory for indicators
INDICATORS_DIR = Path("config/indicators")
USER_INDICATORS_DIR = INDICATORS_DIR / "user_indicators"
TEMPLATES_DIR = INDICATORS_DIR / "templates"
class IndicatorType(str, Enum):
"""Supported indicator types."""
SMA = "sma"
EMA = "ema"
RSI = "rsi"
MACD = "macd"
BOLLINGER_BANDS = "bollinger_bands"
class DisplayType(str, Enum):
"""Chart display types for indicators."""
OVERLAY = "overlay"
SUBPLOT = "subplot"
@dataclass
class IndicatorStyling:
"""Styling configuration for indicators."""
color: str = "#007bff"
line_width: int = 2
opacity: float = 1.0
line_style: str = "solid" # solid, dash, dot, dashdot
@dataclass
class UserIndicator:
"""User-defined indicator configuration."""
id: str
name: str
description: str
type: str # IndicatorType
display_type: str # DisplayType
parameters: Dict[str, Any]
styling: IndicatorStyling
visible: bool = True
created_date: str = ""
modified_date: str = ""
def __post_init__(self):
"""Initialize timestamps if not provided."""
current_time = datetime.now(timezone.utc).isoformat()
if not self.created_date:
self.created_date = current_time
if not self.modified_date:
self.modified_date = current_time
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'name': self.name,
'description': self.description,
'type': self.type,
'display_type': self.display_type,
'parameters': self.parameters,
'styling': asdict(self.styling),
'visible': self.visible,
'created_date': self.created_date,
'modified_date': self.modified_date
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'UserIndicator':
"""Create UserIndicator from dictionary."""
styling_data = data.get('styling', {})
styling = IndicatorStyling(**styling_data)
return cls(
id=data['id'],
name=data['name'],
description=data.get('description', ''),
type=data['type'],
display_type=data['display_type'],
parameters=data.get('parameters', {}),
styling=styling,
visible=data.get('visible', True),
created_date=data.get('created_date', ''),
modified_date=data.get('modified_date', '')
)
class IndicatorManager:
"""Manager for user-defined indicators with file-based storage."""
def __init__(self):
"""Initialize the indicator manager."""
self.logger = logger
self._ensure_directories()
self._create_default_templates()
def _ensure_directories(self):
"""Ensure indicator directories exist."""
try:
USER_INDICATORS_DIR.mkdir(parents=True, exist_ok=True)
TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
self.logger.debug("Indicator directories created/verified")
except Exception as e:
self.logger.error(f"Error creating indicator directories: {e}")
def _get_indicator_file_path(self, indicator_id: str) -> Path:
"""Get file path for an indicator."""
return USER_INDICATORS_DIR / f"{indicator_id}.json"
def _get_template_file_path(self, indicator_type: str) -> Path:
"""Get file path for an indicator template."""
return TEMPLATES_DIR / f"{indicator_type}_template.json"
def save_indicator(self, indicator: UserIndicator) -> bool:
"""
Save an indicator to file.
Args:
indicator: UserIndicator instance to save
Returns:
True if saved successfully, False otherwise
"""
try:
# Update modified date
indicator.modified_date = datetime.now(timezone.utc).isoformat()
file_path = self._get_indicator_file_path(indicator.id)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(indicator.to_dict(), f, indent=2, ensure_ascii=False)
self.logger.info(f"Saved indicator: {indicator.name} ({indicator.id})")
return True
except Exception as e:
self.logger.error(f"Error saving indicator {indicator.id}: {e}")
return False
def load_indicator(self, indicator_id: str) -> Optional[UserIndicator]:
"""
Load an indicator from file.
Args:
indicator_id: ID of the indicator to load
Returns:
UserIndicator instance or None if not found/error
"""
try:
file_path = self._get_indicator_file_path(indicator_id)
if not file_path.exists():
self.logger.warning(f"Indicator file not found: {indicator_id}")
return None
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
indicator = UserIndicator.from_dict(data)
self.logger.debug(f"Loaded indicator: {indicator.name} ({indicator.id})")
return indicator
except Exception as e:
self.logger.error(f"Error loading indicator {indicator_id}: {e}")
return None
def list_indicators(self, visible_only: bool = False) -> List[UserIndicator]:
"""
List all user indicators.
Args:
visible_only: If True, only return visible indicators
Returns:
List of UserIndicator instances
"""
indicators = []
try:
for file_path in USER_INDICATORS_DIR.glob("*.json"):
indicator_id = file_path.stem
indicator = self.load_indicator(indicator_id)
if indicator:
if not visible_only or indicator.visible:
indicators.append(indicator)
# Sort by name
indicators.sort(key=lambda x: x.name.lower())
self.logger.debug(f"Listed {len(indicators)} indicators")
except Exception as e:
self.logger.error(f"Error listing indicators: {e}")
return indicators
def delete_indicator(self, indicator_id: str) -> bool:
"""
Delete an indicator.
Args:
indicator_id: ID of the indicator to delete
Returns:
True if deleted successfully, False otherwise
"""
try:
file_path = self._get_indicator_file_path(indicator_id)
if file_path.exists():
file_path.unlink()
self.logger.info(f"Deleted indicator: {indicator_id}")
return True
else:
self.logger.warning(f"Indicator file not found for deletion: {indicator_id}")
return False
except Exception as e:
self.logger.error(f"Error deleting indicator {indicator_id}: {e}")
return False
def create_indicator(self, name: str, indicator_type: str, parameters: Dict[str, Any],
description: str = "", color: str = "#007bff",
display_type: str = None) -> Optional[UserIndicator]:
"""
Create a new indicator.
Args:
name: Display name for the indicator
indicator_type: Type of indicator (sma, ema, etc.)
parameters: Indicator parameters
description: Optional description
color: Color for chart display
display_type: overlay or subplot (auto-detected if None)
Returns:
Created UserIndicator instance or None if error
"""
try:
# Generate unique ID
indicator_id = f"{indicator_type}_{uuid.uuid4().hex[:8]}"
# Auto-detect display type if not provided
if display_type is None:
display_type = self._get_default_display_type(indicator_type)
# Create styling
styling = IndicatorStyling(color=color)
# Create indicator
indicator = UserIndicator(
id=indicator_id,
name=name,
description=description,
type=indicator_type,
display_type=display_type,
parameters=parameters,
styling=styling
)
# Save to file
if self.save_indicator(indicator):
self.logger.info(f"Created new indicator: {name} ({indicator_id})")
return indicator
else:
return None
except Exception as e:
self.logger.error(f"Error creating indicator: {e}")
return None
def update_indicator(self, indicator_id: str, **updates) -> bool:
"""
Update an existing indicator.
Args:
indicator_id: ID of indicator to update
**updates: Fields to update
Returns:
True if updated successfully, False otherwise
"""
try:
indicator = self.load_indicator(indicator_id)
if not indicator:
return False
# Update fields
for field, value in updates.items():
if hasattr(indicator, field):
if field == 'styling' and isinstance(value, dict):
# Update styling fields
for style_field, style_value in value.items():
if hasattr(indicator.styling, style_field):
setattr(indicator.styling, style_field, style_value)
else:
setattr(indicator, field, value)
return self.save_indicator(indicator)
except Exception as e:
self.logger.error(f"Error updating indicator {indicator_id}: {e}")
return False
def get_indicators_by_type(self, display_type: str) -> List[UserIndicator]:
"""Get indicators by display type (overlay/subplot)."""
indicators = self.list_indicators(visible_only=True)
return [ind for ind in indicators if ind.display_type == display_type]
def get_available_indicator_types(self) -> List[str]:
"""Get list of available indicator types."""
return [t.value for t in IndicatorType]
def _get_default_display_type(self, indicator_type: str) -> str:
"""Get default display type for an indicator type."""
overlay_types = {IndicatorType.SMA, IndicatorType.EMA, IndicatorType.BOLLINGER_BANDS}
subplot_types = {IndicatorType.RSI, IndicatorType.MACD}
if indicator_type in [t.value for t in overlay_types]:
return DisplayType.OVERLAY.value
elif indicator_type in [t.value for t in subplot_types]:
return DisplayType.SUBPLOT.value
else:
return DisplayType.OVERLAY.value # Default
def _create_default_templates(self):
"""Create default indicator templates if they don't exist."""
templates = {
IndicatorType.SMA.value: {
"name": "Simple Moving Average",
"description": "Simple Moving Average indicator",
"type": IndicatorType.SMA.value,
"display_type": DisplayType.OVERLAY.value,
"default_parameters": {"period": 20},
"parameter_schema": {
"period": {"type": "int", "min": 1, "max": 200, "default": 20, "description": "Period for SMA calculation"}
},
"default_styling": {"color": "#007bff", "line_width": 2}
},
IndicatorType.EMA.value: {
"name": "Exponential Moving Average",
"description": "Exponential Moving Average indicator",
"type": IndicatorType.EMA.value,
"display_type": DisplayType.OVERLAY.value,
"default_parameters": {"period": 12},
"parameter_schema": {
"period": {"type": "int", "min": 1, "max": 200, "default": 12, "description": "Period for EMA calculation"}
},
"default_styling": {"color": "#ff6b35", "line_width": 2}
},
IndicatorType.RSI.value: {
"name": "Relative Strength Index",
"description": "RSI oscillator indicator",
"type": IndicatorType.RSI.value,
"display_type": DisplayType.SUBPLOT.value,
"default_parameters": {"period": 14},
"parameter_schema": {
"period": {"type": "int", "min": 2, "max": 50, "default": 14, "description": "Period for RSI calculation"}
},
"default_styling": {"color": "#20c997", "line_width": 2}
},
IndicatorType.MACD.value: {
"name": "MACD",
"description": "Moving Average Convergence Divergence",
"type": IndicatorType.MACD.value,
"display_type": DisplayType.SUBPLOT.value,
"default_parameters": {"fast_period": 12, "slow_period": 26, "signal_period": 9},
"parameter_schema": {
"fast_period": {"type": "int", "min": 2, "max": 50, "default": 12, "description": "Fast EMA period"},
"slow_period": {"type": "int", "min": 5, "max": 100, "default": 26, "description": "Slow EMA period"},
"signal_period": {"type": "int", "min": 2, "max": 30, "default": 9, "description": "Signal line period"}
},
"default_styling": {"color": "#fd7e14", "line_width": 2}
},
IndicatorType.BOLLINGER_BANDS.value: {
"name": "Bollinger Bands",
"description": "Bollinger Bands volatility indicator",
"type": IndicatorType.BOLLINGER_BANDS.value,
"display_type": DisplayType.OVERLAY.value,
"default_parameters": {"period": 20, "std_dev": 2.0},
"parameter_schema": {
"period": {"type": "int", "min": 5, "max": 100, "default": 20, "description": "Period for middle line (SMA)"},
"std_dev": {"type": "float", "min": 0.5, "max": 5.0, "default": 2.0, "description": "Standard deviation multiplier"}
},
"default_styling": {"color": "#6f42c1", "line_width": 1}
}
}
for indicator_type, template_data in templates.items():
template_path = self._get_template_file_path(indicator_type)
if not template_path.exists():
try:
with open(template_path, 'w', encoding='utf-8') as f:
json.dump(template_data, f, indent=2, ensure_ascii=False)
self.logger.debug(f"Created template: {indicator_type}")
except Exception as e:
self.logger.error(f"Error creating template {indicator_type}: {e}")
def get_template(self, indicator_type: str) -> Optional[Dict[str, Any]]:
"""Get indicator template by type."""
try:
template_path = self._get_template_file_path(indicator_type)
if template_path.exists():
with open(template_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
self.logger.warning(f"Template not found: {indicator_type}")
return None
except Exception as e:
self.logger.error(f"Error loading template {indicator_type}: {e}")
return None
# Global instance
indicator_manager = IndicatorManager()
def get_indicator_manager() -> IndicatorManager:
"""Get the global indicator manager instance."""
return indicator_manager

View File

@ -32,6 +32,7 @@ class IndicatorLayerConfig(LayerConfig):
parameters: Dict[str, Any] = None # Indicator-specific parameters
line_width: int = 2
opacity: float = 1.0
show_middle_line: bool = True # For indicators like Bollinger Bands
def __post_init__(self):
super().__post_init__()
@ -341,9 +342,7 @@ class SMALayer(BaseIndicatorLayer):
line=dict(
color=self.config.color or '#2196F3',
width=self.config.line_width
),
row=subplot_row,
col=1
)
)
self.traces = [sma_trace]
@ -442,9 +441,7 @@ class EMALayer(BaseIndicatorLayer):
line=dict(
color=self.config.color or '#FF9800',
width=self.config.line_width
),
row=subplot_row,
col=1
)
)
self.traces = [ema_trace]
@ -550,8 +547,6 @@ class BollingerBandsLayer(BaseIndicatorLayer):
mode='lines',
name=f'BB Upper({period})',
line=dict(color=self.config.color or '#9C27B0', width=1),
row=subplot_row,
col=1,
showlegend=True
)
traces.append(upper_trace)
@ -565,8 +560,6 @@ class BollingerBandsLayer(BaseIndicatorLayer):
line=dict(color=self.config.color or '#9C27B0', width=1),
fill='tonexty',
fillcolor='rgba(156, 39, 176, 0.1)',
row=subplot_row,
col=1,
showlegend=True
)
traces.append(lower_trace)
@ -579,8 +572,6 @@ class BollingerBandsLayer(BaseIndicatorLayer):
mode='lines',
name=f'BB Middle({period})',
line=dict(color=self.config.color or '#9C27B0', width=1, dash='dash'),
row=subplot_row,
col=1,
showlegend=True
)
traces.append(middle_trace)

View File

@ -0,0 +1,30 @@
{
"name": "Bollinger Bands",
"description": "Bollinger Bands volatility indicator",
"type": "bollinger_bands",
"display_type": "overlay",
"default_parameters": {
"period": 20,
"std_dev": 2.0
},
"parameter_schema": {
"period": {
"type": "int",
"min": 5,
"max": 100,
"default": 20,
"description": "Period for middle line (SMA)"
},
"std_dev": {
"type": "float",
"min": 0.5,
"max": 5.0,
"default": 2.0,
"description": "Standard deviation multiplier"
}
},
"default_styling": {
"color": "#6f42c1",
"line_width": 1
}
}

View File

@ -0,0 +1,22 @@
{
"name": "Exponential Moving Average",
"description": "Exponential Moving Average indicator",
"type": "ema",
"display_type": "overlay",
"default_parameters": {
"period": 12
},
"parameter_schema": {
"period": {
"type": "int",
"min": 1,
"max": 200,
"default": 12,
"description": "Period for EMA calculation"
}
},
"default_styling": {
"color": "#ff6b35",
"line_width": 2
}
}

View File

@ -0,0 +1,38 @@
{
"name": "MACD",
"description": "Moving Average Convergence Divergence",
"type": "macd",
"display_type": "subplot",
"default_parameters": {
"fast_period": 12,
"slow_period": 26,
"signal_period": 9
},
"parameter_schema": {
"fast_period": {
"type": "int",
"min": 2,
"max": 50,
"default": 12,
"description": "Fast EMA period"
},
"slow_period": {
"type": "int",
"min": 5,
"max": 100,
"default": 26,
"description": "Slow EMA period"
},
"signal_period": {
"type": "int",
"min": 2,
"max": 30,
"default": 9,
"description": "Signal line period"
}
},
"default_styling": {
"color": "#fd7e14",
"line_width": 2
}
}

View File

@ -0,0 +1,22 @@
{
"name": "Relative Strength Index",
"description": "RSI oscillator indicator",
"type": "rsi",
"display_type": "subplot",
"default_parameters": {
"period": 14
},
"parameter_schema": {
"period": {
"type": "int",
"min": 2,
"max": 50,
"default": 14,
"description": "Period for RSI calculation"
}
},
"default_styling": {
"color": "#20c997",
"line_width": 2
}
}

View File

@ -0,0 +1,22 @@
{
"name": "Simple Moving Average",
"description": "Simple Moving Average indicator",
"type": "sma",
"display_type": "overlay",
"default_parameters": {
"period": 20
},
"parameter_schema": {
"period": {
"type": "int",
"min": 1,
"max": 200,
"default": 20,
"description": "Period for SMA calculation"
}
},
"default_styling": {
"color": "#007bff",
"line_width": 2
}
}

View File

@ -0,0 +1,20 @@
{
"id": "bollinger_bands_08c5ed71",
"name": "Bollinger Tight",
"description": "Tight Bollinger Bands (20, 1.5) for sensitive volatility",
"type": "bollinger_bands",
"display_type": "overlay",
"parameters": {
"period": 20,
"std_dev": 1.5
},
"styling": {
"color": "#e83e8c",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.460797+00:00",
"modified_date": "2025-06-04T04:16:35.460797+00:00"
}

View File

@ -0,0 +1,20 @@
{
"id": "bollinger_bands_69b378e2",
"name": "Bollinger Bands",
"description": "Standard Bollinger Bands (20, 2) for volatility analysis",
"type": "bollinger_bands",
"display_type": "overlay",
"parameters": {
"period": 20,
"std_dev": 2.0
},
"styling": {
"color": "#6f42c1",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.460105+00:00",
"modified_date": "2025-06-04T04:16:35.460105+00:00"
}

View File

@ -0,0 +1,19 @@
{
"id": "ema_ca5fd53d",
"name": "EMA 10",
"description": "12-period Exponential Moving Average for fast signals",
"type": "ema",
"display_type": "overlay",
"parameters": {
"period": 10
},
"styling": {
"color": "#ff6b35",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.455729+00:00",
"modified_date": "2025-06-04T04:54:49.608549+00:00"
}

View File

@ -0,0 +1,19 @@
{
"id": "ema_de4fc14c",
"name": "EMA 26",
"description": "26-period Exponential Moving Average for slower signals",
"type": "ema",
"display_type": "overlay",
"parameters": {
"period": 26
},
"styling": {
"color": "#28a745",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.456253+00:00",
"modified_date": "2025-06-04T04:16:35.456253+00:00"
}

View File

@ -0,0 +1,21 @@
{
"id": "macd_307935a7",
"name": "MACD Fast",
"description": "Fast MACD (5, 13, 4) for quick signals",
"type": "macd",
"display_type": "subplot",
"parameters": {
"fast_period": 5,
"slow_period": 13,
"signal_period": 4
},
"styling": {
"color": "#dc3545",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.459602+00:00",
"modified_date": "2025-06-04T04:16:35.459602+00:00"
}

View File

@ -0,0 +1,21 @@
{
"id": "macd_7335a9bd",
"name": "MACD Standard",
"description": "Standard MACD (12, 26, 9) for trend changes",
"type": "macd",
"display_type": "subplot",
"parameters": {
"fast_period": 12,
"slow_period": 26,
"signal_period": 9
},
"styling": {
"color": "#fd7e14",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.459030+00:00",
"modified_date": "2025-06-04T04:16:35.459030+00:00"
}

View File

@ -0,0 +1,19 @@
{
"id": "rsi_1a0e1320",
"name": "RSI 21",
"description": "21-period RSI for less sensitive momentum signals",
"type": "rsi",
"display_type": "subplot",
"parameters": {
"period": 21
},
"styling": {
"color": "#17a2b8",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.458018+00:00",
"modified_date": "2025-06-04T04:16:35.458018+00:00"
}

View File

@ -0,0 +1,19 @@
{
"id": "rsi_5d160ff7",
"name": "RSI 14",
"description": "14-period RSI for momentum analysis",
"type": "rsi",
"display_type": "subplot",
"parameters": {
"period": 14
},
"styling": {
"color": "#20c997",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.457515+00:00",
"modified_date": "2025-06-04T04:16:35.457515+00:00"
}

View File

@ -0,0 +1,19 @@
{
"id": "sma_0e235df1",
"name": "SMA 50",
"description": "50-period Simple Moving Average for medium-term trend",
"type": "sma",
"display_type": "overlay",
"parameters": {
"period": 50
},
"styling": {
"color": "#6c757d",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.454653+00:00",
"modified_date": "2025-06-04T04:16:35.454653+00:00"
}

View File

@ -0,0 +1,19 @@
{
"id": "sma_8c487df2",
"name": "SMA 20",
"description": "20-period Simple Moving Average for short-term trend",
"type": "sma",
"display_type": "overlay",
"parameters": {
"period": 20
},
"styling": {
"color": "#007bff",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.453614+00:00",
"modified_date": "2025-06-04T04:16:35.453614+00:00"
}

View File

@ -8,6 +8,7 @@ The Modular Chart Layers System is a flexible, strategy-driven chart system that
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Components](#components)
- [User Indicator Management](#user-indicator-management)
- [Configuration System](#configuration-system)
- [Example Strategies](#example-strategies)
- [Validation System](#validation-system)
@ -20,6 +21,7 @@ The Modular Chart Layers System is a flexible, strategy-driven chart system that
### Key Features
- **Modular Architecture**: Chart layers can be independently tested and composed
- **User Indicator Management**: Create, edit, and manage custom indicators with JSON persistence
- **Strategy-Driven Configuration**: JSON-based configurations for different trading strategies
- **Comprehensive Validation**: 10+ validation rules with detailed error reporting
- **Example Strategies**: 5 real-world trading strategy templates
@ -44,7 +46,9 @@ The Modular Chart Layers System is a flexible, strategy-driven chart system that
```
components/charts/
├── config/ # Configuration management
├── indicator_manager.py # User indicator CRUD operations
├── indicator_defaults.py # Default indicator templates
├── config/ # Configuration management
│ ├── indicator_defs.py # Indicator schemas and validation
│ ├── defaults.py # Default configurations and presets
│ ├── strategy_charts.py # Strategy-specific configurations
@ -58,6 +62,65 @@ components/charts/
│ └── signals.py # Signal overlays (future)
├── builder.py # Main chart builder
└── utils.py # Chart utilities
config/indicators/
└── user_indicators/ # User-created indicators (JSON files)
├── sma_abc123.json
├── ema_def456.json
└── ...
```
## User Indicator Management
The system includes a comprehensive user indicator management system that allows creating, editing, and managing custom technical indicators.
### Features
- **Interactive UI**: Modal dialog for creating and editing indicators
- **Real-time Updates**: Charts update immediately when indicators are toggled
- **JSON Persistence**: Each indicator saved as individual JSON file
- **Full CRUD Operations**: Create, Read, Update, Delete functionality
- **Type Validation**: Parameter validation based on indicator type
- **Custom Styling**: Color, line width, and appearance customization
### Quick Access
- **📊 [Complete Indicator Documentation](./indicators.md)** - Comprehensive guide to the indicator system
- **⚡ [Quick Guide: Adding New Indicators](./adding-new-indicators.md)** - Step-by-step checklist for developers
### Current User Indicators
| Indicator | Type | Parameters | Display |
|-----------|------|------------|---------|
| Simple Moving Average (SMA) | `sma` | period (1-200) | Overlay |
| Exponential Moving Average (EMA) | `ema` | period (1-200) | Overlay |
| Bollinger Bands | `bollinger_bands` | period (5-100), std_dev (0.5-5.0) | Overlay |
| Relative Strength Index (RSI) | `rsi` | period (2-50) | Subplot |
| MACD | `macd` | fast_period, slow_period, signal_period | Subplot |
### Usage Example
```python
# Get indicator manager
from components.charts.indicator_manager import get_indicator_manager
manager = get_indicator_manager()
# Create new indicator
indicator = manager.create_indicator(
name="My SMA 50",
indicator_type="sma",
parameters={"period": 50},
description="50-period Simple Moving Average",
color="#ff0000"
)
# Load and update
loaded = manager.load_indicator("sma_abc123")
success = manager.update_indicator("sma_abc123", name="Updated SMA")
# Get indicators by type
overlay_indicators = manager.get_indicators_by_type("overlay")
subplot_indicators = manager.get_indicators_by_type("subplot")
```
## Quick Start

View File

@ -0,0 +1,393 @@
# 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

View File

@ -0,0 +1,310 @@
# Indicator System Documentation
## Overview
The Crypto Trading Bot Dashboard features a comprehensive modular indicator system that allows users to create, customize, and manage technical indicators for chart analysis. The system supports both overlay indicators (displayed on the main price chart) and subplot indicators (displayed in separate panels below the main chart).
## Table of Contents
1. [System Architecture](#system-architecture)
2. [Current Indicators](#current-indicators)
3. [User Interface](#user-interface)
4. [File Structure](#file-structure)
5. [Adding New Indicators](#adding-new-indicators)
6. [Configuration Format](#configuration-format)
7. [API Reference](#api-reference)
8. [Troubleshooting](#troubleshooting)
## System Architecture
### Core Components
```
components/charts/
├── indicator_manager.py # Core indicator CRUD operations
├── indicator_defaults.py # Default indicator templates
├── layers/
│ ├── indicators.py # Overlay indicator rendering
│ └── subplots.py # Subplot indicator rendering
└── config/
└── indicator_defs.py # Indicator definitions and schemas
config/indicators/
└── user_indicators/ # User-created indicators (JSON files)
├── sma_abc123.json
├── ema_def456.json
└── ...
```
### Key Classes
- **`IndicatorManager`**: Handles CRUD operations for user indicators
- **`UserIndicator`**: Data structure for indicator configuration
- **`IndicatorStyling`**: Appearance and styling configuration
- **Indicator Layers**: Rendering classes for different indicator types
## Current Indicators
### Overlay Indicators
These indicators are displayed directly on the price chart:
| Indicator | Type | Parameters | Description |
|-----------|------|------------|-------------|
| **Simple Moving Average (SMA)** | `sma` | `period` (1-200) | Average price over N periods |
| **Exponential Moving Average (EMA)** | `ema` | `period` (1-200) | Weighted average giving more weight to recent prices |
| **Bollinger Bands** | `bollinger_bands` | `period` (5-100), `std_dev` (0.5-5.0) | Price channels based on standard deviation |
### Subplot Indicators
These indicators are displayed in separate panels:
| Indicator | Type | Parameters | Description |
|-----------|------|------------|-------------|
| **Relative Strength Index (RSI)** | `rsi` | `period` (2-50) | Momentum oscillator (0-100 scale) |
| **MACD** | `macd` | `fast_period` (2-50), `slow_period` (5-100), `signal_period` (2-30) | Moving average convergence divergence |
## User Interface
### Adding Indicators
1. **Click " Add New Indicator"** button
2. **Configure Basic Settings**:
- Name: Custom name for the indicator
- Type: Select from available indicator types
- Description: Optional description
3. **Set Parameters**: Type-specific parameters appear dynamically
4. **Customize Styling**:
- Color: Hex color code
- Line Width: 1-5 pixels
5. **Save**: Creates a new JSON file and updates the UI
### Managing Indicators
- **✅ Checkboxes**: Toggle indicator visibility on chart
- **✏️ Edit Button**: Modify existing indicator settings
- **🗑️ Delete Button**: Remove indicator permanently
### Real-time Updates
- Chart updates automatically when indicators are toggled
- Changes are saved immediately to JSON files
- No page refresh required
## File Structure
### Indicator JSON Format
```json
{
"id": "ema_ca5fd53d",
"name": "EMA 10",
"description": "10-period Exponential Moving Average for fast signals",
"type": "ema",
"display_type": "overlay",
"parameters": {
"period": 10
},
"styling": {
"color": "#ff6b35",
"line_width": 2,
"opacity": 1.0,
"line_style": "solid"
},
"visible": true,
"created_date": "2025-06-04T04:16:35.455729+00:00",
"modified_date": "2025-06-04T04:54:49.608549+00:00"
}
```
### Directory Structure
```
config/indicators/
└── user_indicators/
├── sma_abc123.json # Individual indicator files
├── ema_def456.json
├── rsi_ghi789.json
└── macd_jkl012.json
```
## Adding New Indicators
For developers who want to add new indicator types to the system, please refer to the comprehensive step-by-step guide:
**📋 [Quick Guide: Adding New Indicators](./adding-new-indicators.md)**
This guide covers:
- ✅ Complete 11-step implementation checklist
- ✅ Full code examples (Stochastic Oscillator implementation)
- ✅ File modification requirements
- ✅ Testing checklist and common patterns
- ✅ Tips and best practices
## Configuration Format
### User Indicator Structure
```python
@dataclass
class UserIndicator:
id: str # Unique identifier
name: str # Display name
description: str # User description
type: str # Indicator type (sma, ema, etc.)
display_type: str # "overlay" or "subplot"
parameters: Dict[str, Any] # Type-specific parameters
styling: IndicatorStyling # Appearance settings
visible: bool = True # Default visibility
created_date: datetime # Creation timestamp
modified_date: datetime # Last modification timestamp
```
### Styling Options
```python
@dataclass
class IndicatorStyling:
color: str = "#007bff" # Hex color code
line_width: int = 2 # Line thickness (1-5)
opacity: float = 1.0 # Transparency (0.0-1.0)
line_style: str = "solid" # Line style
```
### Parameter Examples
```python
# SMA/EMA Parameters
{"period": 20}
# RSI Parameters
{"period": 14}
# MACD Parameters
{
"fast_period": 12,
"slow_period": 26,
"signal_period": 9
}
# Bollinger Bands Parameters
{
"period": 20,
"std_dev": 2.0
}
```
## API Reference
### IndicatorManager Class
```python
class IndicatorManager:
def create_indicator(self, name: str, indicator_type: str,
parameters: Dict[str, Any], **kwargs) -> Optional[UserIndicator]
def load_indicator(self, indicator_id: str) -> Optional[UserIndicator]
def update_indicator(self, indicator_id: str, **kwargs) -> bool
def delete_indicator(self, indicator_id: str) -> bool
def list_indicators(self) -> List[UserIndicator]
def get_indicators_by_type(self, display_type: str) -> List[UserIndicator]
```
### Usage Examples
```python
# Get indicator manager
manager = get_indicator_manager()
# Create new indicator
indicator = manager.create_indicator(
name="My SMA 50",
indicator_type="sma",
parameters={"period": 50},
description="50-period Simple Moving Average",
color="#ff0000"
)
# Load indicator
loaded = manager.load_indicator("sma_abc123")
# Update indicator
success = manager.update_indicator(
"sma_abc123",
name="Updated SMA",
parameters={"period": 30}
)
# Delete indicator
deleted = manager.delete_indicator("sma_abc123")
# List all indicators
all_indicators = manager.list_indicators()
# Get by type
overlay_indicators = manager.get_indicators_by_type("overlay")
subplot_indicators = manager.get_indicators_by_type("subplot")
```
## Troubleshooting
### Common Issues
1. **Indicator not appearing in dropdown**
- Check if registered in `INDICATOR_REGISTRY`
- Verify the indicator type matches the class name
2. **Parameters not saving**
- Ensure parameter fields are added to save callback
- Check parameter collection logic in `save_new_indicator`
3. **Chart not updating**
- Verify the indicator layer implements `calculate_values` and `create_traces`
- Check if indicator is registered in the correct registry
4. **File permission errors**
- Ensure `config/indicators/user_indicators/` directory is writable
- Check file permissions on existing JSON files
### Debug Information
- Check browser console for JavaScript errors
- Look at application logs for Python exceptions
- Verify JSON file structure with a validator
- Test indicator calculations with sample data
### Performance Considerations
- Indicators with large periods may take longer to calculate
- Consider data availability when setting parameter limits
- Subplot indicators require additional chart space
- Real-time updates may impact performance with many indicators
## Best Practices
1. **Naming Conventions**
- Use descriptive names for indicators
- Include parameter values in names (e.g., "SMA 20")
- Use consistent naming patterns
2. **Parameter Validation**
- Set appropriate min/max values for parameters
- Provide helpful descriptions for parameters
- Use sensible default values
3. **Error Handling**
- Handle insufficient data gracefully
- Provide meaningful error messages
- Log errors for debugging
4. **Performance**
- Cache calculated values when possible
- Optimize calculation algorithms
- Limit the number of active indicators
5. **User Experience**
- Provide immediate visual feedback
- Use intuitive color schemes
- Group related indicators logically

View File

@ -73,9 +73,9 @@ Implementation of a flexible, strategy-driven chart system that supports technic
- [x] 3.7 Unit test configuration system and validation
- [ ] 4.0 Dashboard Integration and UI Controls
- [ ] 4.1 Add indicator selection checkboxes to dashboard layout
- [ ] 4.2 Create real-time chart updates with indicator toggling
- [ ] 4.3 Implement parameter adjustment controls for indicators
- [x] 4.1 Add indicator selection checkboxes to dashboard layout
- [x] 4.2 Create real-time chart updates with indicator toggling
- [x] 4.3 Implement parameter adjustment controls for indicators
- [ ] 4.4 Add strategy selection dropdown for predefined configurations
- [ ] 4.5 Update chart callback functions to handle new layer system
- [ ] 4.6 Ensure backward compatibility with existing dashboard features