Back to Community
Fundamental Growth Strategy

Hey everyone,

I have been plugging away at a couple of my first notebooks & backtests this weekend. Trying to stay in the fundamental/value realm to familiarize myself with the platform to start.

The goal of this strategy was to find top & bottom growth companies by ROIC & revenue growth. This was done by screening for the top & bottom 20% of ROIC and top & bottom 20% by revenue growth, then combining the two factors to create an overall growth category.

The Alphalens tear sheet results on each metric is fairly small. Shifting the strategy to long only, in the top companies might be a better approach. Attaching a backtest here soon.

Loading notebook preview...
11 responses

Associated backtest:

Going to create a long only strategy as well.

Clone Algorithm
111
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
"""
High growth stock picking strategy: The aim here is to invest in companies with high revenue growth & return on invested capital and short companies that are preforming poorly. 

1. Every month, pick high-growth companies based upon revenue growth & roic.
2. Long companies with high growth and short companies at low growth.
3. Every month exit all the positions that are not in either list. 
"""

# Importing needed modules.
from quantopian.algorithm import attach_pipeline, pipeline_output                                                         
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US            

def initialize(context):

    # Rebalance every end of month, an hour before market close
    schedule_function(rebalance, 
                      date_rules.month_end(), 
                      time_rules.market_close(hours=1))
     
    # Record tracking variables at the end of each day.
    schedule_function(record_vars, 
                      date_rules.every_day(), 
                      time_rules.market_close())
     
    # Create our dynamic stock selector.
    attach_pipeline(make_pipeline(), 'my_pipeline')
             
def make_pipeline():
    
      # Base Universe.
    base_universe = Q500US()
    
    # Revenue Growth.
    revenue_growth = morningstar.operation_ratios.revenue_growth.latest

    # Return on Invested Capital.
    roic = morningstar.operation_ratios.roic.latest
    
    # Filtering top and bottom companies by revenue growth. Not 0-20% due to risk of delisting
    high_revenue = revenue_growth.percentile_between(80,100, mask=base_universe)
    low_revenue = revenue_growth.percentile_between(10,30, mask=base_universe)
    
    # Filtering top and bottom companies by return on invested capital. Not 0-20% due to risk of delisting
    high_roic = roic.percentile_between(80,100, mask=base_universe)
    low_roic = roic.percentile_between(10,30, mask=base_universe)
    
    # Setting filters.
    securities_of_high_growth = (high_revenue & high_roic)
    securities_of_low_growth = (low_revenue & low_roic)
    securities_to_trade = (securities_of_high_growth | securities_of_low_growth)
    
    # Return pipe:
    return Pipeline(
        columns = {
            'Revenue Growth': revenue_growth,
            'Return on Invested Capital': roic,
            'Bottom Growth Company': securities_of_low_growth,
            'Top Growth Company': securities_of_high_growth 
        },
        screen = securities_to_trade
    )

    return Pipeline()

def before_trading_start(context, data):
    """
    Setting long and short positions.
    """
    context.output = pipeline_output('my_pipeline')
  
    # List of long positions.
    context.longs = context.output[context.output['Top Growth Company']].index
    
    # List of short positions.
    context.shorts = context.output[context.output['Bottom Growth Company']].index
        
    # Setting context weights.
    context.long_weight, context.short_weight = assign_weights(context)
     
def assign_weights(context):
    """
    Assign weights to securities that we want to order. Choosing a market neutral position of long_weights =                             short_weights
    """
    
    # Setting Long weight.
    long_weight = 0.5 / len(context.longs)
    
    # Setting short weight.
    short_weight = -0.5 / len(context.shorts)
    
    return long_weight, short_weight

def rebalance(context,data):
    """
    Rebalance weights per monthly while exiting all positions at the start of every rebalance. 
    """

    # Exit positions not in either long or shorts.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            order_target_percent(security, 0)
    
    # Set long position orders.
    for security in context.longs:
        if data.can_trade(security):
            order_target_percent(security, context.long_weight)
            
    # Set short position orders.
    for security in context.shorts:
        if data.can_trade(security):
            order_target_percent(security, context.short_weight)
           
def record_vars(context, data):
    """
    Recording long positions, short positions, and leverage.
    """
    
    longs = shorts = 0
     
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1
                      
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
         
There was a runtime error.

Full long only strategy.

Clone Algorithm
111
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
"""
High growth stock picking strategy: The aim here is to invest in companies with high revenue growth & return on invested capital.

1. Every month, pick high-growth companies based upon revenue growth & roic.
2. Long companies with high growth and short companies at low growth.
3. Rebalance monthly - Exit companies not in the list and add new companies now in long list. 
"""

# Importing needed modules.
from quantopian.algorithm import attach_pipeline, pipeline_output                                                         
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US            

def initialize(context):

    # Rebalance every end of month, an hour before market close
    schedule_function(rebalance, 
                      date_rules.month_end(), 
                      time_rules.market_close(hours=1))
     
    # Record tracking variables at the end of each day.
    schedule_function(record_vars, 
                      date_rules.every_day(), 
                      time_rules.market_close())
     
    # Create our dynamic stock selector.
    attach_pipeline(make_pipeline(), 'my_pipeline')
             
def make_pipeline():
    
    # Base Universe.
    base_universe = Q500US()
    
    # Revenue Growth.
    revenue_growth = morningstar.operation_ratios.revenue_growth.latest

    # Return on Invested Capital.
    roic = morningstar.operation_ratios.roic.latest
    
    # Filtering top companies by revenue growth.
    high_revenue = revenue_growth.percentile_between(80,100, mask=base_universe)
    
    # Filtering top companies by return on invested capital.
    high_roic = roic.percentile_between(80,100, mask=base_universe)
    
    # Setting filters.
    securities_of_high_growth = (high_revenue & high_roic)
    
    # Return pipe:
    return Pipeline(
        columns = {
            'Revenue Growth': revenue_growth,
            'Return on Invested Capital': roic,
            'Top Growth Company': securities_of_high_growth 
        },
        screen = securities_of_high_growth
    )

    return Pipeline()

def before_trading_start(context, data):
    """
    Setting long positions.
    """
    context.output = pipeline_output('my_pipeline')
    
    # List of short positions.
    context.longs = context.output[context.output['Top Growth Company']].index
    
    # Gathering weights from assign_weights(). 
    context.long_weight = assign_weights(context)
     
def assign_weights(context):
    """
    Assign weights to securities that we want to order. 
    """
    
    # Set long weights. 
    long_weight = .5 / len(context.longs)
    log.info(long_weight)
                             
    return long_weight 

def rebalance(context,data):
    """
    Rebalance weights per monthly while exiting all positions at the start of every rebalance. 
    """

    # Exit positions not in either long or shorts.
    for security in context.portfolio.positions:
        if security not in context.longs and data.can_trade(security):
            order_target_percent(security, 0)
    
    # Set long position orders.
    for security in context.longs:
        if data.can_trade(security):
            order_target_percent(security, context.long_weight)
           
def record_vars(context, data):
    """
    Recording long positions, short positions, and leverage.
    """
    
    longs = 0
     
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1

    record(leverage=context.account.leverage, long_count=longs)
There was a runtime error.

Great start!

Based on my experience, adjusting for volatility really improves the strategy. I made some modifications on your long only strategy

  • Removed revenue growth, and just kept roic (I generally do this because roic is a much stronger indicator alone)
  • Changed month_end to month_start (I like the beginning :P)
  • Selected low annualized volatility based on last 21 days
  • Increase number of buys (100)

Let me know what you think!
A next step might be to add value or momentum into the mix.

Clone Algorithm
112
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
"""
High growth stock picking strategy: The aim here is to invest in companies with high revenue growth & return on invested capital and short companies that are preforming poorly. 

1. Every month, pick high-growth companies based upon revenue growth & roic.
2. Long companies with high growth and short companies at low growth.
3. Every month exit all the positions that are not in either list. 
"""

# Importing needed modules.
from quantopian.algorithm import attach_pipeline, pipeline_output                                                         
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.factors import AnnualizedVolatility


def initialize(context):

    # Rebalance every end of month, an hour before market close
    schedule_function(rebalance, 
                      date_rules.month_start(), 
                      time_rules.market_close(hours=1))
     
    # Record tracking variables at the end of each day.
    schedule_function(record_vars, 
                      date_rules.month_start(), 
                      time_rules.market_close())
     
    # Create our dynamic stock selector.
    attach_pipeline(make_pipeline(), 'my_pipeline')
             
def make_pipeline():
    
      # Base Universe.
    base_universe = Q500US()
    
    # Return on Invested Capital.
    roic = morningstar.operation_ratios.roic.latest
    
    vol = AnnualizedVolatility(mask=base_universe, window_length=21)
    
    combined_rank = (roic - vol).rank(mask=base_universe)
    
    # Setting filters.
    securities_of_high_growth = combined_rank.percentile_between(80, 100)
    securities_of_low_growth = combined_rank.percentile_between(0, 20)
    
    securities_to_trade = (securities_of_high_growth | securities_of_low_growth)
    
    # Return pipe:
    return Pipeline(
        columns = {
            'Return on Invested Capital': roic,
            'Bottom Growth Company': securities_of_low_growth,
            'Top Growth Company': securities_of_high_growth 
        },
        screen = securities_to_trade
    )

    return Pipeline()

def before_trading_start(context, data):
    """
    Setting long and short positions.
    """
    context.output = pipeline_output('my_pipeline')
  
    # List of long positions.
    context.longs = context.output[context.output['Top Growth Company']].index
    
    # List of short positions.
    context.shorts = context.output[context.output['Bottom Growth Company']].index
        
    # Setting context weights.
    context.long_weight, context.short_weight = assign_weights(context)
     
def assign_weights(context):
    """
    Assign weights to securities that we want to order. Choosing a market neutral position of long_weights =                             short_weights
    """
    
    # Setting Long weight.
    long_weight = 1.0 / len(context.longs)
    
    # Setting short weight.
    short_weight = -0.0 / len(context.shorts)
    
    return long_weight, short_weight

def rebalance(context,data):
    """
    Rebalance weights per monthly while exiting all positions at the start of every rebalance. 
    """

    # Exit positions not in either long or shorts.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            order_target_percent(security, 0)
    
    # Set long position orders.
    for security in context.longs:
        if data.can_trade(security):
            order_target_percent(security, context.long_weight)
            
    # Set short position orders.
    for security in context.shorts:
        if data.can_trade(security):
            order_target_percent(security, context.short_weight)
           
def record_vars(context, data):
    """
    Recording long positions, short positions, and leverage.
    """
    
    longs = shorts = 0
     
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1
                      
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
         
There was a runtime error.

Thank you for that Cheng! That made a substantial difference. Working on a momentum factor right now and I'll post it once its finished.

Thanks for the share, the source code is crystal clear and I will definitely run some tests, let you know how it goes.

I modified it to consider sentiment as well. Seems like it performs a little bit better but didn't make a huge difference.

Clone Algorithm
18
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
"""
https://www.quantopian.com/posts/fundamental-growth-strategy
High growth stock picking strategy: Picks long and short stocks using a fundamental metric representing growth. Original version uses a volatility screen and monthly rebalancing. This version adds a sentiment screen.
"""

# Importing needed modules.
from quantopian.algorithm import attach_pipeline, pipeline_output                                                         
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.factors import AnnualizedVolatility
from quantopian.pipeline.data.sentdex import sentiment_free as sentdex


def initialize(context):

    # Rebalance every end of month, an hour before market close
    schedule_function(rebalance, 
                      date_rules.month_start(), 
                      time_rules.market_close(hours=1))
     
    # Record tracking variables at the end of each day.
    schedule_function(record_vars, 
                      date_rules.month_start(), 
                      time_rules.market_close())
     
    # Create our dynamic stock selector.
    attach_pipeline(make_pipeline(), 'my_pipeline')
             
def make_pipeline():
    
      # Base Universe.
    base_universe = Q500US()
    
    ## m selects fundamental metric to use.
    m = morningstar.operation_ratios.roic.latest * sentdex.sentiment_signal
    
    vol = AnnualizedVolatility(mask=base_universe, window_length=21)
    
    combined_rank = (m - vol).rank(mask=base_universe)
    
    # Setting filters.
    securities_of_high_growth = combined_rank.percentile_between(80, 100)
    securities_of_low_growth = combined_rank.percentile_between(0, 20)
    
    securities_to_trade = (securities_of_high_growth | securities_of_low_growth)
    
    # Return pipe:
    return Pipeline(
        columns = {
            'Metric': m,
            'Bottom Growth Company': securities_of_low_growth,
            'Top Growth Company': securities_of_high_growth 
        },
        screen = securities_to_trade
    )

    return Pipeline()

def before_trading_start(context, data):
    """
    Setting long and short positions.
    """
    context.output = pipeline_output('my_pipeline')
  
    # List of long positions.
    context.longs = context.output[context.output['Top Growth Company']].index
    
    # List of short positions.
    context.shorts = context.output[context.output['Bottom Growth Company']].index
        
    # Setting context weights.
    context.long_weight, context.short_weight = assign_weights(context)
     
def assign_weights(context):
    """
    Assign weights to securities that we want to order, long and short.
    """
    
    # Setting long weight.
    if len(context.longs) != 0:
        long_weight = 0.55 / len(context.longs)
    else:
        long_weight = 0
    
    # Setting short weight.
    if len(context.shorts) != 0:
        short_weight = -0.45 / len(context.shorts)
    else:
        short_weight = 0
    
    return long_weight, short_weight

def rebalance(context,data):
    """
    Rebalance weights each month while exiting all positions at the start of every rebalance. 
    """

    # Exit positions not in either long or shorts.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            order_target_percent(security, 0)
    
    # Set long position orders.
    for security in context.longs:
        if data.can_trade(security):
            order_target_percent(security, context.long_weight)
            
    # Set short position orders.
    for security in context.shorts:
        if data.can_trade(security):
            order_target_percent(security, context.short_weight)
           
def record_vars(context, data):
    """
    Recording long positions, short positions, and leverage.
    """
    
    longs = shorts = 0
     
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1
                      
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
         
There was a runtime error.

Please see my comments on your other tread Jared as they apply to this one as well. If you want to actually beat the SPY, then I'd suggest just doing some algos over 10 years with one paramater to find which fundamental ratios have best predictive value, then work on combining them. In my experience, you should try P/B or P/FCF for example. ROIC, Revenue Growth, ROA, ROE, Current Ratio, and many others barely, if at all, beat the SPY when used for predicting future price appreciation re-balanced on a month to month basis.

If you look for long only, fundamentals based strategies you can look the book:

"Warren Buffett and the Interpretation of Financial Statements: The Search for the Company with a Durable Competitive Advantage"

https://www.amazon.com/gp/product/B007MXBEXK/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&tag=oldschval-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=B007MXBEXK

As we all know the strategy of Warren Buffet is purely long and quite successful. :-)

Warren Buffett is my idol as far as investing goes. I don't think anyone can claim as good returns for such a long time as he has had with the help of Charlie Munger through their success at Berkshire. I've read the Warren Buffet way and a few others but had not heard of the "Interpretation of Financial Statements". I shall give it a look, thanks for sharing. Presently my better algos revolve around a mix of what I have learned from Berkshire and the work by O'Shaughnessy. For some reason or another I can't seem to get any strategies to be as profitable as my fundamental based ones once liquidity and slippage are accounted for.

@Darth. Now I am trying some purely statistical strategies, but including some fundamentals like the WB's favorite ROI is in my list of endeavors.

Hi Darth, Kalin & friends who are also Buffett-Munger fans!

The reason that I love these sort of strategies is that they are robust and continue to stand the test of time.
They don't rely on fancy data mining that finds an apparent alpha which may or may not even be real and, if it is, then it will probably get arbitraged away soon as more people discover it. In contrast, everyone already knows the good fundamental stuff, but it just keeps on working, like trend following (TF). My personal favorite strategies are all combinations of Fundamentals & TF.

In addition to the Berkshire Hathaway letters & O'Shaughnessy (which have been on my shelf for years), a book that I read recently was "Buffett and Beyond" by Joseph Belmonte. It provides a very nice and easy-to-read perspective on Clean Surplus Accounting. Strongly recommended for all real WB fans!

Best wishes, safe & happy trading & investing.
Tony