pivot points option in UI
This commit is contained in:
@@ -1280,13 +1280,15 @@ class CleanTradingDashboard:
|
||||
|
||||
@self.app.callback(
|
||||
Output('price-chart', 'figure'),
|
||||
[Input('interval-component', 'n_intervals')],
|
||||
[Input('interval-component', 'n_intervals'),
|
||||
Input('show-pivots-switch', 'value')],
|
||||
[State('price-chart', 'relayoutData')]
|
||||
)
|
||||
def update_price_chart(n, relayout_data):
|
||||
def update_price_chart(n, pivots_value, relayout_data):
|
||||
"""Update price chart every second, persisting user zoom/pan"""
|
||||
try:
|
||||
fig = self._create_price_chart('ETH/USDT')
|
||||
show_pivots = bool(pivots_value and 'enabled' in pivots_value)
|
||||
fig = self._create_price_chart('ETH/USDT', show_pivots=show_pivots)
|
||||
|
||||
if relayout_data:
|
||||
if 'xaxis.range[0]' in relayout_data and 'xaxis.range[1]' in relayout_data:
|
||||
@@ -1300,6 +1302,15 @@ class CleanTradingDashboard:
|
||||
return go.Figure().add_annotation(text=f"Chart Error: {str(e)}",
|
||||
xref="paper", yref="paper",
|
||||
x=0.5, y=0.5, showarrow=False)
|
||||
|
||||
# Display state label for pivots toggle
|
||||
@self.app.callback(
|
||||
Output('pivots-display', 'children'),
|
||||
[Input('show-pivots-switch', 'value')]
|
||||
)
|
||||
def update_pivots_display(value):
|
||||
enabled = bool(value and 'enabled' in value)
|
||||
return "ON" if enabled else "OFF"
|
||||
|
||||
@self.app.callback(
|
||||
Output('closed-trades-table', 'children'),
|
||||
@@ -1651,7 +1662,7 @@ class CleanTradingDashboard:
|
||||
elif hasattr(panel, 'render'):
|
||||
panel_content = panel.render()
|
||||
else:
|
||||
panel_content = html.Div([html.Div("Training panel not available", className="text-muted small")])
|
||||
panel_content = [html.Div("Training panel not available", className="text-muted small")]
|
||||
|
||||
logger.info("Successfully created training metrics panel")
|
||||
return panel_content
|
||||
@@ -1663,10 +1674,10 @@ class CleanTradingDashboard:
|
||||
logger.error(f"Error updating training metrics with new panel: {e}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
return html.Div([
|
||||
return [
|
||||
html.P("Error loading training panel", className="text-danger small"),
|
||||
html.P(f"Details: {str(e)}", className="text-muted small")
|
||||
], id="training-metrics")
|
||||
]
|
||||
|
||||
# Universal model toggle callback using pattern matching
|
||||
@self.app.callback(
|
||||
@@ -2234,7 +2245,7 @@ class CleanTradingDashboard:
|
||||
# Return None if absolutely nothing available
|
||||
return None
|
||||
|
||||
def _create_price_chart(self, symbol: str) -> go.Figure:
|
||||
def _create_price_chart(self, symbol: str, show_pivots: bool = True) -> go.Figure:
|
||||
"""Create 1-minute main chart with 1-second mini chart - Updated every second"""
|
||||
try:
|
||||
# FIXED: Always get fresh data on startup to avoid gaps
|
||||
@@ -2404,63 +2415,64 @@ class CleanTradingDashboard:
|
||||
self._add_trades_to_chart(fig, symbol, df_main, row=1)
|
||||
|
||||
# ADD PIVOT POINTS TO MAIN CHART (overlay on 1m)
|
||||
try:
|
||||
pivots_input = None
|
||||
if hasattr(self.data_provider, 'get_base_data_input'):
|
||||
bdi = self.data_provider.get_base_data_input(symbol)
|
||||
if bdi and getattr(bdi, 'pivot_points', None):
|
||||
pivots_input = bdi.pivot_points
|
||||
if pivots_input:
|
||||
# Filter pivots within the visible time range of df_main
|
||||
start_ts = df_main.index.min()
|
||||
end_ts = df_main.index.max()
|
||||
xs_high = []
|
||||
ys_high = []
|
||||
xs_low = []
|
||||
ys_low = []
|
||||
for p in pivots_input:
|
||||
ts = getattr(p, 'timestamp', None)
|
||||
price = getattr(p, 'price', None)
|
||||
ptype = getattr(p, 'type', 'low')
|
||||
if ts is None or price is None:
|
||||
continue
|
||||
# Convert pivot timestamp to local tz and make tz-naive to match chart axes
|
||||
try:
|
||||
if hasattr(ts, 'tzinfo') and ts.tzinfo is not None:
|
||||
pt = ts.astimezone(_local_tz) if _local_tz else ts
|
||||
else:
|
||||
# Assume UTC then convert
|
||||
pt = ts.replace(tzinfo=timezone.utc)
|
||||
pt = pt.astimezone(_local_tz) if _local_tz else pt
|
||||
# Drop tzinfo for plotting
|
||||
if show_pivots:
|
||||
try:
|
||||
pivots_input = None
|
||||
if hasattr(self.data_provider, 'get_base_data_input'):
|
||||
bdi = self.data_provider.get_base_data_input(symbol)
|
||||
if bdi and getattr(bdi, 'pivot_points', None):
|
||||
pivots_input = bdi.pivot_points
|
||||
if pivots_input:
|
||||
# Filter pivots within the visible time range of df_main
|
||||
start_ts = df_main.index.min()
|
||||
end_ts = df_main.index.max()
|
||||
xs_high = []
|
||||
ys_high = []
|
||||
xs_low = []
|
||||
ys_low = []
|
||||
for p in pivots_input:
|
||||
ts = getattr(p, 'timestamp', None)
|
||||
price = getattr(p, 'price', None)
|
||||
ptype = getattr(p, 'type', 'low')
|
||||
if ts is None or price is None:
|
||||
continue
|
||||
# Convert pivot timestamp to local tz and make tz-naive to match chart axes
|
||||
try:
|
||||
pt = pt.replace(tzinfo=None)
|
||||
if hasattr(ts, 'tzinfo') and ts.tzinfo is not None:
|
||||
pt = ts.astimezone(_local_tz) if _local_tz else ts
|
||||
else:
|
||||
# Assume UTC then convert
|
||||
pt = ts.replace(tzinfo=timezone.utc)
|
||||
pt = pt.astimezone(_local_tz) if _local_tz else pt
|
||||
# Drop tzinfo for plotting
|
||||
try:
|
||||
pt = pt.replace(tzinfo=None)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pt = ts
|
||||
if start_ts <= pt <= end_ts:
|
||||
if str(ptype).lower() == 'high':
|
||||
xs_high.append(pt)
|
||||
ys_high.append(price)
|
||||
else:
|
||||
xs_low.append(pt)
|
||||
ys_low.append(price)
|
||||
if xs_high or xs_low:
|
||||
fig.add_trace(
|
||||
go.Scatter(x=xs_high, y=ys_high, mode='markers', name='Pivot High',
|
||||
marker=dict(color='#ff7043', size=7, symbol='triangle-up'),
|
||||
hoverinfo='skip'),
|
||||
row=1, col=1
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(x=xs_low, y=ys_low, mode='markers', name='Pivot Low',
|
||||
marker=dict(color='#42a5f5', size=7, symbol='triangle-down'),
|
||||
hoverinfo='skip'),
|
||||
row=1, col=1
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error overlaying pivot points: {e}")
|
||||
pt = ts
|
||||
if start_ts <= pt <= end_ts:
|
||||
if str(ptype).lower() == 'high':
|
||||
xs_high.append(pt)
|
||||
ys_high.append(price)
|
||||
else:
|
||||
xs_low.append(pt)
|
||||
ys_low.append(price)
|
||||
if xs_high or xs_low:
|
||||
fig.add_trace(
|
||||
go.Scatter(x=xs_high, y=ys_high, mode='markers', name='Pivot High',
|
||||
marker=dict(color='#ff7043', size=7, symbol='triangle-up'),
|
||||
hoverinfo='skip'),
|
||||
row=1, col=1
|
||||
)
|
||||
fig.add_trace(
|
||||
go.Scatter(x=xs_low, y=ys_low, mode='markers', name='Pivot Low',
|
||||
marker=dict(color='#42a5f5', size=7, symbol='triangle-down'),
|
||||
hoverinfo='skip'),
|
||||
row=1, col=1
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error overlaying pivot points: {e}")
|
||||
|
||||
# Mini 1-second chart (if available)
|
||||
if has_mini_chart and ws_data_1s is not None:
|
||||
|
||||
Reference in New Issue
Block a user