Back to Community
Trailing stop loss with multiple securities

I'm new to python so bear with me. Im attempting to combine a trailing stop loss with mulitpule securities. I put a rough example together with quantopians' fundamental data example and David Edward's trailing stop. The problem I believe is that context.stop_price just creates a stop price for the first security only and because the other stocks are lower than the context.stop_price they sell the next trade day. I would like some information on how to create a trailing stop loss for a variable amount of securities. Any ideas?

Clone Algorithm
79
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. 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

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
    
    context.stop_pct = 0.50
    
    context.stop_price = 0
    
    # 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 and stock in data:
            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 stock in data:
          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)
    
def before_trading_start(context): 
    """
      Called before the start of each trading day. 
      It updates our universe with the
      securities and values found from get_fundamentals.
    """
    
    num_stocks = 50
    
    # Setup SQLAlchemy query to screen stocks based on PE ratio
    # 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.operation_ratios.roe,
            fundamentals.earnings_report.basic_eps,
            fundamentals.operation_ratios.debtto_assets,
            fundamentals.valuation_ratios.book_value_yield,
            fundamentals.asset_classification.morningstar_sector_code,
            fundamentals.asset_classification.value_score,
            fundamentals.valuation.market_cap
            )
        .filter(fundamentals.valuation.market_cap >= 200000000)
        .filter(fundamentals.valuation.shares_outstanding != None)
        .filter(fundamentals.valuation_ratios.pe_ratio <= 15)
        .filter(fundamentals.operation_ratios.roe >=.05)
        .filter(fundamentals.earnings_report.basic_eps >= .05)
        .filter(fundamentals.operation_ratios.debtto_assets <= .5)
        .filter(fundamentals.valuation_ratios.book_value_yield <= 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']
        
        # 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)
        else:
            sector_pe_dict[sector] = []
    
    # Find average PE per sector
    sector_pe_dict = dict([(sectors, np.average(sector_pe_dict[sectors])) 
                               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 set_trailing_stop(context, data):
    for stock in context.stocks:
        if stock in data:
                price = data[stock].price
                context.stop_price = max(context.stop_price, context.stop_pct * price)
            
def handle_data(context, data):
    """
      Code logic to run during the trading day.
      handle_data() gets called every bar.
    """
    record(leverage = context.account.leverage)
    # track how many positions we're holding
    record(num_positions = len(context.portfolio.positions))
    for stock in context.fundamental_df:
        current_position = context.portfolio.positions[stock].amount
        set_trailing_stop(context, data)
        if (data[stock].price < context.stop_price) and (current_position > 0):
            order_target_percent(stock, 0)
            context.stop_price = 0
            log.info("Trail Selling {} at {}".format(stock.symbol, data[stock].price))
There was a runtime error.
10 responses

Hi, You should get the trailstop code for a basket from this thread, scroll down to the latest iteration of the code
https://www.quantopian.com/posts/trailstop-algo-with-200dma-filter-simple-question-thanks

Hi, I may have phrased this question a little incorrectly. I am trying to set up individual trailing stop's for each of the stocks selected. For example, fundamental data would select 10 different stocks at the beginning of each month and eventuallly partition them off as each of their trailing stops is triggered. I believe that my algorithim only creates one trailing stop for one stock and applies it to all other stocks.

Your context.stop_price should be a dictionary indexed by Security objects representing stocks.

Initialize in line 23:

    context.stop_price={}  

Set or update in line 150:

               context.stop_price[stock] = max(context.stop_price[stock] if stock in context.stop_price else 0,  
                                               context.stop_pct * price)  

Use in line 163:

        if (data[stock].price < context.stop_price[stock]) and (current_position > 0):  

Delete in line 165:

            del context.stop_price[stock]  

@Darrell - That code (by David Edwards, posted on Aug. 28, 2014) also uses one scalar variable as a trailing stop for all securities.

Thanks! Runs well. Now just need to think of good place to reinvest cash from trail sells.

Clone Algorithm
79
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. 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

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
    
    context.stop_pct = 0.98
    
    context.stop_price = {}
    
    # 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 and stock in data:
            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 stock in data:
          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)
    
def before_trading_start(context): 
    """
      Called before the start of each trading day. 
      It updates our universe with the
      securities and values found from get_fundamentals.
    """
    
    num_stocks = 50
    
    # Setup SQLAlchemy query to screen stocks based on PE ratio
    # 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.operation_ratios.roe,
            fundamentals.earnings_report.basic_eps,
            fundamentals.operation_ratios.debtto_assets,
            fundamentals.valuation_ratios.book_value_yield,
            fundamentals.asset_classification.morningstar_sector_code,
            fundamentals.asset_classification.value_score,
            fundamentals.valuation.market_cap
            )
        .filter(fundamentals.valuation.market_cap >= 200000000)
        .filter(fundamentals.valuation.shares_outstanding != None)
        .filter(fundamentals.valuation_ratios.pe_ratio <= 15)
        .filter(fundamentals.operation_ratios.roe >=.05)
        .filter(fundamentals.earnings_report.basic_eps >= .05)
        .filter(fundamentals.operation_ratios.debtto_assets <= .5)
        .filter(fundamentals.valuation_ratios.book_value_yield <= 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']
        
        # 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)
        else:
            sector_pe_dict[sector] = []
    
    # Find average PE per sector
    sector_pe_dict = dict([(sectors, np.average(sector_pe_dict[sectors])) 
                               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 set_trailing_stop(context, data):
    for stock in context.fundamental_df:
        if stock in data:
                price = data[stock].price
                context.stop_price[stock] = max(context.stop_price[stock] if stock in context.stop_price else 0, context.stop_pct * price) 
            
def handle_data(context, data):
    """
      Code logic to run during the trading day.
      handle_data() gets called every bar.
    """
    record(leverage = context.account.leverage)
    # track how many positions we're holding
    record(num_positions = len(context.portfolio.positions))
    for stock in context.fundamental_df:
        current_position = context.portfolio.positions[stock].amount
        set_trailing_stop(context, data)
        if (data[stock].price < context.stop_price[stock]) and (current_position > 0):
            order_target_percent(stock, 0)
            del context.stop_price[stock] 
            log.info("Trail Selling {} at {}".format(stock.symbol, data[stock].price))
There was a runtime error.

@André Hi, do you mean that One stop triggering inside the basket triggers All stops in the basket ? I dont see that in the log‘s
Thanks

@Darell What he means is your code does not work on multiple securities. You aren't trading multiple securities in your example so it is somewhat confusing that you would offer it as a solution for someone who specifically wants a trailing stop that will work on multiple securities. There is nothing in your code that directs it to trail a separate stop price for multiple securities. There is only one stop price, because the code is only setup to trade one security. If you are able to trade multiple securities with it, please show us. I think that would be the most useful code anyone has ever posted on here.

@James Thanks for pointing that out, i was suspecting that it was not allright. If the elders here could pitch in and show how a trail stop on a basket is done, that would be nice. Thanks

@Darrell, @James - Read Harrison's code of July 27, where he incorporated my suggestions from the day before. The code does work on multiple securities, selected each day before market open - see lines 124-125, 131 and 134; a list is available in context.stocks. Each stock has its own trailing stop price context.stop_price[stock]set or updated in line 150. When the most recent price data[stock].price is lower, the position in stock only, if long, is liquidated.

And here's a version that liquidates the entire basket if any security falls below its trailing stop price, if possible. Note that Quantopian does not allow us to sell positions which are no longer in the universe, or to sell them before updating the universe in before_trading_start.

In 2008, this algorithm updates the universe at the start of each month, buys, then liquidates later the same day or the next. Every month. The minute mode run does better because positions are liquidated sooner.

Clone Algorithm
27
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. 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 
    5. Maintain a list of trailing stops for all securities.  If any one falls below its trailing stop, liquidate the whole basket.
      Note that Quantopian does not allow to sell positions which are no longer in the universe, or to sell them before updating the universe in before_trading_start.
    
By Harrison Schwartz, amended by Andre Pajak.
"""

import pandas as pd
import numpy as np

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
    
    context.stop_pct = 0.98
    
    context.stop_price = {}
    
    # 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 and stock in data:
            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 stock in data:
          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)
    
def before_trading_start(context): 
    """
      Called before the start of each trading day. 
      It updates our universe with the
      securities and values found from get_fundamentals.
    """
    
    num_stocks = 50
    
    # Setup SQLAlchemy query to screen stocks based on PE ratio
    # 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.operation_ratios.roe,
            fundamentals.earnings_report.basic_eps,
            fundamentals.operation_ratios.debtto_assets,
            fundamentals.valuation_ratios.book_value_yield,
            fundamentals.asset_classification.morningstar_sector_code,
            fundamentals.asset_classification.value_score,
            fundamentals.valuation.market_cap
            )
        .filter(fundamentals.valuation.market_cap >= 200000000)
        .filter(fundamentals.valuation.shares_outstanding != None)
        .filter(fundamentals.valuation_ratios.pe_ratio <= 15)
        .filter(fundamentals.operation_ratios.roe >=.05)
        .filter(fundamentals.earnings_report.basic_eps >= .05)
        .filter(fundamentals.operation_ratios.debtto_assets <= .5)
        .filter(fundamentals.valuation_ratios.book_value_yield <= 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']
        
        # 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)
        else:
            sector_pe_dict[sector] = []
    
    # Find average PE per sector
    sector_pe_dict = dict([(sectors, np.average(sector_pe_dict[sectors])) 
                               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]
    context.liquidate = False
    
    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 set_trailing_stop(context, data):
    for stock in context.fundamental_df:
        if stock in data:
                price = data[stock].price
                context.stop_price[stock] = max(context.stop_price[stock] if stock in context.stop_price else 0, context.stop_pct * price) 
            
def handle_data(context, data):
    """
      Code logic to run during the trading day.
      handle_data() gets called every bar.
    """
    record(leverage = context.account.leverage)
    # track how many positions we're holding
    record(num_positions = len(context.portfolio.positions))
    if context.liquidate:
        return # No more trading today after liquidating
    for stock in context.fundamental_df:
        current_position = context.portfolio.positions[stock].amount
        set_trailing_stop(context, data)
        if (stock in data) and (data[stock].price < context.stop_price[stock]) and (current_position > 0):
            context.liquidate = True
            log.info("Selling all because of {}*{}: {}<{}".format(current_position, stock.symbol, data[stock].price, context.stop_price[stock]))
    if context.liquidate:
        for stock in set(context.stocks) & set( context.portfolio.positions.keys()):        
          if context.portfolio.positions[stock]>0:
            #order_target_percent(stock, 0)
            order_target(stock, 0)
            #del context.stop_price[stock] 
            log.info("Trail Selling {} at {}".format(stock.symbol, data[stock].price if stock in data else "??"))
There was a runtime error.