cache, pivots wip
This commit is contained in:
@@ -1665,6 +1665,7 @@ class CleanTradingDashboard:
|
||||
[Output('current-price', 'children'),
|
||||
Output('session-pnl', 'children'),
|
||||
Output('current-position', 'children'),
|
||||
Output('open-interest', 'children'),
|
||||
Output('trade-count', 'children'),
|
||||
Output('portfolio-value', 'children'),
|
||||
Output('profitability-multiplier', 'children'),
|
||||
@@ -1822,11 +1823,43 @@ class CleanTradingDashboard:
|
||||
else:
|
||||
cob_status_str = f"Error ({update_rate:.1f}/s)"
|
||||
|
||||
return price_str, session_pnl_str, position_str, trade_str, portfolio_str, multiplier_str, cob_status_str, mexc_status
|
||||
# Open Interest (multi-source via report crawler)
|
||||
try:
|
||||
oi_display = "Loading..."
|
||||
if hasattr(self, 'data_provider') and self.data_provider:
|
||||
# Prefer BTC/USDT for OI if trading BTC, else ETH/USDT
|
||||
oi_symbol = 'BTC/USDT' if 'BTC' in (self.trading_symbol if hasattr(self, 'trading_symbol') else 'BTC/USDT') else 'ETH/USDT'
|
||||
# Lazy import to avoid circulars
|
||||
from core.report_data_crawler import ReportDataCrawler
|
||||
if not hasattr(self, '_report_crawler') or self._report_crawler is None:
|
||||
self._report_crawler = ReportDataCrawler(self.data_provider)
|
||||
report = self._report_crawler.crawl_report_data(oi_symbol)
|
||||
if report and report.open_interest_data:
|
||||
# Show first two sources compactly
|
||||
parts = []
|
||||
for oi in report.open_interest_data[:2]:
|
||||
try:
|
||||
val = float(oi.open_interest) if oi.open_interest else 0
|
||||
parts.append(f"{oi.source.upper()}: {val:,.0f}")
|
||||
except Exception:
|
||||
continue
|
||||
if parts:
|
||||
oi_display = " | ".join(parts)
|
||||
else:
|
||||
oi_display = "N/A"
|
||||
else:
|
||||
oi_display = "N/A"
|
||||
else:
|
||||
oi_display = "N/A"
|
||||
except Exception as e:
|
||||
logger.debug(f"Open Interest display error: {e}")
|
||||
oi_display = "N/A"
|
||||
|
||||
return price_str, session_pnl_str, position_str, oi_display, trade_str, portfolio_str, multiplier_str, cob_status_str, mexc_status
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating metrics: {e}")
|
||||
return "Error", "$0.00", "Error", "0", "$100.00", "0.0x", "Error", "ERROR"
|
||||
return "Error", "$0.00", "Error", "N/A", "0", "$100.00", "0.0x", "Error", "ERROR"
|
||||
|
||||
@self.app.callback(
|
||||
Output('recent-decisions', 'children'),
|
||||
@@ -4305,101 +4338,171 @@ class CleanTradingDashboard:
|
||||
logger.warning(f"Error adding trades to chart: {e}")
|
||||
|
||||
def _add_pivot_points_to_chart(self, fig: go.Figure, symbol: str, df_main: pd.DataFrame, row: int = 1):
|
||||
"""Add nested pivot points to the chart"""
|
||||
"""Add Williams Market Structure pivot points (all 5 levels) to the chart"""
|
||||
try:
|
||||
# Get pivot bounds from data provider
|
||||
if not hasattr(self, 'data_provider') or not self.data_provider:
|
||||
return
|
||||
|
||||
pivot_bounds = self.data_provider.get_pivot_bounds(symbol)
|
||||
if not pivot_bounds or not hasattr(pivot_bounds, 'pivot_support_levels'):
|
||||
return
|
||||
|
||||
support_levels = pivot_bounds.pivot_support_levels
|
||||
resistance_levels = pivot_bounds.pivot_resistance_levels
|
||||
|
||||
if not support_levels and not resistance_levels:
|
||||
# Get Williams pivot levels with trend analysis
|
||||
pivot_levels = self.data_provider.get_williams_pivot_levels(symbol)
|
||||
if not pivot_levels:
|
||||
logger.debug(f"No Williams pivot levels available for {symbol}")
|
||||
return
|
||||
|
||||
# Get chart time range for pivot display
|
||||
chart_start = df_main.index.min()
|
||||
chart_end = df_main.index.max()
|
||||
|
||||
# Convert to timezone-naive for comparison
|
||||
from datetime import timezone
|
||||
import pytz
|
||||
try:
|
||||
_local_tz = pytz.timezone('Europe/Sofia')
|
||||
except Exception:
|
||||
_local_tz = None
|
||||
|
||||
# Define colors for different pivot levels
|
||||
pivot_colors = {
|
||||
'support': ['rgba(0, 255, 0, 0.3)', 'rgba(0, 200, 0, 0.4)', 'rgba(0, 150, 0, 0.5)'],
|
||||
'resistance': ['rgba(255, 0, 0, 0.3)', 'rgba(200, 0, 0, 0.4)', 'rgba(150, 0, 0, 0.5)']
|
||||
# Define colors and sizes for different levels (Level 1 = most detailed, Level 5 = broadest)
|
||||
level_styles = {
|
||||
1: {'high_color': '#ff5252', 'low_color': '#2196f3', 'size': 6, 'opacity': 0.7}, # Red/Blue - Short-term
|
||||
2: {'high_color': '#ff7043', 'low_color': '#42a5f5', 'size': 8, 'opacity': 0.75}, # Orange/Light Blue - Medium-short
|
||||
3: {'high_color': '#ffa726', 'low_color': '#64b5f6', 'size': 10, 'opacity': 0.8}, # Light Orange/Lighter Blue - Medium
|
||||
4: {'high_color': '#ffca28', 'low_color': '#90caf9', 'size': 12, 'opacity': 0.85}, # Yellow/Very Light Blue - Medium-long
|
||||
5: {'high_color': '#ffd54f', 'low_color': '#bbdefb', 'size': 14, 'opacity': 0.9} # Bright Yellow/Pale Blue - Long-term
|
||||
}
|
||||
|
||||
# Add support levels
|
||||
for i, support_price in enumerate(support_levels[-5:]): # Show last 5 support levels
|
||||
color_idx = min(i, len(pivot_colors['support']) - 1)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[chart_start, chart_end],
|
||||
y=[support_price, support_price],
|
||||
mode='lines',
|
||||
line=dict(
|
||||
color=pivot_colors['support'][color_idx],
|
||||
width=2,
|
||||
dash='dot'
|
||||
# Add pivot points from each level
|
||||
for level_num, trend_level in sorted(pivot_levels.items()):
|
||||
if not hasattr(trend_level, 'pivot_points'):
|
||||
continue
|
||||
|
||||
pivot_points = trend_level.pivot_points
|
||||
if not pivot_points:
|
||||
continue
|
||||
|
||||
# Separate highs and lows
|
||||
highs_x, highs_y = [], []
|
||||
lows_x, lows_y = [], []
|
||||
|
||||
for pivot in pivot_points:
|
||||
ts = getattr(pivot, 'timestamp', None)
|
||||
price = getattr(pivot, 'price', None)
|
||||
ptype = getattr(pivot, 'pivot_type', 'low')
|
||||
|
||||
if ts is None or price is None:
|
||||
continue
|
||||
|
||||
# Convert timestamp to tz-naive
|
||||
try:
|
||||
if hasattr(ts, 'tzinfo') and ts.tzinfo is not None:
|
||||
pt = ts.astimezone(_local_tz) if _local_tz else ts
|
||||
else:
|
||||
pt = ts.replace(tzinfo=timezone.utc)
|
||||
pt = pt.astimezone(_local_tz) if _local_tz else pt
|
||||
pt = pt.replace(tzinfo=None)
|
||||
except Exception:
|
||||
pt = ts
|
||||
|
||||
# Only show pivots within visible range
|
||||
if not (chart_start <= pt <= chart_end):
|
||||
continue
|
||||
|
||||
if ptype.lower() == 'high':
|
||||
highs_x.append(pt)
|
||||
highs_y.append(price)
|
||||
else:
|
||||
lows_x.append(pt)
|
||||
lows_y.append(price)
|
||||
|
||||
# Get style for this level
|
||||
style = level_styles.get(level_num, level_styles[1])
|
||||
|
||||
# Add high pivots for this level
|
||||
if highs_x:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=highs_x, y=highs_y,
|
||||
mode='markers',
|
||||
name=f'L{level_num} Pivot High',
|
||||
marker=dict(
|
||||
color=style['high_color'],
|
||||
size=style['size'],
|
||||
symbol='triangle-down',
|
||||
opacity=style['opacity'],
|
||||
line=dict(width=1, color='white')
|
||||
),
|
||||
showlegend=(level_num == 1), # Only show legend for Level 1
|
||||
hovertemplate=f"Level {level_num} High: ${{y:.2f}}<extra></extra>"
|
||||
),
|
||||
name=f'Support L{i+1}: ${support_price:.2f}',
|
||||
showlegend=True,
|
||||
hovertemplate=f"Support Level {i+1}: ${{y:.2f}}<extra></extra>"
|
||||
),
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
# Add resistance levels
|
||||
for i, resistance_price in enumerate(resistance_levels[-5:]): # Show last 5 resistance levels
|
||||
color_idx = min(i, len(pivot_colors['resistance']) - 1)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[chart_start, chart_end],
|
||||
y=[resistance_price, resistance_price],
|
||||
mode='lines',
|
||||
line=dict(
|
||||
color=pivot_colors['resistance'][color_idx],
|
||||
width=2,
|
||||
dash='dot'
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
# Add low pivots for this level
|
||||
if lows_x:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=lows_x, y=lows_y,
|
||||
mode='markers',
|
||||
name=f'L{level_num} Pivot Low',
|
||||
marker=dict(
|
||||
color=style['low_color'],
|
||||
size=style['size'],
|
||||
symbol='triangle-up',
|
||||
opacity=style['opacity'],
|
||||
line=dict(width=1, color='white')
|
||||
),
|
||||
showlegend=(level_num == 1), # Only show legend for Level 1
|
||||
hovertemplate=f"Level {level_num} Low: ${{y:.2f}}<extra></extra>"
|
||||
),
|
||||
name=f'Resistance L{i+1}: ${resistance_price:.2f}',
|
||||
showlegend=True,
|
||||
hovertemplate=f"Resistance Level {i+1}: ${{y:.2f}}<extra></extra>"
|
||||
),
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
# Add pivot context annotation if available
|
||||
if hasattr(pivot_bounds, 'pivot_context') and pivot_bounds.pivot_context:
|
||||
context = pivot_bounds.pivot_context
|
||||
if isinstance(context, dict) and 'trend_direction' in context:
|
||||
trend = context.get('trend_direction', 'UNKNOWN')
|
||||
strength = context.get('trend_strength', 0.0)
|
||||
nested_levels = context.get('nested_levels', 0)
|
||||
|
||||
# Add trend annotation
|
||||
trend_color = {
|
||||
'UPTREND': 'green',
|
||||
'DOWNTREND': 'red',
|
||||
'SIDEWAYS': 'orange'
|
||||
}.get(trend, 'gray')
|
||||
|
||||
fig.add_annotation(
|
||||
xref="paper", yref="paper",
|
||||
x=0.02, y=0.98,
|
||||
text=f"Trend: {trend} ({strength:.1%}) | Pivots: {nested_levels} levels",
|
||||
showarrow=False,
|
||||
bgcolor="rgba(0,0,0,0.7)",
|
||||
bordercolor=trend_color,
|
||||
borderwidth=1,
|
||||
borderpad=4,
|
||||
font=dict(color="white", size=10),
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
logger.debug(f"Added {len(support_levels)} support and {len(resistance_levels)} resistance levels to chart")
|
||||
# Add multi-level trend analysis annotation
|
||||
if pivot_levels:
|
||||
# Build trend summary from all levels
|
||||
trend_lines = []
|
||||
for level_num in sorted(pivot_levels.keys()):
|
||||
trend_level = pivot_levels[level_num]
|
||||
if hasattr(trend_level, 'trend_direction') and hasattr(trend_level, 'trend_strength'):
|
||||
direction = trend_level.trend_direction
|
||||
strength = trend_level.trend_strength
|
||||
|
||||
# Format direction
|
||||
direction_emoji = {
|
||||
'up': '↑',
|
||||
'down': '↓',
|
||||
'sideways': '→'
|
||||
}.get(direction, '?')
|
||||
|
||||
trend_lines.append(f"L{level_num}: {direction_emoji} {strength:.0%}")
|
||||
|
||||
if trend_lines:
|
||||
# Determine overall trend color from Level 5 (longest-term)
|
||||
overall_trend = 'sideways'
|
||||
if 5 in pivot_levels and hasattr(pivot_levels[5], 'trend_direction'):
|
||||
overall_trend = pivot_levels[5].trend_direction
|
||||
|
||||
trend_color = {
|
||||
'up': 'rgba(0, 255, 0, 0.8)',
|
||||
'down': 'rgba(255, 0, 0, 0.8)',
|
||||
'sideways': 'rgba(255, 165, 0, 0.8)'
|
||||
}.get(overall_trend, 'rgba(128, 128, 128, 0.8)')
|
||||
|
||||
fig.add_annotation(
|
||||
xref="paper", yref="paper",
|
||||
x=0.02, y=0.98,
|
||||
text="<br>".join(["Williams Trends:"] + trend_lines),
|
||||
showarrow=False,
|
||||
bgcolor="rgba(0,0,0,0.85)",
|
||||
bordercolor=trend_color,
|
||||
borderwidth=2,
|
||||
borderpad=6,
|
||||
font=dict(color="white", size=9, family="monospace"),
|
||||
align="left",
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
logger.debug(f"Added {len(pivot_levels)} Williams pivot levels to chart")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error adding pivot points to chart: {e}")
|
||||
|
||||
@@ -308,7 +308,7 @@ class DashboardLayoutManager:
|
||||
("current-price", "Live Price", "text-success"),
|
||||
("session-pnl", "Session P&L", ""),
|
||||
("current-position", "Position", "text-info"),
|
||||
# ("leverage-info", "Leverage", "text-primary"),
|
||||
("open-interest", "Open Interest", "text-info"),
|
||||
("trade-count", "Trades", "text-warning"),
|
||||
("portfolio-value", "Portfolio", "text-secondary"),
|
||||
("profitability-multiplier", "Profit Boost", "text-primary"),
|
||||
|
||||
Reference in New Issue
Block a user