Back to Community
Free Cashflow Yield

I've been a long-time lurker on Quantopian but have only recently managed to get a grasp of the platform and put some backtests together.
Having finally figured out how to use the fundamental data and alphalens I've put together a very simple but alarmingly effective strategy based on Free Cash Flow yield. Interestingly it works very well until the start of this year - would be keen to hear thoughts. I suspect a lot of quality-based factor strategies are underperforming this year as we enter into these later stages of the bull market in equities.

Below a brief outline of the strategy - interested to hear thought/criticisms. Open to the possibility that I've made a fatal error somewhere.
1) Rank Q1500 universe by Free Cash Flow Yield
2) Long top Quintile, Short Bottom Quintile
3) Rebalance weekly

Clone Algorithm
307
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
"""
Trading Strategy using Fundamental Data
1. Look at stocks in the Q1500US.
2. Go long in the top decile of stocks by FCF yield
3. Go short in the bottom decile of stocks by FCF yield.
4. Rebalance weekly at market open.
"""

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.filters import Q1500US


def initialize(context):
    
    # Rebalance weekly at open
    schedule_function(rebalance,date_rule=date_rules.week_start(),time_rule=time_rules.market_open())
    #Record end of day positions
    schedule_function(my_record_vars, date_rules.week_start(), time_rules.market_close())
    attach_pipeline(make_pipeline(), 'fundamentals_pipeline')

def make_pipeline():
    #Get free cash flow yield from morningstart database (FCF/price)
    fcf = Fundamentals.fcf_yield.latest.rank(mask = Q1500US(), method ='average')
    universe = (
        Q1500US() 
        & fcf.notnull() 
        #& is_fresh
        # & volatility_filter
    )
    #Sort into deciles
    num_quantiles = 5
    fcf_quantiles = fcf.quantiles(num_quantiles)
    #Build Pipeline, long top, short bottom decile
    pipe = Pipeline(screen=universe, columns = {'FCF': fcf, 'longs': fcf_quantiles.eq(num_quantiles-1), 'shorts': fcf_quantiles.eq(0)})

    return pipe
    
"""
Runs our fundamentals pipeline before the market opens every week
"""
def before_trading_start(context, data): 

    context.pipe_output = pipeline_output('fundamentals_pipeline')

    #Long top decile of FCF yield
    context.longs = context.pipe_output[context.pipe_output['longs']].index

    # Short bottom decile of FCF yield
    context.shorts = context.pipe_output[context.pipe_output['shorts']].index

def rebalance(context, data):
    
    my_positions = context.portfolio.positions
    

    if (len(context.longs) > 0) and (len(context.shorts) > 0):

        # Equally weight all of our long positions and all of our short positions.
        long_weight = 0.5/len(context.longs)
        short_weight = -0.5/len(context.shorts)
        
        # Get our target names for our long and short baskets. We can display these
        # later.
        target_long_symbols = [s.symbol for s in context.longs]
        target_short_symbols = [s.symbol for s in context.shorts]

        log.info("Opening long positions each worth %.2f of our portfolio in: %s" \
                 % (long_weight, ','.join(target_long_symbols)))
        
        log.info("Opening long positions each worth %.2f of our portfolio in: %s" \
                 % (short_weight, ','.join(target_short_symbols)))
        
        # Open long positions in our high p/e stocks.
        for security in context.longs:
            if data.can_trade(security):
                if security not in my_positions:
                    order_target_percent(security, long_weight)
            else:
                log.info("Didn't open long position in %s" % security)

        # Open short positions in our low p/e stocks.
        for security in context.shorts:
            if data.can_trade(security):
                if security not in my_positions:
                    order_target_percent(security, short_weight)
            else:
                log.info("Didn't open short position in %s" % security)
                  

    closed_positions = []
    
    # Close our previous positions that are no longer in our pipeline.
    for security in my_positions:
        if security not in context.longs and security not in context.shorts \
        and data.can_trade(security):
            order_target_percent(security, 0)
            closed_positions.append(security)
    
    log.info("Closing our positions in %s." % ','.join([s.symbol for s in closed_positions]))

def my_record_vars(context, data):
    """
    Record variables at the end of each rebalancing period.
    """
    longs = shorts = 0
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1
    # Record our variables.
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
There was a runtime error.
7 responses

Thank you for sharing.

This is exactly the type of thing that is great for Thursday's webinar. I ran a tear sheet for you. If you send that to [email protected], and register for the webinar, you can get a live analysis of your algo from our Managing Director of Research, Dr. Jess Stauth. I think she'd be able to tell you a lot.

Loading notebook preview...
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 Dan, will do.

Yup I have been doing alot of testing on free cash flow yield too. But with the use of seasonality investing, monthly rebalancing. Performs very well!

@Ben,
Completely agree, and this is one of my favorite numbers. The financial press loves to focus on Earnings (EPS, PE ratio, etc), but in fact there is sufficient scope within generally accepted accounting practices (GAAP) for companies to do a lot of sometimes less-than-completely-honest manipulating of their "Earnings", e.g. by changing depreciation methods, etc. Earnings is just an accounting number, but cash is cash, and there is a lot less scope for manipulating cash flow data.

Focusing on cash flow is a good idea. Even more instructive is what you can find if you start comparing earnings & cash-flow and the various component numbers related to each of them. In addition to making a useful valuation metric, it is also a good way to spot accounting fraud (e.g. Enron). See also Pietroski score item #4: Cash flow from operations > Net Income. Reference https://www.investopedia.com/terms/p/piotroski-score.asp

Dan,

Is this the kind of algorithm that you would consider for allocation, and if not, what are its deficiencies that one can learn from in this regard? Is it the exposure to different factors, or something else, perhaps?

Many thanks in advance.

Tim - I don't mean to be coy, but Jess is going to be able to answer that better than I can. I think you will enjoy the webinar tomorrow for that reason.

Hi Ben,

Perhaps consider adding a filter look at the market cap of each security in pipeline? As one's FCF increases it could be misleading if the market cap is decreasing.