Back to Community
Adding ATR trailing stop to algo

If i want to calculate an ATR 14 trailing stop..to say AAPL, could i use TA-Lib as follows:

atr14 = ta.ATR(timeperiod=14)

atr_14 = atr14(data)

aapl_atr14 = atr_14[context.aapl]

Would this work?

Is there another method for calculating ATR trailing stops?

Thanks for the input!

14 responses

Peter,
Need guidance on some EXIT logic using the ATR stop on Daily data for aapl (out of a basket of 10 stocks):

  • If LONG aapl, Then
  • .. If Close Close + aapl_atr14
    -...Then Sell entire position

How would this look in code?

Thanks!

Conversely:
- If SHORT aapl, Then
- .. If Close > Close + aapl_atr14
-...Then Sell entire position

...basically need to know if there's a parameter i can call that checks if:

  1. I have a position in the security either LONG or SHORT... (from a set of securities)
  2. ..if so....If I"m LONG or SHORT in that security
  3. Then add the logic:
  4. If LONG then: If Close (is less than) Close -aapl_atr14, Then SELL entire position
  5. If SHORT then: If Close (is greater than) Close +aapl_atr14, Then SELL entire position

How would 4 and 5 look in code?

Thanks again!

Hello Adam,

(Sorry - I deleted the wrong post.)

I've made a start so maybe you can build on this? I've disabled the volume slippage model (I think) otherwise the algo would keep selling the same security repeatedly in the case of partial fills.

P.

Clone Algorithm
26
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
import math

atr14 = ta.ATR(timeperiod=14)

def initialize(context):
    context.stocks = [sid(2),  sid(21), sid(24), sid(31), sid(32), \
                      sid(37), sid(39), sid(40), sid(41), sid(47)]
    context.start = True
    set_slippage(slippage.VolumeShareSlippage(volume_limit=1000000, price_impact=0))
    
def handle_data(context, data):
    if context.start:
        # Open some long positions
        position_value = context.portfolio.cash / len(context.stocks)
        for stock in context.stocks:
            if stock.security_end_date > get_datetime():
                qty = math.floor(position_value / data[stock].close_price)
                order(stock, qty)
                log.info("Ordering %s shares of %s" % (qty, stock.symbol))
        context.start = False
    
    atr_14 = atr14(data)
    
    for stock in context.stocks:
        if stock.security_end_date < get_datetime():
            break
        atr = atr_14[stock]
        if context.portfolio.positions[stock]['amount'] > 0:
            if data[stock].close_price < context.portfolio.positions[stock]['cost_basis'] - atr:
                qty = context.portfolio.positions[stock]['amount']
                order(stock, - qty)
                log.info("Closing LONG position of %s shares of %s" % (qty, stock.symbol))
 
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Thanks Peter...will surely take a look! Would "context.portfolio.positions[stock]" define 1) what open positions i have? 2) whether each of those open positions are long OR short.....given a long/short strategy?

Hello Adam,

I believe that

 context.portfolio.positions[stock]  

is the net position for the security, with a short position denoted by a -ve amount. So purchasing 100 shares at $100 and 100 shares later at $120 would give an 'amount' of 200 and a 'cost_basis' of $110. It's an assumption I haven't checked thoroughly.

P.

Hello Adam,

Here is an algo that enters trades based on candle patterns and exits with ATR stops. It may give you some further ideas.

(I've just realised it's a bad teaching example. The '-100, 100' indicator values have nothing to do with the '-100, 100' position sizes.)

P.

Clone Algorithm
263
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
import zipline.transforms.ta as ztt

OpenOrders = {}

@batch_transform(window_length=20, refresh_period=0)
def get_data(datapanel):
    Open   = datapanel['open_price']
    High   = datapanel['high']
    Low    = datapanel['low']
    Close  = datapanel['close_price']
    Volume = datapanel['volume']
    return Open, High, Low, Close, Volume

def initialize(context):
    context.stocks = [sid(26126), sid(12160), sid(6683), sid(12882), sid(20281), \
                      sid(2518),  sid(7696),  sid(9883), sid(6413),  sid(754)]
    
def handle_data(context, data):
    # Get a rolling window of price and volume data
    results = get_data(data)
    if results is None:
        return
    Open, High, Low, Close, Volume = results[0], results[1], results[2], results[3], results[4] 
        
    for stock in data:
        # Check the security is still trading
        if stock.security_end_date < get_datetime():
            # Skip this iteration of the 'for' loop
            continue
        
        # Calculate some indicators
        ATR        = float(ztt.talib.ATR(High[stock], Low[stock],Close[stock], timeperiod=5)[-1:])
        ThreeWhiteSoldiers     = ztt.talib.CDL3WHITESOLDIERS(Open[stock], High[stock], Low[stock], Close[stock])[-1:]
        IdenticalThreeCrows    = ztt.talib.CDLIDENTICAL3CROWS(Open[stock], High[stock], Low[stock], Close[stock])[-1:]
        
        # Do we have a position in this securiy?
        if stock in OpenOrders.keys():
            cost     = context.portfolio.positions[stock]['cost_basis']
            position = context.portfolio.positions[stock]['amount']
            if OpenOrders[stock]['Type'] == 'Long':
                # Should we close a LONG position?
                if data[stock].close_price < cost - ATR:
                    order(stock, -position)
                    print "STOPPED OUT - LONG"
                    del OpenOrders[stock]
            else:
                # Should we close a SHORT position?
                if data[stock].close_price > cost + ATR:
                    order(stock, -position)
                    print "STOPPED OUT - SHORT"
                    del OpenOrders[stock]
                    #print OpenOrders
            # Skip this iteration of the 'for' loop    
            continue
        
        if context.portfolio.positions[stock]['amount'] <> 0 and stock not in OpenOrders.keys():
            # We have an open position in a security we think we have closed i.e. a pending order
            # so skip this iteration of the 'for' loop
            log.info("Partial fill for %s shares of %s" % (context.portfolio.positions[stock]['amount'], stock.symbol))
            continue
        
        if IdenticalThreeCrows == -100:
            # Enter a SHORT position
            order(stock, -100)
            OpenOrders[stock] = {'Type' : 'Short'}
        elif ThreeWhiteSoldiers == 100:
            # Enter a LONG position
            order(stock, 100)
            OpenOrders[stock] = {'Type' : 'Long'}
           
    record(Cash=context.portfolio.cash)
    
    
    
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

for @batch_transform(window_length=20, refresh_period=0)....does '20' corresponding to last 20 trading days? For 'refresh_period' , if i want to check it on a daily basis, would i enter '1'? what does refresh_period = 0 correspond to?

...Also if i wanted to add in , say, 2 more exit signals? Such that i have 3 total exit signals, and whichever is triggered first, i will stop out of that postion? could you provide some insight how that would look in code?
Thanks!

Hello Adam,

Yes, the '20' denotes the length of a rolling windows of trading days. To see what 'batch_transform is doing run this:

@batch_transform(window_length=4, refresh_period=0)  
def get_data(datapanel):  
    Close  = datapanel['close_price']  
    return Close

def initialize(context):  
    context.stocks =[sid(2)]  
def handle_data(context, data):  
    Close = get_data(data)  
    if Close is None:  
        return  
    print Close  

You will see that in daily mode the batch transform updates each day for a frequency of 0 and 1. Try a frequency of 2.

To add more exit signals imply use the 'or' syntax:

if exit_condition_1 or \  
        exit_condition_2 0r \  
        exit_condition_3:  
    order(stock, -position)  

P.

Thanks Peter..that's exactly what i was looking for. Had another question:

  1. How to calculate daily drawdowns on a) current positions b) portfolio?
  2. How to calculate 1% of a given position?

Could you give some guidance what the syntax would look like?
Thanks much!

Hello Peter,
Can you possibly reply to above post?

Thanks!

Hello Adam,

I haven't done any drawdown calculations yet, sorry. There is a post here that might help: https://www.quantopian.com/posts/calculation-of-cumulative-max-drawdown

To calculate 1% of a position in a particular security use:

data[sid(24)].close_price * context.portfolio.positions[sid(24)].amount * 0.01  

P.

Hi Peter,
Your algo above has given me an idea how to solve my problem with comparing past values with new values that you tried to solve for me earlier.
Thanks
Omar