About Upcoming Earnings

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.


About EOD Historical Data

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


About QuantConnect

QuantConnect was founded in 2012 to serve quants everywhere with the best possible algorithmic trading technology. Seeking to disrupt a notoriously closed-source industry, QuantConnect takes a radically open-source approach to algorithmic trading. Through the QuantConnect web platform, more than 50,000 quants are served every month.


Algorithm Example

class UpcomingEarningsExampleAlgorithm(QCAlgorithm):
    options_by_symbol = {}

    def initialize(self) -> None:
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 10, 1)
        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.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))

        # 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.
        return [d.symbol for d in earnings
                if d.report_date <= self.time + timedelta(5)]

    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

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: