book
Checkout our new book! Hands on AI Trading with Python, QuantConnect, and AWS Learn More arrow

US Equity

Corporate Actions

Introduction

US Equity subscriptions provide notifications for splits, dividends, symbol changes, and delistings.

Splits

When a company does a stock split, the number of shares each shareholder owns increases and the price of each share decreases. When a company does a reverse stock split, the number of shares each shareholder owns decreases and the price of each share increases. A company may perform a stock split or a reverse stock split to adjust the price of their stock so that more investors trade it and the liquidity increases.

When a stock split or reverse stock split occurs for an Equity in your algorithm, LEAN sends a Split object to the on_data method. You receive Split objects when a split is in the near future and when it occurs. To know if the split occurs in the near future or now, check the type property.

If you backtest without the RAW data normalization mode, the splits are factored into the price and volume. If you backtest with the RAW data normalization mode or trade live, when a split occurs, LEAN automatically adjusts your positions based on the split_factor. If the post-split quantity isn't a valid lot size, LEAN credits the remaining value to your cashbook in your account currency. If you have indicators in your algorithm, reset and warm-up your indicators with ScaledRaw data when splits occur so that the data in your indicators account for the price adjustments that the splits cause.

To get the Split objects, index the splits object with the security symbol. The splits object may not contain data for your Symbol. To avoid issues, check if the splits object contains data for your security before you index it with the security Symbol.

Select Language:
def on_data(self, slice: Slice) -> None:
    split = slice.splits.get(self._symbol)
    if split:
        pass


def on_splits(self, splits: Splits) -> None:
    split = splits.get(self._symbol)
    if split:
        pass

You can also iterate through the splits dictionary. The keys of the dictionary are the Symbol objects and the values are the Split objects.

Select Language:
def on_data(self, slice: Slice) -> None:
    for symbol, split in slice.splits.items():
        pass

def on_splits(self, splits: Splits) -> None:
    for symbol, split in splits.items():
        pass

To get historical splits, make a history request with the Split type.

Select Language:
splits_df = self.history(Split, symbol, timedelta(4*365))
splits = self.history[Split](symbol, timedelta(4*365))

LEAN stores the data for stock splits in factor files. To view some example factor files, see the LEAN GitHub repository. In backtests, your algorithm receives Split objects at midnight. In live trading, your algorithm receives Split objects when the factor files are ready.

If you hold an Option contract for an underlying Equity when a split occurs, LEAN closes your Option contract position.

If a split event occurs before your order is filled, the unfilled portion of the order is adjusted automatically, where its quantity is multiplied by the split factor and the limit/stop/trigger price (if any) is divided by the split factor.

Split objects have the following properties:

Dividends

A dividend is a payment that a company gives to shareholders to distribute profits. When a dividend payment occurs for an Equity in your algorithm, LEAN sends a Dividend object to the on_data method.

If you backtest with the ADJUSTED or TOTAL_RETURN data normalization mode, the dividends are factored into the price. If you backtest with the other data normalization modes or trade live, when a dividend payment occurs, LEAN automatically adds the payment amount to your cashbook. If you have indicators in your algorithm, reset and warm-up your indicators with ScaledRaw data when dividend payments occur so that the data in your indicators account for the price adjustments that the dividend causes.

To get the Dividend objects, index the dividends object with the security symbol. The dividends object may not contain data for your symbol. To avoid issues, check if the dividends object contains data for your security before you index it with the security symbol.

Select Language:
def on_data(self, slice: Slice) -> None:
    dividend = slice.dividends.get(self._symbol)
    if dividend:
        pass

def on_dividends(self, dividends: Dividends) -> None:
    dividend = dividends.get(self._symbol)
    if dividend:
        pass

You can also iterate through the dividends dictionary. The keys of the dictionary are the Symbol objects and the values are the Dividend objects.

Select Language:
def on_data(self, slice: Slice) -> None:
    for symbol, dividend in slice.dividends.items():
        pass

def on_dividends(self, dividends: Dividends) -> None:
    for symbol, dividend in dividends.items():
        pass

To get historical dividends, make a history request with the Dividend type.

Select Language:
dividends_df = self.history(Dividend, symbol, timedelta(4*365))
dividends = self.history[Split](Dividend, timedelta(4*365))

For a full example, see the DividendAlgorithm in the LEAN GitHub repository.

Dividend objects have the following properties:

Symbol Changes

The benefit of the Symbol class is that it always maps to the same security, regardless of their trading ticker. When a company changes its trading ticker, LEAN sends a SymbolChangedEvent to the on_data method.

To get the SymbolChangedEvent objects, index the symbol_changed_events object with the security symbol. The symbol_changed_events object may not contain data for your symbol. To avoid issues, check if the symbol_changed_events object contains data for your security before you index it with the security symbol.

Select Language:
def on_data(self, slice: Slice) -> None:
    symbol_changed_event = slice.symbol_changed_events.get(self._symbol)
    if symbol_changed_event:
        pass

def on_symbol_changed_events(self, symbol_changed_events: SymbolChangedEvents) -> None:
    symbol_changed_event = symbol_changed_events.get(self._symbol)
    if symbol_changed_event:
        pass

You can also iterate through the symbol_changed_events dictionary. The keys of the dictionary are the Symbol objects and the values are the SymbolChangedEvent objects.

Select Language:
def on_data(self, slice: Slice) -> None:
    for symbol, symbol_changed_event in slice.symbol_changed_events.items():
        pass


def on_symbol_changed_events(self, symbol_changed_events: SymbolChangedEvents) -> None:
    for symbol, symbol_changed_event in symbol_changed_events.items():
        pass

If you have an open order for a security when they change their ticker, LEAN cancels your order. To keep your order, in the on_order_event method, get the quantity and Symbol of the cancelled order and submit a new order.

Select Language:
def on_order_event(self, order_event: OrderEvent) -> None:
    if order_event.status == OrderStatus.CANCELED:
        ticket = self.transactions.get_order_ticket(order_event.order_id)
        if "symbol changed event" in ticket.tag:
            self.transactions.add_order(ticket.submit_request)

LEAN stores the data for ticker changes in map files. To view some example map files, see the LEAN GitHub repository.

SymbolChangedEvent objects have the following properties:

Delistings

When a company is delisting from an exchange, LEAN sends a Delisting object to the on_data method. You receive Delisting objects when a delisting is in the near future and when it occurs. To know if the delisting occurs in the near future or now, check the Type property.

To get the Delisting objects, index the delistings object with the security symbol. The delistings objects may not contain data for your symbol. To avoid issues, check if the delistings object contains data for your security before you index it with the security symbol.

Select Language:
def on_data(self, slice: Slice) -> None:
    delisting = slice.delistings.get(self._symbol)
    if delisting:
        pass

def on_delistings(self, delistings: Delistings) -> None:
    delisting = delistings.get(self._symbol)
    if delisting:
        pass

You can also iterate through the delistings dictionary. The keys of the dictionary are the Symbol objects and the values are the Delisting objects.

Select Language:
def on_data(self, slice: Slice) -> None:
    for symbol, delisting in slice.Delistings.items():
        pass

def on_delistings(self, delistings: Delistings) -> None:
    for symbol, delisting in delistings.items():
        pass

The delist warning occurs on the final trading day of the stock to give you time to gracefully exit out of positions before LEAN automatically liquidates them.

Select Language:
if delisting.type == DelistingType.WARNING:
    # Liquidate with MarketOnOpenOrder on delisting warning
    quantity = self.portfolio[symbol].quantity
    if quantity != 0:
        self.market_on_open_order(symbol, -quantity)

The on_securities_changed event notifies the algorithm when delisted assets are removed from the universe.

To improve iteration speed, LEAN removes delisted securities from the securities primary collection. To access all the securities, iterate the securities.total property.

Select Language:
# Access all the securities.
for security in self.securities.total:
    pass

# Exclude delisted securities.
for security in self.securities.values():
    pass

For a full example, see the DelistingEventsAlgorithm in the LEAN GitHub repository.

Delisting objects have the following properties:

Live Trading Considerations

In backtesting, corporate actions occurs at midnight Eastern Time (ET). In live trading, the live data for corporate actions arrives at 6/7 AM ET, so that's when they occur.

Examples

The following examples demonstrate some common practices for US Equity corporate actions.

Example 1: Resetting Indicators on Split Events

The following algorithm adds raw TSLA data and uses it to update an Exponential Moving Average (EMA) indicator. When a split occurs, it resets and warms up the indicator with scaled raw data to ensure the indicator doesn't reflect price discontinuities.

Select Language:
class USEquityCorporateActionExampleAlgorithm(QCAlgorithm):
        
    def initialize(self) -> None:
        # Add raw US Equity data.
        self._equity = self.add_equity("TSLA", Resolution.DAILY, data_normalization_mode=DataNormalizationMode.RAW)
        # Create an EMA indicator.
        self._equity.ema = self.ema(self._equity.symbol, 50)

    def on_splits(self, splits: Splits) -> None:
        # Wait until the split has occurred.
        split = splits[self._equity.symbol]
        if split.type != SplitType.SPLIT_OCCURRED:
            return            
        # Reset the indicator.
        self._equity.ema.reset()
        # Warm up the indicator with scaled raw data.
        history = self.history[TradeBar](
            self._equity.symbol, self._equity.ema.warm_up_period, 
            data_normalization_mode=DataNormalizationMode.SCALED_RAW
        )
        for bar in history:
            self._equity.ema.update(bar.end_time, bar.close)

Example 2: Reinvesting Dividend Payments

The following algorithm adds raw SPY data. It then holds the SPY and re-invests the dividend payments into additional shares.

Select Language:
class USEquityCorporateActionExampleAlgorithm(QCAlgorithm):
        
    def initialize(self) -> None:
        # Add raw Equity data.
        self._spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW)

    def on_data(self, slice: Slice) -> None:
        # Buy and hold.
        if not self._spy.holdings.invested:
            self.set_holdings(self._spy.symbol, 0.5)

    def on_dividends(self, dividends: Dividends) -> None:        
        # Get the total dividend payment amount.
        total_dividend_amount = dividends[self._spy.symbol].distribution * self._spy.holdings.quantity
        # Calculate the number of shares you can buy with the dividend payment.
        quantity = np.floor(total_dividend_amount / self._spy.price)
        if quantity:
            # Place an order to re-invest the dividends.
            self.market_order(self._spy.symbol, quantity)

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: