Back to Tutorials

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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

tsx
// 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.

Next Steps