diff --git a/ANNOTATE/data/annotations/annotations_db.json b/ANNOTATE/data/annotations/annotations_db.json index 0501be7..4a089f0 100644 --- a/ANNOTATE/data/annotations/annotations_db.json +++ b/ANNOTATE/data/annotations/annotations_db.json @@ -68,10 +68,33 @@ "entry_state": {}, "exit_state": {} } + }, + { + "annotation_id": "a16f92c0-7228-4293-a188-baf74ca9b284", + "symbol": "ETH/USDT", + "timeframe": "1m", + "entry": { + "timestamp": "2025-10-24 08:31", + "price": 3930.54, + "index": 101 + }, + "exit": { + "timestamp": "2025-10-24 08:46", + "price": 3965, + "index": 104 + }, + "direction": "LONG", + "profit_loss_pct": 0.8767243177782196, + "notes": "", + "created_at": "2025-10-24T12:56:59.390345", + "market_context": { + "entry_state": {}, + "exit_state": {} + } } ], "metadata": { - "total_annotations": 3, - "last_updated": "2025-10-23T18:36:05.809750" + "total_annotations": 4, + "last_updated": "2025-10-24T12:56:59.390345" } } \ No newline at end of file diff --git a/ANNOTATE/web/static/css/annotation_ui.css b/ANNOTATE/web/static/css/annotation_ui.css index 1b6d698..c95a43c 100644 --- a/ANNOTATE/web/static/css/annotation_ui.css +++ b/ANNOTATE/web/static/css/annotation_ui.css @@ -21,6 +21,12 @@ height: 100%; overflow-y: auto; overflow-x: hidden; + display: flex; + flex-direction: column; +} + +#chart-container.no-scroll { + overflow-y: hidden; } .timeframe-chart { @@ -28,6 +34,17 @@ border: 1px solid var(--border-color); border-radius: 4px; background-color: var(--bg-tertiary); + transition: all 0.3s ease; +} + +.timeframe-chart.minimized { + margin-bottom: 0.25rem; + opacity: 0.7; +} + +.timeframe-chart.minimized .chart-header { + background-color: var(--bg-primary); + border-bottom: none; } .chart-header { @@ -39,6 +56,25 @@ border-bottom: 1px solid var(--border-color); } +.chart-header-controls { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.minimize-btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 4px; + transition: all 0.2s; +} + +.minimize-btn:hover { + background-color: var(--accent-primary); + border-color: var(--accent-primary); + color: white; +} + .timeframe-label { font-weight: 600; font-size: 0.875rem; diff --git a/ANNOTATE/web/static/js/annotation_manager.js b/ANNOTATE/web/static/js/annotation_manager.js index fda3169..368de65 100644 --- a/ANNOTATE/web/static/js/annotation_manager.js +++ b/ANNOTATE/web/static/js/annotation_manager.js @@ -93,34 +93,52 @@ class AnnotationManager { * Delete old annotation and save updated one */ deleteAndSaveAnnotation(oldId, newAnnotation) { + console.log('Updating annotation:', oldId, newAnnotation); + // Delete old fetch('/api/delete-annotation', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({annotation_id: oldId}) }) - .then(() => { - // Save new - return fetch('/api/save-annotation', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(newAnnotation) - }); + .then(response => response.json()) + .then(deleteData => { + console.log('Delete response:', deleteData); + if (deleteData.success) { + // Save new + return fetch('/api/save-annotation', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(newAnnotation) + }); + } else { + throw new Error('Failed to delete old annotation: ' + deleteData.error.message); + } }) .then(response => response.json()) .then(data => { + console.log('Save response:', data); if (data.success) { // Update app state window.appState.annotations = window.appState.annotations.filter(a => a.annotation_id !== oldId); window.appState.annotations.push(data.annotation); // Update UI - window.renderAnnotationsList(window.appState.annotations); + if (typeof window.renderAnnotationsList === 'function') { + window.renderAnnotationsList(window.appState.annotations); + } + + // Update chart this.chartManager.removeAnnotation(oldId); this.chartManager.addAnnotation(data.annotation); + + window.showSuccess('Annotation updated successfully'); + } else { + throw new Error('Failed to save updated annotation: ' + data.error.message); } }) .catch(error => { + console.error('Update annotation error:', error); window.showError('Failed to update annotation: ' + error.message); }); } diff --git a/ANNOTATE/web/static/js/chart_manager.js b/ANNOTATE/web/static/js/chart_manager.js index 793c48e..137bbc1 100644 --- a/ANNOTATE/web/static/js/chart_manager.js +++ b/ANNOTATE/web/static/js/chart_manager.js @@ -170,7 +170,7 @@ class ChartManager { pivotDots.y.push(pivot.price); pivotDots.text.push(`L${pivot.level} High Pivot
Price: $${pivot.price.toFixed(2)}
Strength: ${(pivot.strength * 100).toFixed(0)}%`); pivotDots.marker.color.push(color); - pivotDots.marker.size.push(8); + pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level)); pivotDots.marker.symbol.push('triangle-down'); // Draw horizontal line ONLY for last pivot of this level @@ -217,7 +217,7 @@ class ChartManager { pivotDots.y.push(pivot.price); pivotDots.text.push(`L${pivot.level} Low Pivot
Price: $${pivot.price.toFixed(2)}
Strength: ${(pivot.strength * 100).toFixed(0)}%`); pivotDots.marker.color.push(color); - pivotDots.marker.size.push(8); + pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level)); pivotDots.marker.symbol.push('triangle-up'); // Draw horizontal line ONLY for last pivot of this level @@ -285,24 +285,42 @@ class ChartManager { annotations: [] }; - // Add click handler for chart + // Add click handler for chart and annotations plotElement.on('plotly_click', (eventData) => { + // Check if this is an annotation click by looking at the clicked element + if (eventData.points && eventData.points.length > 0) { + const point = eventData.points[0]; + // If it's a shape click (annotation line), try to find the annotation + if (point.data && point.data.name && point.data.name.startsWith('line_')) { + const annotationId = point.data.name.replace('line_', ''); + console.log('Line annotation clicked:', annotationId); + this.handleAnnotationClick(annotationId, 'edit'); + return; // Don't process as regular chart click + } + } + + // Regular chart click for annotation marking this.handleChartClick(timeframe, eventData); }); // Add click handler for annotations plotElement.on('plotly_clickannotation', (eventData) => { + console.log('Annotation clicked:', eventData); const annotationName = eventData.annotation.name; if (annotationName) { const parts = annotationName.split('_'); const action = parts[0]; // 'entry', 'exit', or 'delete' const annotationId = parts[1]; + console.log(`Annotation action: ${action}, ID: ${annotationId}`); + if (action === 'delete') { this.handleAnnotationClick(annotationId, 'delete'); } else { this.handleAnnotationClick(annotationId, 'edit'); } + } else { + console.log('No annotation name found in click event'); } }); @@ -418,7 +436,7 @@ class ChartManager { pivotDots.y.push(pivot.price); pivotDots.text.push(`L${pivot.level} High Pivot
Price: $${pivot.price.toFixed(2)}
Strength: ${(pivot.strength * 100).toFixed(0)}%`); pivotDots.marker.color.push(color); - pivotDots.marker.size.push(8); + pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level)); pivotDots.marker.symbol.push('triangle-down'); // Draw horizontal line ONLY for last pivot @@ -464,7 +482,7 @@ class ChartManager { pivotDots.y.push(pivot.price); pivotDots.text.push(`L${pivot.level} Low Pivot
Price: $${pivot.price.toFixed(2)}
Strength: ${(pivot.strength * 100).toFixed(0)}%`); pivotDots.marker.color.push(color); - pivotDots.marker.size.push(8); + pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level)); pivotDots.marker.symbol.push('triangle-up'); // Draw horizontal line ONLY for last pivot @@ -644,13 +662,32 @@ class ChartManager { }); }); - // Update chart layout with annotations + // Get existing pivot annotations (they have specific names like L1H, L2L) + const existingLayout = chart.element.layout || {}; + const existingAnnotations = existingLayout.annotations || []; + const pivotAnnotations = existingAnnotations.filter(ann => + ann.text && ann.text.match(/^L\d+[HL]$/) + ); + + // Merge pivot annotations with trade annotations + const allAnnotations = [...pivotAnnotations, ...plotlyAnnotations]; + + // Get existing pivot shapes + const existingShapes = existingLayout.shapes || []; + const pivotShapes = existingShapes.filter(shape => + shape.layer === 'below' && shape.line && shape.line.dash === 'dash' + ); + + // Merge pivot shapes with trade annotation shapes + const allShapes = [...pivotShapes, ...plotlyShapes]; + + // Update chart layout with merged annotations and shapes Plotly.relayout(chart.plotId, { - annotations: plotlyAnnotations, - shapes: plotlyShapes + annotations: allAnnotations, + shapes: allShapes }); - console.log(`Updated ${timeframeAnnotations.length} annotations for ${timeframe}`); + console.log(`Updated ${timeframeAnnotations.length} trade annotations for ${timeframe} (preserved ${pivotAnnotations.length} pivot annotations)`); } /** @@ -713,83 +750,217 @@ class ChartManager { const annotation = this.annotations[annotationId]; if (!annotation) return; - // Show edit dialog - const action = prompt( - 'Edit annotation:\n' + - '1 - Move entry point\n' + - '2 - Move exit point\n' + - '3 - Delete annotation\n' + - 'Enter choice (1-3):', - '1' - ); + // Create a better edit dialog using Bootstrap modal + this.showEditDialog(annotationId, annotation); + } - if (action === '1') { - // Move entry point - window.showSuccess('Click on chart to set new entry point'); + /** + * Show edit dialog for annotation + */ + showEditDialog(annotationId, annotation) { + // Create modal HTML + const modalHtml = ` + + `; - // Store annotation for editing - if (window.appState && window.appState.annotationManager) { - window.appState.annotationManager.editingAnnotation = { - annotation_id: annotationId, - 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) { - Plotly.relayout(chart.plotId, { - annotations: [{ - x: annotation.exit.timestamp, - y: annotation.exit.price, - text: '▼ (exit)', - showarrow: true, - arrowhead: 2, - ax: 0, - ay: 40, - font: { size: 14, color: '#9ca3af' } - }] - }); - } - } - } 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) { - Plotly.relayout(chart.plotId, { - annotations: [{ - x: annotation.entry.timestamp, - y: annotation.entry.price, - text: '▲ (entry)', - showarrow: true, - arrowhead: 2, - ax: 0, - ay: -40, - font: { size: 14, color: '#9ca3af' } - }] - }); - } - } - } else if (action === '3') { - // Delete - this.handleAnnotationClick(annotationId, 'delete'); + // Remove existing modal if any + const existingModal = document.getElementById('editAnnotationModal'); + if (existingModal) { + existingModal.remove(); } + + // Add modal to page + document.body.insertAdjacentHTML('beforeend', modalHtml); + + // Show modal + const modal = new bootstrap.Modal(document.getElementById('editAnnotationModal')); + modal.show(); + + // Add event listeners + document.getElementById('edit-entry-btn').addEventListener('click', () => { + modal.hide(); + this.startEditMode(annotationId, annotation, 'entry'); + }); + + document.getElementById('edit-exit-btn').addEventListener('click', () => { + modal.hide(); + this.startEditMode(annotationId, annotation, 'exit'); + }); + + document.getElementById('delete-annotation-btn').addEventListener('click', () => { + modal.hide(); + this.handleAnnotationClick(annotationId, 'delete'); + }); + + // Clean up modal when hidden + document.getElementById('editAnnotationModal').addEventListener('hidden.bs.modal', () => { + document.getElementById('editAnnotationModal').remove(); + }); + } + + /** + * Start edit mode for annotation + */ + startEditMode(annotationId, annotation, editMode) { + const message = editMode === 'entry' ? 'Click on chart to set new entry point' : 'Click on chart to set new exit point'; + window.showSuccess(message); + + // Store annotation for editing + if (window.appState && window.appState.annotationManager) { + window.appState.annotationManager.editingAnnotation = { + annotation_id: annotationId, + original: annotation, + editMode: editMode + }; + + // Remove current annotation from display + this.removeAnnotation(annotationId); + + // Show reference marker + const chart = this.charts[annotation.timeframe]; + if (chart) { + const referencePoint = editMode === 'entry' ? annotation.exit : annotation.entry; + const markerText = editMode === 'entry' ? '▼ (exit)' : '▲ (entry)'; + const arrowDirection = editMode === 'entry' ? 40 : -40; + + Plotly.relayout(chart.plotId, { + annotations: [{ + x: referencePoint.timestamp, + y: referencePoint.price, + text: markerText, + showarrow: true, + arrowhead: 2, + ax: 0, + ay: arrowDirection, + font: { size: 14, color: '#9ca3af' }, + bgcolor: 'rgba(31, 41, 55, 0.8)', + borderpad: 4 + }] + }); + } + } + } + + /** + * Clear all annotations from charts + */ + clearAllAnnotations() { + console.log('Clearing all annotations from charts'); + + // Clear from memory + this.annotations = {}; + + // Update all charts + Object.keys(this.charts).forEach(timeframe => { + this.updateChartAnnotations(timeframe); + }); + + console.log('All annotations cleared from charts'); + } + + /** + * Update chart layout when charts are minimized/maximized + */ + updateChartLayout() { + const chartContainer = document.getElementById('chart-container'); + const visibleCharts = document.querySelectorAll('.timeframe-chart:not(.minimized)'); + const minimizedCharts = document.querySelectorAll('.timeframe-chart.minimized'); + + // Remove scroll if all charts are visible or if some are minimized + if (minimizedCharts.length > 0) { + chartContainer.classList.add('no-scroll'); + + // Calculate available height for visible charts + const containerHeight = chartContainer.clientHeight; + const headerHeight = 50; // Approximate header height + const availableHeight = containerHeight - (visibleCharts.length * headerHeight); + const chartHeight = Math.max(200, availableHeight / visibleCharts.length); + + // Update visible chart heights + visibleCharts.forEach(chart => { + const plotElement = chart.querySelector('.chart-plot'); + if (plotElement) { + plotElement.style.height = `${chartHeight}px`; + + // Trigger Plotly resize + const plotId = plotElement.id; + if (plotId) { + Plotly.Plots.resize(plotId); + } + } + }); + } else { + chartContainer.classList.remove('no-scroll'); + + // Reset to default heights + visibleCharts.forEach(chart => { + const plotElement = chart.querySelector('.chart-plot'); + if (plotElement) { + plotElement.style.height = '300px'; + + // Trigger Plotly resize + const plotId = plotElement.id; + if (plotId) { + Plotly.Plots.resize(plotId); + } + } + }); + } + + console.log(`Updated chart layout: ${visibleCharts.length} visible, ${minimizedCharts.length} minimized`); } /** @@ -804,6 +975,15 @@ class ChartManager { return colors[Math.min(level - 1, colors.length - 1)]; } + /** + * Get marker size for pivot level + * L1 = smallest (6px), L5 = largest (14px) + */ + _getPivotMarkerSize(level) { + const sizes = [6, 8, 10, 12, 14]; // L1 to L5 + return sizes[Math.min(level - 1, sizes.length - 1)]; + } + /** * Enable crosshair cursor */ diff --git a/ANNOTATE/web/templates/annotation_dashboard.html b/ANNOTATE/web/templates/annotation_dashboard.html index 9f976fe..9c36dbe 100644 --- a/ANNOTATE/web/templates/annotation_dashboard.html +++ b/ANNOTATE/web/templates/annotation_dashboard.html @@ -299,58 +299,15 @@ toast.addEventListener('hidden.bs.toast', () => toast.remove()); } - function setupGlobalFunctions() { - // Make functions globally available - window.showError = showError; - window.showSuccess = showSuccess; - window.renderAnnotationsList = renderAnnotationsList; - window.deleteAnnotation = deleteAnnotation; - window.highlightAnnotation = highlightAnnotation; - - // Verify functions are set - console.log('Global functions setup complete:'); - console.log(' - window.deleteAnnotation:', typeof window.deleteAnnotation); - console.log(' - window.renderAnnotationsList:', typeof window.renderAnnotationsList); - console.log(' - window.showError:', typeof window.showError); - console.log(' - window.showSuccess:', typeof window.showSuccess); - } - - function renderAnnotationsList(annotations) { - const listElement = document.getElementById('annotations-list'); - if (!listElement) return; - - listElement.innerHTML = ''; - - annotations.forEach(annotation => { - const item = document.createElement('div'); - item.className = 'annotation-item mb-2 p-2 border rounded'; - item.innerHTML = ` -
-
- ${annotation.timeframe} -
- ${annotation.direction} ${annotation.profit_loss_pct >= 0 ? '+' : ''}${annotation.profit_loss_pct.toFixed(2)}% -
- - ${new Date(annotation.entry.timestamp).toLocaleString()} - -
-
- - -
-
- `; - listElement.appendChild(item); - }); - } - function deleteAnnotation(annotationId) { console.log('deleteAnnotation called with ID:', annotationId); + + if (!window.appState || !window.appState.annotations) { + console.error('appState not initialized'); + return; + } + + console.log('Current annotations:', window.appState.annotations.map(a => a.annotation_id)); if (!confirm('Delete this annotation?')) { console.log('Delete cancelled by user'); @@ -406,9 +363,84 @@ } function highlightAnnotation(annotationId) { - if (window.appState.chartManager) { + if (window.appState && window.appState.chartManager) { window.appState.chartManager.highlightAnnotation(annotationId); } } + + function setupGlobalFunctions() { + // Make functions globally available + window.showError = showError; + window.showSuccess = showSuccess; + window.renderAnnotationsList = renderAnnotationsList; + window.deleteAnnotation = deleteAnnotation; + window.highlightAnnotation = highlightAnnotation; + + // Verify functions are set + console.log('Global functions setup complete:'); + console.log(' - window.deleteAnnotation:', typeof window.deleteAnnotation); + console.log(' - window.renderAnnotationsList:', typeof window.renderAnnotationsList); + console.log(' - window.showError:', typeof window.showError); + console.log(' - window.showSuccess:', typeof window.showSuccess); + } + + function renderAnnotationsList(annotations) { + const listElement = document.getElementById('annotations-list'); + if (!listElement) return; + + listElement.innerHTML = ''; + + annotations.forEach(annotation => { + const item = document.createElement('div'); + item.className = 'annotation-item mb-2 p-2 border rounded'; + item.innerHTML = ` +
+
+ ${annotation.timeframe} +
+ ${annotation.direction} ${annotation.profit_loss_pct >= 0 ? '+' : ''}${annotation.profit_loss_pct.toFixed(2)}% +
+ + ${new Date(annotation.entry.timestamp).toLocaleString()} + +
+
+ + +
+
+ `; + + // Add event listeners + item.querySelector('.highlight-btn').addEventListener('click', function(e) { + e.stopPropagation(); + console.log('Highlight button clicked for:', annotation.annotation_id); + if (typeof window.highlightAnnotation === 'function') { + window.highlightAnnotation(annotation.annotation_id); + } + }); + + item.querySelector('.delete-btn').addEventListener('click', function(e) { + e.stopPropagation(); + console.log('Delete button clicked for:', annotation.annotation_id); + console.log('window.deleteAnnotation type:', typeof window.deleteAnnotation); + + if (typeof window.deleteAnnotation === 'function') { + console.log('Calling window.deleteAnnotation...'); + window.deleteAnnotation(annotation.annotation_id); + } else { + console.error('window.deleteAnnotation is not a function:', typeof window.deleteAnnotation); + alert('Delete function not available. Please refresh the page.'); + } + }); + + listElement.appendChild(item); + }); + } + {% endblock %} \ No newline at end of file diff --git a/ANNOTATE/web/templates/components/annotation_list.html b/ANNOTATE/web/templates/components/annotation_list.html index 0e2e113..3e3fdbf 100644 --- a/ANNOTATE/web/templates/components/annotation_list.html +++ b/ANNOTATE/web/templates/components/annotation_list.html @@ -166,12 +166,16 @@ item.querySelector('.delete-annotation-btn').addEventListener('click', function(e) { e.stopPropagation(); console.log('Delete button clicked for:', annotation.annotation_id); + console.log('window.deleteAnnotation type:', typeof window.deleteAnnotation); + console.log('window object keys:', Object.keys(window).filter(k => k.includes('delete'))); // Use window.deleteAnnotation to ensure we get the global function if (typeof window.deleteAnnotation === 'function') { + console.log('Calling window.deleteAnnotation...'); window.deleteAnnotation(annotation.annotation_id); } else { console.error('window.deleteAnnotation is not a function:', typeof window.deleteAnnotation); + console.log('Available functions:', Object.keys(window).filter(k => typeof window[k] === 'function')); alert('Delete function not available. Please refresh the page.'); } }); diff --git a/ANNOTATE/web/templates/components/chart_panel.html b/ANNOTATE/web/templates/components/chart_panel.html index 3a473b0..46ada4f 100644 --- a/ANNOTATE/web/templates/components/chart_panel.html +++ b/ANNOTATE/web/templates/components/chart_panel.html @@ -26,36 +26,60 @@
1 Second - +
+ + +
- +
1 Minute - +
+ + +
- +
1 Hour - +
+ + +
- +
1 Day - +
+ + +
- +
@@ -68,25 +92,25 @@ + + // Minimize button functionality + document.querySelectorAll('.minimize-btn').forEach(btn => { + btn.addEventListener('click', function () { + const timeframe = this.getAttribute('data-timeframe'); + const chartElement = document.getElementById(`chart-${timeframe}`); + const plotElement = document.getElementById(`plot-${timeframe}`); + + if (chartElement.classList.contains('minimized')) { + // Restore chart + chartElement.classList.remove('minimized'); + plotElement.style.display = 'block'; + this.innerHTML = ''; + this.title = 'Minimize Chart'; + + // Update chart layout + if (window.appState && window.appState.chartManager) { + window.appState.chartManager.updateChartLayout(); + } + } else { + // Minimize chart + chartElement.classList.add('minimized'); + plotElement.style.display = 'none'; + this.innerHTML = ''; + this.title = 'Restore Chart'; + + // Update chart layout + if (window.appState && window.appState.chartManager) { + window.appState.chartManager.updateChartLayout(); + } + } + }); + }); + \ No newline at end of file diff --git a/core/williams_market_structure.py b/core/williams_market_structure.py index 9b401a7..0f656f1 100644 --- a/core/williams_market_structure.py +++ b/core/williams_market_structure.py @@ -121,7 +121,10 @@ class WilliamsMarketStructure: # Restore original pivot distance self.min_pivot_distance = original_distance - logger.debug(f"Calculated {len(self.pivot_levels)} pivot levels") + logger.info(f"Calculated {len(self.pivot_levels)} pivot levels from {len(ohlcv_data)} candles") + for level_num, level_data in self.pivot_levels.items(): + logger.info(f" L{level_num}: {len(level_data.pivot_points)} pivots") + return self.pivot_levels except Exception as e: @@ -182,7 +185,7 @@ class WilliamsMarketStructure: ) pivots.append(pivot) - logger.debug(f"Level 1: Found {len(pivots)} pivot points") + logger.info(f"Level 1: Found {len(pivots)} pivot points from {len(df)} candles") return pivots except Exception as e: @@ -197,10 +200,18 @@ class WilliamsMarketStructure: pivot points from the previous level as if they were OHLCV candles """ if level - 1 not in self.pivot_levels: + logger.debug(f"Level {level}: Previous level {level-1} not found") return [] previous_level_pivots = self.pivot_levels[level - 1].pivot_points - if len(previous_level_pivots) < self.min_pivot_distance * 2 + 1: + + # For higher levels (L2+), use distance=1 to allow more pivots + # Only L1 uses the strict min_pivot_distance requirement + pivot_distance = 1 + required_pivots = pivot_distance * 2 + 1 # 3 pivots minimum + + if len(previous_level_pivots) < required_pivots: + logger.debug(f"Level {level}: Insufficient pivots from L{level-1} ({len(previous_level_pivots)} < {required_pivots} required)") return [] pivots = [] @@ -210,13 +221,15 @@ class WilliamsMarketStructure: highs = [p for p in previous_level_pivots if p.pivot_type == 'high'] lows = [p for p in previous_level_pivots if p.pivot_type == 'low'] - # Find swing highs among the high pivots - for i in range(self.min_pivot_distance, len(highs) - self.min_pivot_distance): + logger.debug(f"Level {level}: Processing {len(highs)} highs and {len(lows)} lows from L{level-1}") + + # Find swing highs among the high pivots (using distance=1 for L2+) + for i in range(pivot_distance, len(highs) - pivot_distance): current_pivot = highs[i] # Check if this high is surrounded by lower highs is_swing_high = True - for j in range(i - self.min_pivot_distance, i + self.min_pivot_distance + 1): + for j in range(i - pivot_distance, i + pivot_distance + 1): if j != i and j < len(highs) and highs[j].price >= current_pivot.price: is_swing_high = False break @@ -234,12 +247,12 @@ class WilliamsMarketStructure: pivots.append(pivot) # Find swing lows among the low pivots - for i in range(self.min_pivot_distance, len(lows) - self.min_pivot_distance): + for i in range(pivot_distance, len(lows) - pivot_distance): current_pivot = lows[i] # Check if this low is surrounded by higher lows is_swing_low = True - for j in range(i - self.min_pivot_distance, i + self.min_pivot_distance + 1): + for j in range(i - pivot_distance, i + pivot_distance + 1): if j != i and j < len(lows) and lows[j].price <= current_pivot.price: is_swing_low = False break @@ -259,7 +272,7 @@ class WilliamsMarketStructure: # Sort pivots by timestamp pivots.sort(key=lambda x: x.timestamp) - logger.debug(f"Level {level}: Found {len(pivots)} pivot points") + logger.info(f"Level {level}: Found {len(pivots)} pivot points (from {len(highs)} highs, {len(lows)} lows)") return pivots except Exception as e: