Backtrader: Sizing a position based on a stop loss

The code snippet in this article will show you how to size a position so that if your stop loss is hit, the impact will result in a fixed % drawdown of your account cash. In other words, we are sizing a position based on a stop loss. For example, we size our position so that we will lose 20% of our available cash when the stop-loss is only 5% away from price.

It is worth noting that this technique mostly requires the use of leverage. This is because we often need to have such large size that it would cost more than the total cash available in our account. As such, this technique is more commonly used in leveraged markets such as Forex. 

Please Note: This post does not take into account available margin and free margin requirements that are needed to open and maintain leveraged positions. For more information on margin in Forex markets see: Backtrader: Oanda Margin and Leverage

Before We Start

The code in this post will be executed on test data specifically created for verifying our code is correct. You can obtain a copy of the test data here: Stop Loss Position Sizing Test Data

The test data contains a short set of daily candles. It is all we need to run the tests. To start, the data will open and close at 100 USD. It will maintain these same prices for 10 days. The reason for this is that it will allow us to enter at exactly 100 USD (because we like easy mathematics!).  It will then drop to 90 for another 10 days before dropping to 50 for the final 10 days. This should provide a nice range of prices for testing long stop losses.

The Magic Formula

The key formula we need to size the position is this:

SIZE = $ AMOUNT TO RISK / (ENTRY PRICE – TARGET EXIT PRICE)

So for example, you might want to risk 10% of $10000. In this case, the dollar amount of risk would be $1000. Now let’s say you want to buy an asset for $100 and have a stop loss at $95. Pushing the numbers through the formula would result in:

  • SIZE = $1000 / (100 – 95)
  • SIZE = $1000 / 5
  • SIZE = 200

Let’s verify the formula is correct. Assuming we bought the asset for $100, the total cost/value of our investment is $100 x 200 which equals $20,000. Side Note: Now we can see why this technique requires a leveraged account. We only started with $10,000! If the price then drops to $95, the value of our investment is worth $95 * 200, which equals $19,000. Assuming we exit at this price it would result in a loss of $1000 which is 10% of our account.

Of course, this is an ideal example. We may not always get filled at our desired level due to gapping or slippage. Additionally, we will choose to round down when the numbers do not divide so nicely. Therefore, we should not expect a perfect drawdown on every trade but we should at least be very close!

Now, I hope you paid attention to the figures in that example because we are going to recreate exactly the same trade in the code!

Code: Sizing a Position Based on a Stop Loss

'''
Author: www.backtest-rookies.com

MIT License

Copyright (c) 2018 backtest-rookies.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''

import backtrader as bt
from datetime import datetime
import math

class TestStrategy(bt.Strategy):

    params = (('risk', 0.1), #risk 10%
        ('stop_dist', 0.05)) #stoploss distance 5%

    def next(self):
        date = self.data.datetime.date()
        bar = len(self)
        cash = self.broker.get_cash()

        if bar == 7:
            stop_price = (self.data.close[0] * (1 - self.p.stop_dist))
            qty = math.floor((cash * self.p.risk) / (self.data.close[0] - stop_price))
            self.buy(size=qty)
            self.sell(exectype=bt.Order.Stop, size=qty, price=stop_price)


    def notify_trade(self, trade):
        date = self.data.datetime.datetime()
        if trade.isclosed:
            print('-'*32,' NOTIFY TRADE ','-'*32)
            print('{}, Avg Price: {}, Profit, Gross {}, Net {}'.format(
                                                date,
                                                trade.price,
                                                round(trade.pnl,2),
                                                round(trade.pnlcomm,2)))
            print('-'*80)

startcash = 10000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(TestStrategy)

#Set commission
cerebro.broker.setcommission(leverage=20)

# Create a Data Feed
data = bt.feeds.GenericCSVData(
    timeframe=bt.TimeFrame.Days,
    compression=1,
    dataname='data/PositionSizingTestData.csv',
    nullvalue=0.0,
    dtformat=('%m/%d/%Y'),
    datetime=0,
    time=-1,
    high=2,
    low=3,
    open=1,
    close=4,
    volume=-1,
    openinterest=-1 #-1 means not used
    )

# Add the data
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(startcash)

# Run over everything
cerebro.run()

#Get final portfolio Value
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

#Print out the final result
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))
print('P/L: {}%'.format(((portvalue - startcash)/startcash)*100))
#Finally plot the end results

cerebro.plot(style='candlestick')

Code Commentary

This post is not a beginners post. Therefore, the commentary will not cover every part in details. If you are new to Backtrader, you can check out the getting started series.

In all honesty, there is not much to write home about on this strategy. In fact, there are only 3 parts really worth mentioning!

  1. Don’t forget to change the data path! i.e  dataname='data/PositionSizingTestData.csv'. You will likely have a different folder structure or saved it under a different name.
  2. We set our leverage during the setup of cerebro. This will ensure we can open the position in the trade. If you want to tweak the amount of leverage used, look for the line cerebro.broker.setcommission(leverage=50)
  3. Our formula for determining the qtylooks slightly different to the example formula at the beginning of the post. qty = math.floor((cash * self.p.risk) / (self.data.close[0] - stop_price)). However, it is the same. Here we are just calculating the dollar amount of risk by multiplying our cash by the percentage we want to risk. We place this in brackets() and it is calculated first before dividing it. We use the close price as our assumed entry level. We know we will get filled at this level because we are using test data but in practice, the price may gap a little between the closeand the openof the next bar when you get filled. Unfortunately, we don’t have a magic 8 ball to help us make the calculation perfect!

References: 

Results

Running the script you should see a chart that looks like this:

Sizing a position based on a stop loss

With the following output:

--------------------------------  NOTIFY TRADE  --------------------------------
2018-01-11 23:59:59.999989, Avg Price: 100.0, Profit, Gross -1000.0, Net -1000.0
--------------------------------------------------------------------------------
Final Portfolio Value: $9000.0
P/L: $-1000.0
P/L: -10.0%

You will notice that the amount lost is exactly 10% of the account. You can go ahead and experiment with different risk and stop loss levels. To do this just tweak the parameters at the start of the strategy accordingly.

    params = (('risk', 0.1), #risk 10%
        ('stop_dist', 0.05)) #stoploss distance 5%

 And that is pretty much all there is to it. If you find any issues or have suggestions, please feel free to voice them in the comments below!

 

Find This Post Useful?

If this post saved you time and effort, please consider donating a coffee to support the site!  

3PUY12Tgp8xynrMCbBdLE56DShzCbxFG8i

0xb90252f1a0af77a43c499102be8a08ce5e190e01

Le3ykk29k2TjD3ZFoEyWTFzJgUu9Q9v6Fq