show dummy references

This commit is contained in:
Dobromir Popov
2025-09-09 22:27:07 +03:00
parent 2e1b3be2cd
commit 1f35258a66
4 changed files with 915 additions and 112 deletions

View File

@@ -143,7 +143,7 @@ class EnhancedCNNModel(nn.Module):
def __init__(self,
input_size: int = 60,
feature_dim: int = 50,
output_size: int = 2, # BUY/SELL for 2-action system
output_size: int = 5, # OHLCV prediction (Open, High, Low, Close, Volume)
base_channels: int = 256, # Increased from 128 to 256
num_blocks: int = 12, # Increased from 6 to 12
num_attention_heads: int = 16, # Increased from 8 to 16
@@ -416,39 +416,40 @@ class EnhancedCNNModel(nn.Module):
volatility_pred = self._memory_barrier(self.volatility_predictor(processed_features))
confidence = self._memory_barrier(self.confidence_head(processed_features))
# Combine all features for final decision (8 regime classes + 1 volatility)
# Combine all features for OHLCV prediction
# Create completely independent tensors for concatenation
vol_pred_flat = self._memory_barrier(volatility_pred.reshape(volatility_pred.shape[0], -1)) # Flatten instead of squeeze
combined_features = torch.cat([processed_features, regime_probs, vol_pred_flat], dim=1)
combined_features = self._memory_barrier(combined_features)
trading_logits = self._memory_barrier(self.decision_head(combined_features))
# Apply temperature scaling for better calibration - create new tensor
temperature = 1.5
scaled_logits = trading_logits / temperature
trading_probs = self._memory_barrier(F.softmax(scaled_logits, dim=1))
# Flatten confidence to ensure consistent shape
# OHLCV prediction (Open, High, Low, Close, Volume)
ohlcv_pred = self._memory_barrier(self.decision_head(combined_features))
# Generate confidence based on prediction stability
confidence_flat = self._memory_barrier(confidence.reshape(confidence.shape[0], -1))
volatility_flat = self._memory_barrier(volatility_pred.reshape(volatility_pred.shape[0], -1))
# Calculate prediction confidence based on volatility and regime stability
regime_stability = torch.std(regime_probs, dim=1, keepdim=True)
prediction_confidence = 1.0 / (1.0 + regime_stability + volatility_flat * 0.1)
prediction_confidence = self._memory_barrier(prediction_confidence.squeeze(-1))
return {
'logits': self._memory_barrier(trading_logits),
'probabilities': self._memory_barrier(trading_probs),
'confidence': confidence_flat[:, 0] if confidence_flat.shape[1] > 0 else confidence_flat.reshape(-1)[0],
'ohlcv': self._memory_barrier(ohlcv_pred), # [batch_size, 5] - OHLCV predictions
'confidence': prediction_confidence,
'regime': self._memory_barrier(regime_probs),
'volatility': volatility_flat[:, 0] if volatility_flat.shape[1] > 0 else volatility_flat.reshape(-1)[0],
'features': self._memory_barrier(processed_features)
'features': self._memory_barrier(processed_features),
'regime_stability': self._memory_barrier(regime_stability.squeeze(-1))
}
def predict(self, feature_matrix) -> Dict[str, Any]:
"""
Make predictions on feature matrix
Make OHLCV predictions on feature matrix
Args:
feature_matrix: tensor or numpy array of shape [sequence_length, features]
Returns:
Dictionary with prediction results
Dictionary with OHLCV prediction results and trading signals
"""
self.eval()
@@ -468,17 +469,13 @@ class EnhancedCNNModel(nn.Module):
# Forward pass
outputs = self.forward(x)
# Extract results with proper shape handling
if HAS_NUMPY:
probs = outputs['probabilities'].cpu().numpy()[0]
confidence_tensor = outputs['confidence'].cpu().numpy()
regime = outputs['regime'].cpu().numpy()[0]
volatility = outputs['volatility'].cpu().numpy()
else:
probs = outputs['probabilities'].cpu().tolist()[0]
confidence_tensor = outputs['confidence'].cpu().tolist()
regime = outputs['regime'].cpu().tolist()[0]
volatility = outputs['volatility'].cpu().tolist()
# Extract OHLCV predictions
ohlcv_pred = outputs['ohlcv'].cpu().numpy()[0] if HAS_NUMPY else outputs['ohlcv'].cpu().tolist()[0]
# Extract other outputs
confidence_tensor = outputs['confidence'].cpu().numpy() if HAS_NUMPY else outputs['confidence'].cpu().tolist()
regime = outputs['regime'].cpu().numpy()[0] if HAS_NUMPY else outputs['regime'].cpu().tolist()[0]
volatility = outputs['volatility'].cpu().numpy() if HAS_NUMPY else outputs['volatility'].cpu().tolist()
# Handle confidence shape properly
if HAS_NUMPY and isinstance(confidence_tensor, np.ndarray):
@@ -490,7 +487,7 @@ class EnhancedCNNModel(nn.Module):
confidence = float(confidence_tensor[0] if len(confidence_tensor) > 0 else 0.7)
else:
confidence = float(confidence_tensor)
# Handle volatility shape properly
if HAS_NUMPY and isinstance(volatility, np.ndarray):
if volatility.ndim == 0:
@@ -502,28 +499,68 @@ class EnhancedCNNModel(nn.Module):
else:
volatility = float(volatility)
# Determine action (0=BUY, 1=SELL for 2-action system)
if HAS_NUMPY:
action = int(np.argmax(probs))
else:
action = int(torch.argmax(torch.tensor(probs)).item())
action_confidence = float(probs[action])
# Extract OHLCV values
open_price, high_price, low_price, close_price, volume = ohlcv_pred
# Convert logits to list
if HAS_NUMPY:
raw_logits = outputs['logits'].cpu().numpy()[0].tolist()
else:
raw_logits = outputs['logits'].cpu().tolist()[0]
# Calculate price movement and direction
price_change = close_price - open_price
price_change_pct = (price_change / open_price) * 100 if open_price != 0 else 0
# Calculate candle characteristics
body_size = abs(close_price - open_price)
upper_wick = high_price - max(open_price, close_price)
lower_wick = min(open_price, close_price) - low_price
total_range = high_price - low_price
# Determine trading action based on predicted candle
if price_change_pct > 0.1: # Bullish candle (>0.1% gain)
action = 0 # BUY
action_name = 'BUY'
action_confidence = min(0.95, confidence * (1 + abs(price_change_pct) * 10))
elif price_change_pct < -0.1: # Bearish candle (<-0.1% loss)
action = 1 # SELL
action_name = 'SELL'
action_confidence = min(0.95, confidence * (1 + abs(price_change_pct) * 10))
else: # Sideways/neutral candle
# Use body vs wick analysis for weak signals
if body_size / total_range > 0.7: # Strong directional body
action = 0 if price_change > 0 else 1
action_name = 'BUY' if action == 0 else 'SELL'
action_confidence = confidence * 0.6 # Reduce confidence for weak signals
else:
action = 2 # HOLD
action_name = 'HOLD'
action_confidence = confidence * 0.3 # Very low confidence
# Adjust confidence based on volatility
if volatility > 0.5: # High volatility
action_confidence *= 0.8 # Reduce confidence in volatile conditions
elif volatility < 0.2: # Low volatility
action_confidence *= 1.2 # Increase confidence in stable conditions
action_confidence = min(0.95, action_confidence) # Cap at 95%
return {
'action': action,
'action_name': 'BUY' if action == 0 else 'SELL',
'action_name': action_name,
'confidence': float(confidence),
'action_confidence': action_confidence,
'probabilities': probs if isinstance(probs, list) else probs.tolist(),
'ohlcv_prediction': {
'open': float(open_price),
'high': float(high_price),
'low': float(low_price),
'close': float(close_price),
'volume': float(volume)
},
'price_change_pct': price_change_pct,
'candle_characteristics': {
'body_size': body_size,
'upper_wick': upper_wick,
'lower_wick': lower_wick,
'total_range': total_range
},
'regime_probabilities': regime if isinstance(regime, list) else regime.tolist(),
'volatility_prediction': float(volatility),
'raw_logits': raw_logits
'prediction_quality': 'high' if action_confidence > 0.8 else 'medium' if action_confidence > 0.6 else 'low'
}
def get_memory_usage(self) -> Dict[str, Any]:

View File

@@ -111,16 +111,18 @@ class MultiTimeframePredictor:
adjusted_input_size = min(sequence_length, 300) # Cap at 300 to avoid memory issues
# Create new model instance with horizon-specific parameters
horizon_model = model_class(
input_size=adjusted_input_size,
feature_dim=getattr(base_model, 'feature_dim', 50),
output_size=getattr(base_model, 'output_size', 2),
base_channels=getattr(base_model, 'base_channels', 256),
num_blocks=getattr(base_model, 'num_blocks', 12),
num_attention_heads=getattr(base_model, 'num_attention_heads', 16),
dropout_rate=getattr(base_model, 'dropout_rate', 0.2),
prediction_horizon=horizon.value
)
# Use only the parameters that the model actually accepts
try:
horizon_model = model_class(
input_size=adjusted_input_size,
feature_dim=getattr(base_model, 'feature_dim', 50),
output_size=5, # Always use 5 for OHLCV predictions
prediction_horizon=horizon.value
)
except TypeError:
# If the model doesn't accept these parameters, just create with defaults
logger.warning(f"Model {model_class.__name__} doesn't accept expected parameters, using defaults")
horizon_model = model_class()
# Try to load pre-trained weights if available
try:
@@ -179,48 +181,33 @@ class MultiTimeframePredictor:
def _generate_single_horizon_prediction(self, symbol: str, current_price: float,
horizon: PredictionHorizon, config: Dict,
market_conditions: Dict) -> Optional[Dict[str, Any]]:
"""Generate prediction for single timeframe"""
"""Generate prediction for single timeframe using iterative candle prediction"""
try:
# Get appropriate data for this horizon
sequence_data = self._get_sequence_data_for_horizon(symbol, config['sequence_length'])
# Get base historical data (use shorter sequence for iterative prediction)
base_sequence_length = min(60, config['sequence_length'] // 2) # Use half for base data
base_data = self._get_sequence_data_for_horizon(symbol, base_sequence_length)
if not sequence_data:
if not base_data:
return None
# Generate predictions from available models
model_predictions = []
# Generate iterative predictions for this horizon
iterative_predictions = self._generate_iterative_predictions(
symbol, base_data, horizon.value, market_conditions
)
# CNN prediction
cnn_key = f'cnn_{horizon.value}min'
if cnn_key in self.models:
cnn_pred = self._get_cnn_prediction(
self.models[cnn_key], sequence_data, config
)
if cnn_pred:
model_predictions.append(cnn_pred)
# COB RL prediction
cob_key = f'cob_rl_{horizon.value}min'
if cob_key in self.models:
cob_pred = self._get_cob_rl_prediction(
self.models[cob_key], sequence_data, config
)
if cob_pred:
model_predictions.append(cob_pred)
if not model_predictions:
if not iterative_predictions:
return None
# Ensemble predictions
ensemble_prediction = self._ensemble_predictions(
model_predictions, config, market_conditions
# Analyze the predicted price movement over the horizon
horizon_prediction = self._analyze_horizon_prediction(
iterative_predictions, config, market_conditions
)
# Apply confidence threshold
if ensemble_prediction['confidence'] < config['confidence_threshold']:
if horizon_prediction['confidence'] < config['confidence_threshold']:
return None # Not confident enough for this horizon
return ensemble_prediction
return horizon_prediction
except Exception as e:
logger.error(f"Error generating {horizon.value}-minute prediction: {e}")
@@ -239,16 +226,26 @@ class MultiTimeframePredictor:
if data is not None and len(data) >= sequence_length // 10: # At least 10% of required data
# Convert to tensor format expected by models
return self._convert_data_to_tensor(data)
tensor_data = self._convert_data_to_tensor(data)
if tensor_data is not None:
logger.debug(f"✅ Converted {len(data)} data points to tensor shape: {tensor_data.shape}")
return tensor_data
else:
logger.warning("Failed to convert data to tensor")
return None
else:
logger.warning(f"Insufficient data for {sequence_length}-point prediction")
logger.warning(f"Insufficient data for {sequence_length}-point prediction: {len(data) if data is not None else 'None'}")
return None
return None
# Fallback: create mock data if no data provider available
logger.warning("No data provider available - creating mock sequence data")
return self._create_mock_sequence_data(sequence_length)
except Exception as e:
logger.error(f"Error getting sequence data: {e}")
return None
# Fallback: create mock data on error
logger.warning("Creating mock sequence data due to error")
return self._create_mock_sequence_data(sequence_length)
def _convert_data_to_tensor(self, data) -> torch.Tensor:
"""Convert market data to tensor format"""
@@ -261,12 +258,22 @@ class MultiTimeframePredictor:
for feature in features:
if feature in data.columns:
values = data[feature].fillna(method='ffill').fillna(0).values
values = data[feature].ffill().fillna(0).values
feature_data.append(values)
if feature_data:
# Ensure all feature arrays have the same length
min_length = min(len(arr) for arr in feature_data)
feature_data = [arr[:min_length] for arr in feature_data]
# Stack features
tensor_data = torch.tensor(feature_data, dtype=torch.float32).transpose(0, 1)
# Validate tensor data
if torch.any(torch.isnan(tensor_data)) or torch.any(torch.isinf(tensor_data)):
logger.warning("Found NaN or Inf values in tensor data, replacing with zeros")
tensor_data = torch.nan_to_num(tensor_data, nan=0.0, posinf=0.0, neginf=0.0)
return tensor_data.unsqueeze(0) # Add batch dimension
return None
@@ -276,25 +283,58 @@ class MultiTimeframePredictor:
return None
def _get_cnn_prediction(self, model, sequence_data: torch.Tensor, config: Dict) -> Optional[Dict]:
"""Get CNN model prediction"""
"""Get CNN model prediction using OHLCV prediction"""
try:
# Use the predict method which now handles OHLCV predictions
if hasattr(model, 'predict'):
if sequence_data.dim() == 3: # [batch, seq, features]
sequence_data_flat = sequence_data.squeeze(0) # Remove batch dim
else:
sequence_data_flat = sequence_data
prediction = model.predict(sequence_data_flat)
if prediction and 'action_name' in prediction:
return {
'action': prediction['action_name'],
'confidence': prediction.get('action_confidence', 0.5),
'model': 'cnn',
'horizon': config.get('max_hold_time', 60),
'ohlcv_prediction': prediction.get('ohlcv_prediction'),
'price_change_pct': prediction.get('price_change_pct', 0)
}
# Fallback to direct forward pass if predict method not available
with torch.no_grad():
outputs = model(sequence_data)
if isinstance(outputs, tuple):
predictions, confidence = outputs
else:
predictions = outputs
confidence = torch.softmax(predictions, dim=-1).max().item()
if isinstance(outputs, dict) and 'ohlcv' in outputs:
ohlcv = outputs['ohlcv'].cpu().numpy()[0]
confidence = outputs['confidence'].cpu().numpy()[0] if hasattr(outputs['confidence'], 'cpu') else outputs['confidence']
action_idx = predictions.argmax().item()
actions = ['SELL', 'BUY'] # Adjust based on your model's output format
# Determine action from OHLCV
price_change_pct = ((ohlcv[3] - ohlcv[0]) / ohlcv[0]) * 100 if ohlcv[0] != 0 else 0
return {
'action': actions[action_idx] if action_idx < len(actions) else 'HOLD',
'confidence': confidence,
'model': 'cnn',
'horizon': config.get('max_hold_time', 60)
}
if price_change_pct > 0.1:
action = 'BUY'
elif price_change_pct < -0.1:
action = 'SELL'
else:
action = 'HOLD'
return {
'action': action,
'confidence': float(confidence),
'model': 'cnn',
'horizon': config.get('max_hold_time', 60),
'ohlcv_prediction': {
'open': float(ohlcv[0]),
'high': float(ohlcv[1]),
'low': float(ohlcv[2]),
'close': float(ohlcv[3]),
'volume': float(ohlcv[4])
},
'price_change_pct': price_change_pct
}
except Exception as e:
logger.error(f"Error getting CNN prediction: {e}")
@@ -320,27 +360,58 @@ class MultiTimeframePredictor:
def _ensemble_predictions(self, predictions: List[Dict], config: Dict,
market_conditions: Dict) -> Dict[str, Any]:
"""Ensemble multiple model predictions"""
"""Ensemble multiple model predictions using OHLCV data"""
try:
if not predictions:
return None
# Simple voting ensemble
# Enhanced ensemble considering both action and price movement
action_votes = {}
confidence_sum = 0
price_change_indicators = []
for pred in predictions:
action = pred['action']
confidence = pred['confidence']
# Weight by confidence
if action not in action_votes:
action_votes[action] = 0
action_votes[action] += confidence
confidence_sum += confidence
# Collect price change indicators for ensemble analysis
if 'price_change_pct' in pred:
price_change_indicators.append(pred['price_change_pct'])
# Get winning action
best_action = max(action_votes, key=action_votes.get)
ensemble_confidence = action_votes[best_action] / len(predictions)
if action_votes:
best_action = max(action_votes, key=action_votes.get)
ensemble_confidence = action_votes[best_action] / len(predictions)
else:
best_action = 'HOLD'
ensemble_confidence = 0.1
# Analyze price movement consensus
if price_change_indicators:
avg_price_change = sum(price_change_indicators) / len(price_change_indicators)
price_consensus = abs(avg_price_change) / 0.1 # Normalize around 0.1% threshold
# Boost confidence if price movements are consistent
if len(price_change_indicators) > 1:
price_std = torch.std(torch.tensor(price_change_indicators)).item()
if price_std < 0.05: # Low variability in predictions
ensemble_confidence *= 1.2
elif price_std > 0.15: # High variability
ensemble_confidence *= 0.8
# Override action based on strong price consensus
if abs(avg_price_change) > 0.2: # Strong price movement
if avg_price_change > 0:
best_action = 'BUY'
else:
best_action = 'SELL'
ensemble_confidence = min(ensemble_confidence * 1.3, 0.9)
# Adjust confidence based on market conditions
market_confidence_multiplier = market_conditions.get('confidence_multiplier', 1.0)
@@ -352,7 +423,9 @@ class MultiTimeframePredictor:
'horizon_minutes': config['max_hold_time'] // 60,
'risk_multiplier': config['risk_multiplier'],
'models_used': len(predictions),
'market_conditions': market_conditions
'market_conditions': market_conditions,
'price_change_indicators': price_change_indicators,
'avg_price_change_pct': sum(price_change_indicators) / len(price_change_indicators) if price_change_indicators else 0
}
except Exception as e:
@@ -444,3 +517,264 @@ class MultiTimeframePredictor:
except Exception as e:
logger.error(f"Error determining hold time: {e}")
return 60
def _generate_iterative_predictions(self, symbol: str, base_data: torch.Tensor,
num_steps: int, market_conditions: Dict) -> Optional[List[Dict]]:
"""Generate iterative candle predictions for the specified number of steps"""
try:
predictions = []
current_data = base_data.clone() # Start with base historical data
# Get the CNN model for iterative prediction
cnn_model = None
for model_key, model in self.models.items():
if model_key.startswith('cnn_'):
cnn_model = model
break
if not cnn_model:
logger.warning("No CNN model available for iterative prediction")
return None
# Check if CNN model has predict method
if not hasattr(cnn_model, 'predict'):
logger.warning("CNN model does not have predict method - trying alternative approach")
# Try to use the orchestrator's CNN model directly
if hasattr(self.orchestrator, 'cnn_model') and self.orchestrator.cnn_model:
cnn_model = self.orchestrator.cnn_model
logger.info("Using orchestrator's CNN model for predictions")
# Check if orchestrator's CNN model also lacks predict method
if not hasattr(cnn_model, 'predict'):
logger.error("Orchestrator's CNN model also lacks predict method - creating mock predictions")
return self._create_mock_predictions(num_steps)
else:
logger.error("No CNN model with predict method available - creating mock predictions")
# Create mock predictions for testing
return self._create_mock_predictions(num_steps)
for step in range(num_steps):
# Use CNN model to predict next candle
try:
with torch.no_grad():
# Prepare data for CNN prediction
# Convert tensor to format expected by predict method
if current_data.dim() == 3: # [batch, seq, features]
current_data_flat = current_data.squeeze(0) # Remove batch dim
else:
current_data_flat = current_data
prediction = cnn_model.predict(current_data_flat)
if prediction and 'ohlcv_prediction' in prediction:
# Add timestamp to the prediction
prediction_time = datetime.now() + timedelta(minutes=step + 1)
prediction['timestamp'] = prediction_time
predictions.append(prediction)
logger.debug(f"📊 Step {step}: Added prediction for {prediction_time}, close: {prediction['ohlcv_prediction']['close']:.2f}")
# Extract predicted OHLCV values
ohlcv = prediction['ohlcv_prediction']
new_candle = torch.tensor([
ohlcv['open'],
ohlcv['high'],
ohlcv['low'],
ohlcv['close'],
ohlcv['volume']
], dtype=current_data.dtype)
# Add the predicted candle to our data sequence
# Remove oldest candle and add new prediction
if current_data.dim() == 3:
current_data = torch.cat([
current_data[:, 1:, :], # Remove oldest candle
new_candle.unsqueeze(0).unsqueeze(0) # Add new prediction
], dim=1)
else:
current_data = torch.cat([
current_data[1:, :], # Remove oldest candle
new_candle.unsqueeze(0) # Add new prediction
], dim=0)
else:
logger.warning(f"❌ Step {step}: Invalid prediction format")
break
except Exception as e:
logger.error(f"Error in iterative prediction step {step}: {e}")
break
return predictions if predictions else None
except Exception as e:
logger.error(f"Error in iterative predictions: {e}")
return None
def _create_mock_predictions(self, num_steps: int) -> List[Dict]:
"""Create mock predictions for testing when CNN model is not available"""
try:
logger.info(f"Creating {num_steps} mock predictions for testing")
predictions = []
current_time = datetime.now()
base_price = 4300.0 # Mock base price
for step in range(num_steps):
prediction_time = current_time + timedelta(minutes=step + 1)
price_change = (step - num_steps // 2) * 2.0 # Mock price movement
predicted_price = base_price + price_change
mock_prediction = {
'timestamp': prediction_time,
'ohlcv_prediction': {
'open': predicted_price,
'high': predicted_price + 1.0,
'low': predicted_price - 1.0,
'close': predicted_price + 0.5,
'volume': 1000
},
'confidence': max(0.3, 0.8 - step * 0.05), # Decreasing confidence
'action': 0 if price_change > 0 else 1,
'action_name': 'BUY' if price_change > 0 else 'SELL'
}
predictions.append(mock_prediction)
logger.info(f"✅ Created {len(predictions)} mock predictions")
return predictions
except Exception as e:
logger.error(f"Error creating mock predictions: {e}")
return []
def _create_mock_sequence_data(self, sequence_length: int) -> torch.Tensor:
"""Create mock sequence data for testing when real data is not available"""
try:
logger.info(f"Creating mock sequence data with {sequence_length} points")
# Create mock OHLCV data
base_price = 4300.0
mock_data = []
for i in range(sequence_length):
# Simulate price movement
price_change = (i - sequence_length // 2) * 0.5
price = base_price + price_change
# Create OHLCV candle
candle = [
price, # open
price + 1.0, # high
price - 1.0, # low
price + 0.5, # close
1000.0 # volume
]
mock_data.append(candle)
# Convert to tensor
tensor_data = torch.tensor(mock_data, dtype=torch.float32)
tensor_data = tensor_data.unsqueeze(0) # Add batch dimension
logger.debug(f"✅ Created mock sequence data shape: {tensor_data.shape}")
return tensor_data
except Exception as e:
logger.error(f"Error creating mock sequence data: {e}")
# Return minimal valid tensor
return torch.zeros((1, 10, 5), dtype=torch.float32)
def _analyze_horizon_prediction(self, iterative_predictions: List[Dict],
config: Dict, market_conditions: Dict) -> Optional[Dict[str, Any]]:
"""Analyze the series of iterative predictions to determine overall horizon movement"""
try:
if not iterative_predictions:
return None
# Extract price data from predictions
predicted_prices = []
confidences = []
actions = []
for pred in iterative_predictions:
if 'ohlcv_prediction' in pred:
close_price = pred['ohlcv_prediction']['close']
predicted_prices.append(close_price)
confidence = pred.get('action_confidence', 0.5)
confidences.append(confidence)
action = pred.get('action', 2) # Default to HOLD
actions.append(action)
if not predicted_prices:
return None
# Calculate overall price movement
start_price = predicted_prices[0]
end_price = predicted_prices[-1]
total_change = end_price - start_price
total_change_pct = (total_change / start_price) * 100 if start_price != 0 else 0
# Calculate volatility and trend strength
price_volatility = torch.std(torch.tensor(predicted_prices)).item()
avg_confidence = sum(confidences) / len(confidences)
# Determine overall action based on price movement and confidence
if total_change_pct > 0.5: # Overall bullish movement
action = 0 # BUY
action_name = 'BUY'
confidence_multiplier = 1.2
elif total_change_pct < -0.5: # Overall bearish movement
action = 1 # SELL
action_name = 'SELL'
confidence_multiplier = 1.2
else: # Sideways movement
# Use majority vote from individual predictions
buy_count = sum(1 for a in actions if a == 0)
sell_count = sum(1 for a in actions if a == 1)
if buy_count > sell_count:
action = 0
action_name = 'BUY'
confidence_multiplier = 0.8 # Reduce confidence for mixed signals
elif sell_count > buy_count:
action = 1
action_name = 'SELL'
confidence_multiplier = 0.8
else:
action = 2 # HOLD
action_name = 'HOLD'
confidence_multiplier = 0.5
# Calculate final confidence
final_confidence = avg_confidence * confidence_multiplier
# Adjust for market conditions
market_multiplier = market_conditions.get('confidence_multiplier', 1.0)
final_confidence *= market_multiplier
# Cap confidence at reasonable levels
final_confidence = min(0.95, max(0.1, final_confidence))
# Adjust for volatility
if price_volatility > 0.02: # High volatility in predictions
final_confidence *= 0.9
return {
'action': action,
'action_name': action_name,
'confidence': final_confidence,
'horizon_minutes': config['max_hold_time'] // 60,
'total_price_change_pct': total_change_pct,
'price_volatility': price_volatility,
'avg_prediction_confidence': avg_confidence,
'num_predictions': len(iterative_predictions),
'risk_multiplier': config['risk_multiplier'],
'market_conditions': market_conditions,
'prediction_series': {
'prices': predicted_prices,
'confidences': confidences,
'actions': actions
}
}
except Exception as e:
logger.error(f"Error analyzing horizon prediction: {e}")
return None