annotations work and a re saved

This commit is contained in:
Dobromir Popov
2025-10-18 23:26:54 +03:00
parent 38d6a01f8e
commit 7646137f11
7 changed files with 771 additions and 60 deletions

View File

@@ -6,13 +6,14 @@ class AnnotationManager {
constructor(chartManager) {
this.chartManager = chartManager;
this.pendingAnnotation = null;
this.editingAnnotation = null;
this.enabled = true;
console.log('AnnotationManager initialized');
}
/**
* Handle chart click for marking entry/exit
* Handle chart click for marking entry/exit or editing
*/
handleChartClick(clickData) {
if (!this.enabled) {
@@ -20,6 +21,12 @@ class AnnotationManager {
return;
}
// Check if we're editing an existing annotation
if (this.editingAnnotation) {
this.handleEditClick(clickData);
return;
}
if (!this.pendingAnnotation) {
// Mark entry point
this.markEntry(clickData);
@@ -29,6 +36,95 @@ class AnnotationManager {
}
}
/**
* Handle click while editing an annotation
*/
handleEditClick(clickData) {
const editing = this.editingAnnotation;
const original = editing.original;
if (editing.editMode === 'entry') {
// Update entry point
const newAnnotation = {
...original,
entry: {
timestamp: clickData.timestamp,
price: clickData.price,
index: clickData.index
}
};
// Recalculate P&L
const entryPrice = newAnnotation.entry.price;
const exitPrice = newAnnotation.exit.price;
newAnnotation.direction = exitPrice > entryPrice ? 'LONG' : 'SHORT';
newAnnotation.profit_loss_pct = ((exitPrice - entryPrice) / entryPrice) * 100;
// Delete old annotation and save new one
this.deleteAndSaveAnnotation(editing.annotation_id, newAnnotation);
} else if (editing.editMode === 'exit') {
// Update exit point
const newAnnotation = {
...original,
exit: {
timestamp: clickData.timestamp,
price: clickData.price,
index: clickData.index
}
};
// Recalculate P&L
const entryPrice = newAnnotation.entry.price;
const exitPrice = newAnnotation.exit.price;
newAnnotation.direction = exitPrice > entryPrice ? 'LONG' : 'SHORT';
newAnnotation.profit_loss_pct = ((exitPrice - entryPrice) / entryPrice) * 100;
// Delete old annotation and save new one
this.deleteAndSaveAnnotation(editing.annotation_id, newAnnotation);
}
// Clear editing mode
this.editingAnnotation = null;
window.showSuccess('Annotation updated');
}
/**
* Delete old annotation and save updated one
*/
deleteAndSaveAnnotation(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(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);
this.chartManager.removeAnnotation(oldId);
this.chartManager.addAnnotation(data.annotation);
}
})
.catch(error => {
window.showError('Failed to update annotation: ' + error.message);
});
}
/**
* Mark entry point
*/

View File

@@ -139,11 +139,27 @@ class ChartManager {
annotations: []
};
// Add click handler for 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;
if (annotationName) {
const parts = annotationName.split('_');
const action = parts[0]; // 'entry', 'exit', or 'delete'
const annotationId = parts[1];
if (action === 'delete') {
this.handleAnnotationClick(annotationId, 'delete');
} else {
this.handleAnnotationClick(annotationId, 'edit');
}
}
});
// Add hover handler to update info
plotElement.on('plotly_hover', (eventData) => {
this.updateChartInfo(timeframe, eventData);
@@ -159,10 +175,23 @@ class ChartManager {
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') {
// For candlestick, use close price
price = point.data.close[point.pointIndex];
} else if (point.data.type === 'bar') {
// Skip volume bar clicks
return;
} else {
price = point.y;
}
const clickData = {
timeframe: timeframe,
timestamp: point.x,
price: point.close || point.y,
price: price,
index: point.pointIndex
};
@@ -255,7 +284,7 @@ class ChartManager {
const entryPrice = ann.entry.price;
const exitPrice = ann.exit.price;
// Entry marker
// Entry marker (clickable)
plotlyAnnotations.push({
x: entryTime,
y: entryPrice,
@@ -266,10 +295,12 @@ class ChartManager {
color: ann.direction === 'LONG' ? '#10b981' : '#ef4444'
},
xanchor: 'center',
yanchor: 'bottom'
yanchor: 'bottom',
captureevents: true,
name: `entry_${ann.annotation_id}`
});
// Exit marker
// Exit marker (clickable)
plotlyAnnotations.push({
x: exitTime,
y: exitPrice,
@@ -280,10 +311,12 @@ class ChartManager {
color: ann.direction === 'LONG' ? '#10b981' : '#ef4444'
},
xanchor: 'center',
yanchor: 'top'
yanchor: 'top',
captureevents: true,
name: `exit_${ann.annotation_id}`
});
// P&L label
// 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';
@@ -291,7 +324,7 @@ class ChartManager {
plotlyAnnotations.push({
x: midTime,
y: midPrice,
text: `${ann.profit_loss_pct >= 0 ? '+' : ''}${ann.profit_loss_pct.toFixed(2)}%`,
text: `${ann.profit_loss_pct >= 0 ? '+' : ''}${ann.profit_loss_pct.toFixed(2)}% 🗑️`,
showarrow: true,
arrowhead: 0,
ax: 0,
@@ -304,10 +337,12 @@ class ChartManager {
bgcolor: '#1f2937',
bordercolor: pnlColor,
borderwidth: 1,
borderpad: 4
borderpad: 4,
captureevents: true,
name: `delete_${ann.annotation_id}`
});
// Connecting line
// Connecting line (clickable for selection)
plotlyShapes.push({
type: 'line',
x0: entryTime,
@@ -318,7 +353,8 @@ class ChartManager {
color: ann.direction === 'LONG' ? '#10b981' : '#ef4444',
width: 2,
dash: 'dash'
}
},
name: `line_${ann.annotation_id}`
});
});
@@ -331,6 +367,25 @@ class ChartManager {
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) {
window.deleteAnnotation(annotationId);
}
}
} else if (action === 'edit') {
if (window.appState && window.appState.chartManager) {
window.appState.chartManager.editAnnotation(annotationId);
}
}
}
/**
* Highlight annotation
*/
@@ -366,27 +421,88 @@ class ChartManager {
}
/**
* Edit annotation
* Edit annotation - allows moving entry/exit points
*/
editAnnotation(annotationId) {
const annotation = this.annotations[annotationId];
if (!annotation) return;
// Remove from charts
this.removeAnnotation(annotationId);
// 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'
);
// 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
};
if (action === '1') {
// Move entry point
window.showSuccess('Click on chart to set new entry point');
document.getElementById('pending-annotation-status').style.display = 'block';
// 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');
}
}