Home > Backtesting, R > Stop Loss

Stop Loss

Today I want to share and present an example of the flexible Stop Loss functionality that I added to the Systematic Investor Toolbox.

Let’s examine a simple Moving Average Crossover strategy:

  • Buy is triggered once fast moving average crosses above the slow moving average
  • Sell is triggered once fast moving average crosses below the slow moving average

Here is an example using 20 and 50 day moving averages on SPY.

###############################################################################
# Load Systematic Investor Toolbox (SIT)
# https://systematicinvestor.wordpress.com/systematic-investor-toolbox/
###############################################################################
setInternet2(TRUE)
con = gzcon(url('http://www.systematicportfolio.com/sit.gz', 'rb'))
    source(con)
close(con)
 
    #*****************************************************************
    # Load historical data
    #******************************************************************  
    load.packages('quantmod')  

    tickers = spl('SPY')	
	
    data <- new.env()
        getSymbols(tickers, src = 'yahoo', from = '1970-01-01', env = data, auto.assign = T)
        for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T)		
    bt.prep(data, align='keep.all', dates='1999::')

    #*****************************************************************
    # Code Strategies
    #****************************************************************** 
    prices = data$prices   
	
    models = list()
	
    #*****************************************************************
    # Code Strategies
    #****************************************************************** 
    data$weight[] = NA
        data$weight[] = 1
    models$buy.hold = bt.run.share(data, clean.signal=T)

    #*****************************************************************
    # Code Strategies : MA Cross Over
    #****************************************************************** 
    sma.fast = SMA(prices, 20)
    sma.slow = SMA(prices, 50)

    buy.signal = iif(cross.up(sma.fast, sma.slow), 1, NA)
	
    data$weight[] = NA
        data$weight[] = iif(cross.up(sma.fast, sma.slow), 1, iif(cross.dn(sma.fast, sma.slow), 0, NA))
    models$ma.cross = bt.run.share(data, clean.signal=T, trade.summary = TRUE)

Next, I created the custom.stop.fn() function in bt.stop.r at github to handle user defined stop functions. Let’s have a look at 3 different flavors of stops below:

    #*****************************************************************
    # User Defined Stops
    #****************************************************************** 
    # fixed stop: exit trade once price falls below % from entry price
    fixed.stop <- function(weight, price, tstart, tend, pstop) {
        index = tstart : tend
        if(weight > 0)
            price[ index ] < (1 - pstop) * price[ tstart ]
        else
            price[ index ] > (1 + pstop) * price[ tstart ]
    }
	
    # trailing stop: exit trade once price falls below % from max price since start of trade
    trailing.stop <- function(weight, price, tstart, tend, pstop) {
        index = tstart : tend
        if(weight > 0) 
            price[ index ] < (1 - pstop) * cummax(price[ index ])
        else
            price[ index ] > (1 + pstop) * cummin(price[ index ])
    }	
	
    # trailing stop: exit trade once price either
    # - falls below % from max price since start of trade OR
    # - rises above % from entry price
    trailing.stop.profit.target <- function(weight, price, tstart, tend, pstop, pprofit) {
        index = tstart : tend
        if(weight > 0) {
            temp = price[ index ] < (1 - pstop) * cummax(price[ index ])
			
            # profit target
            temp = temp | price[ index ] > (1 + pprofit) * price[ tstart ]
        } else {
            temp = price[ index ] > (1 + pstop) * cummin(price[ index ])
			
            # profit target
            temp = temp | price[ index ] < (1 - pprofit) * price[ tstart ]		
        }
        return( temp )	
    }

Each user defined stop function have 4 required inputs:

  • weight – signal or weight indicating position
  • price – price for asset
  • tstart – index(time) when trade was open
  • tend – index(time) when trade is closed

Additionally, you can provide any other required information.

For example, the fixed.stop function above, will exit the trade once price falls below given % (pstop) from entry price or trade is closed. The trailing.stop.profit.target function will exit the trade once either:

  • trailing stop is reached. i.e. price falls below given % (pstop) from max price since start of trade OR
  • profit target is reached. i.e. price rises above given % (pprofit) from entry price

Following is an example of integrating these stop losses into the back-tests. Please note that I only use the Buy rule from the original strategy to open the trades and to close the trades I use the stop loss logic.

    #*****************************************************************
    # Exit using fixed stop
    #****************************************************************** 
    data$weight[] = NA
        data$weight[] = custom.stop.fn(coredata(buy.signal), coredata(prices), fixed.stop, 
		pstop = 1/100)
    models$ma.cross.fixed.stop = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
		
    #*****************************************************************
    # Exit using trailing stop
    #****************************************************************** 
    data$weight[] = NA
    	data$weight[] = custom.stop.fn(coredata(buy.signal), coredata(prices), trailing.stop, 
		pstop = 1/100)
    models$ma.cross.trailing.stop = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
	
    #*****************************************************************
    # Exit using trailing stop or profit target
    #****************************************************************** 		
    data$weight[] = NA
    	data$weight[] = custom.stop.fn(coredata(buy.signal), coredata(prices), trailing.stop.profit.target, 
		pstop = 1/100, pprofit = 1.5/100)
    models$ma.cross.trailing.stop.profit.target = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
	
    #*****************************************************************
    # Create Report
    #****************************************************************** 	
    strategy.performance.snapshoot(models, T)

plot1

Nothing exciting to report. The fixed stop loss is not triggered often and resembles the Buy and Hold. The trailing stops reduce the time strategy is in the market and also reduce returns. The trailing stop with profit target is doing a bit better.

We can see functionality of the stops more clearly in the picture below:

    #*****************************************************************
    # Create Plot
    #****************************************************************** 	
    dates = '2010::2010'
    # add moving averages to the strategy plot
    extra.plot.fn <- function() {
	plota.lines(sma.fast, col='red')
	plota.lines(sma.slow, col='blue')
    }
	
    layout(1:4)
    bt.stop.strategy.plot(data, models$ma.cross, dates = dates, layout=T, main = 'MA Cross', extra.plot.fn = extra.plot.fn, plotX = F)
    bt.stop.strategy.plot(data, models$ma.cross.fixed.stop, dates = dates, layout=T, main = 'Fixed Stop', plotX = F)
    bt.stop.strategy.plot(data, models$ma.cross.trailing.stop, dates = dates, layout=T, main = 'Trailing Stop', plotX = F)
    bt.stop.strategy.plot(data, models$ma.cross.trailing.stop.profit.target, dates = dates, layout=T, main = 'Trailing Stop and Profit Target')	

plot2

The shaded green area indicates the times when strategy is invested. In the top chart, the strategy is long once fast moving average (red line) is above the slow moving average (blue line).

The next chart (Fixed Stop) is fully invested because market has taken off since we entered the trade and stop, which is % relative to the trade entry price, is never triggered.

The last 2 charts show the trailing stop logic. Strategy is less often invested once we enforce the aggressive profit target.

Have fun with creating your own customized stop loss functions and let me know if you run into problems.

To view the complete source code for this example, please have a look at the bt.stop.ma.cross.test() function in bt.stop.test.r at github.

Advertisement
Categories: Backtesting, R
  1. July 30, 2013 at 5:02 pm

    Very nice coding. I like Your backtest framework very much.

    How would You integrate timing-systems like this stopploss into Your assetallocation-backtest-framework ( something like: “monthly maxSharpeRatio -assetallocation – but when stopped out the stopped exposure is given to a predefined “min-Risk title” like TLT … )
    I tried it by myself but I’m not sure that it’s the best way: added Tstops – a binary-vector that is 0 when stopped out and 1 when not stopped out) – to bt.run() and modified weight to weight*Tstops.

    does this make sences or are there better ways to integrate tactical timing and more strategical asset allocation into a combinded solution ?)

    best regards
    Markus

    • July 31, 2013 at 1:37 am

      Markus, thank you. Yes, this is one way to do it. Alternatively, you can apply new stop loss function to each asset class and next allocate to TLT any leftover weight.

  2. Philip
    August 2, 2013 at 9:36 pm

    Dear,

    I absolutely love your blog. Beeing an absolute newbie in R it has really helped me to see the power of R.

    I have some questions regarding your script here.

    First of all, would it be possible to base the buy/stop loss signal on the closing price (like it is right now) but then base the actual price at which this happens on the next day opening?

    Secondly, would it be possible to create add to the program a reenter point? I will attempt to clarify with an example.
    The fast moving SMA crosses the slow moving SMA thus we buy. After that we get stopped out by a trailing stop loss. But at the point where the stop loss is hit the original strategy is still valid (Fast moving SMA is above the slow moving SMA). Would it be possible to create a rebuy point when the price rises X% above the stop loss?

    Thank you in advance for any help you could give me!

  3. April 11, 2021 at 12:20 am

    Excellent post. I’ve found that the type of trailing stop used can make a big difference to returns.

    If you are interested I did some of my own research into trailing stops. I found that a 20% trailing stop outperforms most other types of trailing stops such as Chandelier, ATR stop and moving average stops. The Parabolic SAR indicator actually did a surprisingly good job too. You can see the results here: https://decodingmarkets.com/which-trailing-stop-is-the-best/

  1. January 14, 2014 at 3:13 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: