Implement multi-timeframe support for indicators
- Enhanced the `UserIndicator` class to include an optional `timeframe` attribute for custom indicator timeframes. - Updated the `get_indicator_data` method in `MarketDataIntegrator` to fetch and calculate indicators based on the specified timeframe, ensuring proper data alignment and handling. - Modified the `ChartBuilder` to pass the correct DataFrame for plotting indicators with different timeframes. - Added UI elements in the indicator modal for selecting timeframes, improving user experience. - Updated relevant JSON templates to include the new `timeframe` field for all indicators. - Refactored the `prepare_chart_data` function to ensure it returns a DataFrame with a `DatetimeIndex` for consistent calculations. This commit enhances the flexibility and usability of the indicator system, allowing users to analyze data across various timeframes.
This commit is contained in:
@@ -96,6 +96,7 @@ def register_indicator_callbacks(app):
|
||||
[State('indicator-name-input', 'value'),
|
||||
State('indicator-type-dropdown', 'value'),
|
||||
State('indicator-description-input', 'value'),
|
||||
State('indicator-timeframe-dropdown', 'value'),
|
||||
State('indicator-color-input', 'value'),
|
||||
State('indicator-line-width-slider', 'value'),
|
||||
# SMA parameters
|
||||
@@ -115,7 +116,7 @@ def register_indicator_callbacks(app):
|
||||
State('edit-indicator-store', 'data')],
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def save_new_indicator(n_clicks, name, indicator_type, description, color, line_width,
|
||||
def save_new_indicator(n_clicks, name, indicator_type, description, timeframe, color, line_width,
|
||||
sma_period, ema_period, rsi_period,
|
||||
macd_fast, macd_slow, macd_signal,
|
||||
bb_period, bb_stddev, edit_data):
|
||||
@@ -161,7 +162,8 @@ def register_indicator_callbacks(app):
|
||||
name=name,
|
||||
description=description or "",
|
||||
parameters=parameters,
|
||||
styling={'color': color or "#007bff", 'line_width': line_width or 2}
|
||||
styling={'color': color or "#007bff", 'line_width': line_width or 2},
|
||||
timeframe=timeframe or None
|
||||
)
|
||||
|
||||
if success:
|
||||
@@ -176,7 +178,8 @@ def register_indicator_callbacks(app):
|
||||
indicator_type=indicator_type,
|
||||
parameters=parameters,
|
||||
description=description or "",
|
||||
color=color or "#007bff"
|
||||
color=color or "#007bff",
|
||||
timeframe=timeframe or None
|
||||
)
|
||||
|
||||
if not new_indicator:
|
||||
@@ -384,6 +387,7 @@ def register_indicator_callbacks(app):
|
||||
Output('indicator-name-input', 'value'),
|
||||
Output('indicator-type-dropdown', 'value'),
|
||||
Output('indicator-description-input', 'value'),
|
||||
Output('indicator-timeframe-dropdown', 'value'),
|
||||
Output('indicator-color-input', 'value'),
|
||||
Output('edit-indicator-store', 'data'),
|
||||
# Add parameter field outputs
|
||||
@@ -403,7 +407,7 @@ def register_indicator_callbacks(app):
|
||||
"""Load indicator data for editing."""
|
||||
ctx = callback_context
|
||||
if not ctx.triggered or not any(edit_clicks):
|
||||
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
|
||||
return [no_update] * 15
|
||||
|
||||
# Find which button was clicked
|
||||
triggered_id = ctx.triggered[0]['prop_id']
|
||||
@@ -418,41 +422,42 @@ def register_indicator_callbacks(app):
|
||||
|
||||
if indicator:
|
||||
# Store indicator ID for update
|
||||
edit_data = {'indicator_id': indicator_id, 'mode': 'edit', 'open_modal': True}
|
||||
edit_data = {'indicator_id': indicator_id, 'mode': 'edit'}
|
||||
|
||||
# Extract parameter values based on indicator type
|
||||
params = indicator.parameters
|
||||
|
||||
# Default parameter values
|
||||
sma_period = 20
|
||||
ema_period = 12
|
||||
rsi_period = 14
|
||||
macd_fast = 12
|
||||
macd_slow = 26
|
||||
macd_signal = 9
|
||||
bb_period = 20
|
||||
bb_stddev = 2.0
|
||||
sma_period = None
|
||||
ema_period = None
|
||||
rsi_period = None
|
||||
macd_fast = None
|
||||
macd_slow = None
|
||||
macd_signal = None
|
||||
bb_period = None
|
||||
bb_stddev = None
|
||||
|
||||
# Update with actual saved values
|
||||
if indicator.type == 'sma':
|
||||
sma_period = params.get('period', 20)
|
||||
sma_period = params.get('period')
|
||||
elif indicator.type == 'ema':
|
||||
ema_period = params.get('period', 12)
|
||||
ema_period = params.get('period')
|
||||
elif indicator.type == 'rsi':
|
||||
rsi_period = params.get('period', 14)
|
||||
rsi_period = params.get('period')
|
||||
elif indicator.type == 'macd':
|
||||
macd_fast = params.get('fast_period', 12)
|
||||
macd_slow = params.get('slow_period', 26)
|
||||
macd_signal = params.get('signal_period', 9)
|
||||
macd_fast = params.get('fast_period')
|
||||
macd_slow = params.get('slow_period')
|
||||
macd_signal = params.get('signal_period')
|
||||
elif indicator.type == 'bollinger_bands':
|
||||
bb_period = params.get('period', 20)
|
||||
bb_stddev = params.get('std_dev', 2.0)
|
||||
bb_period = params.get('period')
|
||||
bb_stddev = params.get('std_dev')
|
||||
|
||||
return (
|
||||
"✏️ Edit Indicator",
|
||||
f"✏️ Edit Indicator: {indicator.name}",
|
||||
indicator.name,
|
||||
indicator.type,
|
||||
indicator.description,
|
||||
indicator.timeframe,
|
||||
indicator.styling.color,
|
||||
edit_data,
|
||||
sma_period,
|
||||
@@ -465,17 +470,18 @@ def register_indicator_callbacks(app):
|
||||
bb_stddev
|
||||
)
|
||||
else:
|
||||
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
|
||||
return [no_update] * 15
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Indicator callback: Error loading indicator for edit: {e}")
|
||||
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
|
||||
return [no_update] * 15
|
||||
|
||||
# Reset modal form when closed or saved
|
||||
@app.callback(
|
||||
[Output('indicator-name-input', 'value', allow_duplicate=True),
|
||||
Output('indicator-type-dropdown', 'value', allow_duplicate=True),
|
||||
Output('indicator-description-input', 'value', allow_duplicate=True),
|
||||
Output('indicator-timeframe-dropdown', 'value', allow_duplicate=True),
|
||||
Output('indicator-color-input', 'value', allow_duplicate=True),
|
||||
Output('indicator-line-width-slider', 'value'),
|
||||
Output('modal-title', 'children', allow_duplicate=True),
|
||||
@@ -494,9 +500,7 @@ def register_indicator_callbacks(app):
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def reset_modal_form(cancel_clicks, save_clicks):
|
||||
"""Reset the modal form when it's closed or saved."""
|
||||
if cancel_clicks or save_clicks:
|
||||
return "", None, "", "#007bff", 2, "📊 Add New Indicator", None, 20, 12, 14, 12, 26, 9, 20, 2.0
|
||||
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
|
||||
"""Reset the modal form to its default state."""
|
||||
return "", "", "", "", "", 2, "📊 Add New Indicator", None, 20, 12, 14, 12, 26, 9, 20, 2.0
|
||||
|
||||
logger.info("Indicator callbacks: registered successfully")
|
||||
Reference in New Issue
Block a user