fix pred candles viz
This commit is contained in:
@@ -46,7 +46,7 @@ class TradeAnnotation:
|
|||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.created_at is None:
|
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:
|
if self.market_context is None:
|
||||||
self.market_context = {}
|
self.market_context = {}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ class AnnotationManager:
|
|||||||
# Update metadata
|
# Update metadata
|
||||||
self.annotations_db["metadata"] = {
|
self.annotations_db["metadata"] = {
|
||||||
"total_annotations": len(self.annotations_db["annotations"]),
|
"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:
|
with open(self.annotations_file, 'w') as f:
|
||||||
@@ -451,7 +451,7 @@ class AnnotationManager:
|
|||||||
export_data = [asdict(ann) for ann in annotations]
|
export_data = [asdict(ann) for ann in annotations]
|
||||||
|
|
||||||
# Create export file
|
# 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}"
|
export_file = self.storage_path / f"export_{timestamp}.{format_type}"
|
||||||
|
|
||||||
if format_type == 'json':
|
if format_type == 'json':
|
||||||
|
|||||||
@@ -2143,7 +2143,7 @@ class RealTrainingAdapter:
|
|||||||
checkpoint_dir = "models/checkpoints/transformer"
|
checkpoint_dir = "models/checkpoints/transformer"
|
||||||
os.makedirs(checkpoint_dir, exist_ok=True)
|
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")
|
checkpoint_path = os.path.join(checkpoint_dir, f"transformer_epoch{epoch+1}_{timestamp}.pt")
|
||||||
|
|
||||||
torch.save({
|
torch.save({
|
||||||
@@ -2872,7 +2872,7 @@ class RealTrainingAdapter:
|
|||||||
checkpoint_dir = "models/checkpoints/transformer/realtime"
|
checkpoint_dir = "models/checkpoints/transformer/realtime"
|
||||||
os.makedirs(checkpoint_dir, exist_ok=True)
|
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_type = "BEST" if improved else "periodic"
|
||||||
checkpoint_path = os.path.join(checkpoint_dir, f"realtime_{checkpoint_type}_step{step}_{timestamp}.pt")
|
checkpoint_path = os.path.join(checkpoint_dir, f"realtime_{checkpoint_type}_step{step}_{timestamp}.pt")
|
||||||
|
|
||||||
@@ -3137,7 +3137,7 @@ class RealTrainingAdapter:
|
|||||||
if prediction:
|
if prediction:
|
||||||
# Store signal
|
# Store signal
|
||||||
signal = {
|
signal = {
|
||||||
'timestamp': datetime.now().isoformat(),
|
'timestamp': datetime.now(timezone.utc).isoformat(),
|
||||||
'symbol': symbol,
|
'symbol': symbol,
|
||||||
'model': model_name,
|
'model': model_name,
|
||||||
'action': prediction['action'],
|
'action': prediction['action'],
|
||||||
@@ -3158,7 +3158,7 @@ class RealTrainingAdapter:
|
|||||||
# Store prediction for visualization
|
# Store prediction for visualization
|
||||||
if self.orchestrator and hasattr(self.orchestrator, 'store_transformer_prediction'):
|
if self.orchestrator and hasattr(self.orchestrator, 'store_transformer_prediction'):
|
||||||
self.orchestrator.store_transformer_prediction(symbol, {
|
self.orchestrator.store_transformer_prediction(symbol, {
|
||||||
'timestamp': datetime.now(),
|
'timestamp': datetime.now(timezone.utc).isoformat(),
|
||||||
'current_price': current_price,
|
'current_price': current_price,
|
||||||
'predicted_price': current_price * (1.01 if prediction['action'] == 'BUY' else 0.99),
|
'predicted_price': current_price * (1.01 if prediction['action'] == 'BUY' else 0.99),
|
||||||
'price_change': 1.0 if prediction['action'] == 'BUY' else -1.0,
|
'price_change': 1.0 if prediction['action'] == 'BUY' else -1.0,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ sys.path.insert(0, str(parent_dir))
|
|||||||
from flask import Flask, render_template, request, jsonify, send_file
|
from flask import Flask, render_template, request, jsonify, send_file
|
||||||
from dash import Dash, html
|
from dash import Dash, html
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from typing import Optional, Dict, List, Any
|
from typing import Optional, Dict, List, Any
|
||||||
import json
|
import json
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -2448,7 +2448,7 @@ class AnnotationDashboard:
|
|||||||
return {
|
return {
|
||||||
'symbol': symbol,
|
'symbol': symbol,
|
||||||
'timeframe': timeframe,
|
'timeframe': timeframe,
|
||||||
'timestamp': datetime.now().isoformat(),
|
'timestamp': datetime.now(timezone.utc).isoformat(),
|
||||||
'action': random.choice(['BUY', 'SELL', 'HOLD']),
|
'action': random.choice(['BUY', 'SELL', 'HOLD']),
|
||||||
'confidence': random.uniform(0.6, 0.95),
|
'confidence': random.uniform(0.6, 0.95),
|
||||||
'predicted_price': candles[-1].get('close', 0) * (1 + random.uniform(-0.01, 0.01)),
|
'predicted_price': candles[-1].get('close', 0) * (1 + random.uniform(-0.01, 0.01)),
|
||||||
|
|||||||
@@ -15,6 +15,16 @@ class ChartManager {
|
|||||||
this.lastPredictionUpdate = {}; // Track last prediction update per timeframe
|
this.lastPredictionUpdate = {}; // Track last prediction update per timeframe
|
||||||
this.predictionUpdateThrottle = 500; // Min ms between prediction updates
|
this.predictionUpdateThrottle = 500; // Min ms between prediction updates
|
||||||
this.lastPredictionHash = null; // Track if predictions actually changed
|
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);
|
console.log('ChartManager initialized with timeframes:', timeframes);
|
||||||
}
|
}
|
||||||
@@ -2008,12 +2018,37 @@ class ChartManager {
|
|||||||
targetTimestamp = new Date(inferenceTime.getTime() + 60000);
|
targetTimestamp = new Date(inferenceTime.getTime() + 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Next Candle Prediction (Ghost)
|
// 1. Initialize ghost candle history for this timeframe if needed
|
||||||
// Show the prediction at its proper timestamp
|
if (!this.ghostCandleHistory[timeframe]) {
|
||||||
this._addGhostCandlePrediction(candleData, timeframe, predictionTraces, targetTimestamp);
|
this.ghostCandleHistory[timeframe] = [];
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Store as "Last Prediction" for this timeframe
|
// 2. Add new ghost candle to history
|
||||||
// This allows us to visualize the "Shadow" (prediction vs actual) on the next tick
|
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 = {};
|
if (!this.lastPredictions) this.lastPredictions = {};
|
||||||
|
|
||||||
this.lastPredictions[timeframe] = {
|
this.lastPredictions[timeframe] = {
|
||||||
@@ -2022,7 +2057,7 @@ class ChartManager {
|
|||||||
inferenceTime: predictionTimestamp
|
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 open = candleData[0];
|
||||||
const high = candleData[1];
|
const high = candleData[1];
|
||||||
const low = candleData[2];
|
const low = candleData[2];
|
||||||
@@ -2174,9 +2218,10 @@ class ChartManager {
|
|||||||
// Determine color
|
// Determine color
|
||||||
const color = close >= open ? '#10b981' : '#ef4444';
|
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 = {
|
const ghostTrace = {
|
||||||
x: [nextTimestamp],
|
x: [formattedTimestamp],
|
||||||
open: [open],
|
open: [open],
|
||||||
high: [high],
|
high: [high],
|
||||||
low: [low],
|
low: [low],
|
||||||
@@ -2184,26 +2229,39 @@ class ChartManager {
|
|||||||
type: 'candlestick',
|
type: 'candlestick',
|
||||||
name: 'Ghost Prediction',
|
name: 'Ghost Prediction',
|
||||||
increasing: {
|
increasing: {
|
||||||
line: { color: color, width: 1 },
|
line: { color: color, width: 3 }, // 150% wider (normal is 2, so 3)
|
||||||
fillcolor: color
|
fillcolor: color
|
||||||
},
|
},
|
||||||
decreasing: {
|
decreasing: {
|
||||||
line: { color: color, width: 1 },
|
line: { color: color, width: 3 }, // 150% wider
|
||||||
fillcolor: color
|
fillcolor: color
|
||||||
},
|
},
|
||||||
opacity: 0.6, // 60% transparent
|
opacity: 0.6, // 60% transparent
|
||||||
hoverinfo: 'x+y+text',
|
hoverinfo: 'x+y+text',
|
||||||
text: ['Predicted Next Candle']
|
text: ['Predicted Next Candle'],
|
||||||
|
width: 1.5 // 150% width multiplier
|
||||||
};
|
};
|
||||||
|
|
||||||
traces.push(ghostTrace);
|
traces.push(ghostTrace);
|
||||||
console.log('Added ghost candle prediction:', ghostTrace);
|
console.log('Added ghost candle prediction at:', formattedTimestamp, ghostTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
_addShadowCandlePrediction(candleData, timestamp, traces) {
|
_addShadowCandlePrediction(candleData, timestamp, traces) {
|
||||||
// candleData is [Open, High, Low, Close, Volume]
|
// candleData is [Open, High, Low, Close, Volume]
|
||||||
// timestamp is the time where this shadow should appear (matches current candle)
|
// 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 open = candleData[0];
|
||||||
const high = candleData[1];
|
const high = candleData[1];
|
||||||
const low = candleData[2];
|
const low = candleData[2];
|
||||||
@@ -2212,8 +2270,9 @@ class ChartManager {
|
|||||||
// Shadow color (purple to distinguish from ghost)
|
// Shadow color (purple to distinguish from ghost)
|
||||||
const color = '#8b5cf6'; // Violet
|
const color = '#8b5cf6'; // Violet
|
||||||
|
|
||||||
|
// Shadow candles also 150% wider
|
||||||
const shadowTrace = {
|
const shadowTrace = {
|
||||||
x: [timestamp],
|
x: [formattedTimestamp],
|
||||||
open: [open],
|
open: [open],
|
||||||
high: [high],
|
high: [high],
|
||||||
low: [low],
|
low: [low],
|
||||||
@@ -2221,16 +2280,17 @@ class ChartManager {
|
|||||||
type: 'candlestick',
|
type: 'candlestick',
|
||||||
name: 'Shadow Prediction',
|
name: 'Shadow Prediction',
|
||||||
increasing: {
|
increasing: {
|
||||||
line: { color: color, width: 1 },
|
line: { color: color, width: 3 }, // 150% wider
|
||||||
fillcolor: 'rgba(139, 92, 246, 0.0)' // Hollow
|
fillcolor: 'rgba(139, 92, 246, 0.0)' // Hollow
|
||||||
},
|
},
|
||||||
decreasing: {
|
decreasing: {
|
||||||
line: { color: color, width: 1 },
|
line: { color: color, width: 3 }, // 150% wider
|
||||||
fillcolor: 'rgba(139, 92, 246, 0.0)' // Hollow
|
fillcolor: 'rgba(139, 92, 246, 0.0)' // Hollow
|
||||||
},
|
},
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
hoverinfo: 'x+y+text',
|
hoverinfo: 'x+y+text',
|
||||||
text: ['Past Prediction']
|
text: ['Past Prediction'],
|
||||||
|
width: 1.5 // 150% width multiplier
|
||||||
};
|
};
|
||||||
|
|
||||||
traces.push(shadowTrace);
|
traces.push(shadowTrace);
|
||||||
|
|||||||
55
ANNOTATE_TIMEZONE_FIX_SUMMARY.md
Normal file
55
ANNOTATE_TIMEZONE_FIX_SUMMARY.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user