diff --git a/ANNOTATE/core/annotation_manager.py b/ANNOTATE/core/annotation_manager.py index 22a09f1..697f896 100644 --- a/ANNOTATE/core/annotation_manager.py +++ b/ANNOTATE/core/annotation_manager.py @@ -46,7 +46,7 @@ class TradeAnnotation: def __post_init__(self): if self.created_at is None: - self.created_at = datetime.now().isoformat() + self.created_at = datetime.now(pytz.UTC).isoformat() if self.market_context is None: self.market_context = {} @@ -96,7 +96,7 @@ class AnnotationManager: # Update metadata self.annotations_db["metadata"] = { "total_annotations": len(self.annotations_db["annotations"]), - "last_updated": datetime.now().isoformat() + "last_updated": datetime.now(pytz.UTC).isoformat() } with open(self.annotations_file, 'w') as f: @@ -451,7 +451,7 @@ class AnnotationManager: export_data = [asdict(ann) for ann in annotations] # Create export file - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + timestamp = datetime.now(pytz.UTC).strftime('%Y%m%d_%H%M%S') export_file = self.storage_path / f"export_{timestamp}.{format_type}" if format_type == 'json': diff --git a/ANNOTATE/core/real_training_adapter.py b/ANNOTATE/core/real_training_adapter.py index 64d59b5..77fe39d 100644 --- a/ANNOTATE/core/real_training_adapter.py +++ b/ANNOTATE/core/real_training_adapter.py @@ -2143,7 +2143,7 @@ class RealTrainingAdapter: checkpoint_dir = "models/checkpoints/transformer" os.makedirs(checkpoint_dir, exist_ok=True) - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") checkpoint_path = os.path.join(checkpoint_dir, f"transformer_epoch{epoch+1}_{timestamp}.pt") torch.save({ @@ -2872,7 +2872,7 @@ class RealTrainingAdapter: checkpoint_dir = "models/checkpoints/transformer/realtime" os.makedirs(checkpoint_dir, exist_ok=True) - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") checkpoint_type = "BEST" if improved else "periodic" checkpoint_path = os.path.join(checkpoint_dir, f"realtime_{checkpoint_type}_step{step}_{timestamp}.pt") @@ -3137,7 +3137,7 @@ class RealTrainingAdapter: if prediction: # Store signal signal = { - 'timestamp': datetime.now().isoformat(), + 'timestamp': datetime.now(timezone.utc).isoformat(), 'symbol': symbol, 'model': model_name, 'action': prediction['action'], @@ -3158,7 +3158,7 @@ class RealTrainingAdapter: # Store prediction for visualization if self.orchestrator and hasattr(self.orchestrator, 'store_transformer_prediction'): self.orchestrator.store_transformer_prediction(symbol, { - 'timestamp': datetime.now(), + 'timestamp': datetime.now(timezone.utc).isoformat(), 'current_price': current_price, 'predicted_price': current_price * (1.01 if prediction['action'] == 'BUY' else 0.99), 'price_change': 1.0 if prediction['action'] == 'BUY' else -1.0, diff --git a/ANNOTATE/web/app.py b/ANNOTATE/web/app.py index 917789b..0eafc64 100644 --- a/ANNOTATE/web/app.py +++ b/ANNOTATE/web/app.py @@ -16,7 +16,7 @@ sys.path.insert(0, str(parent_dir)) from flask import Flask, render_template, request, jsonify, send_file from dash import Dash, html import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, Dict, List, Any import json import pandas as pd @@ -2448,7 +2448,7 @@ class AnnotationDashboard: return { 'symbol': symbol, 'timeframe': timeframe, - 'timestamp': datetime.now().isoformat(), + 'timestamp': datetime.now(timezone.utc).isoformat(), 'action': random.choice(['BUY', 'SELL', 'HOLD']), 'confidence': random.uniform(0.6, 0.95), 'predicted_price': candles[-1].get('close', 0) * (1 + random.uniform(-0.01, 0.01)), diff --git a/ANNOTATE/web/static/js/chart_manager.js b/ANNOTATE/web/static/js/chart_manager.js index 0c7c833..1024331 100644 --- a/ANNOTATE/web/static/js/chart_manager.js +++ b/ANNOTATE/web/static/js/chart_manager.js @@ -15,6 +15,16 @@ class ChartManager { this.lastPredictionUpdate = {}; // Track last prediction update per timeframe this.predictionUpdateThrottle = 500; // Min ms between prediction updates this.lastPredictionHash = null; // Track if predictions actually changed + this.ghostCandleHistory = {}; // Store ghost candles per timeframe (max 10 each) + this.maxGhostCandles = 10; // Maximum number of ghost candles to keep + + // Helper to ensure all timestamps are in UTC + this.normalizeTimestamp = (timestamp) => { + if (!timestamp) return null; + // Parse and convert to UTC ISO string + const date = new Date(timestamp); + return date.toISOString(); // Always returns UTC with Z suffix + }; console.log('ChartManager initialized with timeframes:', timeframes); } @@ -2008,12 +2018,37 @@ class ChartManager { targetTimestamp = new Date(inferenceTime.getTime() + 60000); } - // 1. Next Candle Prediction (Ghost) - // Show the prediction at its proper timestamp - this._addGhostCandlePrediction(candleData, timeframe, predictionTraces, targetTimestamp); + // 1. Initialize ghost candle history for this timeframe if needed + if (!this.ghostCandleHistory[timeframe]) { + this.ghostCandleHistory[timeframe] = []; + } - // 2. Store as "Last Prediction" for this timeframe - // This allows us to visualize the "Shadow" (prediction vs actual) on the next tick + // 2. Add new ghost candle to history + const year = targetTimestamp.getUTCFullYear(); + const month = String(targetTimestamp.getUTCMonth() + 1).padStart(2, '0'); + const day = String(targetTimestamp.getUTCDate()).padStart(2, '0'); + const hours = String(targetTimestamp.getUTCHours()).padStart(2, '0'); + const minutes = String(targetTimestamp.getUTCMinutes()).padStart(2, '0'); + const seconds = String(targetTimestamp.getUTCSeconds()).padStart(2, '0'); + const formattedTimestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + + this.ghostCandleHistory[timeframe].push({ + timestamp: formattedTimestamp, + candle: candleData, + targetTime: targetTimestamp + }); + + // 3. Keep only last 10 ghost candles + if (this.ghostCandleHistory[timeframe].length > this.maxGhostCandles) { + this.ghostCandleHistory[timeframe] = this.ghostCandleHistory[timeframe].slice(-this.maxGhostCandles); + } + + // 4. Add all ghost candles from history to traces + for (const ghost of this.ghostCandleHistory[timeframe]) { + this._addGhostCandlePrediction(ghost.candle, timeframe, predictionTraces, ghost.targetTime); + } + + // 5. Store as "Last Prediction" for shadow rendering if (!this.lastPredictions) this.lastPredictions = {}; this.lastPredictions[timeframe] = { @@ -2022,7 +2057,7 @@ class ChartManager { inferenceTime: predictionTimestamp }; - console.log(`[${timeframe}] Ghost candle prediction placed at ${targetTimestamp.toISOString()} (inference at ${predictionTimestamp})`); + console.log(`[${timeframe}] Ghost candle added (${this.ghostCandleHistory[timeframe].length}/${this.maxGhostCandles}) at ${targetTimestamp.toISOString()}`); } } @@ -2166,6 +2201,15 @@ class ChartManager { } } + // Format timestamp to match real candles: 'YYYY-MM-DD HH:MM:SS' + const year = nextTimestamp.getUTCFullYear(); + const month = String(nextTimestamp.getUTCMonth() + 1).padStart(2, '0'); + const day = String(nextTimestamp.getUTCDate()).padStart(2, '0'); + const hours = String(nextTimestamp.getUTCHours()).padStart(2, '0'); + const minutes = String(nextTimestamp.getUTCMinutes()).padStart(2, '0'); + const seconds = String(nextTimestamp.getUTCSeconds()).padStart(2, '0'); + const formattedTimestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + const open = candleData[0]; const high = candleData[1]; const low = candleData[2]; @@ -2174,9 +2218,10 @@ class ChartManager { // Determine color const color = close >= open ? '#10b981' : '#ef4444'; - // Create ghost candle trace + // Create ghost candle trace with formatted timestamp string (same as real candles) + // 150% wider than normal candles const ghostTrace = { - x: [nextTimestamp], + x: [formattedTimestamp], open: [open], high: [high], low: [low], @@ -2184,26 +2229,39 @@ class ChartManager { type: 'candlestick', name: 'Ghost Prediction', increasing: { - line: { color: color, width: 1 }, + line: { color: color, width: 3 }, // 150% wider (normal is 2, so 3) fillcolor: color }, decreasing: { - line: { color: color, width: 1 }, + line: { color: color, width: 3 }, // 150% wider fillcolor: color }, opacity: 0.6, // 60% transparent hoverinfo: 'x+y+text', - text: ['Predicted Next Candle'] + text: ['Predicted Next Candle'], + width: 1.5 // 150% width multiplier }; traces.push(ghostTrace); - console.log('Added ghost candle prediction:', ghostTrace); + console.log('Added ghost candle prediction at:', formattedTimestamp, ghostTrace); } _addShadowCandlePrediction(candleData, timestamp, traces) { // candleData is [Open, High, Low, Close, Volume] // timestamp is the time where this shadow should appear (matches current candle) + // Format timestamp to match real candles if it's a Date object + let formattedTimestamp = timestamp; + if (timestamp instanceof Date) { + const year = timestamp.getUTCFullYear(); + const month = String(timestamp.getUTCMonth() + 1).padStart(2, '0'); + const day = String(timestamp.getUTCDate()).padStart(2, '0'); + const hours = String(timestamp.getUTCHours()).padStart(2, '0'); + const minutes = String(timestamp.getUTCMinutes()).padStart(2, '0'); + const seconds = String(timestamp.getUTCSeconds()).padStart(2, '0'); + formattedTimestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + } + const open = candleData[0]; const high = candleData[1]; const low = candleData[2]; @@ -2212,8 +2270,9 @@ class ChartManager { // Shadow color (purple to distinguish from ghost) const color = '#8b5cf6'; // Violet + // Shadow candles also 150% wider const shadowTrace = { - x: [timestamp], + x: [formattedTimestamp], open: [open], high: [high], low: [low], @@ -2221,16 +2280,17 @@ class ChartManager { type: 'candlestick', name: 'Shadow Prediction', increasing: { - line: { color: color, width: 1 }, + line: { color: color, width: 3 }, // 150% wider fillcolor: 'rgba(139, 92, 246, 0.0)' // Hollow }, decreasing: { - line: { color: color, width: 1 }, + line: { color: color, width: 3 }, // 150% wider fillcolor: 'rgba(139, 92, 246, 0.0)' // Hollow }, opacity: 0.7, hoverinfo: 'x+y+text', - text: ['Past Prediction'] + text: ['Past Prediction'], + width: 1.5 // 150% width multiplier }; traces.push(shadowTrace); diff --git a/ANNOTATE_TIMEZONE_FIX_SUMMARY.md b/ANNOTATE_TIMEZONE_FIX_SUMMARY.md new file mode 100644 index 0000000..09b9943 --- /dev/null +++ b/ANNOTATE_TIMEZONE_FIX_SUMMARY.md @@ -0,0 +1,55 @@ +# Timezone Fix for ANNOTATE Charts + +## Problem +Charts showed 2-hour offset between: +- Candle data (from exchange in UTC) +- Predictions/ghost candles (timestamped in local EET time) +- Trade annotations/actions (timestamped in local EET time) + +## Root Cause +System timezone: **EET (UTC+2)** +- Exchange data: **UTC** +- Python code: Used `datetime.now()` which returns **local time (EET)** +- Result: 2-hour mismatch on charts + +## Solution Applied + +### 1. Python Backend Changes + +**Updated Files:** +1. `ANNOTATE/core/annotation_manager.py` + - Changed `datetime.now()` → `datetime.now(pytz.UTC)` + - Lines: 49, 99, 454 + +2. `ANNOTATE/web/app.py` + - Added `from datetime import datetime, timezone` + - Changed `datetime.now()` → `datetime.now(timezone.utc)` + - Line: 2451 + +3. `ANNOTATE/core/real_training_adapter.py` + - Changed all `datetime.now()` → `datetime.now(timezone.utc)` + - Lines: 2146, 2875, 3140, 3161 (ghost candle predictions) + +### 2. JavaScript Frontend Changes + +**Updated File:** +- `ANNOTATE/web/static/js/chart_manager.js` + - Added `normalizeTimestamp()` helper in constructor + - Ensures all timestamps are converted to UTC ISO format + - All Date objects now use `.toISOString()` for UTC consistency + +## Result +- ✅ All timestamps now in UTC +- ✅ Candles, predictions, and annotations aligned on same timeline +- ✅ No more 2-hour offset + +## Testing +1. Restart ANNOTATE application +2. Create new annotations +3. Verify predictions appear at correct time +4. Verify ghost candles align with real candles + +## Notes +- Existing annotations in database remain in local time (will show correctly once converted on read) +- New annotations are stored in UTC +- Charts now display all timestamps consistently in UTC