---
title: "Upcoming Earnings"
description: "Upcoming Earnings dataset from EOD Historical Data."
canonical: https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/eod-historical-data/upcoming-earnings
markdown: https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/eod-historical-data/upcoming-earnings.md
language: python
source: https://github.com/QuantConnect/Documentation/tree/master/03%20Writing%20Algorithms/14%20Datasets/12%20EOD%20Historical%20Data/04%20Upcoming%20Earnings
modified: 2026-11-02
index: https://www.quantconnect.com/llms.txt
---

# Upcoming Earnings

[Documentation](https://www.quantconnect.com/docs/v2.md) > [Writing Algorithms](https://www.quantconnect.com/docs/v2/writing-algorithms.md) > Datasets > [EOD Historical Data](https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/eod-historical-data.md) > Upcoming Earnings

> Code examples are shown in Python. Append `?lang=csharp` to this URL for the C# version of this page.

## Introduction

The Upcoming Earnings dataset, provided by EOD Historical Data (EODHD), is a daily universe of US Equities with an earnings report publication in the upcoming 7 days. The data starts in January 1998 and is delivered on a daily frequency.

Compared to [Nasdaq's Earning Reports](https://www.nasdaq.com/market-activity/earnings) as a benchmark, the Upcoming Earnings dataset has captured 96.79% of the scheduled earnings report, with a 97.25% exact-date precision of the captured events, while having a 99.28% precision within +/- 3 days. Note that the Upcoming Earnings dataset also contains unscheduled special earning reports.

For more information about the Upcoming Earnings dataset, including CLI commands and pricing, see the [dataset listing](https://www.quantconnect.com/datasets/eodhd-upcoming-earnings).

## About the Provider

EOD Historical Data (EODHD) is a financial data provider based in France, and founded in April 2015. They focus on providing clean financial data, including stock prices, splits, dividends, fundamentals, macroeconomic indicators, technical indicators, and alternative data sources, through 24/7 API seamlessly. For more information about EODHD, visit [https://eodhd.com/](https://eodhd.com/).

## Getting Started

The following snippet demonstrates how to request data from the Upcoming Earnings dataset:

```python
self.add_universe(EODHDUpcomingEarnings, self.selection_function)
```

## Data Summary

The following table describes the dataset properties:

| Property | Value |
| --- | --- |
| Start Date | January 1998 |
| Data Density | Sparse |
| Resolution | Daily |
| Timezone | New York |

## Requesting Data

This dataset is designed for universe selection. However, you can add Upcoming Earnings data to your algorithm using the `add_data` method.

```python
class UpcomingEarningsDataAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2019, 1, 1)
        self.set_end_date(2020, 6, 1)
        self.set_cash(100000)

        self._symbol = self.add_equity("AAPL", Resolution.DAILY).symbol
        self._dataset_symbol = self.add_data(EODHDUpcomingEarnings, "earnings").symbol
```

## Accessing Data

To get the current Upcoming Earnings data, call the `get(EODHDUpcomingEarnings)` method from the current [`Slice`](https://www.quantconnect.com/docs/v2/writing-algorithms/key-concepts/time-modeling/timeslices.md) and index the result with the security `Symbol`. `Slice` objects deliver unique events to your algorithm as they happen, but the `Slice` may not contain data for your security at every time step. To avoid issues, check if the `Slice` contains the data you want before you index it.

```python
def on_data(self, slice: Slice) -> None:
    upcomings_earnings_for_symbol = slice.get(EODHDUpcomingEarnings).get(self._symbol)
    if upcomings_earnings_for_symbol:
        self.log(f"{self._symbol} will report earnings at {upcomings_earnings_for_symbol.report_date} {upcomings_earnings_for_symbol.report_time} with estimated EPS {upcomings_earnings_for_symbol.estimate}")
```

You can also iterate through all of the dataset objects in the current `Slice`

```python
def on_data(self, slice: Slice) -> None:
    for equity_symbol, upcomings_earnings_data_point in slice.get(EODHDUpcomingEarnings).items():
        self.log(f"{equity_symbol} will report earnings at {upcomings_earnings_data_point.report_date} {upcomings_earnings_data_point.report_time} with estimated EPS {upcomings_earnings_data_point.estimate}")
```

## Historical Data

To get historical Upcoming Earnings for the universe, call the `history` method with the type `EODHDUpcomingEarnings`.

```python
history = self.history(EODHDUpcomingEarnings, timedelta(100), Resolution.DAILY)
```

To get historical Upcoming Earnings data for a known security, call the `history` method with the type `EODHDUpcomingEarning` cast and the security `Symbol`.

```python
history = self.history[EODHDUpcomingEarnings](timedelta(100), Resolution.DAILY).loc[self._symbol]
```

If there is no data in the period you request, the history result is empty. For more information about historical data, see [History Requests](https://www.quantconnect.com/docs/v2/writing-algorithms/historical-data/history-requests.md).

## Universe Selection

To select a dynamic universe of US Equities based on the Upcoming Earnings dataset, call the `add_universe` method with a `EODHDUpcomingEarnings` cast.

```python
def initialize(self) -> None:
    self._universe = self.add_universe(EODHDUpcomingEarnings, self.universe_selection_filter)

def universe_selection_filter(self, earnings: List[EODHDUpcomingEarnings]) -> List[Symbol]:
    return [d.symbol for d in earnings if d.report_date <= self.time + timedelta(3) and d.estimate > 0]
```

For more information about universe settings, see [Settings](https://www.quantconnect.com/docs/v2/writing-algorithms/universes/settings.md).

## Universe History

You can get historical universe data in an algorithm and in the Research Environment.

### Historical Universe Data in Algorithms

To get historical universe data in an algorithm, call the `history` method with the `Universe` object and the lookback period. If there is no data in the period you request, the history result is empty.

```python
# DataFrame example where the columns are the EODHDUpcomingEarnings attributes:
history_df = self.history(self._universe, 30, Resolution.DAILY, flatten=True)

# Series example where the values are lists of EODHDUpcomingEarnings objects:
universe_history = self.history(self._universe, 30, Resolution.DAILY)
for (_, time), earnings in universe_history.items():
    for earning in earnings:
        self.log(f"{earning.symbol} estimate at {earning.end_time}: {earning.estimate}")
```

### Historical Universe Data in Research

To get historical universe data in research, call the `history` method with the `Universe` object, a start date, and an end date. This method returns the filtered universe. If there is no data in the period you request, the history result is empty.

```python
# DataFrame example where the columns are the EODHDUpcomingEarnings attributes:
history = qb.history(universe, qb.time-timedelta(30), qb.time, flatten=True)
```

## Remove Subscriptions

To remove a subscription, call the `remove_security` method.

```python
self.remove_security(self.dataset_symbol)
```

## Example Applications

The Upcoming Earnings dataset provides timely notifications about earnings announcements, allowing traders to make capitalize on potential price movements and manage risks effectively. Examples include the following strategies:

- [Short Straddle](https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/option-strategies/short-straddle.md) to trade on heightened volatility during earnings report.
- Filter universe for the stocks with or without upcoming earnings report to trade or avoid volatility.
- Hold the stocks with upcoming earnings estimated to be positive.

### Classic Algorithm Example

The following example algorithm long a straddle on the stocks with upcoming earnings report within 5 days to trade the volatility. It allows 5 days to build up the volatility and capitalize the positions 1 day after the earnings announcement.

```python
class UpcomingEarningsExampleAlgorithm(QCAlgorithm):
    options_by_symbol = {}

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 1)
        self.set_end_date(2024, 12, 31)
        self.set_cash(100000)

        # Seed the last price as price since we need to use the underlying price for option contract filtering when it join the universe.
        self.settings.seed_initial_prices = True

        # Trade on daily basis based on daily upcoming earnings signals.
        self.universe_settings.resolution = Resolution.DAILY
        # Option trading requires raw price for strike price comparison.
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
        # Universe consists of equities with upcoming earnings events.
        self.add_universe(EODHDUpcomingEarnings, self.selection)

    def selection(self, earnings: List[EODHDUpcomingEarnings]) -> List[Symbol]:
        # We do not want to lock our fund too early, so filter for stocks is in lower volatility but will go up.
        # Assuming 5 days before the earnings report publish is less volatile.
        # We do not want depository due to their low liquidity.
        for i in range(5, 0, -1):
            target_date = self.time + timedelta(i)
            symbols = set(d.symbol for d in earnings if d.report_date == target_date)
            if symbols:
                self.plot('Universe', 'Count', len(symbols))
                return list(symbols)
        return []

    def on_securities_changed(self, changes: SecurityChanges) -> None:

        # Actions only based on the equity universe changes.
        for added in [security for security in changes.added_securities if security.type == SecurityType.EQUITY]:
            # Select the option contracts to construct a straddle to trade the volatility.
            call, put = self.select_option_contracts(added.symbol)
            if not call or not put:
                continue
            self.options_by_symbol[added.symbol] = (call, put)
            # Request the option contract data for trading.
            call = self.add_option_contract(call).symbol
            put = self.add_option_contract(put).symbol
            # Long a straddle by shorting the selected ATM call and put.
            self.combo_market_order([
                    Leg.create(call, 1),
                    Leg.create(put, 1)
                ],
                1
            )

        # Actions only based on the equity universe changes.
        for removed in [security for security in changes.removed_securities if security.type == SecurityType.EQUITY]:
            # Liquidate any assigned position.
            self.liquidate(removed.symbol)
            # Liquidate the option positions and capitalize the volatility 1-day after the earnings announcement.
            contracts = self.options_by_symbol.pop(removed.symbol, None)
            if contracts:
                for contract in contracts:
                    self.remove_option_contract(contract)

    def select_option_contracts(self, underlying: Symbol) -> Tuple[Symbol, Symbol]:
        # Get all tradable option contracts for filtering.
        option_contract_list = self.option_chain(underlying)

        # Expiry at least 30 days later to have a smaller theta to reduce time decay loss.
        # Yet also be ensure liquidity over the volatility fluctuation hence we take the closet expiry after that.
        long_expiries = [x for x in option_contract_list if x.id.date >= self.time + timedelta(30)]
        if len(long_expiries) < 2:
            return None, None
        expiry = min(x.id.date for x in long_expiries)
        filtered_contracts = [x for x in option_contract_list if x.id.date == expiry]

        # Select ATM call and put to form a straddle for trading the volatility.
        strike = sorted(filtered_contracts,
            key=lambda x: abs(x.id.strike_price - self.securities[underlying].price))[0].id.strike_price
        atm_contracts = [x for x in filtered_contracts if x.id.strike_price == strike]
        if len(atm_contracts) < 2:
            return None, None

        atm_call = next(filter(lambda x: x.id.option_right == OptionRight.CALL, atm_contracts))
        atm_put = next(filter(lambda x: x.id.option_right == OptionRight.PUT, atm_contracts))
        return atm_call, atm_put
```

### Framework Algorithm Example

The following example algorithm long a straddle on the stocks with upcoming earnings report within 5 days to trade the volatility implemented in framework algorithm. It allows 5 days to build up the volatility and capitalize the positions 1 day after the earnings announcement.

```python
class UpcomingEarningsExampleAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 1)
        self.set_end_date(2024, 12, 31)
        self.set_cash(1000000)

        # Seed the last price as price since we need to use the underlying price for option contract filtering when it join the universe.
        self.settings.seed_initial_prices = True

        # Trade on daily basis based on daily upcoming earnings signals.
        self.universe_settings.resolution = Resolution.DAILY
        # Option trading requires raw price for strike price comparison.
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
        # Universe consists of equities with upcoming earnings events.
        self.add_universe(EODHDUpcomingEarnings, self.selection)

        # A custom alpha model that emit signal based on upcoming earnings events.
        self.add_alpha(UpcomingEarningsAlphaModel())

        # To trade 1 contract per signal to ensure equal size per leg of the short straddle.
        self.set_portfolio_construction(SingleSharePortfolioConstructionModel())

    def selection(self, earnings: List[EODHDUpcomingEarnings]) -> List[Symbol]:
        # We do not want to lock our fund too early, so filter for stocks is in lower volatility but will go up.
        # Assuming 5 days before the earnings report publish is less volatile.
        # We do not want depository due to their low liquidity.
        for i in range(5, 0, -1):
            target_date = self.time + timedelta(i)
            symbols = set(d.symbol for d in earnings if d.report_date == target_date)
            if symbols:
                self.plot('Universe', 'Count', len(symbols))
                return list(symbols)
        return []

class UpcomingEarningsAlphaModel(AlphaModel):
    options_by_symbol = {}

    def update(self, algorithm: QCAlgorithm, slice: Slice) -> List[Insight]:
        insights = []

        # Iterate the newly added equities to trade their volatility.
        while len(self.options_by_symbol) > 0:
            key = list(self.options_by_symbol.keys())[0]
            # Obtain the selected option contracts to trade and remove the key-value pair to avoid repeat ordering.
            contracts = self.options_by_symbol.pop(key)
            # Long straddle (long the ATM call and put contracts) for 7 days to trade the volatility.
            # Assuming the volatility will go up due to earning announcement and reach highest volatility at 2-3 days after the report.
            insights.extend([
                Insight.price(contracts[0], timedelta(7), InsightDirection.UP),
                Insight.price(contracts[1], timedelta(7), InsightDirection.UP)
            ])

        # Remove any assigned positions of the underlying.
        for symbol, holding in algorithm.portfolio.items():
            if holding.type == SecurityType.EQUITY and holding.invested:
                insights.append(Insight.price(symbol, timedelta(7), InsightDirection.FLAT))

        return insights

    def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None:
        # Actions only based on the equity universe changes.
        for added in [x for x in changes.added_securities if x.type == SecurityType.EQUITY]:
            # Select the option contracts to construct a straddle to trade the volatility.
            call, put = self.select_option_contracts(algorithm, added.symbol, algorithm.time)
            if not call or not put:
                continue
            # Request the option contract data for trading.
            call = algorithm.add_option_contract(call).symbol
            put = algorithm.add_option_contract(put).symbol
            self.options_by_symbol[added.symbol] = (call, put)

    def select_option_contracts(self, algorithm: QCAlgorithm, underlying: Symbol, time: datetime) -> Tuple[Symbol, Symbol]:
        # Get all tradable option contracts for filtering.
        option_contract_list = algorithm.option_chain(underlying)

        # Expiry at least 30 days later to have a smaller theta to reduce time decay loss.
        # Yet also be ensure liquidity over the volatility fluctuation hence we take the closet expiry after that.
        long_expiries = [x for x in option_contract_list if x.id.date >= time + timedelta(30)]
        if len(long_expiries) < 2:
            return None, None
        expiry = min(x.id.date for x in long_expiries)
        filtered_contracts = [x for x in option_contract_list if x.id.date == expiry]

        # Select ATM call and put to form a straddle for trading the volatility.
        strike = sorted(filtered_contracts,
            key=lambda x: abs(x.id.strike_price - algorithm.securities[underlying].price))[0].id.strike_price
        atm_contracts = [x for x in filtered_contracts if x.id.strike_price == strike]
        if len(atm_contracts) < 2:
            return None, None

        atm_call = next(filter(lambda x: x.id.option_right == OptionRight.CALL, atm_contracts))
        atm_put = next(filter(lambda x: x.id.option_right == OptionRight.PUT, atm_contracts))
        return atm_call, atm_put

class SingleSharePortfolioConstructionModel(PortfolioConstructionModel):
    def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]) -> List[PortfolioTarget]:
        if not insights:
            return []
        # Only a single contract to ensure both legs size is equal
        quantity = min([abs(algorithm.calculate_order_quantity(i.symbol, int(i.direction)))
            for i in insights]) / 100
        return [PortfolioTarget(i.symbol, int(i.direction)) for i in insights]
```

## Data Point Attributes

The EODHD Upcoming Earnings dataset provides `EODHDUpcomingEarnings` objects, which have the following attributes:

*Members of `QuantConnect.DataSource.EODHDUpcomingEarnings` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.DataSource.EODHDUpcomingEarnings&language=python*

To filter the EODHD Upcoming Earnings data with their reporting timing, use the `EODHD.ReportTime` enum, which have the following attributes:

*Members of `QuantConnect.DataSource.EODHD.ReportTime` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.DataSource.EODHD.ReportTime&language=python*
