For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Multiple stop loss issued



  • I have this code for a RSI strategy that I've tried to implement Stop Loss.

    import backtrader as bt
    from datetime import datetime
    
    
    class firstStrategy(bt.Strategy):
        params = (
            ('stopLoss', 0.25),
            ('upperband', 75),
            ('lowerband', 25),
            ('period', 9)
        )
    
        def __init__(self):
            self.rsi = bt.indicators.RelativeStrengthIndex(self.data.close, period=9, safediv=True)
            self.order = None
            self.stopOrder = None
    
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                return
    
            if order.status in [order.Completed]:
                if order.isbuy():
                    if self.params.stopLoss:
                        self.stopOrder = self.close(price=order.executed.price, exectype=bt.Order.StopTrail,
                                                    trailpercent=self.params.stopLoss)
            self.order = None
    
        def next(self):
            if not self.position:
                if self.rsi <= 25:
                    self.buy()
            elif self.rsi >= 75:
                self.close()
    
        # def next(self):
        #     if not self.position:
        #         if self.rsi <= 25:
        #             self.buy()
        #     elif self.rsi >= 75:
        #         self.close()
    
    
    startcash = 100
    
    cerebro = bt.Cerebro()
    
    cerebro.addstrategy(firstStrategy)
    
    data = bt.feeds.GenericCSVData(
        dataname='NEOUSDT-15m-data.csv',
        fromdate=datetime(2020, 2, 1, 0, 0),
        todate=datetime(2020, 6, 1, 0, 0),
        timeframe=bt.TimeFrame.Minutes,
        compression=15,
        dtformat='%Y-%m-%d %H:%M:%S'
    )
    
    cerebro.adddata(data)
    
    cerebro.broker.setcash(startcash)
    
    cerebro.addsizer(bt.sizers.PercentSizer, percents=90)  # Set the order size
    
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='Drawdown')
    
    cerebro.broker.setcommission(commission=0.0001, margin=False)
    
    cerebro.run()
    
    portvalue = round(cerebro.broker.getvalue(), 2)
    pnl = round(portvalue - startcash, 2)
    
    print('Final Portfolio Value: ${}'.format(portvalue))
    print('P/L: ${}'.format(pnl))
    cerebro.plot(style='candlestick')
    
    

    The results were quite ridiculous, and the graph showed that multiple Stop Loss order were issued, even tho the order was supossed to be closed, as you can see:
    alt text
    So what can I do to stop it?



  • I've changed the code, and now I'm trying to use a bracket order. However, the trailpercent arg doesn't do a thing. It can be 0.1, it can be 1, or even 100, and the results are same. This is the new code:

    import backtrader as bt
    from datetime import datetime
    
    
    class firstStrategy(bt.Strategy):
        params = (
            ('stopLoss', 0.25),
            ('upperband', 75),
            ('lowerband', 25),
            ('period', 9)
        )
    
        def __init__(self):
            self.rsi = bt.indicators.RelativeStrengthIndex(self.data.close, period=self.p.period, upperband=self.p.upperband, lowerband=self.p.lowerband, safediv=True)
    
        def next(self):
            if not self.position and self.rsi <= self.p.lowerband:
                    # self.buy()
                    self.buy_bracket(exectype=bt.Order.Market, price=self.data.close, limitexec=None, stopexec=bt.Order.StopTrail, trailpercent=0.1, stopprice=self.data.close)
            elif self.rsi >= self.p.upperband:
                self.close()
    
    
    startcash = 100
    
    cerebro = bt.Cerebro()
    
    cerebro.addstrategy(firstStrategy)
    
    data = bt.feeds.GenericCSVData(
        dataname='NEOUSDT-15m-data.csv',
        fromdate=datetime(2020, 2, 1, 0, 0),
        todate=datetime(2020, 6, 1, 0, 0),
        timeframe=bt.TimeFrame.Minutes,
        compression=15,
        dtformat='%Y-%m-%d %H:%M:%S'
    )
    
    cerebro.adddata(data)
    
    cerebro.broker.setcash(startcash)
    
    cerebro.addsizer(bt.sizers.PercentSizer, percents=90)  # Set the order size
    
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='Drawdown')
    
    cerebro.broker.setcommission(commission=0.0001, margin=False)
    
    cerebro.run()
    
    portvalue = round(cerebro.broker.getvalue(), 2)
    pnl = round(portvalue - startcash, 2)
    
    print('Final Portfolio Value: ${}'.format(portvalue))
    print('P/L: ${}'.format(pnl))
    cerebro.plot(style='candlestick')
    
    

    If this is intended behaviour, can I use another kind of order?



  • BUMP....



  • Looks like your first script with StopTrail orders worked as expected. You used 25% stop from the price of order execution. Your first orders were executed at prices around 14.5 - 15.5 and you stops were set to approx 11.5 - 12.0. You can see them executed at that spike of the cash, 6 red triangles in a row.



  • @Eduardo-Menges-Mattje said in Multiple stop loss issued:

    However, the trailpercent arg doesn't do a thing. It can be 0.1, it can be 1, or even 100

    Just to clarify (Docs - Orders - StopTrail)

    Percentage based distance: trailpercent=0.02 (i.e.: 2%)

    You start with 0.1 which is 10% and your price range is much less than this value. You need to use much less % of stop in order to hit it before your self.close() closes position in regular way.

    You may want to add more logging into your script, this will help you to understand what is going on.



  • @ab_trader said in Multiple stop loss issued:

    Just to clarify (Docs - Orders - StopTrail)

    Percentage based distance: trailpercent=0.02 (i.e.: 2%)

    You start with 0.1 which is 10% and your price range is much less than this value. You need to use much less % of stop in order to hit it before your self.close() closes position in regular way.
    Even with trailpercent=0.01, the result is the same as 0.1 or any other value. As I said, it is as if trailpercent is completely disregarded.

    You may want to add more logging into your script, this will help you to understand what is going on.
    Ok. This is the code with the logging:

    import backtrader as bt
    from datetime import datetime
    
    
    class firstStrategy(bt.Strategy):
        params = dict(
            stopLoss=0.01,
            upperband=75,
            lowerband=25,
            period=9
        )
    
        def log(self, txt, dt=None):
            dt = dt or self.data.datetime[0]
            if isinstance(dt, float):
                dt = bt.num2date(dt)
            print('%s, %s' % (dt.isoformat(), txt))
    
        def notify_order(self, order):
            if order.status in [order.Completed]:
                if order.isbuy():
                    self.log(
                        'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                        (order.executed.price,
                         order.executed.value,
                         order.executed.comm))
    
                else:
                    self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                             (order.executed.price,
                              order.executed.value,
                              order.executed.comm))
    
            self.order = None
    
        def __init__(self):
            self.rsi = bt.indicators.RelativeStrengthIndex(period=self.p.period, upperband=self.p.upperband,
                                                           lowerband=self.p.lowerband, safediv=True)
    
        def next(self):
            if not self.position and self.rsi <= self.p.lowerband:
                # self.buy()
                self.buy_bracket(exectype=bt.Order.Market, price=self.data.close, limitexec=None,
                                 stopexec=bt.Order.StopTrail, trailpercent=self.p.stopLoss, stopprice=self.data.close)
                self.log('BUY CREATE, exectype Market, price %.2f' % self.data.close[0])
            elif self.position and self.rsi >= self.p.upperband:
                self.close()
                self.log('BUY CREATE, exectype Close, price %.2f' % self.data.close[0])
    
    
    startcash = 100
    
    cerebro = bt.Cerebro()
    
    cerebro.addstrategy(firstStrategy)
    
    data = bt.feeds.GenericCSVData(
        dataname='NEOUSDT-15m-data.csv',
        fromdate=datetime(2020, 2, 1, 0, 0),
        todate=datetime(2020, 6, 1, 0, 0),
        timeframe=bt.TimeFrame.Minutes,
        compression=15,
        dtformat='%Y-%m-%d %H:%M:%S'
    )
    
    cerebro.adddata(data)
    
    cerebro.broker.setcash(startcash)
    
    cerebro.addsizer(bt.sizers.PercentSizer, percents=90)  # Set the order size
    
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='Drawdown')
    
    cerebro.broker.setcommission(commission=0.0001, margin=False)
    
    cerebro.run()
    
    portvalue = round(cerebro.broker.getvalue(), 2)
    pnl = round(portvalue - startcash, 2)
    
    print('Final Portfolio Value: ${}'.format(portvalue))
    print('P/L: ${}'.format(pnl))
    cerebro.plot(style='candlestick')
    
    

    This is the log with trailpercent=0.01:
    https://controlc.com/90e3b126
    This is the log with trailpercent=0.10:
    https://controlc.com/80136684



  • I'll take a look. Also please log ohlc prices and rsi values.



  • @ab_trader said in Multiple stop loss issued:

    I'll take a look. Also please log ohlc prices and rsi values.

    This is the csv file: https://drive.google.com/file/d/1zx6Ywdts38sHVJqr9ZYfYV5Izxh_MBVk/view?usp=sharing



  • BUMP....



  • So it turns out that this is intended behaviour.
    StopTrail compares the latest price, not the executed price.



  • Good you figured this out. Maybe this is true in your case since you use Market order as a first order.For this type of order the execution price is unknown at the moment when order is issued. You passed close prices and maybe stoptrail started to work from that value.

    I didn't have a chance to go thru all the info you dropped here. Especially having no RSI values and having outputs started from the middle of the whole price array. But I am still thinking that my guess above is true. Even 1% stop trail is large for your set of prices.



  • @ab_trader said in Multiple stop loss issued:

    Good you figured this out. Maybe this is true in your case since you use Market order as a first order.For this type of order the execution price is unknown at the moment when order is issued. You passed close prices and maybe stoptrail started to work from that value.

    I didn't have a chance to go thru all the info you dropped here. Especially having no RSI values and having outputs started from the middle of the whole price array. But I am still thinking that my guess above is true. Even 1% stop trail is large for your set of prices.

    Is there a way for StopLoss to trigger only when executed price - current price <= percentage?



  • bt's stop order triggers when the current price is lower than the stop price. so stop price need to be defined as executed price * ( 1 - stop percent).

    i've played with your data and script, and it seems to me that the issue is in the stoptrail side of the bracket order. I've changed it to the regular stop order, it worked as expected but with the StopTrail the results were weird.

    In your case if you want to do the following:

    • issue market buy order based on rsi value
    • than issue stoptrail order with the position open
    • and issue closing order based on rsi condition with the cancellation of the existing stoptrail order

    than you may want to take a look on the OCO orders.


Log in to reply
 

});