Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
GrahamFundmantals algo - simple screening on Benjamin Graham #Fundamentals

Built a simple GrahamFundmantals algo based on screening for some simple Benjamin Graham fundamental data. Still lots work to do. Since it is fundamental based, it really shouldn't re-balance the total portfolio every month. We need to give the Deep Value stocks some time to recover before selling them off and adding more value candidates. I will continue to work on the algo adding more detailed Graham value criteria and changing the re-balancing. Thinking I will start by arranging to have an average hold time of 6-months and see how that improves the results.

In the fullness of time, I will combine Fundamental Screening with Technical Analysis for entry and exit. That is my holy grail of quant investing.

AlphaInvestor


#    Trading Strategy using Fundamental Data  
#    1. Filter the top 50 companies by market cap  
#   2. Find the top two sectors that have the highest average PE ratio  
#    3. Every month exit all the positions before entering new ones at the month  
#    4. Log the positions that we need 

import pandas as pd  
import numpy as np  
import talib

def initialize(context):  
    # Dictionary of stocks and their respective weights  
    context.stock_weights = {}  
    # Count of days before rebalancing  
    context.days = 0  
    # Number of sectors to go long in  
    context.sect_numb = 2  
    # Sector mappings  
    context.sector_mappings = {  
       101.0: "Basic Materials",  
       102.0: "Consumer Cyclical",  
       103.0: "Financial Services",  
       104.0: "Real Estate",  
       205.0: "Consumer Defensive",  
       206.0: "Healthcare",  
       207.0: "Utilites",  
       308.0: "Communication Services",  
       309.0: "Energy",  
       310.0: "Industrials",  
       311.0: "Technology"  
    }  
    # Rebalance monthly on the first day of the month at market open  
    schedule_function(rebalance,  
                      date_rule=date_rules.month_start(),  
                      time_rule=time_rules.market_open())  
def rebalance(context, data):  
    # Exit all positions before starting new ones  
    for stock in context.portfolio.positions:  
        if stock not in context.fundamental_df:  
            order_target_percent(stock, 0)

    log.info("The two sectors we are ordering today are %r" % context.sectors)

    # Create weights for each stock  
    weight = create_weights(context, context.stocks)

    # Rebalance all stocks to target weights  
    for stock in context.fundamental_df:  
        if weight != 0:  
            log.info("Ordering %0.0f%% percent of %s in %s"  
                     % (weight * 100,  
                        stock.symbol,  
                        context.sector_mappings[context.fundamental_df[stock]['morningstar_sector_code']]))  
        order_target_percent(stock, weight)

    # track how many positions we're holding  
    record(num_positions = len(context.fundamental_df))

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.  
    """  
    num_stocks = 50  
    # 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.valuation_ratios.pe_ratio,  
            fundamentals.valuation_ratios.peg_ratio,  
            fundamentals.valuation_ratios.pb_ratio,  
            fundamentals.earnings_report.diluted_eps,  
            fundamentals.valuation_ratios.book_value_per_share,  
            fundamentals.asset_classification.morningstar_sector_code  
        )  
        .filter(fundamentals.valuation.market_cap != None)  
        .filter(fundamentals.valuation.shares_outstanding != None)  
        .filter(fundamentals.operation_ratios.quick_ratio >= 1.0)  
        .filter(fundamentals.valuation_ratios.pe_ratio < 15.0)  
        .filter(fundamentals.valuation_ratios.pb_ratio < 1.5)  
        #        .filter(fundamentals.valuation_ratios.peg_ratio < 1.5)  
        .order_by(fundamentals.valuation.market_cap.desc())  
        .limit(num_stocks)  
    )

    # Find sectors with the highest average PE  
    sector_pe_dict = {}  
    for stock in fundamental_df:  
        sector = fundamental_df[stock]['morningstar_sector_code']  
        pe = fundamental_df[stock]['pe_ratio']  
#       peg = fundamental_df[stock]['peg_ratio']  
#       pb = fundamental_df[stock]['pb_ratio']  
#       bvps = fundamental_df[stock]['book_value_per_share']  
#       eps = fundamental_df[stock]['diluted_eps']  
#        if fundamental_df[stock]['morningstar_sector_code'] = 103:  
#            graham_number = sqrt(15 * eps * bvps)  
#        else:  
#            graham_number = sqrt(22.5 * eps * bvps)  
#  
#        graham_margin_of_safety = price / graham number  
        # If it exists add our pe to the existing list.  
        # Otherwise don't add it.  
        if sector in sector_pe_dict:  
            sector_pe_dict[sector].append(pe)  
 #          sector_pe_dict[sector].append(peg)  
 #          sector_pe_dict[sector].append(pb)  
 #          sector_pe_dict[sector].append(bvps)  
 #          sector_pe_dict[sector].append(eps)  
 #           sector_pe_dict[sector].append(graham_number)  
 #           sector_pe_dict[sector].append(graham_margin_of_safety)  
        else:  
            sector_pe_dict[sector] = []

    print sector_pe_dict  
   # Find average PE per sector  
    sector_pe_dict = dict([(sectors, np.mean(sector_pe_dict[sectors],axis=0))  
                               for sectors in sector_pe_dict if len(sector_pe_dict[sectors]) > 0])

    # Sort in ascending order  
    sectors = sorted(sector_pe_dict, key=lambda x: sector_pe_dict[x], reverse=True)[:context.sect_numb]  
    # Filter out only stocks with that particular sector  
    context.stocks = [stock for stock in fundamental_df  
                      if fundamental_df[stock]['morningstar_sector_code'] in sectors]  

    # Initialize a context.sectors variable  
    context.sectors = [context.sector_mappings[sect] for sect in sectors]

    # Update context.fundamental_df with the securities (and pe_ratio) that we need  
    context.fundamental_df = fundamental_df[context.stocks]  

    update_universe(context.fundamental_df.columns.values)  

def create_weights(context, stocks):  
    """  
        Takes in a list of securities and weights them all equally  
    """  
    if len(stocks) == 0:  
        return 0  
    else:  
        weight = 1.0/len(stocks)  
        return weight  
def handle_data(context, data):  
    """  
      Code logic to run during the trading day.  
      handle_data() gets called every bar.  
    """  
    pass
5 responses

Your 'valuation_ratios.book_value_per_share' took my combo technical/fundamental 6-yr backtest from 211% to 355%, check's in the mail.

    # SQLAlchemy screening  
    fundamental_df = get_fundamentals(  
        query(  
            fundamentals.asset_classification.morningstar_sector_code  
        )  
        .filter(fundamentals.valuation.market_cap                  > 80e6   )  
        .filter(fundamentals.valuation_ratios.pe_ratio             < 11     )  
        .filter(fundamentals.valuation_ratios.pe_ratio             > 3      )  
        .filter(fundamentals.operation_ratios.roa                  > 0.1    )  
        .filter(fundamentals.balance_sheet.current_debt            < 80e6   )  
        .filter(fundamentals.valuation_ratios.book_value_per_share > 8      )  
        #.filter(fundamentals.valuation_ratios.pb_ratio             < 3      )  
        #.filter(fundamentals.valuation_ratios.payout_ratio         > .1     )  
        #.filter(fundamentals.valuation.shares_outstanding          != None  )  
        #.filter(fundamentals.operation_ratios.avg5_yrs_roic        > .2     )  
        #.filter(fundamentals.operation_ratios.roic                 > 0.1    )  
        #.filter(fundamentals.operation_ratios.roe                  > 0.1    )  
        #.filter(fundamentals.earnings_ratios.diluted_eps_growth    > .01    )  
        #.filter(fundamentals.operation_ratios.quick_ratio          >= 1.0   )  

        .order_by(  
            #fundamentals.valuation_ratios.pe_ratio.asc(),  
            fundamentals.valuation.market_cap.desc(),  
            #fundamentals.operation_ratios.roa.desc(),  
            #fundamentals.balance_sheet.current_debt.asc(),  
            #fundamentals.valuation_ratios.book_value_per_share.desc(),  
        )  
        .limit(7)  
    )  

Awesome. I cannot wait to cash it.
Glad that I could help in some small way.

  • AlphaInvestor

Bruce, could you explain your logic to me? Why are you finding the top two sectors that have the highest average PE ratio ? woudln't you want the lowest PE?

My original thought was that after making a very strict Fundamental screen for VALUE, I would pick the ones with highest P/E. That would give some momentum flavor to the selection without doing anything with technical analysis indicators.

My intent was always to add a technical analysis component to this screen. That would match the way that I trade with my real money accounts:
1) select stocks as investment candidates based on Fundamental Analysis
2) select entry and exit points based on Technical Analysis

I have made some progress towards adding a TA flavor to this screen but it is not ready for prime time yet.

Hi Bruce, I am very interested in your strategy. Are you close to sharing your results or backtesting? Will you be participating in the contest to manage 100k? Do you share your investment strategies elsewhere?