Price Forecasting
ML-powered day-ahead price predictions
Overview
Our ML-powered price forecasting model predicts UK electricity prices for the next day, providing traders, energy managers, and developers with actionable insights for decision-making. The model uses XGBoost trained on historical prices, gas costs, and carbon intensity data.
Key Features
Day-Ahead Forecast
Predictions for all 48 settlement periods of the next day, updated regularly.
Gas Price Correlation
Model incorporates SAP gas prices - the primary driver of UK electricity costs.
Carbon Intensity
Factors in grid carbon intensity as a proxy for generation mix and demand patterns.
Summary Statistics
Average, min, max prices plus model accuracy metrics for risk assessment.
Model Accuracy & Limitations
Current model performance: MAPE ~24% (Mean Absolute Percentage Error). This means predictions are typically within ±24% of actual prices.
- • Best for relative comparisons (peak vs off-peak timing)
- • Less reliable during extreme market events (price spikes)
- • Model trained on prices £20-200/MWh; extreme prices outside this range less accurate
- • Always use alongside other market intelligence for critical decisions
API Endpoint
Fetch the day-ahead forecast with a simple GET request.
# Get day-ahead price forecast
curl -X GET "https://api.energyoracle.io/uk/prices/forecast/day-ahead" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
# Response
{
"target_date": "2025-01-12",
"generated_at": "2025-01-11T16:30:00Z",
"forecasts": [
{ "settlement_period": 1, "predicted_price": 72.45, "confidence": 0.7, "unit": "GBP/MWh" },
{ "settlement_period": 2, "predicted_price": 71.20, "confidence": 0.7, "unit": "GBP/MWh" },
// ... 48 settlement periods
],
"summary": {
"average_price": 78.50,
"min_price": 65.30,
"max_price": 105.20,
"settlement_periods": 48,
"unit": "GBP/MWh"
},
"inputs": {
"gas_sap_price": 2.85,
"gas_7day_avg": 2.90,
"carbon_intensity": 185
},
"model": {
"type": "XGBoost",
"trained_at": "2025-01-10T14:30:00Z",
"metrics": { "cv_mape_mean": 24.3, "cv_mae_mean": 12.50 }
}
}JavaScript/TypeScript
Find optimal consumption windows using the forecast data.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Get day-ahead forecast
const forecast = await oracle.uk.prices.forecast.dayAhead();
console.log(`Tomorrow's average: £${forecast.summary.average_price}/MWh`);
console.log(`Price range: £${forecast.summary.min_price} - £${forecast.summary.max_price}`);
// Find optimal time windows for high consumption
const cheapestPeriods = forecast.forecasts
.sort((a, b) => a.predicted_price - b.predicted_price)
.slice(0, 6); // Cheapest 3 hours
console.log('Best time to consume:');
cheapestPeriods.forEach(p => {
const hour = Math.floor((p.settlement_period - 1) / 2);
const min = ((p.settlement_period - 1) % 2) * 30;
console.log(` ${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')} - £${p.predicted_price}/MWh`);
});Python with Pandas
Analyze peak and off-peak pricing with pandas for load shifting decisions.
from energyoracle import EnergyOracle
import pandas as pd
oracle = EnergyOracle(api_key="your-key")
# Get forecast for tomorrow
forecast = oracle.uk.prices.forecast.day_ahead()
# Convert to DataFrame for analysis
df = pd.DataFrame(forecast.forecasts)
# Add time column
df['hour'] = (df['settlement_period'] - 1) // 2
df['minute'] = ((df['settlement_period'] - 1) % 2) * 30
df['time'] = df.apply(lambda x: f"{x['hour']:02d}:{x['minute']:02d}", axis=1)
# Peak period analysis (7am-10am and 4:30pm-9pm)
morning_peak = df[(df['settlement_period'] >= 14) & (df['settlement_period'] <= 20)]
evening_peak = df[(df['settlement_period'] >= 33) & (df['settlement_period'] <= 42)]
off_peak = df[~df.index.isin(morning_peak.index) & ~df.index.isin(evening_peak.index)]
print(f"Morning Peak Avg: £{morning_peak['predicted_price'].mean():.2f}/MWh")
print(f"Evening Peak Avg: £{evening_peak['predicted_price'].mean():.2f}/MWh")
print(f"Off-Peak Avg: £{off_peak['predicted_price'].mean():.2f}/MWh")
# Calculate potential savings by shifting load
peak_avg = pd.concat([morning_peak, evening_peak])['predicted_price'].mean()
savings_per_mwh = peak_avg - off_peak['predicted_price'].mean()
print(f"Potential savings by load shifting: £{savings_per_mwh:.2f}/MWh")Use Cases
1. Trading Signal Generation
Compare forecast vs current prices to identify trading opportunities.
// Trading decision support based on forecast
async function evaluateTradingOpportunity(oracle) {
const forecast = await oracle.uk.prices.forecast.dayAhead();
const currentPrice = await oracle.uk.prices.latest();
const signals = [];
// Compare forecast average vs current
const priceDelta = forecast.summary.average_price - currentPrice.price;
if (Math.abs(priceDelta) > 5) {
signals.push({
type: priceDelta > 0 ? 'BUY_NOW' : 'WAIT',
reason: `Tomorrow expected ${priceDelta > 0 ? 'higher' : 'lower'} by £${Math.abs(priceDelta).toFixed(2)}/MWh`,
confidence: forecast.model.metrics.cv_mape_mean < 15 ? 'HIGH' : 'MEDIUM'
});
}
// Identify extreme price periods
const highPricePeriods = forecast.forecasts
.filter(f => f.predicted_price > forecast.summary.average_price * 1.3);
if (highPricePeriods.length > 0) {
signals.push({
type: 'SELL_OPPORTUNITY',
reason: `${highPricePeriods.length} periods expected 30%+ above average`,
periods: highPricePeriods.map(p => p.settlement_period)
});
}
return signals;
}2. Risk Management
Calculate uncertainty bands based on model accuracy for hedging decisions.
// Risk management with forecast confidence
function assessForecastRisk(forecast) {
const mape = forecast.model.metrics.cv_mape_mean;
// Calculate price uncertainty band
const avgPrice = forecast.summary.average_price;
const uncertainty = avgPrice * (mape / 100);
return {
expectedPrice: avgPrice,
lowerBound: avgPrice - uncertainty,
upperBound: avgPrice + uncertainty,
confidenceLevel: mape < 15 ? 'HIGH' : mape < 25 ? 'MEDIUM' : 'LOW',
// Risk metrics
maxExposure: forecast.summary.max_price * 1.1, // 10% buffer on max
valueAtRisk: uncertainty * 2, // 2-sigma VaR approximation
recommendation: mape > 25
? 'Consider hedging - model uncertainty is elevated'
: 'Normal market conditions'
};
}
// Example usage
const risk = assessForecastRisk(forecast);
console.log(`Expected: £${risk.expectedPrice.toFixed(2)} (±£${risk.valueAtRisk.toFixed(2)})`);
console.log(`Confidence: ${risk.confidenceLevel}`);
console.log(`Recommendation: ${risk.recommendation}`);3. Load Scheduling (EV Charging)
Optimize flexible loads like EV charging to minimize costs.
// Optimal EV charging schedule based on forecast
async function createChargingSchedule(oracle, requiredKwh, maxPowerKw) {
const forecast = await oracle.uk.prices.forecast.dayAhead();
// Calculate how many periods needed
const periodsNeeded = Math.ceil(requiredKwh / (maxPowerKw * 0.5)); // 30-min periods
// Sort by price (cheapest first)
const ranked = [...forecast.forecasts]
.sort((a, b) => a.predicted_price - b.predicted_price);
// Select cheapest periods
const schedule = ranked.slice(0, periodsNeeded).map(p => {
const hour = Math.floor((p.settlement_period - 1) / 2);
const min = ((p.settlement_period - 1) % 2) * 30;
return {
start: `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`,
end: `${min === 0 ? hour.toString().padStart(2, '0') : (hour + 1).toString().padStart(2, '0')}:${min === 0 ? '30' : '00'}`,
powerKw: maxPowerKw,
expectedCost: p.predicted_price * (maxPowerKw * 0.5) / 1000,
period: p.settlement_period
};
}).sort((a, b) => a.period - b.period);
const totalCost = schedule.reduce((sum, s) => sum + s.expectedCost, 0);
const avgPriceAll = forecast.summary.average_price;
const savings = (avgPriceAll * requiredKwh / 1000) - totalCost;
return {
schedule,
totalCost,
savings,
savingsPercent: (savings / (avgPriceAll * requiredKwh / 1000)) * 100
};
}
// Example: Charge 50 kWh at max 7 kW
const plan = await createChargingSchedule(oracle, 50, 7);
console.log(`Charging schedule saves £${plan.savings.toFixed(2)} (${plan.savingsPercent.toFixed(1)}%)`);Model Details
| Algorithm | XGBoost Regressor |
| Training Data | ~35,000 samples (prices £20-200/MWh) |
| Validation | 5-fold Time Series Cross-Validation |
| Key Features | Gas SAP price, Carbon intensity, Time patterns, Price lags |
| MAPE | ~24% (on validation set) |
| MAE | ~£12.50/MWh |
The model is retrained regularly as new data becomes available. Check the model.trained_at field in the response for the last training timestamp.
Best Practices
1. Use for Timing, Not Absolute Values
The forecast is most reliable for comparing relative prices across periods. Use it to identify when prices will be higher or lower, not to predict exact values.
2. Monitor Model Metrics
Check model.metrics.cv_mape_mean in the response. Higher MAPE indicates increased uncertainty - consider wider hedging margins.
3. Combine with Market Context
The forecast uses historical patterns. For unusual events (outages, weather extremes, holidays), supplement with your own market knowledge and other data sources.
4. Cache Appropriately
Forecasts are updated periodically (check generated_at). Cache responses for 15-30 minutes to reduce API calls while maintaining freshness.