Back to Community
When a company gets acquired, my portfolio still owns shares of the original company. Is that right?

Hi,

Let's say I've got 100 shares of AWE in 2003. I hold those and on 2004-10-26, AWE gets acquired by cingular. What should happen to the shares of AWE in my portfolio? According to my test it seems like I still have them. I would expect to have zero shares of AWE and get some number of shares of whatever purchased them. Wouldn't that be what happens in the real world? Or perhaps they'd get sold and I'd get cash. Neither of those things seem to happen in the backtest. Is that right?

thanks

Clone Algorithm
8
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""
TEST 08

QUESTION
What happens when I own a stock that gets acquired?

HYPOTHESIS
I would hope it gets sold.

EXPERIMENT
Buy AWE before 2004-10-26.

CONCLUSION
The stock is still owned after it's delisted.

NOTES
You have to turn off automatic data management to get the backtester to run past the day
the stock was delisted.
"""
from pytz import timezone

def initialize(context):

    # AWE
    context.security = sid(21361)
  
def handle_data(context, data):

    record(shares=context.portfolio.positions[sid(21361)].amount, cash=context.portfolio.cash)
    
    # If we don't own any shares, buy 100 shares
    if context.portfolio.positions[context.security].amount < 1:
        order(context.security,+1000)
        log.info("Buying %s" % (context.security))
        return
    
    datetime = get_datetime()
    if (sid(21361).end_date.year == datetime.year 
        and sid(21361).end_date.month == datetime.month
        and sid(21361).end_date.day == datetime.day):
        order_percent(context.security, 0.0)
    
There was a runtime error.
14 responses

You 'll have to read the detail of the corporate action. In some case shares of the acquired company may continue to trade, in some you may get new shares.....

I see. There is a similar problem with delisted stocks or anytime a stock stops trading. I saw a code snippet that excluded stocks that end before the end date of the backtest but that doesn't work if you are using set_universe. I suppose I can call set_universe in initialize, then exclude all the stocks that end before the backtest ends. I'll only be missing out on new additions. That will probably still work for me.

Brett,

You're right. Usually when a stock gets delisted it remains in your portfolio (E.g. in the case of Gilette) even though you can no longer buy or sell that stock after it's end date. Unfortunately, as Lionel pointed out, you'll have to look up the acquisition details in order to find out what actually happened but if you wanted to go the route of simulating a cash acquisition one thing you could do is to sell all the shares a few days before it's end date:

def initialize(context):  
    #: Look up Gillette which expired in 2005  
    set_symbol_lookup_date('2003-01-01')  
    context.stock = symbol('G')

def handle_data(context, data):  
    #: Only order shares if it's greater than 2 days before expiration date  
    if (context.stock.end_date - get_datetime()).days > 2:  
        log.info("ordering")  
        order_target_percent(context.stock, 1)  
    #: Sell all shares just before expiration date to simulate a cash buyout  
    else:  
        log.info("selling")  
        order_target_percent(context.stock, 0)  

And I wasn't sure if you needed help with removing all the stocks that end before the backtest ends but here's a code snippet just in case you needed it:

from datetime import datetime  
from pytz import timezone

def initialize(context):  
    set_universe(universe.DollarVolumeUniverse(99.0, 100.0))  
    #: Set the timezone here and identify the custom end date you'd want to be using  
    #: datetime(year, month, day)  
    tz = timezone("US/Eastern")  
    context.custom_end_date = tz.localize(datetime(2007, 10, 26, 0, 0, 0))  
def handle_data(context, data):  
    #: Remove all stocks that aren't valid  
    to_remove = []  
    #: Add all stocks to be removed into a list  
    for stock in data:  
        if stock.end_date < context.custom_end_date:  
            log.info("Removing %s with end_date %s" % (stock, stock.end_date))  
            to_remove.append(stock)  
    #: Go through list and delete stocks  
    for i in to_remove:  
        del data[i]  
    """  
        Perform all valid operations here after the stocks have been removed  
    """  
    #: Do order operations here  

Seong

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks Seong. I came up with something similar for keeping stocks that stop trading out of my positions but I do plan to try the "sell before it stops trading" approach also.

Along similar lines, I just run into the case of CEPH which got acquired in October 2011. A generic way to deal with this, that avoids having to deal individually would be to sell the disappeared stock for the last known price. So money will be conserved and there will be no stale positions. I run into these problem with a larger universe (volumetraded), but the algorithm I attach demonstrates the problem. Can anyone modify this so that when it finds that there are open orders for CEPH, it cancels them and furthermore eliminates any accumulated position in favor of cash?

Thank you

Clone Algorithm
0
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
# def initialize(context):
#     pass

#    order(sid(24), 50)

'''
    This algorithm defines a long-only equal weight portfolio and buys it once
    NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

'''
# Import the libraries we will use here
import datetime
import pytz
import pandas as pd

def initialize(context):
    # This initialize function sets any data or variables 
    # that you'll use in your algorithm. 
    # You'll also want to define any parameters or values 
    # you're going to use.

    # In our example, we're looking at 9 sector ETFs.  
    context.secs = symbols('CEPH', 'SPY' ) 

    # Change this variable if you want to rebalance less frequently
    context.Rebalance_Days = 1

    # These other variables are used in the algorithm for leverage, trade time, etc.
    context.rebalance_date = None
  #  context.weights = 0.99/len(context.secs)
    context.rebalance_hour_start = 9
    context.rebalance_hour_end = 20

    # These are the default commission and slippage settings.  Change them to fit your
    # brokerage fees. These settings only matter for backtesting.  When you trade this 
    # algorithm, they are moot - the brokerage and real market takes over.
    set_commission(commission.PerTrade(cost=0.03))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
 
    # If it's a rebalance day (defined in initialize()) then rebalance:
#    if (exchange_time.date() == '2002-03-15' and exchange_time.hour == 12 and exchange_time.minute == 3) or (exchange_time.date() == '2002-03-15'):

##    if exchange_time.date() == datetime.date(2002,3,15): #,0,0,tzinfo=):
##    if exchange_time.date() == datetime.datetime(2002,3,15,0,0,tzinfo=):
##  if str(exchange_time.date()) == '2002-03-15':
    
#    if str(get_datetime().date()) == '2003-03-14':
 
     # Do nothing if there are open orders:
    if has_orders(context):
        print('has open orders - doing nothing!')
        return
 
    rebalance(context, data, exchange_time)  

def rebalance(context, data, exchange_time):  
    # Only rebalance if we are in the user specified rebalance time-of-day window
    if exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:
        return

    # Do the rebalance. Loop through each of the stocks and order to the target
    # percentage.  If already at the target, this command doesn't do anything.
    # A future improvement could be to set rebalance thresholds.
    for sec in context.secs:
        order(sec,1) 
        

    context.rebalance_date = exchange_time
    log.info("Rebalanced to target portfolio weights at %s" % str(exchange_time))

def has_orders(context):
    # Return true if there are pending orders.
    has_orders = False
    for sec in context.secs:
        orders = get_open_orders(sec)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec)  
                log.info(message)

            has_orders = True
    return has_orders
There was a runtime error.

Hi Georgios,

One way to do this is to check every day at the start of your trading, remove any securities that have a end date that is more than 30 days from today like so:

from datetime import timedelta

def handle_data(context, data):  
    for stock in data:  
        if stock.end_date <= (get_datetime() + timedelta(days=30)):  
            order_target_percent(stock, 0)  

And if you're checking to remove any stocks that you want to not order any more, you can build off the code snippet above by adding the stocks to a variable like context.to_not_order = [].

Then you can have a code snippet like:

for sec in context.to_not_order:  
        orders = get_open_orders(sec)  
            if orders:  
                for oo in orders:  
                    cancel(oo)  

Try implementing those in your algorithm and let me know how it goes.

A note on managing expired securities; if you use this code:

if stock.end_date <= (get_datetime() + timedelta(days=30)):  

You will effectively remove all of your positions 30 days from the end of your test as all securities have at least an end_date of the end of the test.

In addition use a reference security (SPY) that you know doesn't expire in concert with the rolling get_datetime() to managed your expired list (Thanks to Grant K. for this):

    context.Expired = []  
    schedule_function(ExpiredStop)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def ExpiredStop(context, data):  
    for stock in data:  
        if (stock in context.Expired):  
            continue  
        if (stock.end_date >= context.REF.end_date):  
            continue  
        if (stock.end_date > (get_datetime() + datetime.timedelta(days=5))):  
            continue  
        order_target_percent(stock, 0)  
        print("Expired {0}".format(stock.symbol))  
        context.Expired.append(stock)  

And of course when you go to your entries, check to see if the target security is in the Expired list first.

    # Rebalance all stocks to target portions  
    for stock in data:  
        if (stock in context.Expired):  
            continue  
        order_target_percent(stock, context.Portions[stock])  

Is this being treated like a bug by Quantopian? Adding custom python to everyone's algorithms to handle the corporate actions that Quantopian doesn't seems like a gross hack.

I have to agree with Simon here. Have there been any developments on this front or do we still need to manage this individually?

All of the discussion appears to be in regards to back testing. What do you do for your real money algo? What happens if you buy a cusip say tomorrow and the cusip gets delisted in one week, but you did not sell prior to the delisting. What will happen during the contest? Will the value drop to zero?

Ujae, to answer your questions:

What do you do for your real money algo?

This will depend how the exchange and broker handle the delisting. In live trading with the broker, we query your IB account every minute to check the latest cash and portfolio values. If there are any differences (for example if you bought a stock manually in TWS/WebTrader outside of your algo), we accept IB's values as truth. So if the broker says you own 0 shares of the stock ABC, then this will be reflected in your algo. Alternatively, if the broker says you own 10 shares of ABC at $0.00, then you will have the position in your portfolio.

What will happen during the contest?

The behavior will be identical to the backtest - the position will stay in your portfolio.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

To complement Market Tech post, I post some more filterings that I am doing. Please note: I did not invent those but found those in several community posts and I thought it would be good to put it all together:

import zipline

def initialize(context):  
         context.formation_days = 200  
         context.spy = symbol('SPY')

class DataStock(zipline.protocol.SIDData):  
    def __init__(this, sid, dataSid):  
        this.Update(dataSid)

    def Update(this, dataSid):  
        this.Prices = dataSid.price  
        this.Open   = dataSid.open_price  
        this.High   = dataSid.high  
        this.Low    = dataSid.low  
        this.Close  = dataSid.close_price  
        this.Volume = dataSid.volume

def expired_stocks(context, data):  
    close_prices = history(context.formation_days, '1d', 'close_price').dropna(axis=1)  
    prices = history(context.formation_days, '1d', 'price').dropna(axis=1)  
    open_prices = history(context.formation_days, '1d', 'open_price').dropna(axis=1)

    context.S = {}  
    for stock in data:

        if (stock in context.expired):  
            continue  
        elif (stock.end_date < context.spy.end_date):  
            context.expired.append(stock)  
            log.info("Filtered because date less then spy date: %s" % (stock.symbol))  
        elif (stock.end_date <= (get_datetime() + datetime.timedelta(days=5))):  
            context.expired.append(stock)  
            log.info("Filtered because date will expire: %s" % (stock.symbol))  
        elif stock in security_lists.leveraged_etf_list:  
            context.expired.append(stock)  
            log.info("Filtered because in ETF: %s" % (stock.symbol))  
        elif (stock not in prices or stock not in open_prices or stock not in close_prices):  
            context.expired.append(stock)  
            log.info("Filtered because in no prices: %s" % (stock.symbol))  
        else:  
            context.S[stock] = DataStock(stock, data[stock])

            std = prices[stock].std()  
            if abs(prices[stock][-1] - prices[stock][-2]) <= 10 * std:

                context.S[stock].Prices = prices[stock]  
            else:  
                context.expired.append(stock)

            std = close_prices[stock].std()  
            if abs(close_prices[stock][-1] - close_prices[stock][-2]) <= 10 * std:  
                context.S[stock].Close = close_prices[stock]  
            else:  
                context.expired.append(stock)

            std = open_prices[stock].std()  
            if abs(open_prices[stock][-1] - open_prices[stock][-2]) <= 10 * std:  
                context.S[stock].Open = open_prices[stock]  
            else:  
                context.expired.append(stock)  

This should be called after fundemental data collection and before trading.

And because everything is stored in context.S, one can use it later for different calculations (reducing "history" calles).

Yes I know, you could also "append" more intelligently.

FWIW, we've been discussing this issue in Zipline for a little while here (thanks to Thomas for pointing out this conversation!). This really needs to be handled on the back end because of how Zipline deals with securities that no longer have prices.

We've made an update to how the backtester handles delisted securities and removes them from your portfolio. You can learn more about it here.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.