anotation system operational
This commit is contained in:
@@ -49,52 +49,84 @@ class ChartManager {
|
||||
low: data.low,
|
||||
close: data.close,
|
||||
type: 'candlestick',
|
||||
name: timeframe,
|
||||
increasing: {line: {color: '#10b981'}},
|
||||
decreasing: {line: {color: '#ef4444'}}
|
||||
name: 'Price',
|
||||
increasing: {
|
||||
line: {color: '#10b981', width: 1},
|
||||
fillcolor: '#10b981'
|
||||
},
|
||||
decreasing: {
|
||||
line: {color: '#ef4444', width: 1},
|
||||
fillcolor: '#ef4444'
|
||||
},
|
||||
xaxis: 'x',
|
||||
yaxis: 'y'
|
||||
};
|
||||
|
||||
// Create volume trace
|
||||
// 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,
|
||||
type: 'bar',
|
||||
name: 'Volume',
|
||||
yaxis: 'y2',
|
||||
marker: {color: '#3b82f6', opacity: 0.3}
|
||||
marker: {
|
||||
color: volumeColors,
|
||||
opacity: 0.3
|
||||
},
|
||||
hoverinfo: 'y'
|
||||
};
|
||||
|
||||
const layout = {
|
||||
title: '',
|
||||
showlegend: false,
|
||||
xaxis: {
|
||||
rangeslider: {visible: false},
|
||||
gridcolor: '#374151',
|
||||
color: '#9ca3af'
|
||||
color: '#9ca3af',
|
||||
showgrid: true,
|
||||
zeroline: false
|
||||
},
|
||||
yaxis: {
|
||||
title: 'Price',
|
||||
title: {
|
||||
text: 'Price (USD)',
|
||||
font: {size: 10}
|
||||
},
|
||||
gridcolor: '#374151',
|
||||
color: '#9ca3af'
|
||||
color: '#9ca3af',
|
||||
showgrid: true,
|
||||
zeroline: false,
|
||||
domain: [0.3, 1]
|
||||
},
|
||||
yaxis2: {
|
||||
title: 'Volume',
|
||||
overlaying: 'y',
|
||||
side: 'right',
|
||||
title: {
|
||||
text: 'Volume',
|
||||
font: {size: 10}
|
||||
},
|
||||
gridcolor: '#374151',
|
||||
color: '#9ca3af',
|
||||
showgrid: false,
|
||||
color: '#9ca3af'
|
||||
zeroline: false,
|
||||
domain: [0, 0.25]
|
||||
},
|
||||
plot_bgcolor: '#1f2937',
|
||||
paper_bgcolor: '#1f2937',
|
||||
font: {color: '#f8f9fa'},
|
||||
margin: {l: 50, r: 50, t: 20, b: 40},
|
||||
hovermode: 'x unified'
|
||||
font: {color: '#f8f9fa', size: 11},
|
||||
margin: {l: 60, r: 20, t: 10, b: 40},
|
||||
hovermode: 'x unified',
|
||||
dragmode: 'pan'
|
||||
};
|
||||
|
||||
const config = {
|
||||
responsive: true,
|
||||
displayModeBar: true,
|
||||
modeBarButtonsToRemove: ['lasso2d', 'select2d'],
|
||||
displaylogo: false
|
||||
modeBarButtonsToRemove: ['lasso2d', 'select2d', 'autoScale2d'],
|
||||
displaylogo: false,
|
||||
scrollZoom: true
|
||||
};
|
||||
|
||||
Plotly.newPlot(plotId, [candlestickTrace, volumeTrace], layout, config);
|
||||
@@ -103,7 +135,8 @@ class ChartManager {
|
||||
this.charts[timeframe] = {
|
||||
plotId: plotId,
|
||||
data: data,
|
||||
element: plotElement
|
||||
element: plotElement,
|
||||
annotations: []
|
||||
};
|
||||
|
||||
// Add click handler for annotations
|
||||
@@ -111,7 +144,12 @@ class ChartManager {
|
||||
this.handleChartClick(timeframe, eventData);
|
||||
});
|
||||
|
||||
console.log(`Chart created for ${timeframe}`);
|
||||
// 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`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,16 +238,156 @@ class ChartManager {
|
||||
* Update chart annotations
|
||||
*/
|
||||
updateChartAnnotations(timeframe) {
|
||||
// TODO: Implement annotation rendering on charts
|
||||
console.log(`Updating annotations for ${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
|
||||
plotlyAnnotations.push({
|
||||
x: entryTime,
|
||||
y: entryPrice,
|
||||
text: '▲',
|
||||
showarrow: false,
|
||||
font: {
|
||||
size: 20,
|
||||
color: ann.direction === 'LONG' ? '#10b981' : '#ef4444'
|
||||
},
|
||||
xanchor: 'center',
|
||||
yanchor: 'bottom'
|
||||
});
|
||||
|
||||
// Exit marker
|
||||
plotlyAnnotations.push({
|
||||
x: exitTime,
|
||||
y: exitPrice,
|
||||
text: '▼',
|
||||
showarrow: false,
|
||||
font: {
|
||||
size: 20,
|
||||
color: ann.direction === 'LONG' ? '#10b981' : '#ef4444'
|
||||
},
|
||||
xanchor: 'center',
|
||||
yanchor: 'top'
|
||||
});
|
||||
|
||||
// P&L label
|
||||
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,
|
||||
text: `${ann.profit_loss_pct >= 0 ? '+' : ''}${ann.profit_loss_pct.toFixed(2)}%`,
|
||||
showarrow: true,
|
||||
arrowhead: 0,
|
||||
ax: 0,
|
||||
ay: -40,
|
||||
font: {
|
||||
size: 12,
|
||||
color: pnlColor,
|
||||
family: 'monospace'
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
bordercolor: pnlColor,
|
||||
borderwidth: 1,
|
||||
borderpad: 4
|
||||
});
|
||||
|
||||
// Connecting line
|
||||
plotlyShapes.push({
|
||||
type: 'line',
|
||||
x0: entryTime,
|
||||
y0: entryPrice,
|
||||
x1: exitTime,
|
||||
y1: exitPrice,
|
||||
line: {
|
||||
color: ann.direction === 'LONG' ? '#10b981' : '#ef4444',
|
||||
width: 2,
|
||||
dash: 'dash'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update chart layout with annotations
|
||||
Plotly.relayout(chart.plotId, {
|
||||
annotations: plotlyAnnotations,
|
||||
shapes: plotlyShapes
|
||||
});
|
||||
|
||||
console.log(`Updated ${timeframeAnnotations.length} annotations for ${timeframe}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight annotation
|
||||
*/
|
||||
highlightAnnotation(annotationId) {
|
||||
console.log('Highlighting annotation:', annotationId);
|
||||
// TODO: Implement highlight effect
|
||||
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 => {
|
||||
// Create a copy with highlighted color
|
||||
return {
|
||||
...ann,
|
||||
font: {
|
||||
...ann.font,
|
||||
color: '#fbbf24' // Yellow highlight
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Plotly.relayout(chart.plotId, {annotations: highlightedAnnotations});
|
||||
|
||||
// Restore original colors after 1 second
|
||||
setTimeout(() => {
|
||||
this.updateChartAnnotations(timeframe);
|
||||
}, 1000);
|
||||
|
||||
console.log('Highlighted annotation:', annotationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit annotation
|
||||
*/
|
||||
editAnnotation(annotationId) {
|
||||
const annotation = this.annotations[annotationId];
|
||||
if (!annotation) return;
|
||||
|
||||
// Remove from charts
|
||||
this.removeAnnotation(annotationId);
|
||||
|
||||
// Set as pending annotation for editing
|
||||
if (window.appState && window.appState.annotationManager) {
|
||||
window.appState.annotationManager.pendingAnnotation = {
|
||||
annotation_id: annotationId,
|
||||
symbol: annotation.symbol,
|
||||
timeframe: annotation.timeframe,
|
||||
entry: annotation.entry,
|
||||
isEditing: true
|
||||
};
|
||||
|
||||
document.getElementById('pending-annotation-status').style.display = 'block';
|
||||
window.showSuccess('Click on chart to set new exit point');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,7 +427,54 @@ class ChartManager {
|
||||
*/
|
||||
syncTimeNavigation(timestamp) {
|
||||
this.syncedTime = timestamp;
|
||||
// TODO: Implement time synchronization
|
||||
console.log('Syncing charts to timestamp:', 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) {
|
||||
minDiff = diff;
|
||||
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)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user