stored
This commit is contained in:
@@ -84,7 +84,29 @@ class AnnotationDashboard:
|
||||
def __init__(self):
|
||||
"""Initialize the dashboard"""
|
||||
# Load configuration
|
||||
self.config = get_config() if get_config else {}
|
||||
try:
|
||||
# Always try YAML loading first since get_config might not work in standalone mode
|
||||
import yaml
|
||||
with open('config.yaml', 'r') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
logger.info(f"Loaded config via YAML: {len(self.config)} keys")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load config via YAML: {e}")
|
||||
try:
|
||||
# Fallback to get_config if available
|
||||
if get_config:
|
||||
self.config = get_config()
|
||||
logger.info(f"Loaded config via get_config: {len(self.config)} keys")
|
||||
else:
|
||||
raise Exception("get_config not available")
|
||||
except Exception as e2:
|
||||
logger.warning(f"Could not load config via get_config: {e2}")
|
||||
# Final fallback config with SOL/USDT
|
||||
self.config = {
|
||||
'symbols': ['ETH/USDT', 'BTC/USDT', 'SOL/USDT'],
|
||||
'timeframes': ['1s', '1m', '1h', '1d']
|
||||
}
|
||||
logger.info("Using fallback config")
|
||||
|
||||
# Initialize Flask app
|
||||
self.server = Flask(
|
||||
@@ -207,10 +229,15 @@ class AnnotationDashboard:
|
||||
|
||||
logger.info(f"Loading dashboard with {len(annotations_data)} existing annotations")
|
||||
|
||||
# Get symbols and timeframes from config
|
||||
symbols = self.config.get('symbols', ['ETH/USDT', 'BTC/USDT'])
|
||||
timeframes = self.config.get('timeframes', ['1s', '1m', '1h', '1d'])
|
||||
|
||||
# Prepare template data
|
||||
template_data = {
|
||||
'current_symbol': 'ETH/USDT',
|
||||
'timeframes': ['1s', '1m', '1h', '1d'],
|
||||
'current_symbol': symbols[0] if symbols else 'ETH/USDT', # Use first symbol as default
|
||||
'symbols': symbols,
|
||||
'timeframes': timeframes,
|
||||
'annotations': annotations_data
|
||||
}
|
||||
|
||||
@@ -328,59 +355,36 @@ class AnnotationDashboard:
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
# Capture market state at entry and exit times
|
||||
# Capture market state at entry and exit times using data provider
|
||||
entry_market_state = {}
|
||||
exit_market_state = {}
|
||||
|
||||
if self.data_loader:
|
||||
if self.data_provider:
|
||||
try:
|
||||
# Parse timestamps
|
||||
entry_time = datetime.fromisoformat(data['entry']['timestamp'].replace('Z', '+00:00'))
|
||||
exit_time = datetime.fromisoformat(data['exit']['timestamp'].replace('Z', '+00:00'))
|
||||
|
||||
# Fetch market data for all timeframes at entry time
|
||||
timeframes = ['1s', '1m', '1h', '1d']
|
||||
for tf in timeframes:
|
||||
df = self.data_loader.get_data(
|
||||
symbol=data['symbol'],
|
||||
timeframe=tf,
|
||||
end_time=entry_time,
|
||||
limit=100
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
entry_market_state[f'ohlcv_{tf}'] = {
|
||||
'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()
|
||||
}
|
||||
# Use the new data provider method to get market state at entry time
|
||||
entry_market_state = self.data_provider.get_market_state_at_time(
|
||||
symbol=data['symbol'],
|
||||
timestamp=entry_time,
|
||||
context_window_minutes=5
|
||||
)
|
||||
|
||||
# Fetch market data at exit time
|
||||
for tf in timeframes:
|
||||
df = self.data_loader.get_data(
|
||||
symbol=data['symbol'],
|
||||
timeframe=tf,
|
||||
end_time=exit_time,
|
||||
limit=100
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
exit_market_state[f'ohlcv_{tf}'] = {
|
||||
'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()
|
||||
}
|
||||
# Use the new data provider method to get market state at exit time
|
||||
exit_market_state = self.data_provider.get_market_state_at_time(
|
||||
symbol=data['symbol'],
|
||||
timestamp=exit_time,
|
||||
context_window_minutes=5
|
||||
)
|
||||
|
||||
logger.info(f"Captured market state: {len(entry_market_state)} timeframes at entry, {len(exit_market_state)} at exit")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error capturing market state: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Create annotation with market context
|
||||
annotation = self.annotation_manager.create_annotation(
|
||||
@@ -456,6 +460,103 @@ class AnnotationDashboard:
|
||||
}
|
||||
})
|
||||
|
||||
@self.server.route('/api/clear-all-annotations', methods=['POST'])
|
||||
def clear_all_annotations():
|
||||
"""Clear all annotations"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
symbol = data.get('symbol', None)
|
||||
|
||||
# Get current annotations count
|
||||
annotations = self.annotation_manager.get_annotations(symbol=symbol)
|
||||
deleted_count = len(annotations)
|
||||
|
||||
if deleted_count == 0:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'deleted_count': 0,
|
||||
'message': 'No annotations to clear'
|
||||
})
|
||||
|
||||
# Clear all annotations
|
||||
for annotation in annotations:
|
||||
annotation_id = annotation.annotation_id if hasattr(annotation, 'annotation_id') else annotation.get('annotation_id')
|
||||
self.annotation_manager.delete_annotation(annotation_id)
|
||||
|
||||
logger.info(f"Cleared {deleted_count} annotations")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'deleted_count': deleted_count,
|
||||
'message': f'Cleared {deleted_count} annotations'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error clearing all annotations: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': {
|
||||
'code': 'CLEAR_ALL_ANNOTATIONS_ERROR',
|
||||
'message': str(e)
|
||||
}
|
||||
})
|
||||
|
||||
@self.server.route('/api/refresh-data', methods=['POST'])
|
||||
def refresh_data():
|
||||
"""Refresh chart data from data provider"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
symbol = data.get('symbol', 'ETH/USDT')
|
||||
timeframes = data.get('timeframes', ['1s', '1m', '1h', '1d'])
|
||||
|
||||
logger.info(f"Refreshing data for {symbol} with timeframes: {timeframes}")
|
||||
|
||||
# Force refresh data from data provider
|
||||
chart_data = {}
|
||||
|
||||
if self.data_provider:
|
||||
for timeframe in timeframes:
|
||||
try:
|
||||
# Force refresh by setting refresh=True
|
||||
df = self.data_provider.get_historical_data(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
limit=1000,
|
||||
refresh=True
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
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()
|
||||
}
|
||||
logger.info(f"Refreshed {timeframe}: {len(df)} candles")
|
||||
else:
|
||||
logger.warning(f"No data available for {timeframe}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing {timeframe} data: {e}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'chart_data': chart_data,
|
||||
'message': f'Refreshed data for {symbol}'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing data: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': {
|
||||
'code': 'REFRESH_DATA_ERROR',
|
||||
'message': str(e)
|
||||
}
|
||||
})
|
||||
|
||||
@self.server.route('/api/generate-test-case', methods=['POST'])
|
||||
def generate_test_case():
|
||||
"""Generate test case from annotation"""
|
||||
|
||||
@@ -4,9 +4,14 @@
|
||||
<i class="fas fa-tags"></i>
|
||||
Annotations
|
||||
</h6>
|
||||
<button class="btn btn-sm btn-outline-light" id="export-annotations-btn" title="Export">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-sm btn-outline-light" id="export-annotations-btn" title="Export">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" id="clear-all-annotations-btn" title="Clear All">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="list-group list-group-flush" id="annotations-list">
|
||||
@@ -48,6 +53,48 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Clear all annotations
|
||||
document.getElementById('clear-all-annotations-btn').addEventListener('click', function() {
|
||||
if (appState.annotations.length === 0) {
|
||||
showError('No annotations to clear');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Are you sure you want to delete all ${appState.annotations.length} annotations? This action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/clear-all-annotations', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
symbol: appState.currentSymbol
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Clear from app state
|
||||
appState.annotations = [];
|
||||
|
||||
// Update UI
|
||||
renderAnnotationsList(appState.annotations);
|
||||
|
||||
// Clear from chart
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.clearAllAnnotations();
|
||||
}
|
||||
|
||||
showSuccess(`Cleared ${data.deleted_count} annotations`);
|
||||
} else {
|
||||
showError('Failed to clear annotations: ' + data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showError('Network error: ' + error.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Function to render annotations list
|
||||
function renderAnnotationsList(annotations) {
|
||||
const listContainer = document.getElementById('annotations-list');
|
||||
|
||||
@@ -10,30 +10,31 @@
|
||||
<div class="mb-3">
|
||||
<label for="symbol-select" class="form-label">Symbol</label>
|
||||
<select class="form-select form-select-sm" id="symbol-select">
|
||||
<option value="ETH/USDT" selected>ETH/USDT</option>
|
||||
<option value="BTC/USDT">BTC/USDT</option>
|
||||
{% for symbol in symbols %}
|
||||
<option value="{{ symbol }}" {% if symbol == current_symbol %}selected{% endif %}>{{ symbol }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Timeframe Selection -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Timeframes</label>
|
||||
{% for timeframe in timeframes %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1s" value="1s" checked>
|
||||
<label class="form-check-label" for="tf-1s">1 Second</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1m" value="1m" checked>
|
||||
<label class="form-check-label" for="tf-1m">1 Minute</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1h" value="1h" checked>
|
||||
<label class="form-check-label" for="tf-1h">1 Hour</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1d" value="1d" checked>
|
||||
<label class="form-check-label" for="tf-1d">1 Day</label>
|
||||
<input class="form-check-input" type="checkbox" id="tf-{{ timeframe }}" value="{{ timeframe }}" checked>
|
||||
<label class="form-check-label" for="tf-{{ timeframe }}">
|
||||
{% if timeframe == '1s' %}1 Second
|
||||
{% elif timeframe == '1m' %}1 Minute
|
||||
{% elif timeframe == '1h' %}1 Hour
|
||||
{% elif timeframe == '1d' %}1 Day
|
||||
{% elif timeframe == '5m' %}5 Minutes
|
||||
{% elif timeframe == '15m' %}15 Minutes
|
||||
{% elif timeframe == '4h' %}4 Hours
|
||||
{% else %}{{ timeframe }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Time Navigation -->
|
||||
@@ -73,6 +74,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Refresh -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Data</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-success" id="refresh-data-btn" title="Refresh Data">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
Refresh
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-info" id="auto-refresh-toggle" title="Auto Refresh">
|
||||
<i class="fas fa-play" id="auto-refresh-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted">Refresh chart data from data provider</small>
|
||||
</div>
|
||||
|
||||
<!-- Annotation Mode -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Annotation Mode</label>
|
||||
@@ -168,4 +184,87 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Data refresh functionality
|
||||
let autoRefreshInterval = null;
|
||||
let isAutoRefreshEnabled = false;
|
||||
|
||||
// Manual refresh button
|
||||
document.getElementById('refresh-data-btn').addEventListener('click', function() {
|
||||
refreshChartData();
|
||||
});
|
||||
|
||||
// Auto refresh toggle
|
||||
document.getElementById('auto-refresh-toggle').addEventListener('click', function() {
|
||||
toggleAutoRefresh();
|
||||
});
|
||||
|
||||
function refreshChartData() {
|
||||
const refreshBtn = document.getElementById('refresh-data-btn');
|
||||
const icon = refreshBtn.querySelector('i');
|
||||
|
||||
// Show loading state
|
||||
icon.className = 'fas fa-spinner fa-spin';
|
||||
refreshBtn.disabled = true;
|
||||
|
||||
fetch('/api/refresh-data', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
symbol: appState.currentSymbol,
|
||||
timeframes: appState.currentTimeframes
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update charts with new data
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.updateCharts(data.chart_data);
|
||||
}
|
||||
showSuccess('Chart data refreshed successfully');
|
||||
} else {
|
||||
showError('Failed to refresh data: ' + data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showError('Network error: ' + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
// Reset button state
|
||||
icon.className = 'fas fa-sync-alt';
|
||||
refreshBtn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAutoRefresh() {
|
||||
const toggleBtn = document.getElementById('auto-refresh-toggle');
|
||||
const icon = document.getElementById('auto-refresh-icon');
|
||||
|
||||
if (isAutoRefreshEnabled) {
|
||||
// Disable auto refresh
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
autoRefreshInterval = null;
|
||||
}
|
||||
isAutoRefreshEnabled = false;
|
||||
icon.className = 'fas fa-play';
|
||||
toggleBtn.title = 'Enable Auto Refresh';
|
||||
showSuccess('Auto refresh disabled');
|
||||
} else {
|
||||
// Enable auto refresh (every 30 seconds)
|
||||
autoRefreshInterval = setInterval(refreshChartData, 30000);
|
||||
isAutoRefreshEnabled = true;
|
||||
icon.className = 'fas fa-pause';
|
||||
toggleBtn.title = 'Disable Auto Refresh';
|
||||
showSuccess('Auto refresh enabled (30s interval)');
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up interval when page unloads
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user