4 practical methods to set your stop-loss when algo-trading Bitcoin

Often times you hear about the importance of setting a stop-loss before opening trades. But if you are a quant, I assume you already know the importance of it.

You may use stop-loss for two reasons:

  • Setting your stop-loss at the beginning of opening a trade
  • Exiting your open positions with (some kind of) trailing stop

Note #1: A stop order can be used for opening trades too, which is an advanced usage of a stop order. I will not be talking about that in this tutorial. 

Note #2: As in all my tutorials, I'll be using the Python language and the JesseAI framework. Both are open-source and free to get started. 

Whether your strategy uses (some kind of) a trailing stop to exit or it exits at a specific price, it doesn't matter for now. But the idea of setting a stop-loss below your entry price is just non-negotiable. In my opinion, your strategies are flawed if they don't use it.

Why is the exact stop-loss price so crucial?

Let's say you decide to use a stop-loss. But what would be your exact stop price? Does it really matter? Yes! It is crucial actually. 

While increasing the margin of your stop-loss will increase the win-rate of your strategy, it will reduce the size of your position. Smaller positions sizes mean smaller profit in the long term. And of course, long-term profit is what algo-trading is all about.

I'll explain this with an example: 

Image your total capital is 10,000 and you intend to risk 3% of it per trade.  Let's say your strategies tell you to enter the market at $100, and to set your stop-loss at $80 (in this article I'm covering ways to determine the exact price). How much should the size of your position be? 

Since talk is cheap, I'm gonna anwer with the code:

entry_price = 100
stop_price = 80
risk_percentage = 0.03
capital_size = 10000
risk_per_qty = 100 - 80 # 20

position_size = ((risk_percentage * capital_size) / risk_per_qty) * entry_price

position_qty = position_size / entry_price # 15

Thus the position quantity should be 15 so that I only risk 3% of my capital which is $300.  Now let's see what will happen if I tighten my stop-loss and still risk 3% ($300). Let's say I put my stop loss at $90 instead:

entry_price = 100
stop_price = 90
risk_percentage = 0.03
capital_size = 10000
risk_per_qty = 100 - 90 # 10

position_size = ((risk_percentage * capital_size) / risk_per_qty) * entry_price

position_qty = position_size / entry_price # 30

Note that I'm still risking $300 per each trade but my position size is doubled, and so is the possible profit (if it turns to be a profitable trade). But of course, a tighter stop-loss means you'll get stopped out of your trades more often which means the win-rate will be reduced. 

You want your stop-loss not be too tight, neither too loose. 

I have created a small website that lets you play with this formula to fully understand how position sizing works. 

Now I want you to talk about 4 ways that I use to determine the exact stop-loss in my algo strategies. 

1. ATR indicator

The Average True Range is my favorite indicator for setting a dynamic stop-loss. On a chart, it might seem a little scary at first:

ATR indicator (ATR indicator on TradingView)

As you can see, the ATR value changes as the volatility of the price does. This is the key reason why I'm using it for stop-loss. 

The stop-loss price must be set at a level to avoid getting hit by market noise. Volatility and noise go hand in hand. 

Using ATR is actually easier when algo-trading than in trading manually. In below example which is for a long trade, I set my stop-loss 3*ATR away from the entry price:

# get the ATR indicator value for current candle
atr = ta.atr(self.candles)

# enter at current price with a market order
entry = self.price 

stop = entry - (atr * 3)

Depending on the timeframe you are trading, you might need to change the multiplier. I used 3 for the 4h timeframe. For smaller timeframes, you need to decrease the multiplier. 

2. Moving average 

Moving averages are like the "hello world" of technical trading. But not all know that they can use them for stop-loss too. 

For example, we could use two EMAs in our strategy, one with a smaller period for entry, and one with a bigger period for stop-loss. 

Moving average  (3h timeframe ETHUSD - blue line: EMA50 - Purple line: EMA100)

In the above example, we could have bought at the EMA50 which would have been when there was blood on the market, and used EMA100 as our stop-loss.

import jesse.indicators as ta

entry = ta.ema(self.candles, 50)
stop = ta.ema(self.candles, 100)

This is a tricky strategy and requires careful optimizing; but if done right, it could give you a high risk to reward ratio. 

3. The previous N bar's lowest price

The market has a short-term memory. It remembers the price levels of previous candles up to a few days sometimes.  I can't put my finger on the fact why this stop-loss method works, but it usually does. You could use the lowest price of the previous N bars as a minimum level you would expect the price affected by noise to reach. Hence it might be a good idea to put your stop-loss either at that level or even slightly below it. 

Determining the lowest of N bars is easy with the naked eye:

Moving average  (The purple arrow indicates entry price - Blue line indicates the stop-loss price)

What about the code? Well, I use the Jesse framework, and it's easy to accomplish this. Let's say we want to use the lowest price of the last N=20 bars:

# "4" is the index of low of the candle in Jesse's strategy API. 
# for more, read https://docs.jesse-ai.com/docs/strategies/api.html#current-candle
last_20_lows = self.candles[-20:, 4]

previous_low = np.min(last_20_lows)

Notice that self.candles returns a numpy array as is explained in Jesse's documentation. 

4. Previous day's low

This one is the simplest and easiest to write the code for; and yet it works pretty well sometimes. Especially if you're a day trader.

First, you need to tell Jesse to load candles for 1D timeframe at your routes.py file's extra_candle list. Here's an example for BTCUSD:

# My trading route is "4h". 
routes = [
    ('Bitfinex', 'BTCUSD', '4h', 'TrendFollowingStrategy'),

# I need to add an extra route to "extra_candles" below:
extra_candles = [
    ('Bitfinex', 'BTCUSD', '1D'),

Now in the strategy, first I select all daily candles, then the previous day's candle, and then pick the low price of it (index=4):

# docs for self.get_candles: 
# https://docs.jesse-ai.com/docs/strategies/api.html#get-candles
all_daily_candles = self.get_candles(self.exchange, self.symbol, '1D')

# notice that "-1" would have have given me the current candle, 
# hence "-2" gives me the one before that which is yesterday's
previous_day_candle = all_daily_candles[-2]

previous_day_candle_low = previous_day_candle[4]


The final question might be, which one of the mentioned methods is the best? Well, I can't say for sure. What I personally do, is to try them all out every time I develop a new strategy.

You'll be surprised how much the result varies. Each method might work well on a specific market and timeframe. For example, the ATR method has worked well for me on BTCUSD on the 4h timeframe; and the moving average method has worked well on ETHUSD on a 3h timeframe.

You could also use Jesse's optimize mode and let the algorithm decide which stop-loss method to use! The optimize mode however is not out yet. Make sure to subscribe to be notified when it is released.

Credits: Featured image by Chris Liverani on Unsplash