Energy Information Administration

US Electricity

Introduction

The US Electricity dataset by the Energy Information Administration (EIA) tracks daily demand and generation on the US power grid, with generation broken down by fuel type. The data covers 81 balancing authorities, starts in January 2019, and is delivered on a daily frequency. This dataset is created by processing the Form EIA-930 data that balancing authorities report to the EIA. Some balancing authorities do not report the fuel mix.

For more information about the US Electricity dataset, including CLI commands and pricing, see the dataset listing.

About the Provider

The U.S. Energy Information Administration (EIA) is the statistical agency of the US Department of Energy, created by Congress in 1977. It collects and publishes energy data that is independent of policy. Policymakers, markets, and the public use its data to understand energy in the United States.

Getting Started

The following snippet demonstrates how to request data from the US Electricity dataset:

self.dataset_symbol = self.add_data(EIAElectricity, EIA.BalancingAuthorities.PJM, Resolution.DAILY).symbol
_datasetSymbol = AddData<EIAElectricity>(EIA.BalancingAuthorities.PJM, Resolution.Daily).Symbol;

Data Summary

The following table describes the dataset properties:

PropertyValue
Start DateJanuary 2019
Asset Coverage81 US Balancing Authorities
Data DensityRegular
ResolutionDaily
TimezoneNew York

Requesting Data

To add US Electricity data to your algorithm, call the AddDataadd_data method. Save a reference to the dataset Symbol so you can access the data later in your algorithm.

class EIAElectricityDataAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 6, 1)
        self.set_end_date(2020, 9, 1)
        self.set_cash(100000)

        self._spy = self.add_equity("SPY", Resolution.DAILY).symbol
        self._pjm = self.add_data(EIAElectricity, EIA.BalancingAuthorities.PJM, Resolution.DAILY).symbol
public class EIAElectricityDataAlgorithm : QCAlgorithm
{
    private Symbol _spy, _pjm;

    public override void Initialize()
    {
        SetStartDate(2020, 6, 1);
        SetEndDate(2020, 9, 1);
        SetCash(100000);

        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
        _pjm = AddData<EIAElectricity>(EIA.BalancingAuthorities.PJM, Resolution.Daily).Symbol;
    }
}

Accessing Data

To get the current US Electricity data, index the current Slice with the dataset Symbol. Slice objects deliver unique events to your algorithm as they happen, but the Slice may not contain data for your dataset at every time step. To avoid issues, check if the Slice contains the data you want before you index it.

def on_data(self, slice: Slice) -> None:
    if slice.contains_key(self._pjm):
        data_point = slice[self._pjm]
        self.log(f"{self._pjm} demand at {slice.time}: {data_point.demand}")
public override void OnData(Slice slice)
{
    if (slice.ContainsKey(_pjm))
    {
        var dataPoint = slice[_pjm];
        Log($"{_pjm} demand at {slice.Time}: {dataPoint.Demand}");
    }
}

To iterate through all of the dataset objects in the current Slice, call the Getget method.

def on_data(self, slice: Slice) -> None:
    for dataset_symbol, data_point in slice.get(EIAElectricity).items():
        self.log(f"{dataset_symbol} at {slice.time}: demand {data_point.demand}, net generation {data_point.net_generation}")
public override void OnData(Slice slice)
{
    foreach (var kvp in slice.Get<EIAElectricity>())
    {
        var datasetSymbol = kvp.Key;
        var dataPoint = kvp.Value;
        Log($"{datasetSymbol} at {slice.Time}: demand {dataPoint.Demand}, net generation {dataPoint.NetGeneration}");
    }
}

Historical Data

To get historical US Electricity data, call the Historyhistory method with the dataset Symbol. If there is no data in the period you request, the history result is empty.

# DataFrame
history_df = self.history(self._pjm, 100, Resolution.DAILY)

# Dataset objects
history_bars = self.history[EIAElectricity](self._pjm, 100, Resolution.DAILY)
var history = History<EIAElectricity>(_pjm, 100, Resolution.Daily);

For more information about historical data, see History Requests.

Remove Subscriptions

To remove your subscription to US Electricity data, call the RemoveSecurityremove_security method.

self.remove_security(self._pjm)
RemoveSecurity(_pjm);

Supported Balancing Authorities

The following table shows the accessor code you need to add each major balancing authority to your algorithm:

ConstantBalancing Authority
EIA.BalancingAuthorities.PJMPJM Interconnection, the largest balancing authority in the country
EIA.BalancingAuthorities.ERCOTElectric Reliability Council of Texas
EIA.BalancingAuthorities.CAISOCalifornia Independent System Operator
EIA.BalancingAuthorities.MISOMidcontinent Independent System Operator
EIA.BalancingAuthorities.NYISONew York Independent System Operator
EIA.BalancingAuthorities.ISONEISO New England
EIA.BalancingAuthorities.SPPSouthwest Power Pool
EIA.BalancingAuthorities.BPABonneville Power Administration

Example Applications

The US Electricity dataset enables you to trade on the daily state of the US power grid. Examples include the following strategies:

  • Trading utility stocks when actual demand runs above the day-ahead forecast
  • Trading natural gas based on how much power comes from gas plants
  • Trading coal and gas producers as their share of generation shifts

Classic Algorithm Example

The following example algorithm buys SPY when PJM demand runs more than 2% above the day-ahead forecast. When demand undershoots the forecast by the same margin, it liquidates the position.

from AlgorithmImports import *

class EIAElectricityDataAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2020, 6, 1)
        self.set_end_date(2020, 9, 1)
        self.set_cash(100000)

        self._spy = self.add_equity("SPY", Resolution.DAILY).symbol
        self._pjm = self.add_data(EIAElectricity, EIA.BalancingAuthorities.PJM, Resolution.DAILY).symbol
        history = self.history(EIAElectricity, self._pjm, 60, Resolution.DAILY)

        self.debug(f"We got {len(history)} items from our history request")

    def on_data(self, slice):
        if not slice.contains_key(self._pjm):
            return

        grid = slice[self._pjm]
        if grid.demand is None or not grid.demand_forecast:
            return

        # Calculate the load surprise as a fraction of the forecast.
        load_surprise = (grid.demand - grid.demand_forecast) / grid.demand_forecast

        # Buy SPY when demand runs more than 2% above the forecast.
        if load_surprise > 0.02:
            self.set_holdings(self._spy, 1)

        # Liquidate when demand runs more than 2% below the forecast.
        elif load_surprise < -0.02:
            self.liquidate(self._spy)
public class EIAElectricityDataAlgorithm : QCAlgorithm
{
    private Symbol _spy, _pjm;

    public override void Initialize()
    {
        SetStartDate(2020, 6, 1);
        SetEndDate(2020, 9, 1);
        SetCash(100000);

        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
        _pjm = AddData<EIAElectricity>(EIA.BalancingAuthorities.PJM, Resolution.Daily).Symbol;
        var history = History<EIAElectricity>(_pjm, 60, Resolution.Daily);

        Debug($"We got {history.Count()} items from our history request");
    }

    public override void OnData(Slice slice)
    {
        if (!slice.ContainsKey(_pjm))
        {
            return;
        }

        var grid = slice[_pjm];
        if (!grid.Demand.HasValue || !grid.DemandForecast.HasValue || grid.DemandForecast == 0)
        {
            return;
        }

        // Calculate the load surprise as a fraction of the forecast.
        var loadSurprise = (grid.Demand.Value - grid.DemandForecast.Value) / grid.DemandForecast.Value;

        // Buy SPY when demand runs more than 2% above the forecast.
        if (loadSurprise > 0.02m)
        {
            SetHoldings(_spy, 1);
        }

        // Liquidate when demand runs more than 2% below the forecast.
        else if (loadSurprise < -0.02m)
        {
            Liquidate(_spy);
        }
    }
}

Framework Algorithm Example

The following example algorithm emits up insights for SPY when PJM demand runs more than 2% above the day-ahead forecast. When demand runs more than 2% below the forecast, it emits down insights.

from AlgorithmImports import *

class EIAElectricityFrameworkAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2020, 6, 1)
        self.set_end_date(2020, 9, 1)
        self.set_cash(100000)

        self.universe_settings.resolution = Resolution.DAILY
        # Select SPY to trade.
        symbols = [Symbol.create("SPY", SecurityType.EQUITY, Market.USA)]
        self.set_universe_selection(ManualUniverseSelectionModel(symbols))
        # Add a custom alpha model that emits insights from PJM grid operations.
        self.add_alpha(EIAElectricityAlphaModel(self))
        # Equally invest to dissipate non-systematic capital concentration risk on individual stock
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())

class EIAElectricityAlphaModel(AlphaModel):
    def __init__(self, algorithm):
        # Add the PJM grid operations data.
        self._pjm = algorithm.add_data(EIAElectricity, EIA.BalancingAuthorities.PJM, Resolution.DAILY).symbol
        self._tradable_symbols = []

        history = algorithm.history(EIAElectricity, self._pjm, 60, Resolution.DAILY)
        algorithm.log(f"We got {len(history)} items from our history request")

    def update(self, algorithm, slice):
        insights = []
        if not slice.contains_key(self._pjm):
            return insights

        grid = slice[self._pjm]
        if grid.demand is None or not grid.demand_forecast:
            return insights

        # Calculate the load surprise as a fraction of the forecast.
        load_surprise = (grid.demand - grid.demand_forecast) / grid.demand_forecast

        # Emit insights in the direction of the load surprise when it exceeds 2%.
        if abs(load_surprise) > 0.02:
            direction = InsightDirection.UP if load_surprise > 0 else InsightDirection.DOWN
            insights = [Insight.price(symbol, timedelta(days=7), direction) for symbol in self._tradable_symbols]

        return insights

    def on_securities_changed(self, algorithm, changes):
        for security in changes.added_securities:
            if security.symbol != self._pjm:
                self._tradable_symbols.append(security.symbol)
        for security in changes.removed_securities:
            if security.symbol in self._tradable_symbols:
                self._tradable_symbols.remove(security.symbol)
public class EIAElectricityFrameworkAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2020, 6, 1);
        SetEndDate(2020, 9, 1);
        SetCash(100000);

        UniverseSettings.Resolution = Resolution.Daily;
        // Select SPY to trade.
        var symbols = new[] { QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA) };
        SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
        // Add a custom alpha model that emits insights from PJM grid operations.
        AddAlpha(new EIAElectricityAlphaModel(this));
        // Equally invest to dissipate non-systematic capital concentration risk on individual stock
        SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
    }
}

public class EIAElectricityAlphaModel : AlphaModel
{
    private readonly Symbol _pjm;
    private readonly List<Symbol> _tradableSymbols = new();

    public EIAElectricityAlphaModel(QCAlgorithm algorithm)
    {
        // Add the PJM grid operations data.
        _pjm = algorithm.AddData<EIAElectricity>(EIA.BalancingAuthorities.PJM, Resolution.Daily).Symbol;

        var history = algorithm.History<EIAElectricity>(_pjm, 60, Resolution.Daily);
        algorithm.Log($"We got {history.Count()} items from our history request");
    }

    public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice slice)
    {
        var insights = new List<Insight>();
        if (!slice.ContainsKey(_pjm))
        {
            return insights;
        }

        var grid = slice.Get<EIAElectricity>(_pjm);
        if (!grid.Demand.HasValue || !grid.DemandForecast.HasValue || grid.DemandForecast == 0)
        {
            return insights;
        }

        // Calculate the load surprise as a fraction of the forecast.
        var loadSurprise = (grid.Demand.Value - grid.DemandForecast.Value) / grid.DemandForecast.Value;

        // Emit insights in the direction of the load surprise when it exceeds 2%.
        if (Math.Abs(loadSurprise) > 0.02m)
        {
            var direction = loadSurprise > 0 ? InsightDirection.Up : InsightDirection.Down;
            foreach (var symbol in _tradableSymbols)
            {
                insights.Add(Insight.Price(symbol, TimeSpan.FromDays(7), direction));
            }
        }

        return insights;
    }

    public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            if (security.Symbol != _pjm)
            {
                _tradableSymbols.Add(security.Symbol);
            }
        }
        foreach (var security in changes.RemovedSecurities)
        {
            _tradableSymbols.Remove(security.Symbol);
        }
    }
}

Data Point Attributes

The US Electricity dataset provides EIAElectricity objects, which have the following attributes:

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: