save/load data anotations

This commit is contained in:
Dobromir Popov
2025-10-18 23:44:02 +03:00
parent 7646137f11
commit 002d0f7858
7 changed files with 1563 additions and 69 deletions

View File

@@ -354,6 +354,31 @@ class AnnotationDashboard:
# Save annotation
self.annotation_manager.save_annotation(annotation)
# Automatically generate test case with ±5min data
try:
test_case = self.annotation_manager.generate_test_case(
annotation,
data_provider=self.data_provider,
auto_save=True
)
# Log test case details
market_state = test_case.get('market_state', {})
timeframes_with_data = [k for k in market_state.keys() if k.startswith('ohlcv_')]
logger.info(f"Auto-generated test case: {test_case['test_case_id']}")
logger.info(f" Timeframes: {timeframes_with_data}")
for tf_key in timeframes_with_data:
candle_count = len(market_state[tf_key].get('timestamps', []))
logger.info(f" {tf_key}: {candle_count} candles")
if 'training_labels' in market_state:
logger.info(f" Training labels: {len(market_state['training_labels'].get('labels_1m', []))} labels")
except Exception as e:
logger.error(f"Failed to auto-generate test case: {e}")
import traceback
traceback.print_exc()
return jsonify({
'success': True,
'annotation': annotation.__dict__ if hasattr(annotation, '__dict__') else annotation
@@ -477,17 +502,35 @@ class AnnotationDashboard:
data = request.get_json()
model_name = data['model_name']
annotation_ids = data['annotation_ids']
annotation_ids = data.get('annotation_ids', [])
# Get annotations
annotations = self.annotation_manager.get_annotations()
selected_annotations = [a for a in annotations
if (a.annotation_id if hasattr(a, 'annotation_id')
else a.get('annotation_id')) in annotation_ids]
# If no specific annotations provided, use all
if not annotation_ids:
annotations = self.annotation_manager.get_annotations()
annotation_ids = [
a.annotation_id if hasattr(a, 'annotation_id') else a.get('annotation_id')
for a in annotations
]
# Generate test cases
test_cases = [self.annotation_manager.generate_test_case(ann)
for ann in selected_annotations]
# Load test cases from disk (they were auto-generated when annotations were saved)
all_test_cases = self.annotation_manager.get_all_test_cases()
# Filter to selected annotations
test_cases = [
tc for tc in all_test_cases
if tc['test_case_id'].replace('annotation_', '') in annotation_ids
]
if not test_cases:
return jsonify({
'success': False,
'error': {
'code': 'NO_TEST_CASES',
'message': f'No test cases found for {len(annotation_ids)} annotations'
}
})
logger.info(f"Starting training with {len(test_cases)} test cases for model {model_name}")
# Start training
training_id = self.training_simulator.start_training(
@@ -497,7 +540,8 @@ class AnnotationDashboard:
return jsonify({
'success': True,
'training_id': training_id
'training_id': training_id,
'test_cases_count': len(test_cases)
})
except Exception as e:
@@ -572,6 +616,107 @@ class AnnotationDashboard:
'message': str(e)
}
})
@self.server.route('/api/realtime-inference/start', methods=['POST'])
def start_realtime_inference():
"""Start real-time inference mode"""
try:
data = request.get_json()
model_name = data.get('model_name')
symbol = data.get('symbol', 'ETH/USDT')
if not self.training_simulator:
return jsonify({
'success': False,
'error': {
'code': 'TRAINING_UNAVAILABLE',
'message': 'Training simulator not available'
}
})
# Start real-time inference
inference_id = self.training_simulator.start_realtime_inference(
model_name=model_name,
symbol=symbol,
data_provider=self.data_provider
)
return jsonify({
'success': True,
'inference_id': inference_id
})
except Exception as e:
logger.error(f"Error starting real-time inference: {e}")
return jsonify({
'success': False,
'error': {
'code': 'INFERENCE_START_ERROR',
'message': str(e)
}
})
@self.server.route('/api/realtime-inference/stop', methods=['POST'])
def stop_realtime_inference():
"""Stop real-time inference mode"""
try:
data = request.get_json()
inference_id = data.get('inference_id')
if not self.training_simulator:
return jsonify({
'success': False,
'error': {
'code': 'TRAINING_UNAVAILABLE',
'message': 'Training simulator not available'
}
})
self.training_simulator.stop_realtime_inference(inference_id)
return jsonify({
'success': True
})
except Exception as e:
logger.error(f"Error stopping real-time inference: {e}")
return jsonify({
'success': False,
'error': {
'code': 'INFERENCE_STOP_ERROR',
'message': str(e)
}
})
@self.server.route('/api/realtime-inference/signals', methods=['GET'])
def get_realtime_signals():
"""Get latest real-time inference signals"""
try:
if not self.training_simulator:
return jsonify({
'success': False,
'error': {
'code': 'TRAINING_UNAVAILABLE',
'message': 'Training simulator not available'
}
})
signals = self.training_simulator.get_latest_signals()
return jsonify({
'success': True,
'signals': signals
})
except Exception as e:
logger.error(f"Error getting signals: {e}")
return jsonify({
'success': False,
'error': {
'code': 'SIGNALS_ERROR',
'message': str(e)
}
})
def run(self, host='127.0.0.1', port=8051, debug=False):
"""Run the application"""

View File

@@ -3,6 +3,22 @@
{% block title %}Trade Annotation Dashboard{% endblock %}
{% block content %}
<!-- Live Mode Banner -->
<div id="live-mode-banner" class="alert alert-success mb-0" style="display: none; border-radius: 0;">
<div class="container-fluid">
<div class="d-flex align-items-center justify-content-between">
<div>
<span class="badge bg-danger me-2">🔴 LIVE</span>
<strong>Real-Time Inference Active</strong>
<span class="ms-3 small">Charts updating with live data every second</span>
</div>
<div>
<span class="badge bg-light text-dark" id="live-update-count">0 updates</span>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<!-- Left Sidebar - Controls -->
<div class="col-md-2">
@@ -96,9 +112,15 @@
window.appState.chartManager.initializeCharts(data.chart_data);
// Load existing annotations
console.log('Loading', window.appState.annotations.length, 'existing annotations');
window.appState.annotations.forEach(annotation => {
window.appState.chartManager.addAnnotation(annotation);
});
// Update annotation list
if (typeof renderAnnotationsList === 'function') {
renderAnnotationsList(window.appState.annotations);
}
} else {
showError('Failed to load chart data: ' + data.error.message);
}

View File

@@ -59,12 +59,34 @@
</div>
</div>
<!-- Inference Simulation -->
<!-- Real-Time Inference -->
<div class="mb-3">
<button class="btn btn-secondary btn-sm w-100" id="simulate-inference-btn">
<i class="fas fa-brain"></i>
Simulate Inference
<label class="form-label small">Real-Time Inference</label>
<button class="btn btn-success btn-sm w-100" id="start-inference-btn">
<i class="fas fa-play"></i>
Start Live Inference
</button>
<button class="btn btn-danger btn-sm w-100 mt-1" id="stop-inference-btn" style="display: none;">
<i class="fas fa-stop"></i>
Stop Inference
</button>
</div>
<!-- Inference Status -->
<div id="inference-status" style="display: none;">
<div class="alert alert-success py-2 px-2 mb-2">
<div class="d-flex align-items-center mb-1">
<div class="spinner-border spinner-border-sm me-2" role="status">
<span class="visually-hidden">Running...</span>
</div>
<strong class="small">🔴 LIVE</strong>
</div>
<div class="small">
<div>Signal: <span id="latest-signal" class="fw-bold">--</span></div>
<div>Confidence: <span id="latest-confidence">--</span></div>
<div class="text-muted" style="font-size: 0.7rem;">Charts updating every 1s</div>
</div>
</div>
</div>
<!-- Test Case Stats -->
@@ -231,22 +253,232 @@
showSuccess('Training completed successfully');
}
// Simulate inference button
document.getElementById('simulate-inference-btn').addEventListener('click', function() {
// Real-time inference controls
let currentInferenceId = null;
let signalPollInterval = null;
document.getElementById('start-inference-btn').addEventListener('click', function() {
const modelName = document.getElementById('model-select').value;
if (appState.annotations.length === 0) {
showError('No annotations available for inference simulation');
if (!modelName) {
showError('Please select a model first');
return;
}
// Open inference modal
const modal = new bootstrap.Modal(document.getElementById('inferenceModal'));
modal.show();
// Start inference simulation
if (appState.trainingController) {
appState.trainingController.simulateInference(modelName, appState.annotations);
}
// Start real-time inference
fetch('/api/realtime-inference/start', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
model_name: modelName,
symbol: appState.currentSymbol
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentInferenceId = data.inference_id;
// Update UI
document.getElementById('start-inference-btn').style.display = 'none';
document.getElementById('stop-inference-btn').style.display = 'block';
document.getElementById('inference-status').style.display = 'block';
// Start polling for signals
startSignalPolling();
showSuccess('Real-time inference started');
} else {
showError('Failed to start inference: ' + data.error.message);
}
})
.catch(error => {
showError('Network error: ' + error.message);
});
});
document.getElementById('stop-inference-btn').addEventListener('click', function() {
if (!currentInferenceId) return;
// Stop real-time inference
fetch('/api/realtime-inference/stop', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({inference_id: currentInferenceId})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update UI
document.getElementById('start-inference-btn').style.display = 'block';
document.getElementById('stop-inference-btn').style.display = 'none';
document.getElementById('inference-status').style.display = 'none';
// Stop polling
stopSignalPolling();
currentInferenceId = null;
showSuccess('Real-time inference stopped');
}
})
.catch(error => {
showError('Network error: ' + error.message);
});
});
function startSignalPolling() {
signalPollInterval = setInterval(function() {
// Poll for signals
fetch('/api/realtime-inference/signals')
.then(response => response.json())
.then(data => {
if (data.success && data.signals.length > 0) {
const latest = data.signals[0];
document.getElementById('latest-signal').textContent = latest.action;
document.getElementById('latest-confidence').textContent =
(latest.confidence * 100).toFixed(1) + '%';
// Update chart with signal markers
if (appState.chartManager) {
displaySignalOnChart(latest);
}
}
})
.catch(error => {
console.error('Error polling signals:', error);
});
// Update charts with latest data
updateChartsWithLiveData();
}, 1000); // Poll every second
}
function updateChartsWithLiveData() {
// Fetch latest chart data
fetch('/api/chart-data', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
symbol: appState.currentSymbol,
timeframes: appState.currentTimeframes,
start_time: null,
end_time: null
})
})
.then(response => response.json())
.then(data => {
if (data.success && appState.chartManager) {
// Update each chart with new data
Object.keys(data.chart_data).forEach(timeframe => {
const chartData = data.chart_data[timeframe];
if (appState.chartManager.charts[timeframe]) {
updateSingleChart(timeframe, chartData);
}
});
}
})
.catch(error => {
console.error('Error updating charts:', error);
});
}
function updateSingleChart(timeframe, newData) {
const chart = appState.chartManager.charts[timeframe];
if (!chart) return;
// Update candlestick data
Plotly.update(chart.plotId, {
x: [newData.timestamps],
open: [newData.open],
high: [newData.high],
low: [newData.low],
close: [newData.close]
}, {}, [0]);
// Update volume data
const volumeColors = newData.close.map((close, i) => {
if (i === 0) return '#3b82f6';
return close >= newData.open[i] ? '#10b981' : '#ef4444';
});
Plotly.update(chart.plotId, {
x: [newData.timestamps],
y: [newData.volume],
'marker.color': [volumeColors]
}, {}, [1]);
}
function stopSignalPolling() {
if (signalPollInterval) {
clearInterval(signalPollInterval);
signalPollInterval = null;
}
}
function displaySignalOnChart(signal) {
// Add signal marker to chart
if (!appState.chartManager || !appState.chartManager.charts) return;
// Add marker to all timeframe charts
Object.keys(appState.chartManager.charts).forEach(timeframe => {
const chart = appState.chartManager.charts[timeframe];
if (!chart) return;
// Get current annotations
const currentAnnotations = chart.element.layout.annotations || [];
// Determine marker based on signal
let markerText = '';
let markerColor = '#9ca3af';
if (signal.action === 'BUY') {
markerText = '🔵 BUY';
markerColor = '#10b981';
} else if (signal.action === 'SELL') {
markerText = '🔴 SELL';
markerColor = '#ef4444';
} else {
return; // Don't show HOLD signals
}
// Add new signal marker
const newAnnotation = {
x: signal.timestamp,
y: signal.price,
text: markerText,
showarrow: true,
arrowhead: 2,
ax: 0,
ay: -40,
font: {
size: 12,
color: markerColor
},
bgcolor: '#1f2937',
bordercolor: markerColor,
borderwidth: 2,
borderpad: 4,
opacity: 0.8
};
// Keep only last 10 signal markers
const signalAnnotations = currentAnnotations.filter(ann =>
ann.text && (ann.text.includes('BUY') || ann.text.includes('SELL'))
).slice(-9);
// Combine with existing non-signal annotations
const otherAnnotations = currentAnnotations.filter(ann =>
!ann.text || (!ann.text.includes('BUY') && !ann.text.includes('SELL'))
);
const allAnnotations = [...otherAnnotations, ...signalAnnotations, newAnnotation];
// Update chart
Plotly.relayout(chart.plotId, {
annotations: allAnnotations
});
});
console.log('Signal displayed:', signal.action, '@', signal.price);
}
</script>