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 = `
+
+
+
+
+
+
+
Current Annotation:
+
+
+
+ Entry:
+ ▲
+ ${new Date(annotation.entry.timestamp).toLocaleString()}
+ $${annotation.entry.price.toFixed(2)}
+
+
+ Exit:
+ ▼
+ ${new Date(annotation.exit.timestamp).toLocaleString()}
+ $${annotation.exit.price.toFixed(2)}
+
+
+
+ P&L:
+
+ ${annotation.profit_loss_pct >= 0 ? '+' : ''}${annotation.profit_loss_pct.toFixed(2)}%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
- // 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 @@
-
+
-
+
-
+
-
+
@@ -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: