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...")