wip
This commit is contained in:
@@ -2626,6 +2626,95 @@ class AnnotationDashboard:
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
@self.server.route('/api/live-updates-batch', methods=['POST'])
|
||||
def get_live_updates_batch():
|
||||
"""Get live chart and prediction updates for multiple timeframes (optimized batch endpoint)"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
symbol = data.get('symbol', 'ETH/USDT')
|
||||
timeframes = data.get('timeframes', ['1m'])
|
||||
|
||||
response = {
|
||||
'success': True,
|
||||
'chart_updates': {}, # Dict of timeframe -> chart_update
|
||||
'prediction': None # Single prediction for all timeframes
|
||||
}
|
||||
|
||||
# Get latest candle for each requested timeframe
|
||||
if self.data_loader:
|
||||
for timeframe in timeframes:
|
||||
try:
|
||||
df = self.data_loader.get_data(symbol, timeframe, limit=2, direction='latest')
|
||||
if df is not None and not df.empty:
|
||||
latest_candle = df.iloc[-1]
|
||||
|
||||
# Format timestamp as ISO string
|
||||
timestamp = latest_candle.name
|
||||
if hasattr(timestamp, 'isoformat'):
|
||||
if timestamp.tzinfo is not None:
|
||||
timestamp_str = timestamp.astimezone(timezone.utc).isoformat()
|
||||
else:
|
||||
timestamp_str = timestamp.isoformat() + 'Z'
|
||||
else:
|
||||
timestamp_str = str(timestamp)
|
||||
|
||||
is_confirmed = len(df) >= 2
|
||||
|
||||
response['chart_updates'][timeframe] = {
|
||||
'symbol': symbol,
|
||||
'timeframe': timeframe,
|
||||
'candle': {
|
||||
'timestamp': timestamp_str,
|
||||
'open': float(latest_candle['open']),
|
||||
'high': float(latest_candle['high']),
|
||||
'low': float(latest_candle['low']),
|
||||
'close': float(latest_candle['close']),
|
||||
'volume': float(latest_candle['volume'])
|
||||
},
|
||||
'is_confirmed': is_confirmed
|
||||
}
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting candle for {timeframe}: {e}")
|
||||
|
||||
# Get latest model predictions (same for all timeframes)
|
||||
if self.orchestrator:
|
||||
try:
|
||||
predictions = {}
|
||||
|
||||
# DQN predictions
|
||||
if hasattr(self.orchestrator, 'recent_dqn_predictions') and symbol in self.orchestrator.recent_dqn_predictions:
|
||||
dqn_preds = list(self.orchestrator.recent_dqn_predictions[symbol])
|
||||
if dqn_preds:
|
||||
predictions['dqn'] = dqn_preds[-1]
|
||||
|
||||
# CNN predictions
|
||||
if hasattr(self.orchestrator, 'recent_cnn_predictions') and symbol in self.orchestrator.recent_cnn_predictions:
|
||||
cnn_preds = list(self.orchestrator.recent_cnn_predictions[symbol])
|
||||
if cnn_preds:
|
||||
predictions['cnn'] = cnn_preds[-1]
|
||||
|
||||
# Transformer predictions
|
||||
if hasattr(self.orchestrator, 'recent_transformer_predictions') and symbol in self.orchestrator.recent_transformer_predictions:
|
||||
transformer_preds = list(self.orchestrator.recent_transformer_predictions[symbol])
|
||||
if transformer_preds:
|
||||
transformer_pred = transformer_preds[-1].copy()
|
||||
predictions['transformer'] = self._serialize_prediction(transformer_pred)
|
||||
|
||||
if predictions:
|
||||
response['prediction'] = predictions
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting predictions: {e}")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in batch live updates: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
@self.server.route('/api/realtime-inference/signals', methods=['GET'])
|
||||
def get_realtime_signals():
|
||||
"""Get latest real-time inference signals"""
|
||||
|
||||
@@ -3059,30 +3059,37 @@ class ChartManager {
|
||||
|
||||
let targetPrice = currentPrice;
|
||||
|
||||
// CRITICAL FIX: Check if price_delta is normalized (< 1.0) or real price change
|
||||
if (trendVector.price_delta !== undefined && trendVector.price_delta !== null) {
|
||||
const priceDelta = parseFloat(trendVector.price_delta);
|
||||
|
||||
// If price_delta is very small (< 1.0), it's likely normalized - scale it
|
||||
if (Math.abs(priceDelta) < 1.0) {
|
||||
// Normalized value - treat as percentage of current price
|
||||
targetPrice = currentPrice * (1 + priceDelta);
|
||||
// CRITICAL FIX: Use calculated_direction and calculated_steepness from trend_vector
|
||||
// The price_delta in trend_vector is the pivot range, not the predicted change
|
||||
// We should use direction and steepness to estimate the trend
|
||||
const direction = parseFloat(trendVector.calculated_direction) || 0; // -1, 0, or 1
|
||||
const steepness = parseFloat(trendVector.calculated_steepness) || 0;
|
||||
|
||||
// Steepness is in price units, but we need to scale it reasonably
|
||||
// If steepness is > 100, it's likely in absolute price units (too large)
|
||||
// Scale it down to a reasonable percentage move
|
||||
let priceChange = 0;
|
||||
|
||||
if (steepness > 0) {
|
||||
// If steepness is large (> 10), treat it as absolute price change but cap it
|
||||
if (steepness > 10) {
|
||||
// Cap at 2% of current price
|
||||
const maxChange = 0.02 * currentPrice;
|
||||
priceChange = Math.min(steepness, maxChange) * direction;
|
||||
} else {
|
||||
// Real price delta - add directly
|
||||
targetPrice = currentPrice + priceDelta;
|
||||
// Small steepness - use as percentage
|
||||
priceChange = (steepness / 100) * currentPrice * direction;
|
||||
}
|
||||
} else {
|
||||
// Fallback: Use direction and steepness
|
||||
const direction = trendVector.direction === 'up' ? 1 :
|
||||
(trendVector.direction === 'down' ? -1 : 0);
|
||||
const steepness = parseFloat(trendVector.steepness) || 0; // 0 to 1
|
||||
|
||||
// Estimate price change based on steepness (max 1% move per projection period)
|
||||
const maxChange = 0.01 * currentPrice;
|
||||
const projectedChange = maxChange * steepness * direction;
|
||||
targetPrice = currentPrice + projectedChange;
|
||||
// Fallback: Use angle if available
|
||||
const angle = parseFloat(trendVector.calculated_angle) || 0;
|
||||
// Angle is in radians, convert to price change
|
||||
// Small angle = small change, large angle = large change
|
||||
priceChange = Math.tan(angle) * currentPrice * 0.01; // Scale down
|
||||
}
|
||||
|
||||
targetPrice = currentPrice + priceChange;
|
||||
|
||||
// Sanity check: Don't let target price go to 0 or negative
|
||||
if (targetPrice <= 0 || !isFinite(targetPrice)) {
|
||||
console.warn('Invalid target price calculated:', targetPrice, 'using current price instead');
|
||||
|
||||
@@ -57,28 +57,42 @@ class LiveUpdatesPolling {
|
||||
}
|
||||
|
||||
_poll() {
|
||||
// Poll each subscription
|
||||
// OPTIMIZATION: Batch all subscriptions into a single API call
|
||||
// Group by symbol to reduce API calls from 4 to 1
|
||||
const symbolGroups = {};
|
||||
this.subscriptions.forEach(sub => {
|
||||
fetch('/api/live-updates', {
|
||||
if (!symbolGroups[sub.symbol]) {
|
||||
symbolGroups[sub.symbol] = [];
|
||||
}
|
||||
symbolGroups[sub.symbol].push(sub.timeframe);
|
||||
});
|
||||
|
||||
// Make one call per symbol with all timeframes
|
||||
Object.entries(symbolGroups).forEach(([symbol, timeframes]) => {
|
||||
fetch('/api/live-updates-batch', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
symbol: sub.symbol,
|
||||
timeframe: sub.timeframe
|
||||
symbol: symbol,
|
||||
timeframes: timeframes
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Handle chart update (even if null, predictions should still be processed)
|
||||
if (data.chart_update && this.onChartUpdate) {
|
||||
this.onChartUpdate(data.chart_update);
|
||||
// Handle chart updates for each timeframe
|
||||
if (data.chart_updates && this.onChartUpdate) {
|
||||
// chart_updates is an object: { '1s': {...}, '1m': {...}, ... }
|
||||
Object.entries(data.chart_updates).forEach(([timeframe, update]) => {
|
||||
if (update) {
|
||||
this.onChartUpdate(update);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Handle prediction update properly
|
||||
// data.prediction is already in format { transformer: {...}, dqn: {...}, cnn: {...} }
|
||||
// Handle prediction update (single prediction for all timeframes)
|
||||
// data.prediction is in format { transformer: {...}, dqn: {...}, cnn: {...} }
|
||||
if (data.prediction && this.onPredictionUpdate) {
|
||||
// Log prediction data for debugging
|
||||
console.log('[Live Updates] Received prediction data:', {
|
||||
has_transformer: !!data.prediction.transformer,
|
||||
has_dqn: !!data.prediction.dqn,
|
||||
@@ -88,10 +102,7 @@ class LiveUpdatesPolling {
|
||||
has_predicted_candle: !!data.prediction.transformer?.predicted_candle
|
||||
});
|
||||
|
||||
// Pass the prediction object directly (it's already in the correct format)
|
||||
this.onPredictionUpdate(data.prediction);
|
||||
} else if (!data.prediction) {
|
||||
console.debug('[Live Updates] No prediction data in response');
|
||||
}
|
||||
} else {
|
||||
console.debug('[Live Updates] Response not successful:', data);
|
||||
|
||||
Reference in New Issue
Block a user