fix pivots - WORKING PIVOTS!

This commit is contained in:
Dobromir Popov
2025-10-24 11:44:25 +03:00
parent de2ad92602
commit bd95ff610c
4 changed files with 402 additions and 179 deletions

View File

@@ -18,6 +18,7 @@ from dash import Dash, html
import logging import logging
from datetime import datetime from datetime import datetime
import json import json
import pandas as pd
# Import core components from main system # Import core components from main system
try: try:
@@ -197,6 +198,70 @@ class AnnotationDashboard:
storage_thread = threading.Thread(target=enable_storage, daemon=True) storage_thread = threading.Thread(target=enable_storage, daemon=True)
storage_thread.start() storage_thread.start()
def _get_pivot_markers_for_timeframe(self, symbol: str, timeframe: str, df: pd.DataFrame) -> dict:
"""
Get pivot markers for a specific timeframe
Returns dict with indices mapping to pivot info for that candle
"""
try:
if not self.data_provider:
return {}
# Get Williams pivot levels for this timeframe
pivot_levels = self.data_provider.get_williams_pivot_levels(
symbol=symbol,
base_timeframe=timeframe,
limit=len(df)
)
if not pivot_levels:
return {}
# Build a map of timestamp -> pivot info
pivot_map = {}
# For each level (1-5), find the last high and last low pivot
for level_num, trend_level in pivot_levels.items():
if not hasattr(trend_level, 'pivot_points') or not trend_level.pivot_points:
continue
# Find last high and last low for this level
last_high = None
last_low = None
for pivot in trend_level.pivot_points:
if pivot.pivot_type == 'high':
last_high = pivot
elif pivot.pivot_type == 'low':
last_low = pivot
# Add to pivot map
if last_high:
ts_str = last_high.timestamp.strftime('%Y-%m-%d %H:%M:%S')
if ts_str not in pivot_map:
pivot_map[ts_str] = {'highs': [], 'lows': []}
pivot_map[ts_str]['highs'].append({
'level': level_num,
'price': last_high.price,
'strength': last_high.strength
})
if last_low:
ts_str = last_low.timestamp.strftime('%Y-%m-%d %H:%M:%S')
if ts_str not in pivot_map:
pivot_map[ts_str] = {'highs': [], 'lows': []}
pivot_map[ts_str]['lows'].append({
'level': level_num,
'price': last_low.price,
'strength': last_low.strength
})
return pivot_map
except Exception as e:
logger.error(f"Error getting pivot markers for {timeframe}: {e}")
return {}
def _setup_routes(self): def _setup_routes(self):
"""Setup Flask routes""" """Setup Flask routes"""
@@ -333,6 +398,9 @@ class AnnotationDashboard:
) )
if df is not None and not df.empty: if df is not None and not df.empty:
# Get pivot points for this timeframe
pivot_markers = self._get_pivot_markers_for_timeframe(symbol, timeframe, df)
# Convert to format suitable for Plotly # Convert to format suitable for Plotly
chart_data[timeframe] = { chart_data[timeframe] = {
'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(), 'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(),
@@ -340,7 +408,8 @@ class AnnotationDashboard:
'high': df['high'].tolist(), 'high': df['high'].tolist(),
'low': df['low'].tolist(), 'low': df['low'].tolist(),
'close': df['close'].tolist(), 'close': df['close'].tolist(),
'volume': df['volume'].tolist() 'volume': df['volume'].tolist(),
'pivot_markers': pivot_markers # Optional: only present if pivots exist
} }
# Get pivot bounds for the symbol # Get pivot bounds for the symbol
@@ -569,13 +638,17 @@ class AnnotationDashboard:
) )
if df is not None and not df.empty: if df is not None and not df.empty:
# Get pivot markers for this timeframe
pivot_markers = self._get_pivot_markers_for_timeframe(symbol, timeframe, df)
chart_data[timeframe] = { chart_data[timeframe] = {
'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(), 'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(),
'open': df['open'].tolist(), 'open': df['open'].tolist(),
'high': df['high'].tolist(), 'high': df['high'].tolist(),
'low': df['low'].tolist(), 'low': df['low'].tolist(),
'close': df['close'].tolist(), 'close': df['close'].tolist(),
'volume': df['volume'].tolist() 'volume': df['volume'].tolist(),
'pivot_markers': pivot_markers # Optional: only present if pivots exist
} }
logger.info(f"Refreshed {timeframe}: {len(df)} candles") logger.info(f"Refreshed {timeframe}: {len(df)} candles")
else: else:

View File

@@ -20,15 +20,24 @@ class ChartManager {
console.log('Initializing charts with data:', chartData); console.log('Initializing charts with data:', chartData);
console.log('Pivot bounds:', pivotBounds); console.log('Pivot bounds:', pivotBounds);
this.timeframes.forEach(timeframe => { // Use requestAnimationFrame to batch chart creation
let index = 0;
const createNextChart = () => {
if (index < this.timeframes.length) {
const timeframe = this.timeframes[index];
if (chartData[timeframe]) { if (chartData[timeframe]) {
this.createChart(timeframe, chartData[timeframe], pivotBounds); this.createChart(timeframe, chartData[timeframe], pivotBounds);
} }
}); index++;
requestAnimationFrame(createNextChart);
// Enable crosshair } else {
// Enable crosshair after all charts are created
this.enableCrosshair(); this.enableCrosshair();
} }
};
requestAnimationFrame(createNextChart);
}
/** /**
* Create a single chart for a timeframe * Create a single chart for a timeframe
@@ -52,11 +61,11 @@ class ChartManager {
type: 'candlestick', type: 'candlestick',
name: 'Price', name: 'Price',
increasing: { increasing: {
line: {color: '#10b981', width: 1}, line: { color: '#10b981', width: 1 },
fillcolor: '#10b981' fillcolor: '#10b981'
}, },
decreasing: { decreasing: {
line: {color: '#ef4444', width: 1}, line: { color: '#ef4444', width: 1 },
fillcolor: '#ef4444' fillcolor: '#ef4444'
}, },
xaxis: 'x', xaxis: 'x',
@@ -86,7 +95,7 @@ class ChartManager {
title: '', title: '',
showlegend: false, showlegend: false,
xaxis: { xaxis: {
rangeslider: {visible: false}, rangeslider: { visible: false },
gridcolor: '#374151', gridcolor: '#374151',
color: '#9ca3af', color: '#9ca3af',
showgrid: true, showgrid: true,
@@ -95,7 +104,7 @@ class ChartManager {
yaxis: { yaxis: {
title: { title: {
text: 'Price (USD)', text: 'Price (USD)',
font: {size: 10} font: { size: 10 }
}, },
gridcolor: '#374151', gridcolor: '#374151',
color: '#9ca3af', color: '#9ca3af',
@@ -106,7 +115,7 @@ class ChartManager {
yaxis2: { yaxis2: {
title: { title: {
text: 'Volume', text: 'Volume',
font: {size: 10} font: { size: 10 }
}, },
gridcolor: '#374151', gridcolor: '#374151',
color: '#9ca3af', color: '#9ca3af',
@@ -116,10 +125,13 @@ class ChartManager {
}, },
plot_bgcolor: '#1f2937', plot_bgcolor: '#1f2937',
paper_bgcolor: '#1f2937', paper_bgcolor: '#1f2937',
font: {color: '#f8f9fa', size: 11}, font: { color: '#f8f9fa', size: 11 },
margin: {l: 60, r: 20, t: 10, b: 40}, margin: { l: 60, r: 20, t: 10, b: 40 },
hovermode: 'x unified', hovermode: 'x unified',
dragmode: 'pan' dragmode: 'pan',
// Performance optimizations
autosize: true,
staticPlot: false
}; };
const config = { const config = {
@@ -127,52 +139,113 @@ class ChartManager {
displayModeBar: true, displayModeBar: true,
modeBarButtonsToRemove: ['lasso2d', 'select2d', 'autoScale2d'], modeBarButtonsToRemove: ['lasso2d', 'select2d', 'autoScale2d'],
displaylogo: false, displaylogo: false,
scrollZoom: true scrollZoom: true,
// Performance optimizations
doubleClick: false,
showAxisDragHandles: false,
showAxisRangeEntryBoxes: false
}; };
// Prepare chart data with pivot bounds // Prepare chart data with pivot bounds
const chartData = [candlestickTrace, volumeTrace]; const chartData = [candlestickTrace, volumeTrace];
// Add pivot levels if available // Add pivot markers from chart data (last high/low for each level L1-L5)
if (pivotBounds && pivotBounds.support_levels && pivotBounds.resistance_levels) { const shapes = [];
// Add support levels const annotations = [];
pivotBounds.support_levels.forEach((level, index) => {
chartData.push({ if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) {
x: data.timestamps, const xMin = data.timestamps[0];
y: Array(data.timestamps.length).fill(level), const xMax = data.timestamps[data.timestamps.length - 1];
type: 'scatter',
mode: 'lines', // Process each timestamp that has pivot markers
Object.entries(data.pivot_markers).forEach(([timestamp, pivots]) => {
// Draw horizontal lines for last high pivots (resistance)
if (pivots.highs && pivots.highs.length > 0) {
pivots.highs.forEach(pivot => {
const color = this._getPivotColor(pivot.level, 'high');
shapes.push({
type: 'line',
x0: xMin,
y0: pivot.price,
x1: xMax,
y1: pivot.price,
line: { line: {
color: '#28a745', color: color,
width: 1, width: 1,
dash: 'dash' dash: 'dash'
}, },
name: `Support ${index + 1}`, layer: 'below'
showlegend: index === 0, // Only show legend for first support level
hovertemplate: `Support: $%{y:.2f}<extra></extra>`
});
}); });
// Add resistance levels // Add label for the level
pivotBounds.resistance_levels.forEach((level, index) => { annotations.push({
chartData.push({ x: xMax,
x: data.timestamps, y: pivot.price,
y: Array(data.timestamps.length).fill(level), text: `L${pivot.level}H`,
type: 'scatter', showarrow: false,
mode: 'lines', xanchor: 'left',
line: { font: {
color: '#dc3545', size: 9,
width: 1, color: color
dash: 'dash'
}, },
name: `Resistance ${index + 1}`, bgcolor: '#1f2937',
showlegend: index === 0, // Only show legend for first resistance level borderpad: 2
hovertemplate: `Resistance: $%{y:.2f}<extra></extra>`
}); });
}); });
} }
Plotly.newPlot(plotId, chartData, layout, config); // Draw horizontal lines for last low pivots (support)
if (pivots.lows && pivots.lows.length > 0) {
pivots.lows.forEach(pivot => {
const color = this._getPivotColor(pivot.level, 'low');
shapes.push({
type: 'line',
x0: xMin,
y0: pivot.price,
x1: xMax,
y1: pivot.price,
line: {
color: color,
width: 1,
dash: 'dash'
},
layer: 'below'
});
// Add label for the level
annotations.push({
x: xMax,
y: pivot.price,
text: `L${pivot.level}L`,
showarrow: false,
xanchor: 'left',
font: {
size: 9,
color: color
},
bgcolor: '#1f2937',
borderpad: 2
});
});
}
});
console.log(`Added ${shapes.length} pivot levels to ${timeframe} chart`);
}
// Add shapes and annotations to layout
if (shapes.length > 0) {
layout.shapes = shapes;
}
if (annotations.length > 0) {
layout.annotations = annotations;
}
// Use Plotly.react for better performance on updates
Plotly.newPlot(plotId, chartData, layout, config).then(() => {
// Optimize rendering after initial plot
plotElement._fullLayout._replotting = false;
});
// Store chart reference // Store chart reference
this.charts[timeframe] = { this.charts[timeframe] = {
@@ -253,68 +326,133 @@ class ChartManager {
Object.keys(newData).forEach(timeframe => { Object.keys(newData).forEach(timeframe => {
if (this.charts[timeframe]) { if (this.charts[timeframe]) {
const plotId = this.charts[timeframe].plotId; const plotId = this.charts[timeframe].plotId;
const data = newData[timeframe];
// Create volume colors
const volumeColors = data.close.map((close, i) => {
if (i === 0) return '#3b82f6';
return close >= data.open[i] ? '#10b981' : '#ef4444';
});
// Prepare chart data // Prepare chart data
const chartData = [ const chartData = [
{ {
x: newData[timeframe].timestamps, x: data.timestamps,
open: newData[timeframe].open, open: data.open,
high: newData[timeframe].high, high: data.high,
low: newData[timeframe].low, low: data.low,
close: newData[timeframe].close, close: data.close,
type: 'candlestick', type: 'candlestick',
name: 'Price' name: 'Price',
increasing: {
line: { color: '#10b981', width: 1 },
fillcolor: '#10b981'
},
decreasing: {
line: { color: '#ef4444', width: 1 },
fillcolor: '#ef4444'
}
}, },
{ {
x: newData[timeframe].timestamps, x: data.timestamps,
y: newData[timeframe].volume, y: data.volume,
type: 'bar', type: 'bar',
yaxis: 'y2', yaxis: 'y2',
name: 'Volume', name: 'Volume',
marker: { color: 'rgba(0, 123, 255, 0.3)' } marker: {
color: volumeColors,
opacity: 0.3
},
hoverinfo: 'y'
} }
]; ];
// Add pivot levels if available // Add pivot markers from chart data
if (pivotBounds && pivotBounds.support_levels && pivotBounds.resistance_levels) { const shapes = [];
// Add support levels const annotations = [];
pivotBounds.support_levels.forEach((level, index) => {
chartData.push({ if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) {
x: newData[timeframe].timestamps, const xMin = data.timestamps[0];
y: Array(newData[timeframe].timestamps.length).fill(level), const xMax = data.timestamps[data.timestamps.length - 1];
type: 'scatter',
mode: 'lines', // Process each timestamp that has pivot markers
Object.entries(data.pivot_markers).forEach(([timestamp, pivots]) => {
// Draw horizontal lines for last high pivots
if (pivots.highs && pivots.highs.length > 0) {
pivots.highs.forEach(pivot => {
const color = this._getPivotColor(pivot.level, 'high');
shapes.push({
type: 'line',
x0: xMin,
y0: pivot.price,
x1: xMax,
y1: pivot.price,
line: { line: {
color: '#28a745', color: color,
width: 1, width: 1,
dash: 'dash' dash: 'dash'
}, },
name: `Support ${index + 1}`, layer: 'below'
showlegend: index === 0, // Only show legend for first support level
hovertemplate: `Support: $%{y:.2f}<extra></extra>`
});
}); });
// Add resistance levels annotations.push({
pivotBounds.resistance_levels.forEach((level, index) => { x: xMax,
chartData.push({ y: pivot.price,
x: newData[timeframe].timestamps, text: `L${pivot.level}H`,
y: Array(newData[timeframe].timestamps.length).fill(level), showarrow: false,
type: 'scatter', xanchor: 'left',
mode: 'lines', font: {
line: { size: 9,
color: '#dc3545', color: color
width: 1,
dash: 'dash'
}, },
name: `Resistance ${index + 1}`, bgcolor: '#1f2937',
showlegend: index === 0, // Only show legend for first resistance level borderpad: 2
hovertemplate: `Resistance: $%{y:.2f}<extra></extra>`
}); });
}); });
} }
Plotly.react(plotId, chartData); // Draw horizontal lines for last low pivots
if (pivots.lows && pivots.lows.length > 0) {
pivots.lows.forEach(pivot => {
const color = this._getPivotColor(pivot.level, 'low');
shapes.push({
type: 'line',
x0: xMin,
y0: pivot.price,
x1: xMax,
y1: pivot.price,
line: {
color: color,
width: 1,
dash: 'dash'
},
layer: 'below'
});
annotations.push({
x: xMax,
y: pivot.price,
text: `L${pivot.level}L`,
showarrow: false,
xanchor: 'left',
font: {
size: 9,
color: color
},
bgcolor: '#1f2937',
borderpad: 2
});
});
}
});
}
// Use Plotly.react for efficient updates
const update = {
shapes: shapes,
annotations: annotations
};
Plotly.react(plotId, chartData, update);
} }
}); });
} }
@@ -498,7 +636,7 @@ class ChartManager {
}; };
}); });
Plotly.relayout(chart.plotId, {annotations: highlightedAnnotations}); Plotly.relayout(chart.plotId, { annotations: highlightedAnnotations });
// Restore original colors after 1 second // Restore original colors after 1 second
setTimeout(() => { setTimeout(() => {
@@ -552,7 +690,7 @@ class ChartManager {
arrowhead: 2, arrowhead: 2,
ax: 0, ax: 0,
ay: 40, ay: 40,
font: {size: 14, color: '#9ca3af'} font: { size: 14, color: '#9ca3af' }
}] }]
}); });
} }
@@ -583,7 +721,7 @@ class ChartManager {
arrowhead: 2, arrowhead: 2,
ax: 0, ax: 0,
ay: -40, ay: -40,
font: {size: 14, color: '#9ca3af'} font: { size: 14, color: '#9ca3af' }
}] }]
}); });
} }
@@ -594,6 +732,18 @@ class ChartManager {
} }
} }
/**
* Get color for pivot level
*/
_getPivotColor(level, type) {
// Different colors for different levels
const highColors = ['#dc3545', '#ff6b6b', '#ff8787', '#ffa8a8', '#ffc9c9'];
const lowColors = ['#28a745', '#51cf66', '#69db7c', '#8ce99a', '#b2f2bb'];
const colors = type === 'high' ? highColors : lowColors;
return colors[Math.min(level - 1, colors.length - 1)];
}
/** /**
* Enable crosshair cursor * Enable crosshair cursor
*/ */

View File

@@ -61,8 +61,8 @@
// Initialize application state // Initialize application state
window.appState = { window.appState = {
currentSymbol: '{{ current_symbol }}', currentSymbol: '{{ current_symbol }}',
currentTimeframes: '{{ timeframes | tojson }}', currentTimeframes: {{ timeframes | tojson }},
annotations: '{{ annotations | tojson }}', annotations: {{ annotations | tojson }},
pendingAnnotation: null, pendingAnnotation: null,
chartManager: null, chartManager: null,
annotationManager: null, annotationManager: null,

View File

@@ -2244,8 +2244,8 @@ class DataProvider:
# Initialize Williams Market Structure analyzer # Initialize Williams Market Structure analyzer
try: try:
# Use pivot length of 2 (tip + 2 prev + 2 next = 5 candles)
williams = WilliamsMarketStructure(1) williams = WilliamsMarketStructure(min_pivot_distance=2)
# Calculate 5 levels of recursive pivot points # Calculate 5 levels of recursive pivot points
logger.info("Running Williams Market Structure analysis...") logger.info("Running Williams Market Structure analysis...")