diff --git a/ANNOTATE/core/live_pivot_trainer.py b/ANNOTATE/core/live_pivot_trainer.py index 79ce19a..de448db 100644 --- a/ANNOTATE/core/live_pivot_trainer.py +++ b/ANNOTATE/core/live_pivot_trainer.py @@ -65,8 +65,10 @@ class LivePivotTrainer: # Williams Market Structure for pivot detection try: from core.williams_market_structure import WilliamsMarketStructure - self.williams_1s = WilliamsMarketStructure(num_levels=5) - self.williams_1m = WilliamsMarketStructure(num_levels=5) + # Fix: WilliamsMarketStructure.__init__ does not accept num_levels + # It defaults to 5 levels internally + self.williams_1s = WilliamsMarketStructure() + self.williams_1m = WilliamsMarketStructure() logger.info("Williams Market Structure initialized for pivot detection") except Exception as e: logger.error(f"Failed to initialize Williams Market Structure: {e}") diff --git a/ANNOTATE/core/real_training_adapter.py b/ANNOTATE/core/real_training_adapter.py index 971c505..a28f1ed 100644 --- a/ANNOTATE/core/real_training_adapter.py +++ b/ANNOTATE/core/real_training_adapter.py @@ -1336,13 +1336,16 @@ class RealTrainingAdapter: if result_1s: price_data_1s, norm_params_dict['1s'] = result_1s else: + # Don't fail on missing 1s data, it's often unavailable in annotations price_data_1s = None result_1m = self._extract_timeframe_data(timeframes.get('1m', {}), target_seq_len) if '1m' in timeframes else None if result_1m: price_data_1m, norm_params_dict['1m'] = result_1m else: - price_data_1m = None + # Warning: 1m data is critical + logger.warning(f"Missing 1m data for transformer batch (sample: {training_sample.get('test_case_id')})") + return None result_1h = self._extract_timeframe_data(timeframes.get('1h', {}), target_seq_len) if '1h' in timeframes else None if result_1h: @@ -1558,6 +1561,12 @@ class RealTrainingAdapter: # Model predicts price change ratio, not absolute price exit_price = training_sample.get('exit_price') + # Handle 'expected_outcome' nesting from LivePivotTrainer + if exit_price is None: + expected_outcome = training_sample.get('expected_outcome', {}) + if isinstance(expected_outcome, dict): + exit_price = expected_outcome.get('exit_price') + if exit_price and current_price > 0: # Normalize: (exit_price - current_price) / current_price # This gives the expected price change as a ratio @@ -2547,6 +2556,7 @@ class RealTrainingAdapter: if session['last_candle_time'] == latest_candle_time: return # Same candle, no training needed + logger.debug(f"New candle detected: {latest_candle_time} (last: {session['last_candle_time']})") session['last_candle_time'] = latest_candle_time # Get the completed candle (second to last) @@ -2613,6 +2623,7 @@ class RealTrainingAdapter: # Convert to batch format batch = self._convert_annotation_to_transformer_batch(training_sample) if not batch: + logger.warning(f"Per-candle training failed: Could not convert sample to batch") return # Train on this batch diff --git a/ANNOTATE/web/app.py b/ANNOTATE/web/app.py index 67b7ed3..48028d0 100644 --- a/ANNOTATE/web/app.py +++ b/ANNOTATE/web/app.py @@ -1916,8 +1916,14 @@ class AnnotationDashboard: def get_available_models(): """Get list of available models with their load status""" try: - # Use self.available_models which is a simple list of strings - # Don't call training_adapter.get_available_models() as it may return objects + # Ensure self.available_models is a list + if not isinstance(self.available_models, list): + logger.warning(f"self.available_models is not a list: {type(self.available_models)}. Resetting to default.") + self.available_models = ['Transformer', 'COB_RL', 'CNN', 'DQN'] + + # Ensure self.loaded_models is a list/set + if not hasattr(self, 'loaded_models'): + self.loaded_models = [] # Build model state dict with checkpoint info logger.info(f"Building model states for {len(self.available_models)} models: {self.available_models}") @@ -1973,12 +1979,16 @@ class AnnotationDashboard: logger.error(f"Error getting available models: {e}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") + # Return a fallback list so the UI doesn't hang return jsonify({ - 'success': False, - 'error': { - 'code': 'MODEL_LIST_ERROR', - 'message': str(e) - } + 'success': True, + 'models': [ + {'name': 'Transformer', 'loaded': False, 'can_train': False, 'can_infer': False}, + {'name': 'COB_RL', 'loaded': False, 'can_train': False, 'can_infer': False} + ], + 'loaded_count': 0, + 'available_count': 2, + 'error': str(e) }) @self.server.route('/api/load-model', methods=['POST']) diff --git a/ANNOTATE/web/static/js/chart_manager.js b/ANNOTATE/web/static/js/chart_manager.js index 1bd348b..9cc99f5 100644 --- a/ANNOTATE/web/static/js/chart_manager.js +++ b/ANNOTATE/web/static/js/chart_manager.js @@ -160,22 +160,42 @@ class ChartManager { marker: { color: [[volumeColor]] } }, [1]); } else { - // Update last candle using restyle - const lastIndex = candlestickTrace.x.length - 1; + // Update last candle using restyle - simpler approach for updating single point + // We need to get the full arrays, modify last element, and send back + // This is less efficient but more reliable for updates than complex index logic + + const x = candlestickTrace.x; + const open = candlestickTrace.open; + const high = candlestickTrace.high; + const low = candlestickTrace.low; + const close = candlestickTrace.close; + const volume = volumeTrace.y; + const colors = volumeTrace.marker.color; + + const lastIdx = x.length - 1; + + // Update local arrays + x[lastIdx] = candleTimestamp; + open[lastIdx] = candle.open; + high[lastIdx] = candle.high; + low[lastIdx] = candle.low; + close[lastIdx] = candle.close; + volume[lastIdx] = candle.volume; + colors[lastIdx] = candle.close >= candle.open ? '#10b981' : '#ef4444'; + + // Push updates to Plotly Plotly.restyle(plotId, { - 'x': [[...candlestickTrace.x.slice(0, lastIndex), candleTimestamp]], - 'open': [[...candlestickTrace.open.slice(0, lastIndex), candle.open]], - 'high': [[...candlestickTrace.high.slice(0, lastIndex), candle.high]], - 'low': [[...candlestickTrace.low.slice(0, lastIndex), candle.low]], - 'close': [[...candlestickTrace.close.slice(0, lastIndex), candle.close]] + x: [x], + open: [open], + high: [high], + low: [low], + close: [close] }, [0]); - // Update volume - const volumeColor = candle.close >= candle.open ? '#10b981' : '#ef4444'; Plotly.restyle(plotId, { - 'x': [[...volumeTrace.x.slice(0, lastIndex), candleTimestamp]], - 'y': [[...volumeTrace.y.slice(0, lastIndex), candle.volume]], - 'marker.color': [[...volumeTrace.marker.color.slice(0, lastIndex), volumeColor]] + x: [x], + y: [volume], + 'marker.color': [colors] }, [1]); } diff --git a/ANNOTATE/web/templates/components/training_panel.html b/ANNOTATE/web/templates/components/training_panel.html index 6eb0d61..6e4267b 100644 --- a/ANNOTATE/web/templates/components/training_panel.html +++ b/ANNOTATE/web/templates/components/training_panel.html @@ -281,7 +281,14 @@ .catch(error => { console.error('❌ Error loading models:', error); const modelSelect = document.getElementById('model-select'); - modelSelect.innerHTML = ''; + modelSelect.innerHTML = ''; + + // Allow retry by clicking + modelSelect.addEventListener('click', function() { + if (modelSelect.value === "") { + loadAvailableModels(); + } + }, { once: true }); }); } @@ -834,17 +841,29 @@ // Display last 5 predictions (most recent first) const html = predictionHistory.slice(0, 5).map(pred => { - const time = new Date(pred.timestamp).toLocaleTimeString(); + // Safely parse timestamp + let timeStr = '--:--:--'; + try { + if (pred.timestamp) { + const date = new Date(pred.timestamp); + if (!isNaN(date.getTime())) { + timeStr = date.toLocaleTimeString(); + } + } + } catch (e) { + console.error('Error parsing timestamp:', e); + } + const actionColor = pred.action === 'BUY' ? 'text-success' : pred.action === 'SELL' ? 'text-danger' : 'text-secondary'; const confidence = (pred.confidence * 100).toFixed(1); - const price = pred.predicted_price ? pred.predicted_price.toFixed(2) : '--'; + const price = (pred.predicted_price && !isNaN(pred.predicted_price)) ? pred.predicted_price.toFixed(2) : '--'; return `