Back to Community
Help with Pipeline - old algo

Hi,

After reading Millennial Money by Patrick O'Shaughness (good read btw!!), i was inspired to see if there was any similar trading strategies on Q. To my delight, and after a simple google, i came across this wacky algo: https://www.quantopian.com/posts/patrick-oshaughnessys-millennial-money-value-investing-algorithm-number-fundamentals . However, as you will notice it uses the V1 syntax which allowed get_fundamentals.

With no experience of using Pipeline i have attempted to redesign the algorithm to incorporate the updated syntax which, according to Q, will enable a faster gathering of fundamental data. It's fair to say i have hit a slight brick wall and my python skills are simply slightly out of their depth.

I have attached my updated source code which gathers pipeline data and simply copy and pasted the end of the original algo's code on for your benefit in manipulating the code. The trouble is, i know exactly what the original code is reading, however, i do not know how to implicate this into my algo. You may notice in the original algo (with other 6000% returns) that the majority of this is derived from leverage, and i want to attempt to reduce this. If someone is able to explain where i should go next, that would be much appreciated. I'm well aware i am using many restrictions on the fundamnetal data but this can all be changed.

Open to any suggestions/advice.

Cheers,

James

Here is the code if it does not come through as i believe the backtest returned no results:
EDIT: apologies for the nasty format - I commented a lot of the script out.

""" Patrick O'Shaughnessy - Millennial Money

1. Stakeholder yield < 5%. Stakeholder yield = Cash from financing 12m / Market Cap Q1  
2. ROIC > 25%  
   ROIC = operating_income /  (invested_capital - cash)  
3. CFO > Net Income (Earnings Quality)  
4. EV/FCF < 15 (Value)  
5. 6M Relative Strength top three-quarters of the market.  
   6M Relative Strength = 6M Stock Total Return / 6M Total Return S&P500 (Momentum)  
6. Positions are kept at least for 1 quarter  
7. implemented 15% stop loss; if the stock hits stop loss it cannot be repurchased for a half year  
8. market cap > 30m  
9. Debt/Equity < 0.5  
10. Ebitda Margin > 15%  

The positions are updated quarterly.  

"""

from quantopian.pipeline import Pipeline
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar

import pandas as pd
import numpy as np

def initialize(context):

pipe = Pipeline ()  
attach_pipeline(pipe, 'mmoney')  

sector_code = morningstar.asset_classification.morningstar_sector_code.latest  
pipe.add(sector_code, 'sector_code')  

country_id = morningstar.company_reference.country_id.latest  
pipe.add(country_id, 'country_id')  

primary_exchange_id = morningstar.company_reference.primary_exchange_id.latest  
pipe.add(primary_exchange_id, 'primary_exchange_id')  

is_depositary_receipt = morningstar.share_class_reference.is_depositary_receipt.latest  
pipe.add(is_depositary_receipt, 'is_depositary_receipt')  

is_primary_share = morningstar.share_class_reference.is_primary_share.latest  
pipe.add(is_primary_share, 'is_primary_share')  

financing_cash_flow = morningstar.cash_flow_statement.financing_cash_flow.latest  
pipe.add(financing_cash_flow, 'financing_cash_flow')  

market_cap = morningstar.valuation.market_cap.latest  
pipe.add(market_cap, 'market_cap')  

shares_outstanding = morningstar.valuation.shares_outstanding.latest  
pipe.add(shares_outstanding, 'shares_outstanding')  

operating_income = morningstar.income_statement.operating_income.latest  
pipe.add(operating_income, 'operating_income')  

invested_capital = morningstar.balance_sheet.invested_capital.latest  
pipe.add(invested_capital, 'invested_capital')  

cash_and_cash_equivalents = morningstar.balance_sheet.cash_and_cash_equivalents.latest  
pipe.add(cash_and_cash_equivalents, 'cash_and_cash_equivalents')  

enterprise_value = morningstar.valuation.enterprise_value.latest  
pipe.add(enterprise_value, 'enterprise_value')  

free_cash_flow = morningstar.cash_flow_statement.free_cash_flow.latest  
pipe.add(free_cash_flow, 'free_cash_flow')  

total_debt_equity_ratio = morningstar.operation_ratios.total_debt_equity_ratio.latest  
pipe.add(total_debt_equity_ratio, 'total_debt_equity_ratio')  

ebitda_margin = morningstar.operation_ratios.ebitda_margin.latest  
pipe.add(ebitda_margin, 'ebitda_margin')  

context.account.leverage = 0  
context.bought_for={}  
context.all_current_stocks = []  
context.prices = {}  
context.max_num_stocks = 50  
context.days = 64  
context.quarter_days = 65  
context.quarters_alive = {}  
context.relative_strength_6m = {}  
context.banned_days = {}  

# No Financials (103) and Real Estate (104) Stocks, no ADR or PINK, only USA  
sector_code_filter = (sector_code != 103 & 104)  
country_id_filter = (country_id == "USA")  
is_depositary_receipt_filter = (is_depositary_receipt == False)  
is_primary_share_filter = (is_primary_share == True)  
primary_exchange_id_filter = (primary_exchange_id != "OTCPK")  

# Check for data correctness (i,e. avoid division by zero)  
market_cap_filter = (market_cap > 30000000)  
shares_outstanding_filter = (shares_outstanding > 0)  
free_cash_flow_filter = (free_cash_flow > 0)  
ebitda_margin_filter = (ebitda_margin > 0.15)  
invested_capital_filter = (invested_capital > 0)  
total_debt_equity_ratio_filter = (total_debt_equity_ratio < 0.5)  
cash_and_cash_equivalents_filter = (cash_and_cash_equivalents > 0)  
invested_capital_filter = (invested_capital != cash_and_cash_equivalents)  
shy_filter = ((financing_cash_flow / market_cap) < 0.05)  
roic_filter = ((operating_income / (invested_capital - cash_and_cash_equivalents)) > 0.25)  
evfcf_filter = ((enterprise_value / free_cash_flow) < 10)  

pipe.set_screen (sector_code_filter & country_id_filter & is_depositary_receipt_filter & is_primary_share_filter &         primary_exchange_id_filter & market_cap_filter & shares_outstanding_filter & free_cash_flow_filter & ebitda_margin_filter & invested_capital_filter & total_debt_equity_ratio_filter & cash_and_cash_equivalents_filter & invested_capital_filter & shy_filter & roic_filter & evfcf_filter)  

schedule_function(func=compute_strength_and_rebalance, date_rule=date_rules.every_day())  

#schedule_function(func=rebalance, date_rule=date_rules.every_day())

def quarter_passed(context):

"""  
Screener results quarterly updated  
"""  
return context.days % context.quarter_days == 0  

def rebalance(context, data):

stock_already_bought_not_matching_latest_search = []  
# Exit positions before starting new ones  
#for stock in context.portfolio.positions:  
    exceded_required_quarters = (stock not in context.quarters_alive or context.quarters_alive[stock] >= 2)  
    if stock not in context.output and exceded_required_quarters:  
        if data.can_trade(stock):  
            order_target_percent(stock, 0)  
        if stock in context.quarters_alive:  
            del(context.quarters_alive[stock])  
    elif stock not in context.output:  
        stock_already_bought_not_matching_latest_search.append(stock)  


# Filtering out stocks without data and applying  momentum criteria  
# -0.6745 approximation for the top three-quarters of the market  
context.stocks = [stock for stock in context.stocks  
                  if data.can_trade(stock) and context.relative_strength_6m[stock] > -0.6745]  

# make sure to get out of delisted stocks so they don't sit stagnant in portfolio  
context.stocks = [stock for stock in context.stocks  
                  if (stock.end_date - get_datetime()).days > 100]  

new_stock =  [stock for stock in context.stocks  
                  if stock not in context.portfolio.positions]  

# save price for SL  
save_buy_price(context, data, new_stock)  

context.stocks = context.stocks + stock_already_bought_not_matching_latest_search  

# remove banned stock  
context.stocks = remove_banned_stock(context, context.stocks)  

#if len(context.stocks) == 0:  
    log.info("No Stocks to buy")  
    return  

weight = 1.0/len(context.stocks)

log.info("Ordering %0.0f%% for each of %s (%d stocks)" % (weight * 100, ', '.join(stock.symbol for stock #in context.stocks), len(context.stocks)))  

# buy all stocks equally  
#for stock in context.stocks:  
    #if data.can_trade(stock):  
        if stock not in context.quarters_alive:  
            context.quarters_alive[stock] = 0  
        order_target_percent(stock,  weight)

def save_buy_price(context, data, new_stock):

context.prices = data.history(new_stock, 'price', 1, '1d')  
for stock in new_stock:  
    if stock in context.banned_days and context.banned_days[stock] > 0:  
        continue  
    context.bought_for[stock] = context.prices[stock][0]

def remove_banned_stock(context, stocks):

for stock in stocks:  
    if stock in context.banned_days:  
        if context.banned_days[stock] > 0:  
            stocks.remove(stock)  
return stocks  

def compute_relative_strength(context, data):

prices = data.history(context.security_list + [symbol('SPY')], 'price', 150, '1d')  
# Price % change in the last 6 months  
pct_change = (prices.ix[-130] - prices.ix[0]) / prices.ix[0]  

pct_change_spy = pct_change[symbol('SPY')]  
pct_change = pct_change - pct_change_spy  
if pct_change_spy != 0:  
    pct_change = pct_change / abs(pct_change_spy)  
pct_change = pct_change.drop(symbol('SPY'))  
context.relative_strength_6m = pct_change  

def update_stocks_qarters(context):

for stock in context.quarters_alive:  
    context.quarters_alive[stock] +=1

def update_banned_days(context):

for stock in context.banned_days:  
    context.banned_days[stock] -=1

def day_trading(context, data):

context.prices = data.history(context.all_current_stocks, 'price', 1, '1d')  
for stock in context.portfolio.positions:  
    if stock not in context.bought_for:  
        continue  
    if stock not in context.prices:  
        continue  

    if context.bought_for[stock] == 0.0:  
        continue  

    if len(context.prices[stock]) == 0:  
        continue  

    try:  
        if hit_stop_loss(context, stock):  
            log.info("%s hit stop loss" % stock.symbol)  
            context.banned_days[stock] = 130 #banned for half year  
            order_target_percent(stock, 0)  
    except:  
        pass  

def hit_stop_loss(context, stock):

 pct_change = (context.prices[stock][0] - context.bought_for[stock])/context.bought_for[stock]  
 return pct_change <= -0.15  

def compute_strength_and_rebalance(context, data):

record(num_positions = len(context.portfolio.positions))  
day_trading(context, data)  
update_banned_days(context)  

if not quarter_passed(context):  
    return  
update_stocks_qarters(context)  

#compute_relative_strength(context, data)  
#rebalance(context, data)  

""""""

Clone Algorithm
4
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
# Backtest ID: 5a0f51b03d9bbd44a1a1cd65
There was a runtime error.
1 response

Hello James,

I rewrote the fundamentals query from the original algo using the Pipeline API. You can find the code in the notebook attached.

The next step would be to include the pipeline definition in your algorithm. I would recommend you going through the Pipeline Tutorial, lesson 12 in particular, to learn how to move pipeline definitions from Research to the IDE.

I hope this helps.

Loading notebook preview...
Notebook previews are currently unavailable.
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.