anotation system operational

This commit is contained in:
Dobromir Popov
2025-10-18 18:41:58 +03:00
parent 3d91cb0e8f
commit 38d6a01f8e
12 changed files with 1996 additions and 116 deletions

View File

@@ -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)}`;
}
}
}