Back to Community
Anees' multiple based on tweaking the Deep Value Algorithm

**Ongoing project to understand Value based trading with the one and only Anees Rao **

I like this talk so much that I just watched the whole thing again on the pretext of copying the hyperlink to send to you!

On Thu, Jun 30, 2016 at 10:37 PM, Anees Rao wrote:
Shit shit shit sorry! This completely slipped my mind! Though in my defence I have been a little busy. I had to run to Ahmedabad just last weekend coz my grandfather suddenly got hospitalized with a stroke. He's better now, so no tension!

The ADR and PINK terminology is alien to me, so I just looked that up. They seem to be derivatives, based off the value of some foreign stock. I don't think I would care too much about that.

Valuation - that is the big question, right? If you go back to our first discussion, basically valuation is something that you have to arrive at yourself. So I'm not sure what you're doing with (market_Cap - valuation).
For a very fun and quick insight into what the world of valuation is about, check out some pitches on Shark Tank. The people who are coming on it are simpler companies, because they are often selling just one product or service. They come in with a valuation of their business, and the Sharks then decide for themselves if the valuation they've arrived at is justified, or it has to be reduced to account for hard-to-enter markets, riskiness of the product, lack of proof of concept, and so on. Very rarely, but it does happen, are valuations increased.
The Sharks are often doing quick-and-dirty methods for valuation, based on what are called multiples. So if someone is earning a million dollars in profits and is valuing his business at 10 million, he's asking for multiple of 10 times his earnings. (Also called 10 times free cash flow). Based on the type of industry, Sharks have a rough idea on how much they're willing to pay for the business.

The amount they pay is also directly linked to how long they're going to take to get returns on their money. So in the above example, if you're paying 10 times free cash flow, if profits just plateau and go nowhere, you're going to take 10 years to earn back your money.So obviously, less is good, coz you'll make back your money in lesser time.

Some valuations are also done based on the multiple of the book value. The book value itself is basically the price of all your assets in your books. Machinery, real estate, unsold inventory, patents, intellectual property: all these come under book value. So someone could be willing to pay 2 times your book value, for example.

For more on this, see Ashwath Damodaran's Talk at google. He talks about various valuation techniques. he also puts up full recordings of his full semester worth of classes.

Coming back to Acquirer's multiple: can you share the code book with me, and also give me a basic lesson on how the code works? That way I can also play around with it. The discount as I had suggested it should be (cash reserves per share * number of shares outstanding - market cap). That's the same thing as (cash reserves per share - share price). This should be taken as a percentage of the market cap or share price, depending on whether you take the first one or the second.

You're right about the starting date making a big impact. I've read some studies which state that for mutual funds the best date to invest is the 10th of the month, based on some back testing. I wonder if something similar applies to stock investing too?

I wonder why the 3 month rebalancing strategy is going into negative territory. There must be some stock which is favourable according to the metrics, but is tanking somewhere down the line. I believe you can see what trades are being made somewhere, it would be interesting to see what trade is causing it to fail. Also, I'm actually surprised that the algo is making such less returns. I guess it is not actually finding enough trades in the period so maybe holding on for longer than 6 months would be useful? Maybe relaxing the debt to equity ratio requirement could also help? It would be fun to play around and see!

On Thu, Jun 30, 2016 at 6:55 PM, Shreyas Vathul Subramanian wrote:
Busy fellow Anees!

On Wed, Jun 22, 2016 at 12:57 PM, Shreyas Vathul Subramanian wrote:
Ok, I've started to get my hands dirty with the Quantopian interface. Toby C's video lecture really helped.

So far, I modified an existing Acquirer's multiple algorithm that should give me most of the functionality required for implementing your idea. I finally found the variable to access total debt-equity ration and am filtering out ones that are > 0.5. This decreased overall returns by 1% (some fine tuning may be necessary. Other stocks that are filtered out are financials, real estate, utility stocks, ADR, PINK and non-USA ones. Currently stocks are ordered by market cap and not by the amount of discount they provide, which I calculated as (Market cap - valuation) - is this correct? Ordering by EV/EBITDA, Market cap. and discount made no change to the 22% average return over 5 years compared to SPY's 33%.

For some reason, rebalancing every 3 months gave me a negative return (as compared to the 6 months scenario). Of course, this is very specific to the current test / portfolio it chooses, starting date etc.

Feedback requested!

Shreyas

On Mon, Jun 6, 2016 at 10:31 PM, Anees Rao wrote:
Faaaaastly my friend! :)

Regards,
Anees Rao

On Jun 6, 2016 7:37 PM, "Shreyas Vathul Subramanian" wrote:
Still finishing up the second timepass project - will check in once that is done! :)

On Fri, May 27, 2016 at 10:34 AM, Shreyas Vathul Subramanian wrote:
It's official!

On Thu, May 26, 2016 at 9:45 AM, Shreyas Vathul Subramanian wrote:
Hey Anees,

Great summary and (for me,) intro to these concepts. I am especially intrigued by the discussion that followed this - "sometimes the market incorrectly prices a stock issue", which I understand, is a dumbed-down version of "asking price not= market value" . I went through the podcast article and Quantopian links, and it should be easy to start off by cloning one of the existing, value-based algorithms there. Going back to the Anees Algorithm, Quantopian may or may not have a way to access companies' cash reserves per share or debt levels. Let me do some more reading and get back to you. I'm finishing up another machine learning project right now, so I will be a little distracted. But we will see this through!👍

Regards,
Shreyas

On Wed, May 25, 2016 at 8:12 AM, Anees Rao wrote:
Hey!

So firstly on the HFT front: if you're into listening to podcasts, this is where I learnt all i know about HFT.

https://www.theinvestorspodcast.com/ep19-how-high-frequency-trading-works-a-summary-of-michael-lewis-book/

THis is basically an episode discussing a Michael Lewis book on HFT. You could also check out the summary they've attached.

Basically it works on using the power of faster networks to trade on any information / event / arbitrage oppourtunity. So if there's some positive news that comes out, if your HFT can take in that information as soon as it hits the Bloomberg terminal and get its orders to the exchange faster than all the humans out there, its already done its job. Now you have to sit back and watch the market take the price higher after seeing all the volumes and buzz the stock is creating, and sell it a few minutes later for a few cents profit per share. When you're playing with a million dollars those cents add up.

So I'm interested in what is called quantitative value. Value investing is what people like Ben Graham and Warren Buffet practised all their lives. The basic idea there is that the sometimes the market incorrectly prices a stock issue, and the trick to making money in the stock market is to find these stocks.

Then people like Joel Greenblatt and Tobias Carlisle came along and tried to make this into a algorithm based approach, where Buffett was more based on rigorous study and expanding your own knowledge. These are the algos which I want to try out on Quantopian. There are other people doing it as well.

Check out this thread:

https://www.quantopian.com/posts/value-investing-in-quantopian-comparing-the-acquirers-multiple-to-the-magic-formula

Someone actually tried running the Acquirer's multiple (which is Toby Carlisle's invention) using only 3 positiions and he's made some stellar returns! With that kind of numbers that's a strategy that a human can run quite easily without any fancy computers and just the help of a free screener. Its the playing around with the numbers which will be fun!

The algorithm I suggested is actually what happened when Buffett bought into Berkshire Hathway - then a struggling textile company which was truggling to make ends meet. What was interesting about it was that they were sitting on $70 long term highly stable long term debt, while the stock was trading for $35. This is the kind of bargain that Buffett loves, and that's what I'm trying to see if we can find thru Quantopian.

The other side of algos are based on triggers - quarterly profit numbers, sales numbers, and any other data that quantopian gives you. Those kind of algorithms are only limited by your imagination.
For example, for the day, buy all the companies on the front page of the day's copy of the WSJ which have positive news, and shorting those which are negatively ranked.
Or, weather based: buy air conditioning companies at the onset of spring, and sell them just before winter. Use the Met department data for this.
And so on.

You could go short term / mid term on these kinds of strategies. Value based strategies work only on the long term.

There was a time when somone suddenly discovered that when 200 day moving average crosses the 50 day moving averrage for a stock, that's when it is on the upswing. THe problem was when they publicised this, the strategy stopped working. And that is the problem with "technical" strategies - the day they become public knowledge they stop working.

Maybe we can talk about this over the weekend?

Cheers!
Anees

On Sat, May 21, 2016 at 7:20 AM, Anees Rao wrote:
Hey!

So here's my algo suggestion, and it's based on a value investing approach. I don't know if you have heard of it, so you can tell me if you want to know more.

The idea is that often you find companies that trade for less than the amount of cash they have on the balance sheet.

  1. On the first trading day of every month, check for companies whose cash reserves per share, or the cash reserves divided by the number of shares outstanding, is less than the market value of the share. This is your invest able universe.

  2. Next, check the debt levels of these companies. If the debt to equity levels are more than 0.5, knock them off.

  3. Of the ones that remain, rank them in order of how much discount you're getting on the cash. Like if it's trading at 80$ and they have a 100$ cash in the balance sheet for every share, it's at a 20% discount.

  4. Buy putting weights according to this ranking. The more the discount, higher the buildings.

  5. Rebalance every 3 months. If they are no longer trading at a discount then sell all holdings in the stock.

Clone Algorithm
33
Loading...
Backtest from to with initial capital
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
"""
    Adapted from Tobias Carlisle's "Deep Value"
    
    1. Filter the top 500 companies by market cap 
    2. Filter for companies where EV/EBITDA is lowest
    3. Rank by EV/EBITDA (lowest is best)
    4. Buy top 30 ranked stocks each month
    5. Rebalance at the end of June
    
    Edits by Shreyas:
    
    6/22/2016
    1. Removed deprecated use of update_universe()
    2. Removed deprecated use of data[] object and used data. instead
"""

import pandas as pd
import numpy as np
import datetime

# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    
    set_long_only()
    #set_max_order_count(30)
    
    # Dictionary of orders and order dates
    context.orders = {}
    
    context.fundamental_df = None
    
    context.max_holdings = 30
    context.rebalance_month = 6
    
    context.separate_sells = False
    
    # Rebalance monthly on the first day of the month at market open
    schedule_function(rebalance,
                      date_rule=date_rules.month_end(),
                      time_rule=time_rules.market_open())
    
    if context.separate_sells:
        # Sell a few days early, before buying new stocks
        schedule_function(rebalance_sell,
                          date_rule=date_rules.month_start(days_offset=15),
                          time_rule=time_rules.market_open())
    
    
def get_shares_held(context, stock):
    """ Return the number of shares of the given stoc that we hold """

    positions = context.portfolio.positions
    for position in positions:
        if stock == position:
            return positions[position].amount
        
    # Else
    return 0

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 = 'Open order for {stock}'  
            message = message.format(amount=orders[oo][0].amount, stock=oo.symbol)  
            log.debug(message)

            has_orders = True
            
    return has_orders

def cancel_orders(context):
    # Cancel all open orders
    #for sec in context.secs:
    orders = get_open_orders() #(sec)
    if orders:
        for oo in orders:                  
            cancel_order(oo)
            
    return True

def rebalance_sell(context, data):
    
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    if exchange_time.month != context.rebalance_month:
        # track how many positions we're holding
        record(num_positions = len(context.portfolio.positions))
        record(capital_used=context.portfolio.capital_used)
        return
    
    log.info("Selling")
    
    # Cancel open orders if they exist
    if has_orders(context):
        log.debug("Has open orders - doing nothing!")
        cancel_orders(context)
    
    # Sell present positions:
    for stock in context.portfolio.positions:
        try:
            order_target_percent(stock, 0)
        except:
            log.error("Failed to sell %s maybe it is de-listed?" % (str(stock)))
            # FIXME Maybe show/test the security_end_date value to test if it is de-listed
    
def rebalance(context, data):
    
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    if exchange_time.month != context.rebalance_month:
        # track how many positions we're holding
        record(num_positions = len(context.portfolio.positions))
        record(capital_used=context.portfolio.capital_used)
        record(pnl=context.portfolio.pnl)
        return
    
    log.info("Rebalancing")
    
    # Cancel open orders if they exist
    if has_orders(context):
        log.debug("Has open orders - doing nothing!")
        cancel_orders(context)
    
    if not context.separate_sells:
        # Sell present positions:
        for stock in context.portfolio.positions:
            try:
                order_target_percent(stock, 0)
            except:
                log.error("Failed to sell %s maybe it is de-listed?" % (str(stock)))
                # FIXME Maybe show/test the security_end_date value to test if it is de-listed
    else:
        orders = get_open_orders()  
        if orders:  
            for oo in orders:  
                log.debug(get_order(oo))
    
    # Only order 2 stocks and don't overlap with existing positions
    order_cnt = 0
    orders = []
    for stock in context.fundamental_df:
        if order_cnt >= context.max_holdings:
            break
        if stock not in context.portfolio.positions:
            #oid = order_target_percent(stock, context.max_holdings/100.)#int(100./24.)/100.)
            try:
                if data.current(stock,'price') < 5.0:
                    continue
                #oid = order_target_percent(stock, int(100./float(context.max_holdings))/100.)
                oid = order_target_percent(stock, float(1.0/float(context.max_holdings)))
                orders.append(stock)
                order_cnt += 1
                #log.info("Bought into %s with AM of %f: %r at a price of %f" % (str(stock), context.fundamental_df[stock]['acq_mult'], oid, data[stock].price))
                #log.info("%d" % data[stock].price)
            except:
                log.error("Failed to buy into %s with AM of %f" % (str(stock), context.fundamental_df[stock]['acq_mult']))
            #if 'acq_mult' not in context.fundamental_df[stock]:
            #    pass
            try:
                
                
                # Save the order info for later
                # FIXME: We are saving the order date, not the fill date, gotta figure that out
                context.orders[oid] = dict(stock=stock, date=exchange_time)
            except:
                pass
    
    log.info("Bought: %s" % ', '.join([str(stock) for stock in orders]))
    
    
def before_trading_start(context): 
    """
      Called before the start of each trading day. 
      It updates our universe with the
      securities and values found from fetch_fundamentals.
    """
    
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    #if True: #not exchange_time.day == 30:
    if exchange_time.month != (context.rebalance_month - 1) and context.fundamental_df is not None:
        # Should check for specific realance day too...
        #update_universe(context.fundamental_df.columns.values)
        context.assets = context.fundamental_df.columns.values
        return
    else:
        #log.info(exchange_time.day)
    #if  context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):
    
        # Setup SQLAlchemy query to screen stocks based on PE ration
        # and industry sector. Then filter results based on 
        # market cap and shares outstanding.
        # We limit the number of results to num_stocks and return the data
        # in descending order.
        fundamental_df = get_fundamentals(
            query(
                # put your query in here by typing "fundamentals."
                fundamentals.operation_ratios.total_debt_equity_ratio,
                fundamentals.valuation_ratios.ev_to_ebitda,
                fundamentals.valuation.market_cap,
                fundamentals.valuation.enterprise_value,
                fundamentals.income_statement.ebit
            )
            # No Financials (103), Real Estate (104) or Utility (207) Stocks, no ADR or PINK, only USA
            .filter(fundamentals.asset_classification.morningstar_sector_code != 103)
            .filter(fundamentals.asset_classification.morningstar_sector_code != 104)
            .filter(fundamentals.asset_classification.morningstar_sector_code != 207)
            .filter(fundamentals.company_reference.country_id == "USA")
            .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
            .filter(fundamentals.share_class_reference.is_primary_share == True)
            # Only pick active stocks
            .filter(fundamentals.share_class_reference.share_class_status == "A")
            # Only Common Stock
            .filter(fundamentals.share_class_reference.security_type == "ST00000001")
            .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
            
            .filter(fundamentals.valuation.market_cap != None)
            .filter(fundamentals.valuation.market_cap >= 2e9)#200e6) #1.8e9)
            .filter(fundamentals.valuation.shares_outstanding != None)
            .filter(fundamentals.valuation_ratios.ev_to_ebitda != None)
            .filter(fundamentals.operation_ratios.total_debt_equity_ratio <= 0.5)
            # Try allowing a negative EV.  It might outperform
            .filter(fundamentals.valuation_ratios.ev_to_ebitda >= 0.0)
            .filter(fundamentals.income_statement.ebitda != None)
            .filter(fundamentals.income_statement.ebitda >= 0.0)
            .filter(fundamentals.income_statement.ebit != None)
            .filter(fundamentals.income_statement.ebit >= 0.0)
            .filter(fundamentals.valuation.enterprise_value != None)
            .filter(fundamentals.valuation.enterprise_value >= 0.0)
            .filter(fundamentals.valuation_ratios.ev_to_ebitda >= 0.)
            .filter((fundamentals.valuation.enterprise_value / fundamentals.income_statement.ebit) >= 0.0)
            #.filter((fundamentals.valuation.enterprise_value / fundamentals.income_statement.ebit) <= 5.0)
            #(1.0 / 0.03))
            #.filter(fundamentals.valuation_ratios.ev_to_ebitda <= 5.) #(1.0 / 0.03))
            .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
            #.order_by((fundamentals.valuation.market_cap - fundamentals.valuation.enterprise_value).desc())
            #.order_by(fundamentals.valuation.market_cap.desc())
            #.limit(num_stocks*100)
        )
        
        #log.info(fundamental_df)
        
        # Eliminate possible bankruptcies in progress
        remove = []
        for stock in fundamental_df:
            if str(stock.symbol).endswith("_WI"):
                remove.append(stock)
        for stock in remove:
            del(fundamental_df[stock])
                         
        # Add the Acquirer's Multiple score
        temp =  fundamental_df.T
        temp['acq_mult'] = temp['ev_to_ebitda']
        #temp['ev'] = temp['market_cap'] + temp['long_term_debt'] + temp['current_debt'] + temp['minority_interest'] + temp['preferred_stock'] - temp['cash_and_cash_equivalents'] #temp['cash']
        #temp['acq_mult'] = temp['ev'] / temp['ebit']
        #temp['acq_mult'] = temp['enterprise_value'] / temp['ebit']
        #temp = temp[temp['acq_mult'] <= 5.0]
        temp.sort('acq_mult', ascending=True, inplace=True)
        temp = temp.head(200)
        fundamental_df = temp.T
            
        ## Scalp the last 5
        #fundamental_df = fundamental_df.T.head(5).T
            
        #for stock in fundamental_df:
        #    log.info("The %s stock is %f" % (str(stock), fundamental_df[stock]['acq_mult']))

        # Filter out our stocks
        context.stocks = [stock for stock in fundamental_df]

        # Update context.fundamental_df with the securities that we need
        context.fundamental_df = fundamental_df[context.stocks]
        
        #update_universe(context.fundamental_df.columns.values)
        context.assets = context.fundamental_df.columns.values

# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    # Implement your algorithm logic here.

    # data[sid(X)] holds the trade event data for that security.
    # context.portfolio holds the current portfolio state.

    # Place orders with the order(SID, amount) method.

    # TODO: implement your own logic here.
    pass
    #order(sid(24), 50)
There was a runtime error.