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:
parent
d71cb763bc
commit
476bd67f14
@ -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
|
||||
)
|
||||
@ -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)}")
|
||||
133
components/charts/indicator_defaults.py
Normal file
133
components/charts/indicator_defaults.py
Normal 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()
|
||||
446
components/charts/indicator_manager.py
Normal file
446
components/charts/indicator_manager.py
Normal 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
|
||||
@ -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)
|
||||
|
||||
30
config/indicators/templates/bollinger_bands_template.json
Normal file
30
config/indicators/templates/bollinger_bands_template.json
Normal 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
|
||||
}
|
||||
}
|
||||
22
config/indicators/templates/ema_template.json
Normal file
22
config/indicators/templates/ema_template.json
Normal 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
|
||||
}
|
||||
}
|
||||
38
config/indicators/templates/macd_template.json
Normal file
38
config/indicators/templates/macd_template.json
Normal 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
|
||||
}
|
||||
}
|
||||
22
config/indicators/templates/rsi_template.json
Normal file
22
config/indicators/templates/rsi_template.json
Normal 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
|
||||
}
|
||||
}
|
||||
22
config/indicators/templates/sma_template.json
Normal file
22
config/indicators/templates/sma_template.json
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
19
config/indicators/user_indicators/ema_ca5fd53d.json
Normal file
19
config/indicators/user_indicators/ema_ca5fd53d.json
Normal 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"
|
||||
}
|
||||
19
config/indicators/user_indicators/ema_de4fc14c.json
Normal file
19
config/indicators/user_indicators/ema_de4fc14c.json
Normal 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"
|
||||
}
|
||||
21
config/indicators/user_indicators/macd_307935a7.json
Normal file
21
config/indicators/user_indicators/macd_307935a7.json
Normal 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"
|
||||
}
|
||||
21
config/indicators/user_indicators/macd_7335a9bd.json
Normal file
21
config/indicators/user_indicators/macd_7335a9bd.json
Normal 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"
|
||||
}
|
||||
19
config/indicators/user_indicators/rsi_1a0e1320.json
Normal file
19
config/indicators/user_indicators/rsi_1a0e1320.json
Normal 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"
|
||||
}
|
||||
19
config/indicators/user_indicators/rsi_5d160ff7.json
Normal file
19
config/indicators/user_indicators/rsi_5d160ff7.json
Normal 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"
|
||||
}
|
||||
19
config/indicators/user_indicators/sma_0e235df1.json
Normal file
19
config/indicators/user_indicators/sma_0e235df1.json
Normal 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"
|
||||
}
|
||||
19
config/indicators/user_indicators/sma_8c487df2.json
Normal file
19
config/indicators/user_indicators/sma_8c487df2.json
Normal 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"
|
||||
}
|
||||
@ -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
|
||||
|
||||
393
docs/components/charts/adding-new-indicators.md
Normal file
393
docs/components/charts/adding-new-indicators.md
Normal 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
|
||||
310
docs/components/charts/indicators.md
Normal file
310
docs/components/charts/indicators.md
Normal 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
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user