Skip to main content

Understanding Prediction Output

Standard Prediction Format

All CryptoView Pro models return predictions in a standardized DataFrame format:
predictions = predictor.predict_future(df, periods=24)
print(predictions.head())
Output:
                     predicted_price  lower_bound  upper_bound
2026-03-08 01:00:00        42150.23     41580.12     42720.34
2026-03-08 02:00:00        42189.45     41619.34     42759.56
2026-03-08 03:00:00        42225.78     41655.67     42795.89

Key Columns

  • predicted_price: The model’s best estimate
  • lower_bound: Lower confidence interval (typically 2.5th percentile)
  • upper_bound: Upper confidence interval (typically 97.5th percentile)
  • trend (Prophet only): Underlying trend component

Interpreting Confidence Intervals

What Confidence Intervals Mean

Confidence intervals quantify prediction uncertainty. A 95% confidence interval means:
  • There’s a 95% probability the actual price will fall within the range
  • Wider intervals = higher uncertainty
  • Narrower intervals = higher confidence

Analyzing Interval Width

# Calculate interval width
predictions['interval_width'] = predictions['upper_bound'] - predictions['lower_bound']
predictions['uncertainty_pct'] = (
    predictions['interval_width'] / predictions['predicted_price'] * 100
)

print(f"Average uncertainty: {predictions['uncertainty_pct'].mean():.2f}%")

# Visualize uncertainty over time
import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Scatter(
    x=predictions.index,
    y=predictions['uncertainty_pct'],
    mode='lines',
    name='Uncertainty %'
))
fig.update_layout(title='Prediction Uncertainty Over Time')
fig.show()

Interpretation Guidelines

Uncertainty %InterpretationAction
< 2%Very high confidenceSafe to act on predictions
2-5%Good confidenceNormal for crypto predictions
5-10%Moderate uncertaintyConsider additional analysis
> 10%High uncertaintyUse caution, market volatility high

Model Performance Metrics

XGBoost Metrics

When training XGBoost, you receive comprehensive metrics:
metrics = predictor.train(df, train_size=0.8)
print(metrics)
Output:
{
    'train_mae': 125.34,        # Mean Absolute Error (training)
    'test_mae': 187.56,         # Mean Absolute Error (test)
    'train_rmse': 165.23,       # Root Mean Square Error (training)
    'test_rmse': 243.78,        # Root Mean Square Error (test)
    'train_mape': 0.31,         # Mean Absolute Percentage Error (training)
    'test_mape': 0.45,          # Mean Absolute Percentage Error (test)
    'train_direction_accuracy': 68.5,  # % correct price direction (train)
    'test_direction_accuracy': 61.2    # % correct price direction (test)
}

Understanding Key Metrics

Mean Absolute Error (MAE)

# MAE tells you the average prediction error in dollars
test_mae = metrics['test_mae']
current_price = df['close'].iloc[-1]

error_pct = (test_mae / current_price) * 100
print(f"Average error: ${test_mae:.2f} ({error_pct:.2f}% of price)")

if error_pct < 1:
    print("✅ Excellent accuracy")
elif error_pct < 2:
    print("✅ Good accuracy")
elif error_pct < 5:
    print("⚠️ Acceptable accuracy")
else:
    print("❌ Poor accuracy - retrain or use more data")

Direction Accuracy

# Direction accuracy is often more important than exact price
direction_acc = metrics['test_direction_accuracy']

print(f"Direction accuracy: {direction_acc:.1f}%")

if direction_acc > 60:
    print("✅ Model predicts price direction well")
elif direction_acc > 55:
    print("⚠️ Better than random, but marginal")
else:
    print("❌ Not better than random - retrain")

MAPE (Mean Absolute Percentage Error)

# MAPE is scale-independent, good for comparing models
mape = metrics['test_mape']

print(f"MAPE: {mape:.2f}%")

if mape < 1:
    print("✅ Exceptional accuracy")
elif mape < 3:
    print("✅ Very good accuracy")
elif mape < 5:
    print("✅ Good accuracy")
else:
    print("⚠️ Moderate accuracy")

Prophet Metrics

metrics = predictor.train(df)
print(metrics)
Output:
{
    'mae': 215.67,
    'rmse': 289.45,
    'mape': 0.52,
    'direction_accuracy': 58.3,
    'training_points': 1856
}

Comparative Analysis

Comparing Multiple Models

When using the Hybrid model, compare predictions:
from models.hybrid_model import HybridCryptoPredictor

predictor = HybridCryptoPredictor()
predictor.train(df)

predictions_dict = predictor.predict_future(df, periods=48)

# Compare XGBoost vs Prophet
if 'xgboost' in predictions_dict and 'prophet' in predictions_dict:
    xgb_pred = predictions_dict['xgboost']['predicted_price'].iloc[-1]
    prophet_pred = predictions_dict['prophet']['predicted_price'].iloc[-1]
    hybrid_pred = predictions_dict['hybrid']['predicted_price'].iloc[-1]
    
    print(f"XGBoost: ${xgb_pred:.2f}")
    print(f"Prophet:  ${prophet_pred:.2f}")
    print(f"Hybrid:   ${hybrid_pred:.2f}")
    
    # Check model agreement
    diff = abs(xgb_pred - prophet_pred)
    diff_pct = (diff / xgb_pred) * 100
    
    if diff_pct < 2:
        print("✅ Models agree - high confidence")
    elif diff_pct < 5:
        print("⚠️ Models differ slightly - moderate confidence")
    else:
        print("❌ Models disagree - high uncertainty")

Visualizing Model Comparison

import plotly.graph_objects as go

fig = go.Figure()

# Historical
fig.add_trace(go.Scatter(
    x=df.index[-100:],
    y=df['close'].tail(100),
    mode='lines',
    name='Historical',
    line=dict(color='cyan')
))

# XGBoost
fig.add_trace(go.Scatter(
    x=predictions_dict['xgboost'].index,
    y=predictions_dict['xgboost']['predicted_price'],
    mode='lines',
    name='XGBoost',
    line=dict(color='lime', dash='dash')
))

# Prophet
fig.add_trace(go.Scatter(
    x=predictions_dict['prophet'].index,
    y=predictions_dict['prophet']['predicted_price'],
    mode='lines',
    name='Prophet',
    line=dict(color='gold', dash='dash')
))

# Hybrid
fig.add_trace(go.Scatter(
    x=predictions_dict['hybrid'].index,
    y=predictions_dict['hybrid']['predicted_price'],
    mode='lines',
    name='Hybrid',
    line=dict(color='magenta', width=3)
))

fig.update_layout(
    title='Model Comparison',
    template='plotly_dark',
    height=600
)
fig.show()

Backtesting Analysis

Running Backtest

from models.xgboost_model import backtest_model

results = backtest_model(df, predictor, train_size=0.8)

print("Backtest Results:")
print(f"Test MAE: ${results['metrics']['test_mae']:.2f}")
print(f"Test RMSE: ${results['metrics']['test_rmse']:.2f}")
print(f"Direction Accuracy: {results['metrics']['test_direction_accuracy']:.1f}%")

Visualizing Backtest Results

import numpy as np

actual = results['test_actual']
predicted = results['test_predicted']

# Plot actual vs predicted
fig = go.Figure()

fig.add_trace(go.Scatter(
    y=actual,
    mode='lines',
    name='Actual',
    line=dict(color='cyan')
))

fig.add_trace(go.Scatter(
    y=predicted,
    mode='lines',
    name='Predicted',
    line=dict(color='lime', dash='dash')
))

# Add error band
error = np.abs(actual - predicted)
fig.add_trace(go.Scatter(
    y=error,
    mode='lines',
    name='Error',
    line=dict(color='red'),
    fill='tozeroy'
))

fig.update_layout(
    title='Backtest: Actual vs Predicted',
    template='plotly_dark'
)
fig.show()

Error Distribution Analysis

errors = actual - predicted
error_pct = (errors / actual) * 100

print(f"Error Statistics:")
print(f"  Mean Error: ${errors.mean():.2f}")
print(f"  Std Dev: ${errors.std():.2f}")
print(f"  Max Error: ${errors.max():.2f}")
print(f"  Min Error: ${errors.min():.2f}")

# Plot error distribution
import plotly.express as px
fig = px.histogram(error_pct, nbins=50, title='Prediction Error Distribution (%)')
fig.show()

Feature Importance Analysis

Understanding What Drives Predictions

# Get feature importance from XGBoost
if hasattr(predictor, 'get_feature_importance'):
    importance_df = predictor.get_feature_importance()
    
    print("Top 10 Most Important Features:")
    print(importance_df.head(10))
    
    # Visualize
    top_features = importance_df.head(15)
    
    fig = go.Figure(go.Bar(
        x=top_features['importance'],
        y=top_features['feature'],
        orientation='h'
    ))
    
    fig.update_layout(
        title='Feature Importance',
        xaxis_title='Importance Score',
        yaxis_title='Feature',
        template='plotly_dark',
        height=500
    )
    fig.show()
Example output (see app.py:301-318):
           feature  importance
0       close_lag_1      0.1845
1    return_24       0.1523
2    volatility_7    0.1012
3    ema_12          0.0891
4    rsi_normalized  0.0765

Trend Analysis

def analyze_trend(predictions, current_price):
    """
    Analyze the predicted trend
    """
    first_pred = predictions['predicted_price'].iloc[0]
    last_pred = predictions['predicted_price'].iloc[-1]
    
    change = last_pred - current_price
    change_pct = (change / current_price) * 100
    
    # Calculate trend strength
    price_changes = predictions['predicted_price'].pct_change().dropna()
    trend_consistency = (price_changes > 0).sum() / len(price_changes)
    
    print(f"Trend Analysis:")
    print(f"  Current: ${current_price:.2f}")
    print(f"  Predicted: ${last_pred:.2f}")
    print(f"  Change: {change_pct:+.2f}%")
    print(f"  Trend Consistency: {trend_consistency*100:.1f}%")
    
    if change_pct > 2 and trend_consistency > 0.7:
        print("  ✅ Strong Bullish Trend")
    elif change_pct > 0:
        print("  📈 Weak Bullish Trend")
    elif change_pct < -2 and trend_consistency < 0.3:
        print("  ❌ Strong Bearish Trend")
    else:
        print("  📉 Weak Bearish Trend")
    
    return change_pct, trend_consistency

# Usage
current_price = df['close'].iloc[-1]
change_pct, consistency = analyze_trend(predictions, current_price)

Risk Assessment

Calculating Risk Metrics

def assess_prediction_risk(predictions, df):
    """
    Assess risk of predictions
    """
    # 1. Volatility-based risk
    historical_vol = df['close'].pct_change().tail(168).std()
    predicted_vol = predictions['predicted_price'].pct_change().std()
    vol_ratio = predicted_vol / historical_vol
    
    # 2. Interval width risk
    avg_interval_pct = (
        (predictions['upper_bound'] - predictions['lower_bound']) / 
        predictions['predicted_price'] * 100
    ).mean()
    
    # 3. Trend reversal risk
    recent_trend = df['close'].iloc[-24:].pct_change().mean()
    predicted_trend = predictions['predicted_price'].pct_change().mean()
    trend_reversal = (recent_trend * predicted_trend) < 0
    
    print("Risk Assessment:")
    print(f"  Volatility Ratio: {vol_ratio:.2f}")
    print(f"  Avg Interval Width: {avg_interval_pct:.2f}%")
    print(f"  Trend Reversal: {'Yes' if trend_reversal else 'No'}")
    
    # Risk score (0-10)
    risk_score = 0
    if vol_ratio > 1.5: risk_score += 3
    if avg_interval_pct > 5: risk_score += 3
    if trend_reversal: risk_score += 4
    
    if risk_score < 3:
        risk_level = "Low Risk 🟢"
    elif risk_score < 6:
        risk_level = "Moderate Risk 🟡"
    else:
        risk_level = "High Risk 🔴"
    
    print(f"  Overall Risk: {risk_level} ({risk_score}/10)")
    
    return risk_score

# Usage
risk_score = assess_prediction_risk(predictions, df)

Best Practices

1. Always Validate Against Technical Indicators

from utils.indicators import TechnicalIndicators

signals = TechnicalIndicators.get_signals(df)
predicted_change = predictions['predicted_price'].iloc[-1] - df['close'].iloc[-1]

if signals['overall'] == 'sell' and predicted_change > 0:
    print("⚠️ WARNING: Prediction bullish but indicators bearish")
elif signals['overall'] == 'buy' and predicted_change < 0:
    print("⚠️ WARNING: Prediction bearish but indicators bullish")
else:
    print("✅ Prediction aligns with technical indicators")

2. Monitor Prediction Stability

# Check if predictions are stable across multiple runs
predictions_1 = predictor.predict_future(df, periods=24)
predictions_2 = predictor.predict_future(df, periods=24)

diff = abs(predictions_1['predicted_price'] - predictions_2['predicted_price']).mean()
if diff / df['close'].iloc[-1] > 0.01:  # More than 1% difference
    print("⚠️ WARNING: Predictions unstable - model may need retraining")

3. Document Your Analysis

def generate_prediction_report(predictions, metrics, df):
    """
    Generate comprehensive prediction report
    """
    current_price = df['close'].iloc[-1]
    predicted_price = predictions['predicted_price'].iloc[-1]
    change_pct = ((predicted_price - current_price) / current_price) * 100
    
    report = f"""
    PREDICTION REPORT
    =================
    
    Current Price: ${current_price:.2f}
    Predicted Price: ${predicted_price:.2f}
    Expected Change: {change_pct:+.2f}%
    
    Model Performance:
      - Test MAE: ${metrics['test_mae']:.2f}
      - Direction Accuracy: {metrics['test_direction_accuracy']:.1f}%
      - MAPE: {metrics['test_mape']:.2f}%
    
    Confidence:
      - Average Interval Width: {((predictions['upper_bound'] - predictions['lower_bound']) / predictions['predicted_price'] * 100).mean():.2f}%
    """
    
    return report

# Usage
report = generate_prediction_report(predictions, metrics, df)
print(report)

Next Steps

Build docs developers (and LLMs) love