Back to Community
Sentdex's Quantopian Tutorials Updated (by me) for Quantopian 2: Algorithm 2 Videos 4-7

This is the second in my series updating Sentdex's Tutorials for Quantopian 2. It covers parts 4-7 of Sentdex's tutorial

(Here's the first.)

This second algorithm is a simple screen for 'value' using fundamental data and again highlights some differences between appropriate structure of algorithms in Quantopian 1 to Quantopian 2.

  1. Instead of using stock in data to ensure that a stock is
    tradeable, you should use data.can_trade(stock)
  2. The fundamental data fetching should be done through the pipeline
    API, not through the query language. There is suppose to be a
    tutorial coming out on the pipeline API shortly. I'm still a little
    uncertain on whether or not the fundamental query syntax is fully
    deprecated or if it still has use cases. If you know please speak
    up!
  3. Lastly, for any daily / weekly / monthly strategy, the trades should
    be executed through some sort of scheduled rebalance function and
    not through a handle_data call. You will notice an extreme difference in backtesting speed between the two.

I also put my own order entry syntax that I think is much cleaner than Sentdex's. I hope you agree.

Finally, I'm 95% certain that the stop-order that he uses in his tutorial is not a stop loss order. What his order is doing is "wait until the stock pulls back another 0.5% to enter the position." If you wanted a true stop-loss order you would have to enter one order to buy and a second order to sell if the trade went against you X%. And since open orders are canceled at the end of each day in backtesting, this would have to be re-entered every day. I have not included stop-loss logic in this one, let me know if you really want to see it and I'll add it in.

Clone Algorithm
75
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
 
class PE_Ratio(CustomFactor):
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pe_ratio]
    window_length = 1

    # Compute market cap value
    def compute(self, today, assets, out, pe_ratio):
        print("Computing PE_ratio")
        out[:] = pe_ratio[-1]
    
class PB_Ratio(CustomFactor):
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pb_ratio]
    window_length = 1

    # Compute market cap value
    def compute(self, today, assets, out, pb_ratio):
        print("Computing PB_ratio")
        out[:] = pb_ratio[-1]

class MarketCap(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [USEquityPricing.close, morningstar.valuation.shares_outstanding] 
    window_length = 1
    
    # Compute market cap value
    def compute(self, today, assets, out, close, shares):       
        out[:] = close[-1] * shares[-1]
        
def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    context.limit = 10 # max number of securities to hold at once

    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=1))
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    attach_pipeline(my_pipeline(context), 'my_pipeline')
      
def my_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    pipe = Pipeline()

    market_cap = MarketCap()    
    pe_ratio = PE_Ratio()
    pb_ratio = PB_Ratio()
    
    pipe.add(pe_ratio, 'pe_ratio')
    pipe.add(pb_ratio, 'pb_ratio')
    pipe.add(market_cap, 'market_cap')
    
    # valuation screen
    isValue = (pe_ratio < 14) & (pb_ratio < 2)
    
    # only want the 10 highest market_cap stocks that pass the valuation screen
    market_cap_rank = market_cap.rank(mask=isValue, ascending=False)
    top10_market_cap = market_cap_rank <= 10
    
    pipe.add(market_cap_rank, 'market_cap_rank')
    
    # set screen
    pipe.set_screen( isValue & top10_market_cap)
     
    return pipe
 
def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    context.output = pipeline_output('my_pipeline')  
    
    # These are the securities that we are interested in trading each day.
    context.signal = (context.output['pe_ratio'] < 11).to_dict()
     
    
def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """    
    #go long assets that pass our screen
    for stock in context.signal:
        # if we have a positive signal, can trade the stock, and don't already have a position
        if context.signal[stock] & data.can_trade(stock) & (stock not in context.portfolio.positions):
            order_target_percent(stock,1./context.limit)
    
    # close positions that are no longer 'value'
    for stock in context.portfolio.positions:
        if data.can_trade(stock) & (stock not in context.output.index):
            order_target(stock,0)
 
def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(Leverage=context.account.leverage,longs=len(context.portfolio.positions))

There was a runtime error.
6 responses

Thanks for these posts, Michael! The explanations are incredibly useful for any new users following along with Sentdex's tutorials. Looking forward to the next post.

@RyanBranch Here's the next post. Glad to hear your that they are useful to you!

Hi Michael, it IS a very useful work you're doing here, thanks for that. I'm new to Python and to Quantopian, so please bear with me for a while.
In the following piece in the end of the code:

for stock in context.portfolio.positions:  
        if data.can_trade(stock) & (stock not in context.output.index):  
            order_target(stock,0)  

What does this part mean (stock not in context.output.index)

I removed the selling part from your code to replicate the exact results from Sentdex Lesson 4-5, but it's not the same. In his case returns were 3.2% and after running this code I get -4% . I wonder why is this difference?

Here is the backtest results from running your code with the same dates as from the Video Lesson - 5

Clone Algorithm
17
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar

class PE_Ratio(CustomFactor):
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pe_ratio]
    window_length = 1

    # Compute market cap value
    def compute(self, today, assets, out, pe_ratio):
        print("Computing PE_ratio")
        out[:] = pe_ratio[-1]
    
class PB_Ratio(CustomFactor):
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pb_ratio]
    window_length = 1

    # Compute market cap value
    def compute(self, today, assets, out, pb_ratio):
        print("Computing PB_ratio")
        out[:] = pb_ratio[-1]

class MarketCap(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [USEquityPricing.close, morningstar.valuation.shares_outstanding] 
    window_length = 1
    
    # Compute market cap value
    def compute(self, today, assets, out, close, shares):       
        out[:] = close[-1] * shares[-1]


 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    context.limit = 10 # max number of securities to hold at once

    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=1))
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    attach_pipeline(my_pipeline(context), 'my_pipeline')
      
def my_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    pipe = Pipeline()

    market_cap = MarketCap()    
    pe_ratio = PE_Ratio()
    pb_ratio = PB_Ratio()
    
    pipe.add(pe_ratio, 'pe_ratio')
    pipe.add(pb_ratio, 'pb_ratio')
    pipe.add(market_cap, 'market_cap')
    
    # valuation screen
    isValue = (pe_ratio < 14) & (pb_ratio < 2)
    
    # only want the 10 highest market_cap stocks that pass the valuation screen
    market_cap_rank = market_cap.rank(mask=isValue, ascending=False)
    top10_market_cap = market_cap_rank <= 10
    
    pipe.add(market_cap_rank, 'market_cap_rank')
    
    # set screen
    pipe.set_screen( isValue & top10_market_cap)
     
    return pipe

def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    context.output = pipeline_output('my_pipeline')  
    
    # These are the securities that we are interested in trading each day.
    context.signal = (context.output['pe_ratio'] < 11).to_dict()
     
    

def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """    
    #go long assets that pass our screen
    for stock in context.signal:
        # if we have a positive signal, can trade the stock, and don't already have a position
        if context.signal[stock] & data.can_trade(stock) & (stock not in context.portfolio.positions):
            order_target_percent(stock,1./context.limit)
    
    # close positions that are no longer 'value'
"""  for stock in context.portfolio.positions:
        if data.can_trade(stock) & (stock not in context.output.index):
            order_target(stock,0)
"""
def my_record_vars(context, data):
    record(Leverage=context.account.leverage, longs=len(context.portfolio.positions))
 
    """
    Plot variables at the end of each day.
    """
     



 
    
    
"""def my_rebalance(context,data):
   
    #Execute orders according to our schedule_function() timing. 
   
    cash = context.portfolio.cash
    current_positions = context.portfolio.positions
    for stock in context.fundamentals:
        current_position = context.portfolio.positions[stock].amount
        stock_price = data.current(stock, 'price')
        plausible_investment = cash / 10.0
        share_amount = int(plausible_investment / stock_price)
        
        try:
            if data.can_trade(stock) and stock_price < plausible_investment:
                if current_position == 0:
                    if context.fundamentals[stock]['pe_ratio'] < 11:
                        ordet(stock, share_amount)
            
            
            
        except Exception as e:
            print str((e))
""" 
def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    
 
def handle_data(context,data):
    """
    Called every minute.
    """
    pass
There was a runtime error.

This one gives almost the same result as in the video 5. Thanks again

Clone Algorithm
17
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar

class PE_Ratio(CustomFactor):
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pe_ratio]
    window_length = 1

    # Compute market cap value
    def compute(self, today, assets, out, pe_ratio):
        print("Computing PE_ratio")
        out[:] = pe_ratio[-1]
    
class PB_Ratio(CustomFactor):
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pb_ratio]
    window_length = 1

    # Compute market cap value
    def compute(self, today, assets, out, pb_ratio):
        print("Computing PB_ratio")
        out[:] = pb_ratio[-1]

class MarketCap(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [USEquityPricing.close, morningstar.valuation.shares_outstanding] 
    window_length = 1
    
    # Compute market cap value
    def compute(self, today, assets, out, close, shares):       
        out[:] = close[-1] * shares[-1]


 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    context.limit = 10 # max number of securities to hold at once

    schedule_function(my_rebalance, 
                      date_rules.every_day(), 
                      time_rules.market_open())
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    attach_pipeline(my_pipeline(context), 'my_pipeline')
      
def my_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    pipe = Pipeline()

    market_cap = MarketCap()    
    pe_ratio = PE_Ratio()
    pb_ratio = PB_Ratio()
    
    pipe.add(pe_ratio, 'pe_ratio')
    pipe.add(pb_ratio, 'pb_ratio')
    pipe.add(market_cap, 'market_cap')
    
    # valuation screen
    isValue = (pe_ratio < 14) & (pb_ratio < 2)
    
    # only want the 10 highest market_cap stocks that pass the valuation screen
    market_cap_rank = market_cap.rank(mask=isValue, ascending=False)
    top10_market_cap = market_cap_rank <= 10
    
    pipe.add(market_cap_rank, 'market_cap_rank')
    
    # set screen
    pipe.set_screen( isValue & top10_market_cap)
     
    return pipe

def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    context.output = pipeline_output('my_pipeline')  
    
    # These are the securities that we are interested in trading each day.
    context.signal = (context.output['pe_ratio'] < 11).to_dict()
     
    

def my_rebalance(context,data):
    cash = context.portfolio.cash
    
    
    """
    Execute orders according to our schedule_function() timing. 
    """    
    #go long assets that pass our screen
    for stock in context.signal:
        plausible_investment = cash / 10.0
        stock_price = data.current(stock, 'price')
        share_amount = int(plausible_investment / stock_price)
        stop_price = stock_price - stock_price * 0.005
        # if we have a positive signal, can trade the stock, and don't already have a position
        if context.signal[stock] & data.can_trade(stock) & (stock not in context.portfolio.positions):
            order_target(stock, share_amount, style=StopOrder(stop_price))
    
    # close positions that are no longer 'value'
    for stock in context.portfolio.positions:
        if data.can_trade(stock) & (stock not in context.output.index):
            order_target(stock,0)

def my_record_vars(context, data):
    record(Leverage=context.account.leverage, longs=len(context.portfolio.positions))
 
    """
    Plot variables at the end of each day.
    """
     



 
    
    
"""def my_rebalance(context,data):
   
    #Execute orders according to our schedule_function() timing. 
   
    cash = context.portfolio.cash
    current_positions = context.portfolio.positions
    for stock in context.fundamentals:
        current_position = context.portfolio.positions[stock].amount
        stock_price = data.current(stock, 'price')
        plausible_investment = cash / 10.0
        share_amount = int(plausible_investment / stock_price)
        
        try:
            if data.can_trade(stock) and stock_price < plausible_investment:
                if current_position == 0:
                    if context.fundamentals[stock]['pe_ratio'] < 11:
                        ordet(stock, share_amount)
            
            
            
        except Exception as e:
            print str((e))
""" 
def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    
 
def handle_data(context,data):
    """
    Called every minute.
    """
    pass

There was a runtime error.

Thanks man!