Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

    Help with Stop-Loss

    General Code/Help
    3
    4
    114
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • KonstantinKlychkov
      KonstantinKlychkov last edited by

      Hi there!

      I've been reading documentation and articles about a Stop-loss in Backtrader since the start of the week, and to be honest, it's far away from my mind.

      I've tried to follow by examples but something wrong happened and my strategy sells shares into the minus. Please help with understanding the Stop-loss realization in Backtrader.

      Here is my code:

      import pandas as pd
      import numpy as np
      
      import aiomoex
      import asyncio
      
      import plotly
      import matplotlib.pyplot as plt
      import matplotlib.pylab as pylab
      %matplotlib inline
      
      import backtrader as bt
      
      from datetime import datetime
      
      """
      Download data from MOEX.
      """
      async def load_candles(security_tiker, candle_interval, start_date, end_date):
          async with aiomoex.ISSClientSession():
              data = await aiomoex.get_market_candles(security=security_tiker, interval=candle_interval, start=start_date, end="2020-06-23", market='shares', engine='stock')
              security_df = pd.DataFrame(data)
          return security_df
      
      # set variables for downloading equities
      start_date = "2019-07-01"
      end_date = "2020-07-01"
      candle_interval = 1
          #1 - 1 minute
          #10 - 10 minutes
          #60 - 1 hour
          #24 - 1 day
          #7 - 1 week
          #31 - 1 month
          #4 - 1 quater
      
      benchmark = await load_candles("FXIT", candle_interval, start_date, end_date)
      test_security = await load_candles("FXIT", candle_interval, start_date, end_date)
      
      # do Backtrader-like data
      test_security_backtrader = test_security.filter(['end','open','high', 'low', 'close', 'value'])
      test_security_backtrader.columns = ['date','open','high', 'low', 'close', 'volume']
      test_security_backtrader['openInterest'] = 0
      
      test_security_backtrader['date'] = test_security_backtrader.astype('datetime64[ns]')
      
      
      '''
      Test trading strategy with Backtrader
      '''
      class MyStrategy(bt.Strategy):
          # list of parameters which are configurable for the strategy
          params = (('EMA_fast_period', 26),
                    ('EMA_slow_period', 287),
                    ('trail', 0.02)
                   )
      
          def __init__(self):
              # keep track of pending orders/buy price/buy commission
              self.order = None
              self.price = None
              self.comm = None
              
              # add an indicators
              self.EMA_fast = bt.indicators.EMA(self.data, period=self.p.EMA_fast_period)
              self.EMA_slow = bt.indicators.EMA(self.data, period=self.p.EMA_slow_period)
              self.Trend = bt.ind.CrossOver(self.EMA_fast, self.EMA_slow)  # crossover signal
          
          def log(self, txt):
              #Logging function
              dt = self.datas[0].datetime.datetime(0)
              print('%s, %s' % (dt.strftime('%Y-%m-%d %H:%M:%S'), txt))
          
          def notify_order(self, order):
              if order.status in [order.Submitted, order.Accepted]:
                  # order already submitted/accepted - no action required
                  return
      
              # report executed order
              if order.status in [order.Completed]:
                  if order.exectype in [bt.Order.StopTrail]:
                      self.log(f'STOP-LOSS EXECUTED')
                  else:
                      if order.isbuy():
                          self.log(f'BUY EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}')
                          self.price = order.executed.price
                          self.comm = order.executed.comm
                      else:
                          self.log(f'SELL EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}')
              
              # report failed order
              elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                  self.log('Order Failed')
      
              # set no pending order
              self.order = None    
          
          def next(self):
              if not self.position:  # not in the market
                  if self.Trend > 0:  # if fast crosses slow to the upside it's UP trend
                      if self.order: # do nothing if an order is pending
                          return
                          
                      # calculate the max number of shares ('all-in')
                      size = int(self.broker.getcash() / self.data.open)
                      # buy order
                      self.order = self.buy(size=size) # enter long
                      #notificate about buy_order creation
                      self.log(f'BUY CREATED --- Size: {size}, Cash: {self.broker.getcash():.2f}, Open: {self.datas[0].open[0]}, Close: {self.datas[0].close[0]}')
                 
                      '''
                      There is a STOP-LOSS
                      '''
                      self.stop_loss = self.sell(exectype=bt.Order.StopTrail, trailamount=self.p.trail)
                      
                      
              if self.position:  # in the market    
                  # Selling by signals
                  if self.Trend < 0: # cross to the downside it's DOWN trend
                      #sell order
                      self.order = self.sell(size=self.position.size)
                      #notificate about sell_order creation
                      self.log(f'SELL CREATED --- Size: {self.position.size}')
      
      
      # download data
      data = bt.feeds.PandasData(dataname=test_security_backtrader, 
                                 datetime='date',
                                 timeframe=bt.TimeFrame.Minutes, #set our timeframe of our data
                                 compression=60) #compress timeframe to hours
      
      # create a Cerebro entity
      cerebro = bt.Cerebro(stdstats = True)
      
      # set up the backtest
      #cerebro.adddata(data)
      cerebro.resampledata(data,
                           timeframe=bt.TimeFrame.Minutes,
                           compression=60)
      cerebro.broker.setcash(600000.0)
      cerebro.addstrategy(MyStrategy)
      cerebro.addobserver(bt.observers.Value)
      
      # run backtest
      print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}')
      cerebro.run()
      print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}')
      
      # plot results
      pylab.rcParams['figure.figsize'] = 12, 12  # that's image size for this interactive session
      pylab.rcParams['figure.facecolor'] = '#2D3642' 
      cerebro.plot(style='bars', iplot=True, volume=False)
      

      And as a result, we can see the strange thing with negative sell orders. What's wrong with Stop-loss?

      Starting Portfolio Value: 600000.00
      2019-08-13 17:00:00, BUY CREATED --- Size: 120, Cash: 600000.00, Open: 4994.0, Close: 5093.0
      2019-08-13 18:00:00, Order Failed
      2019-08-13 18:00:00, Order Failed
      2019-08-16 17:00:00, BUY CREATED --- Size: 119, Cash: 600000.00, Open: 5041.0, Close: 5055.0
      2019-08-16 18:00:00, Order Failed
      2019-08-16 18:00:00, STOP-LOSS EXECUTED
      2019-09-16 14:00:00, SELL CREATED --- Size: -1
      2019-09-16 15:00:00, SELL EXECUTED --- Price: 5048.00, Cost: -5048.00, Commission: 0.00
      2019-10-02 11:00:00, SELL CREATED --- Size: -2
      2019-10-02 12:00:00, SELL EXECUTED --- Price: 5058.00, Cost: -10116.00, Commission: 0.00
      2019-10-21 18:00:00, SELL CREATED --- Size: -4
      2019-10-21 23:59:59, SELL EXECUTED --- Price: 5060.00, Cost: -20240.00, Commission: 0.00
      2019-10-22 12:00:00, SELL CREATED --- Size: -8
      2019-10-22 13:00:00, SELL EXECUTED --- Price: 5068.00, Cost: -40544.00, Commission: 0.00
      2019-10-22 23:59:59, SELL CREATED --- Size: -16
      2019-10-23 11:00:00, SELL EXECUTED --- Price: 5029.00, Cost: -80464.00, Commission: 0.00
      2020-02-26 17:00:00, SELL CREATED --- Size: -32
      2020-02-26 18:00:00, SELL EXECUTED --- Price: 5873.00, Cost: -187936.00, Commission: 0.00
      Final Portfolio Value: 491674.98
      
      1 Reply Last reply Reply Quote 0
      • D
        dasch last edited by

        From your log, the buy orders fail, the stop trail orders are being created. Without a size provided, it will use the set sizer. So it will set the size to 1. When size is 1, it will next time add the amount to the sell pos 1+1 = 2 + 2 = 4 + 4 = 8 ... and so on. To fix that you may consider using a buy_bracket or check if the buy order is created and then create a stop trail oder with the size of the buy order.

        1 Reply Last reply Reply Quote 1
        • run-out
          run-out last edited by

          I think you might have a few issues at play:

          First, you are setting the buy size of your trade to be the maximum possilbe.

          size = int(self.broker.getcash() / self.data.open)
          

          This is problematic because in an upward trending market, the total cash you need will be greater than what you have, resulting in a 'margin' failure of the order. To set up your algorithm, use a trade value much smaller than the market value, say 50%. You can adjust the buffer later. Some people will use the volatility or trading range to assist in setting the buffer. I typically will just you 2.5% or so and see if I get stopped out.

          Second, you have two sell orders possible for every trade. So you need to have better verification if the trade should happen. When selling if there is a crossover down, you could use close instead of sell. This will only close an open position for the amount it is open. See here.

          if self.position:  # in the market    
              if self.Trend < 0:  # cross to the downside it's DOWN trend
                  self.order = self.close()
          

          Next, your sell stop trail has a very small value (0.02) compared to the size of unit price (5000). This means you are definitly going to get stopped out right away just with regular volatility.

          The final issue is that you need to be able to kill the stop trail order if your sell due to crossdown occurs. As it stands, if you sell due to the cross down, your stop sell is still out there waiting to be triggered.

          You can solve this by cancelling the order when you sell by crossdown.

          if self.position:  # in the market    
              if self.Trend < 0:  # cross to the downside it's DOWN trend
                  self.order = self.close()
                  self.cancel(self.stop_loss)
          
          KonstantinKlychkov 1 Reply Last reply Reply Quote 3
          • KonstantinKlychkov
            KonstantinKlychkov @run-out last edited by

            @run-out thanks!

            I had heeded your advice. It taked me several days for studying documentation and at the end of the days, I've implemented sizer and bracket orders. Now it's better.

            Thank you for the bracket feature that is really powerful.

            1 Reply Last reply Reply Quote 1
            • 1 / 1
            • First post
              Last post
            Copyright © 2016, 2017, 2018 NodeBB Forums | Contributors
            $(document).ready(function () { app.coldLoad(); }); }