cache, pivots wip

This commit is contained in:
Dobromir Popov
2025-10-20 15:21:44 +03:00
parent ba8813f04f
commit e993bc2831
9 changed files with 1630 additions and 99 deletions

View File

@@ -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}")

View File

@@ -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"),