Back to Community
Using Stop Loss Orders

I have a very simple algorithm that is supposed to buy 5 shares of SPY at the beginning of the month and then sell if the stop loss is triggered (at 5%). Somehow, my algorithm is selling randomly (at least I don't understand what the logic is). My stop loss is implemented in the handle data function/method (Please note that my attached algorithm also contains the StopLoss manager class from this post:
https://www.quantopian.com/posts/how-to-manage-stop-loss
, but I am not using it because I could not get it to work, if someone knows how to get it to work...I would be happy to use that as well)

Does anyone have an idea how I need to modify the algorithm so that it verifies whether there are any open positions and then sell them if the stop loss is reached?

Clone Algorithm
2
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 stategy to demonstrate ordering by fixed weights

"""

# import pipeline methods 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor

# import the built in filters and factors
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.filters import Q500US
from quantopian.pipeline.factors import SimpleMovingAverage

# import any datasets we need
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline.data.builtin import USEquityPricing

# import optimize
import quantopian.optimize as opt

# import numpy and pandas just in case
import numpy as np
import pandas as pd


def initialize(context):
    """
    Called once at the start of the algorithm.
    """
    context.security = symbol('SPY')
    current_positions = context.portfolio.positions[symbol('SPY')].amount
    # Set the constant weight we want
    context.target_leverage = 1.0
    context.target_stock_qty = 50
    context.target_weight = context.target_leverage / context.target_stock_qty
    context.daily_message = "Day {}."
    context.daily_message = 'Current number of shares of SPY: ' + str(current_positions)
    context.day_count = 0
    
   

    # Set up our pipeline
    attach_pipeline(make_pipeline(), 'pipeline')
    
    # # Initializing the StopLoss-Manager       
    #context.SL_Manager = StopLoss_Manager(pct_init=0.000005, pct_trail=0.00003)
    
    # Managing stop-orders for open positions at market opening.
    #schedule_function(context.SL_Manager.manage_orders, date_rules.every_day(), time_rules.market_close())   
    
    
    # order every day, 1 hour after market open.
    schedule_function(
        rebalance,
        date_rules.month_start(),
        time_rules.market_open(hours=1),
        
        )
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation
    on pipeline can be found here:
    https://www.quantopian.com/help#pipeline-title
    """

    # Base universe set to the QTradableStocksUS
    #base_universe = QTradableStocksUS()
    base_universe = Q500US()

    # Factor of yesterday's close price.
    yesterday_close = USEquityPricing.close.latest

    pipe = Pipeline(
        columns={
            'close': yesterday_close,
        },
        screen=base_universe
    )
    return pipe


def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    context.output = pipeline_output('pipeline')
    context.day_count += 1
    log.info(context.daily_message, context.day_count)
    #context.pipeline_data = algo.pipeline_output('data_pipe')
    
    # Let's just take the top stocks (in no particular order)
    #context.my_stocks = context.output.head(context.target_stock_qty).index
    context.my_best_stock_list = context.output.nsmallest(1, 'close').index.tolist()
    


def rebalance(context, data):
    """
    Use Optimize to place orders all at once
    """
    # Specify our series with the weights we want
    # Need to specify BOTH the securities (as the index) and the weight
    
    #reactive my_weights = pd.Series(index=context.my_best_stock_list, data=context.target_weight)
    
    # Create our maximize alpha objective
    #reactive weight_objective = opt.TargetWeights(my_weights) 
    # at the end of rebalance and wherever you create new orders  
    #context.SL_Manager.manage_orders(context, data)  
    # Set the constraints

    
    # Execute the order_optimal_portfolio method with above objective and constraint
    #reactive order_optimal_portfolio(objective = weight_objective, constraints = [])
   
    #context.SL_Manager.manage_orders(context, data)
    #order(stock,share_amount)
    #cash = context.portfolio.cash
    #current_positions = context.portfolio.positions[symbol]('SPY').amount
    current_positions = 0

       
    if current_positions == 0:
        current_positions = 5
        number_of_shares = 5
        order(context.security, number_of_shares)
        log.info('Buying shares')
    elif current_positions !=0:        
        log.info('Selling shares')
       
        #order_target(context.security, 0)
        
    
                                                       

    
    
def handle_data(context, data):
    """
    Called every minute.
    
    """
    current_positions = context.portfolio.positions[symbol('SPY')].amount
    
    if current_positions != 0:
        
        stockPrice = context.portfolio.positions[symbol('SPY')].cost_basis
        stopPrice = stockPrice - (stockPrice*0.01)
        order(context.security, -1, style=StopOrder(stopPrice))

class StopLoss_Manager:
    """
    Class to manage to stop-orders for any open position or open (non-stop)-order. This will be done for long- and short-positions.
    
    Parameters:  
        pct_init (optional),
        pct_trail (optional),
        (a detailed description can be found in the set_params function)
              
    Example Usage:
        context.SL = StopLoss_Manager(pct_init=0.005, pct_trail=0.03)
        context.SL.manage_orders(context, data)
    """
                
    def set_params(self, **params):
        """
        Set values of parameters:
        
        pct_init (optional float between 0 and 1):
            - After opening a new position, this value 
              is the percentage above or below price, 
              where the first stop will be place. 
        pct_trail (optional float between 0 and 1):
            - For any existing position the price of the stop 
              will be trailed by this percentage.
        """
        additionals = set(params.keys()).difference(set(self.params.keys()))
        if len(additionals)>1:
            log.warn('Got additional parameter, which will be ignored!')
            del params[additionals]
        self.params.update(params)
       
    def manage_orders(self, context, data):
        """
        This will:
            - identify any open positions and orders with no stop
            - create new stop levels
            - manage existing stop levels
            - create StopOrders with appropriate price and amount
        """        
        self._refresh_amounts(context)
      
                
        for sec in self.stops.index:
            cancel_order(self.stops['id'][sec])
            if not data.can_trade(sec):
                if self._np.isnan(self.stops['price'][sec]):
                    stop = (1-self.params['pct_init'])*data.current(sec, 'close')
                else:
                    o = self._np.sign(self.stops['amount'][sec])
                    new_stop = (1-o*self.params['pct_trail'])*data.current(sec, 'close')
                    stop = o*max(o*self.stops['price'][sec], o*new_stop)
                
                self.stops.loc[sec, 'price'] = stop           
                self.stops.loc[sec, 'id'] = order(sec, -self.stops['amount'][sec], style=StopOrder(stop))
 
    def __init__(self, **params):
        """
        Creatin new StopLoss-Manager object.
        """
        self._import()
        self.params = {'pct_init': 0.01, 'pct_trail': 0.03}
        self.stops = self._pd.DataFrame(columns=['amount', 'price', 'id'])        
        self.set_params(**params)        
    
    def _refresh_amounts(self, context):
        """
        Identify open positions and orders.
        """
        
        # Reset position amounts
        self.stops.loc[:, 'amount'] = 0.
        
        # Get open orders and remember amounts for any order with no defined stop.
        open_orders = get_open_orders()
        new_amounts = []
        for sec in open_orders:
            for order in open_orders[sec]:
                if order.stop is None:
                    new_amounts.append((sec, order.amount))                
            
        # Get amounts from portfolio positions.
        for sec in context.portfolio.positions:
            new_amounts.append((sec, context.portfolio.positions[sec].amount))
            
        # Sum amounts up.
        for (sec, amount) in new_amounts:
            if not sec in self.stops.index:
                self.stops.loc[sec, 'amount'] = amount
            else:
                self.stops.loc[sec, 'amount'] = +amount
            
        # Drop securities, with no position/order any more. 
        drop = self.stops['amount'] == 0.
        self.stops.drop(self.stops.index[drop], inplace=True)
        
    def _import(self):
        """
        Import of needed packages.
        """
        import numpy
        self._np = numpy
        
        import pandas
        self._pd = pandas
There was a runtime error.
4 responses

What happens with a trailing stop something like this, untested.

def handle_data(context, data):  
    s   = sid(8554)  
    pos = context.portfolio.positions  
    prc = data.current(s, 'price')  
    stp = 0.01      # threshold ratio relative to 1.0

    if pos[s].amount:  
        if prc < pos[s].cost_basis:  
            order_target(s, 0)     # Outright close below cost basis, perhaps.  
        else:                      # At or above cost basis now.  
            stop_exists = 0  
            new_stop    = 0

            for o in get_open_orders(s):  
                if o.stop:  
                    stop_exists = 1  
                    if prc > o.stop + (o.stop * (1.0 + stp))  
                        cancel_order(o.id)      # cancel previous stop order  
                        new_stop = 1  
                    break   # exit loop, altho there should only ever be one stop order

            if new_stop or not stop_exists:  
                order_target(s, 0, style=StopOrder(prc * (1.0 - stp)))    # New stop

Very nice, I am going to try that out. Thanks so much for the help!!

For a faster backtest, checking stops every some-number-of-minutes and loop in case multiple stocks.

def initialize(context):  
    context.stocks = [sid(8554), sid(39840)]  
    context.stp_threshold = .01   # threshold ratio relative to 1.0

    opens_minute    =  5  
    every_n_minutes = 15

    schedule_function(opens, date_rules.every_day(), time_rules.market_open(minutes=opens_minute))

    # Considering open positions next market open, should probably be checking them before opens()  
    #   especially if opens_minute is far greater like 90 or something.  
    # Note opens_minute position here is non-inclusive, only up to opens_minute - 1  
    #   and that's great in this case, leaving ordering in the opens() minute alone by itself.  
    for m in range(1, opens_minute, every_n_minutes):  
        schedule_function(stoploss, date_rules.every_day(), time_rules.market_open(minutes=m))

    # Checking stops periodically, after opens()  
    for m in range(opens_minute + 1, 390, every_n_minutes):  # start, until, every n minutes  
        schedule_function(stoploss, date_rules.every_day(), time_rules.market_open(minutes=m))

def stoploss(context, data):  
    pos = context.portfolio.positions

    for s in pos:  
        if not data.can_trade(s): continue  # skip any delist

        stop_exists = 0  
        new_stop    = 0  
        prc = data.current(s, 'price')

        for o in get_open_orders(s):  
            if o.stop:  
                stop_exists = 1  
                if prc > o.stop + (o.stop * (1.0 + context.stp_threshold))  
                    cancel_order(o.id)      # cancel previous stop order  
                    new_stop = 1  
                break   # exit loop, should only ever be one stop order

        if new_stop or not stop_exists:  
            order_target(s, 0, style=StopOrder(prc * (1.0 - context.stp_threshold)))    # New stop

def opens(context, data):  
    for s in context.stocks:  
        if not data.can_trade(s): continue

        if s not in context.portfolio.positions:  
            order_target_percent(s, 1.0 / len(context.stocks))  # open  

Very nice, thank you for the suggestion. This will speed up things.