diff --git a/ANNOTATE/web/app.py b/ANNOTATE/web/app.py index b9a7ae0..11f6739 100644 --- a/ANNOTATE/web/app.py +++ b/ANNOTATE/web/app.py @@ -18,6 +18,7 @@ from dash import Dash, html import logging from datetime import datetime import json +import pandas as pd # Import core components from main system try: @@ -197,6 +198,70 @@ class AnnotationDashboard: storage_thread = threading.Thread(target=enable_storage, daemon=True) 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): """Setup Flask routes""" @@ -333,6 +398,9 @@ class AnnotationDashboard: ) 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 chart_data[timeframe] = { 'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(), @@ -340,7 +408,8 @@ class AnnotationDashboard: 'high': df['high'].tolist(), 'low': df['low'].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 @@ -569,13 +638,17 @@ class AnnotationDashboard: ) 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] = { 'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(), 'open': df['open'].tolist(), 'high': df['high'].tolist(), 'low': df['low'].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") else: diff --git a/ANNOTATE/web/static/js/chart_manager.js b/ANNOTATE/web/static/js/chart_manager.js index 50148fb..4c9757c 100644 --- a/ANNOTATE/web/static/js/chart_manager.js +++ b/ANNOTATE/web/static/js/chart_manager.js @@ -9,39 +9,48 @@ class ChartManager { this.charts = {}; this.annotations = {}; this.syncedTime = null; - + console.log('ChartManager initialized with timeframes:', timeframes); } - + /** * Initialize charts for all timeframes with pivot bounds */ initializeCharts(chartData, pivotBounds = null) { console.log('Initializing charts with data:', chartData); console.log('Pivot bounds:', pivotBounds); - - this.timeframes.forEach(timeframe => { - if (chartData[timeframe]) { - this.createChart(timeframe, chartData[timeframe], pivotBounds); + + // Use requestAnimationFrame to batch chart creation + let index = 0; + const createNextChart = () => { + if (index < this.timeframes.length) { + const timeframe = this.timeframes[index]; + if (chartData[timeframe]) { + this.createChart(timeframe, chartData[timeframe], pivotBounds); + } + index++; + requestAnimationFrame(createNextChart); + } else { + // Enable crosshair after all charts are created + this.enableCrosshair(); } - }); - - // Enable crosshair - this.enableCrosshair(); + }; + + requestAnimationFrame(createNextChart); } - + /** * Create a single chart for a timeframe */ createChart(timeframe, data, pivotBounds = null) { const plotId = `plot-${timeframe}`; const plotElement = document.getElementById(plotId); - + if (!plotElement) { console.error(`Plot element not found: ${plotId}`); return; } - + // Create candlestick trace const candlestickTrace = { x: data.timestamps, @@ -52,23 +61,23 @@ class ChartManager { type: 'candlestick', name: 'Price', increasing: { - line: {color: '#10b981', width: 1}, + line: { color: '#10b981', width: 1 }, fillcolor: '#10b981' }, decreasing: { - line: {color: '#ef4444', width: 1}, + line: { color: '#ef4444', width: 1 }, fillcolor: '#ef4444' }, xaxis: 'x', yaxis: 'y' }; - + // Create volume trace with color based on price direction const volumeColors = data.close.map((close, i) => { if (i === 0) return '#3b82f6'; return close >= data.open[i] ? '#10b981' : '#ef4444'; }); - + const volumeTrace = { x: data.timestamps, y: data.volume, @@ -81,12 +90,12 @@ class ChartManager { }, hoverinfo: 'y' }; - + const layout = { title: '', showlegend: false, xaxis: { - rangeslider: {visible: false}, + rangeslider: { visible: false }, gridcolor: '#374151', color: '#9ca3af', showgrid: true, @@ -95,7 +104,7 @@ class ChartManager { yaxis: { title: { text: 'Price (USD)', - font: {size: 10} + font: { size: 10 } }, gridcolor: '#374151', color: '#9ca3af', @@ -106,7 +115,7 @@ class ChartManager { yaxis2: { title: { text: 'Volume', - font: {size: 10} + font: { size: 10 } }, gridcolor: '#374151', color: '#9ca3af', @@ -116,64 +125,128 @@ class ChartManager { }, plot_bgcolor: '#1f2937', paper_bgcolor: '#1f2937', - font: {color: '#f8f9fa', size: 11}, - margin: {l: 60, r: 20, t: 10, b: 40}, + font: { color: '#f8f9fa', size: 11 }, + margin: { l: 60, r: 20, t: 10, b: 40 }, hovermode: 'x unified', - dragmode: 'pan' + dragmode: 'pan', + // Performance optimizations + autosize: true, + staticPlot: false }; - + const config = { responsive: true, displayModeBar: true, modeBarButtonsToRemove: ['lasso2d', 'select2d', 'autoScale2d'], displaylogo: false, - scrollZoom: true + scrollZoom: true, + // Performance optimizations + doubleClick: false, + showAxisDragHandles: false, + showAxisRangeEntryBoxes: false }; - + // Prepare chart data with pivot bounds const chartData = [candlestickTrace, volumeTrace]; + + // Add pivot markers from chart data (last high/low for each level L1-L5) + const shapes = []; + const annotations = []; - // Add pivot levels if available - if (pivotBounds && pivotBounds.support_levels && pivotBounds.resistance_levels) { - // Add support levels - pivotBounds.support_levels.forEach((level, index) => { - chartData.push({ - x: data.timestamps, - y: Array(data.timestamps.length).fill(level), - type: 'scatter', - mode: 'lines', - line: { - color: '#28a745', - width: 1, - dash: 'dash' - }, - name: `Support ${index + 1}`, - showlegend: index === 0, // Only show legend for first support level - hovertemplate: `Support: $%{y:.2f}` - }); + if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) { + const xMin = data.timestamps[0]; + const xMax = data.timestamps[data.timestamps.length - 1]; + + // 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: { + color: color, + width: 1, + dash: 'dash' + }, + layer: 'below' + }); + + // Add label for the level + annotations.push({ + x: xMax, + y: pivot.price, + text: `L${pivot.level}H`, + showarrow: false, + xanchor: 'left', + font: { + size: 9, + color: color + }, + bgcolor: '#1f2937', + borderpad: 2 + }); + }); + } + + // 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 + }); + }); + } }); - // Add resistance levels - pivotBounds.resistance_levels.forEach((level, index) => { - chartData.push({ - x: data.timestamps, - y: Array(data.timestamps.length).fill(level), - type: 'scatter', - mode: 'lines', - line: { - color: '#dc3545', - width: 1, - dash: 'dash' - }, - name: `Resistance ${index + 1}`, - showlegend: index === 0, // Only show legend for first resistance level - hovertemplate: `Resistance: $%{y:.2f}` - }); - }); + console.log(`Added ${shapes.length} pivot levels to ${timeframe} chart`); } - - Plotly.newPlot(plotId, chartData, layout, config); - + + // 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 this.charts[timeframe] = { plotId: plotId, @@ -181,12 +254,12 @@ class ChartManager { element: plotElement, annotations: [] }; - + // Add click handler for chart plotElement.on('plotly_click', (eventData) => { this.handleChartClick(timeframe, eventData); }); - + // Add click handler for annotations plotElement.on('plotly_clickannotation', (eventData) => { const annotationName = eventData.annotation.name; @@ -194,7 +267,7 @@ class ChartManager { const parts = annotationName.split('_'); const action = parts[0]; // 'entry', 'exit', or 'delete' const annotationId = parts[1]; - + if (action === 'delete') { this.handleAnnotationClick(annotationId, 'delete'); } else { @@ -202,23 +275,23 @@ class ChartManager { } } }); - + // Add hover handler to update info plotElement.on('plotly_hover', (eventData) => { this.updateChartInfo(timeframe, eventData); }); - + console.log(`Chart created for ${timeframe} with ${data.timestamps.length} candles`); } - + /** * Handle chart click for annotation */ handleChartClick(timeframe, eventData) { if (!eventData.points || eventData.points.length === 0) return; - + const point = eventData.points[0]; - + // Get the actual price from candlestick data let price; if (point.data.type === 'candlestick') { @@ -230,22 +303,22 @@ class ChartManager { } else { price = point.y; } - + const clickData = { timeframe: timeframe, timestamp: point.x, price: price, index: point.pointIndex }; - + console.log('Chart clicked:', clickData); - + // Trigger annotation manager if (window.appState && window.appState.annotationManager) { window.appState.annotationManager.handleChartClick(clickData); } } - + /** * Update charts with new data including pivot levels */ @@ -253,81 +326,146 @@ class ChartManager { Object.keys(newData).forEach(timeframe => { if (this.charts[timeframe]) { 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 const chartData = [ { - x: newData[timeframe].timestamps, - open: newData[timeframe].open, - high: newData[timeframe].high, - low: newData[timeframe].low, - close: newData[timeframe].close, + x: data.timestamps, + open: data.open, + high: data.high, + low: data.low, + close: data.close, 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, - y: newData[timeframe].volume, + x: data.timestamps, + y: data.volume, type: 'bar', yaxis: 'y2', name: 'Volume', - marker: { color: 'rgba(0, 123, 255, 0.3)' } + marker: { + color: volumeColors, + opacity: 0.3 + }, + hoverinfo: 'y' } ]; + + // Add pivot markers from chart data + const shapes = []; + const annotations = []; - // Add pivot levels if available - if (pivotBounds && pivotBounds.support_levels && pivotBounds.resistance_levels) { - // Add support levels - pivotBounds.support_levels.forEach((level, index) => { - chartData.push({ - x: newData[timeframe].timestamps, - y: Array(newData[timeframe].timestamps.length).fill(level), - type: 'scatter', - mode: 'lines', - line: { - color: '#28a745', - width: 1, - dash: 'dash' - }, - name: `Support ${index + 1}`, - showlegend: index === 0, // Only show legend for first support level - hovertemplate: `Support: $%{y:.2f}` - }); - }); + if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) { + const xMin = data.timestamps[0]; + const xMax = data.timestamps[data.timestamps.length - 1]; - // Add resistance levels - pivotBounds.resistance_levels.forEach((level, index) => { - chartData.push({ - x: newData[timeframe].timestamps, - y: Array(newData[timeframe].timestamps.length).fill(level), - type: 'scatter', - mode: 'lines', - line: { - color: '#dc3545', - width: 1, - dash: 'dash' - }, - name: `Resistance ${index + 1}`, - showlegend: index === 0, // Only show legend for first resistance level - hovertemplate: `Resistance: $%{y:.2f}` - }); + // 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: { + color: color, + width: 1, + dash: 'dash' + }, + layer: 'below' + }); + + annotations.push({ + x: xMax, + y: pivot.price, + text: `L${pivot.level}H`, + showarrow: false, + xanchor: 'left', + font: { + size: 9, + color: color + }, + bgcolor: '#1f2937', + borderpad: 2 + }); + }); + } + + // 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 + }); + }); + } }); } - - Plotly.react(plotId, chartData); + + // Use Plotly.react for efficient updates + const update = { + shapes: shapes, + annotations: annotations + }; + Plotly.react(plotId, chartData, update); } }); } - + /** * Add annotation to charts */ addAnnotation(annotation) { console.log('Adding annotation to charts:', annotation); - + // Store annotation this.annotations[annotation.annotation_id] = annotation; - + // Add markers to relevant timeframe chart const timeframe = annotation.timeframe; if (this.charts[timeframe]) { @@ -335,7 +473,7 @@ class ChartManager { this.updateChartAnnotations(timeframe); } } - + /** * Remove annotation from charts */ @@ -343,35 +481,35 @@ class ChartManager { if (this.annotations[annotationId]) { const annotation = this.annotations[annotationId]; delete this.annotations[annotationId]; - + // Update chart if (this.charts[annotation.timeframe]) { this.updateChartAnnotations(annotation.timeframe); } } } - + /** * Update chart annotations */ updateChartAnnotations(timeframe) { const chart = this.charts[timeframe]; if (!chart) return; - + // Get annotations for this timeframe const timeframeAnnotations = Object.values(this.annotations) .filter(ann => ann.timeframe === timeframe); - + // Build Plotly annotations and shapes const plotlyAnnotations = []; const plotlyShapes = []; - + timeframeAnnotations.forEach(ann => { const entryTime = ann.entry.timestamp; const exitTime = ann.exit.timestamp; const entryPrice = ann.entry.price; const exitPrice = ann.exit.price; - + // Entry marker (clickable) plotlyAnnotations.push({ x: entryTime, @@ -387,7 +525,7 @@ class ChartManager { captureevents: true, name: `entry_${ann.annotation_id}` }); - + // Exit marker (clickable) plotlyAnnotations.push({ x: exitTime, @@ -403,12 +541,12 @@ class ChartManager { captureevents: true, name: `exit_${ann.annotation_id}` }); - + // P&L label with delete button const midTime = new Date((new Date(entryTime).getTime() + new Date(exitTime).getTime()) / 2); const midPrice = (entryPrice + exitPrice) / 2; const pnlColor = ann.profit_loss_pct >= 0 ? '#10b981' : '#ef4444'; - + plotlyAnnotations.push({ x: midTime, y: midPrice, @@ -429,7 +567,7 @@ class ChartManager { captureevents: true, name: `delete_${ann.annotation_id}` }); - + // Connecting line (clickable for selection) plotlyShapes.push({ type: 'line', @@ -445,22 +583,22 @@ class ChartManager { name: `line_${ann.annotation_id}` }); }); - + // Update chart layout with annotations Plotly.relayout(chart.plotId, { annotations: plotlyAnnotations, shapes: plotlyShapes }); - + console.log(`Updated ${timeframeAnnotations.length} annotations for ${timeframe}`); } - + /** * Handle annotation click for editing/deleting */ handleAnnotationClick(annotationId, action) { console.log(`Annotation ${action}:`, annotationId); - + if (action === 'delete') { if (confirm('Delete this annotation?')) { if (window.deleteAnnotation) { @@ -473,18 +611,18 @@ class ChartManager { } } } - + /** * Highlight annotation */ highlightAnnotation(annotationId) { const annotation = this.annotations[annotationId]; if (!annotation) return; - + const timeframe = annotation.timeframe; const chart = this.charts[timeframe]; if (!chart) return; - + // Flash the annotation by temporarily changing its color const originalAnnotations = chart.element.layout.annotations || []; const highlightedAnnotations = originalAnnotations.map(ann => { @@ -497,24 +635,24 @@ class ChartManager { } }; }); - - Plotly.relayout(chart.plotId, {annotations: highlightedAnnotations}); - + + Plotly.relayout(chart.plotId, { annotations: highlightedAnnotations }); + // Restore original colors after 1 second setTimeout(() => { this.updateChartAnnotations(timeframe); }, 1000); - + console.log('Highlighted annotation:', annotationId); } - + /** * Edit annotation - allows moving entry/exit points */ editAnnotation(annotationId) { const annotation = this.annotations[annotationId]; if (!annotation) return; - + // Show edit dialog const action = prompt( 'Edit annotation:\n' + @@ -524,11 +662,11 @@ class ChartManager { 'Enter choice (1-3):', '1' ); - + if (action === '1') { // Move entry point window.showSuccess('Click on chart to set new entry point'); - + // Store annotation for editing if (window.appState && window.appState.annotationManager) { window.appState.annotationManager.editingAnnotation = { @@ -536,10 +674,10 @@ class ChartManager { original: annotation, editMode: 'entry' }; - + // Remove current annotation from display this.removeAnnotation(annotationId); - + // Show exit marker as reference const chart = this.charts[annotation.timeframe]; if (chart) { @@ -552,7 +690,7 @@ class ChartManager { arrowhead: 2, ax: 0, ay: 40, - font: {size: 14, color: '#9ca3af'} + font: { size: 14, color: '#9ca3af' } }] }); } @@ -560,17 +698,17 @@ class ChartManager { } else if (action === '2') { // Move exit point window.showSuccess('Click on chart to set new exit point'); - + if (window.appState && window.appState.annotationManager) { window.appState.annotationManager.editingAnnotation = { annotation_id: annotationId, original: annotation, editMode: 'exit' }; - + // Remove current annotation from display this.removeAnnotation(annotationId); - + // Show entry marker as reference const chart = this.charts[annotation.timeframe]; if (chart) { @@ -583,7 +721,7 @@ class ChartManager { arrowhead: 2, ax: 0, ay: -40, - font: {size: 14, color: '#9ca3af'} + font: { size: 14, color: '#9ca3af' } }] }); } @@ -593,6 +731,18 @@ class ChartManager { this.handleAnnotationClick(annotationId, 'delete'); } } + + /** + * 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 @@ -601,7 +751,7 @@ class ChartManager { // Crosshair is enabled via hovermode in layout console.log('Crosshair enabled'); } - + /** * Handle zoom */ @@ -613,7 +763,7 @@ class ChartManager { }); }); } - + /** * Reset zoom */ @@ -625,23 +775,23 @@ class ChartManager { }); }); } - + /** * Synchronize time navigation across charts */ syncTimeNavigation(timestamp) { this.syncedTime = timestamp; - + // Update all charts to center on this timestamp Object.values(this.charts).forEach(chart => { const data = chart.data; const timestamps = data.timestamps; - + // Find index closest to target timestamp const targetTime = new Date(timestamp); let closestIndex = 0; let minDiff = Infinity; - + timestamps.forEach((ts, i) => { const diff = Math.abs(new Date(ts) - targetTime); if (diff < minDiff) { @@ -649,35 +799,35 @@ class ChartManager { closestIndex = i; } }); - + // Center the view on this index const rangeSize = 100; // Show 100 candles const startIndex = Math.max(0, closestIndex - rangeSize / 2); const endIndex = Math.min(timestamps.length - 1, closestIndex + rangeSize / 2); - + Plotly.relayout(chart.plotId, { 'xaxis.range': [timestamps[startIndex], timestamps[endIndex]] }); }); - + console.log('Synced charts to timestamp:', timestamp); } - + /** * Update chart info display on hover */ updateChartInfo(timeframe, eventData) { if (!eventData.points || eventData.points.length === 0) return; - + const point = eventData.points[0]; const infoElement = document.getElementById(`info-${timeframe}`); - + if (infoElement && point.data.type === 'candlestick') { const open = point.data.open[point.pointIndex]; const high = point.data.high[point.pointIndex]; const low = point.data.low[point.pointIndex]; const close = point.data.close[point.pointIndex]; - + infoElement.textContent = `O: ${open.toFixed(2)} H: ${high.toFixed(2)} L: ${low.toFixed(2)} C: ${close.toFixed(2)}`; } } diff --git a/ANNOTATE/web/templates/annotation_dashboard.html b/ANNOTATE/web/templates/annotation_dashboard.html index a20167a..9f976fe 100644 --- a/ANNOTATE/web/templates/annotation_dashboard.html +++ b/ANNOTATE/web/templates/annotation_dashboard.html @@ -61,8 +61,8 @@ // Initialize application state window.appState = { currentSymbol: '{{ current_symbol }}', - currentTimeframes: '{{ timeframes | tojson }}', - annotations: '{{ annotations | tojson }}', + currentTimeframes: {{ timeframes | tojson }}, + annotations: {{ annotations | tojson }}, pendingAnnotation: null, chartManager: null, annotationManager: null, diff --git a/core/data_provider.py b/core/data_provider.py index 559f168..f2e3936 100644 --- a/core/data_provider.py +++ b/core/data_provider.py @@ -2244,8 +2244,8 @@ class DataProvider: # Initialize Williams Market Structure analyzer try: - - williams = WilliamsMarketStructure(1) + # Use pivot length of 2 (tip + 2 prev + 2 next = 5 candles) + williams = WilliamsMarketStructure(min_pivot_distance=2) # Calculate 5 levels of recursive pivot points logger.info("Running Williams Market Structure analysis...")