Real-Time Prices
Trading signals and live market data
Overview
Build trading applications with live UK electricity market data. Receive real-time price updates, set up alerts, and access forecasts for informed trading decisions.
Data Sources
System Prices (SSP/SBP)
Elexon BMRS system prices updated every 30 minutes. The key reference for imbalance settlement.
Day-Ahead Prices
N2EX auction results published at 11:00 for next day. 24 hourly prices.
Intraday Market
Continuous trading up to gate closure. Prices available from EPEX Spot.
Imbalance Data
NIV, bid/offer acceptances, and system imbalance direction.
Polling for Updates
The simplest approach: periodically fetch the latest prices.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Simple polling approach
async function pollPrices() {
while (true) {
const price = await oracle.uk.prices.latest();
console.log(`Price: £${price.value}/MWh at ${price.timestamp}`);
// Update your UI or trigger logic
updateDashboard(price);
// Elexon updates every 30 minutes
await new Promise(resolve => setTimeout(resolve, 60 * 1000)); // Poll every minute
}
}WebSocket Streaming
For real-time applications, use WebSocket subscriptions to receive instant updates.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Subscribe to real-time price updates
const unsubscribe = oracle.uk.prices.subscribe({
onPrice: (price) => {
console.log(`New price: £${price.value}/MWh`);
console.log(`Settlement period: ${price.settlementPeriod}`);
updateDashboard(price);
},
onError: (error) => {
console.error('WebSocket error:', error);
},
onReconnect: () => {
console.log('Reconnected to price stream');
}
});
// Unsubscribe when done
// unsubscribe();
// Subscribe to multiple streams
oracle.uk.subscribe({
prices: (price) => console.log('Price:', price.value),
carbon: (carbon) => console.log('Carbon:', carbon.intensity),
fuelMix: (mix) => console.log('Wind:', mix.wind, '%'),
});Price Alerts
Set up automated alerts for price spikes, drops, negative prices, and volatility.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Price spike alert
oracle.uk.prices.onSpike({
threshold: 100, // Alert when price > £100/MWh
callback: async (price) => {
console.log(`⚠️ Price spike: £${price.value}/MWh`);
// Send notification
await sendSlackAlert({
channel: '#trading',
message: `Price spike: £${price.value}/MWh at ${price.timestamp}`
});
}
});
// Price drop alert
oracle.uk.prices.onDrop({
threshold: 20, // Alert when price < £20/MWh
callback: (price) => {
console.log(`💰 Low price: £${price.value}/MWh - good time to consume`);
}
});
// Negative price alert
oracle.uk.prices.onNegative({
callback: (price) => {
console.log(`🔋 Negative price: £${price.value}/MWh - get paid to consume!`);
triggerBatteryCharging();
}
});
// Volatility alert
oracle.uk.prices.onVolatility({
lookbackPeriods: 6, // Last 3 hours
stdDevThreshold: 2, // Alert when > 2 std devs from mean
callback: (stats) => {
console.log(`📊 High volatility: σ=${stats.stdDev.toFixed(2)}`);
}
});Simple Trading Bot
Example of a basic signal generator using moving average crossover.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Simple trading signal generator
class TradingBot {
private oracle: EnergyOracle;
private movingAvg: number[] = [];
constructor(oracle: EnergyOracle) {
this.oracle = oracle;
}
async start() {
this.oracle.uk.prices.subscribe({
onPrice: (price) => this.processPrice(price)
});
}
processPrice(price: SystemPrice) {
// Update moving average (last 12 periods = 6 hours)
this.movingAvg.push(price.value);
if (this.movingAvg.length > 12) {
this.movingAvg.shift();
}
const avg = this.movingAvg.reduce((a, b) => a + b, 0) / this.movingAvg.length;
// Generate signals
if (price.value > avg * 1.2) {
this.signal('SELL', price, avg);
} else if (price.value < avg * 0.8) {
this.signal('BUY', price, avg);
}
}
signal(action: string, price: SystemPrice, avg: number) {
console.log(`Signal: ${action} at £${price.value}/MWh (avg: £${avg.toFixed(2)})`);
}
}
const bot = new TradingBot(oracle);
bot.start();Disclaimer: This is a simplified example for educational purposes. Real trading systems require proper risk management, position sizing, and regulatory compliance.
Forecasts
Access price, demand, and generation forecasts to plan ahead.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Get price forecast
const forecast = await oracle.uk.prices.forecast({
horizon: '24h', // Next 24 hours
});
forecast.forEach(period => {
console.log(`${period.datetime}: £${period.forecast}/MWh (±${period.confidence})`);
});
// Get demand forecast
const demand = await oracle.uk.demand.forecast();
console.log(`Peak demand today: ${demand.peak.value} MW at ${demand.peak.time}`);
// Get wind generation forecast
const wind = await oracle.uk.generation.forecast('wind');
wind.forEach(period => {
console.log(`${period.datetime}: ${period.forecast} MW`);
});
// Combined view: low prices often correlate with high wind
const combined = await Promise.all([
oracle.uk.prices.forecast({ horizon: '24h' }),
oracle.uk.generation.forecast('wind', { horizon: '24h' })
]);
const [priceForecast, windForecast] = combined;
// Find best times to consume (low price + high renewable)
const bestTimes = priceForecast
.map((p, i) => ({
...p,
wind: windForecast[i]?.forecast || 0,
score: p.forecast / (windForecast[i]?.forecast || 1) // Lower is better
}))
.sort((a, b) => a.score - b.score)
.slice(0, 5);
console.log('Best consumption times (low price, high wind):');
bestTimes.forEach(t => {
console.log(` ${t.datetime}: £${t.forecast}/MWh, Wind: ${t.wind}MW`);
});Imbalance Data
Track system imbalance for arbitrage opportunities and risk management.
import { EnergyOracle } from '@energyoracle/sdk';
const oracle = new EnergyOracle({ apiKey: 'your-key' });
// Get current imbalance
const imbalance = await oracle.uk.balancing.current();
console.log(`System imbalance: ${imbalance.volume} MW`);
console.log(`Direction: ${imbalance.direction}`); // 'long' or 'short'
console.log(`NIV: ${imbalance.niv} MWh`);
// Historical imbalance analysis
const history = await oracle.uk.balancing.range('2024-12-01', '2024-12-20');
// Imbalance volatility = trading opportunity
const avgImbalance = history.reduce((sum, h) => sum + Math.abs(h.volume), 0) / history.length;
console.log(`Avg absolute imbalance: ${avgImbalance.toFixed(0)} MW`);
// Subscribe to imbalance updates
oracle.uk.balancing.subscribe({
onImbalance: (data) => {
if (Math.abs(data.volume) > 500) {
console.log(`⚠️ Large imbalance: ${data.volume} MW ${data.direction}`);
}
}
});Building a Dashboard
React component example for a real-time trading dashboard.
// React component for real-time trading dashboard
import { useEffect, useState } from 'react';
import { EnergyOracle } from '@energyoracle/sdk';
function TradingDashboard() {
const [price, setPrice] = useState(null);
const [history, setHistory] = useState([]);
const [alerts, setAlerts] = useState([]);
useEffect(() => {
const oracle = new EnergyOracle({ apiKey: process.env.ENERGY_ORACLE_API_KEY });
// Subscribe to prices
const unsubscribe = oracle.uk.prices.subscribe({
onPrice: (newPrice) => {
setPrice(newPrice);
setHistory(prev => [...prev.slice(-47), newPrice]); // Keep last 24h
}
});
// Set up alerts
oracle.uk.prices.onSpike({
threshold: 100,
callback: (p) => {
setAlerts(prev => [...prev, {
type: 'spike',
message: `Price spike: £${p.value}/MWh`,
timestamp: new Date()
}]);
}
});
return () => unsubscribe();
}, []);
return (
<div>
<div className="price-display">
<h2>Current Price</h2>
<span className={`price ${price?.value > 80 ? 'high' : 'normal'}`}>
£{price?.value}/MWh
</span>
</div>
<div className="chart">
<PriceChart data={history} />
</div>
<div className="alerts">
{alerts.map((alert, i) => (
<Alert key={i} type={alert.type}>{alert.message}</Alert>
))}
</div>
</div>
);
}Rate Limits & Best Practices
WebSocket vs Polling
WebSocket connections are more efficient and don't count against your API rate limit. Use polling only as a fallback.
Rate Limits by Tier
- • Free: 1,000 requests/day, 5-minute delay
- • Developer: 100,000 requests/day, real-time
- • Production: Unlimited, dedicated WebSocket
Caching
Settlement period prices are immutable once published. Cache aggressively for historical data to reduce API calls.