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

Why doesn't my 'buy' trigger?



  • I have a very simple system, but I'm running into a small problem. I'm using an if/else block to facilitate my buys and sells.

    and its structured like this:

    if not self.position:
       if (entry signal):
            print('entry signal triggered')
            self.buy()
    

    now I'm finding that the buy function isn't following through all the time, sometimes it works and sometimes it doesn't. I'm tracking the trades via order.status. sometimes order.status reports 'BUY EXECUTED' other times it doesn't while the if/else blocks still prints 'entry signal triggered'. this is again confirmed on the plot where I can visually see a buy should have been triggered, but it's not.

    Am I missing something about the self.buy() function?



  • It would be helpful to share your code.



  • Here's my code:

    class HighBreakout(bt.Strategy):
    
        params = (('max', 100),
                  ('min', 100),
                  ('Long_MA', 200),
                  ('Short_MA', 50),
                  ('oneplot', True)
                 )
    
        def log(self, txt, dt=None):
            ''' Logging function for this strategy'''
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))
    
    
        def __init__(self):
            #Keep a reference to the "close" line in the data[0] dataseries
            self.inds = dict()
            self.holding = 0
            self.buyprice = 0
            self.sellprice = 0
            self.wins = 0
            self.losses = 0
            self.totalwin = 0
            self.totalloss =0
    
            print(self.params.max)
    
            for i, d in enumerate(self.datas):
                self.inds[d] = dict()
                self.inds[d]['MAX'] = bt.talib.MAX(d.close, timeperiod=self.params.max)
                self.inds[d]['MIN'] = bt.talib.MIN(d.close, timeperiod=self.params.min)
                self.inds[d]['sma_long'] = bt.indicators.SMA(d.close, period=self.params.Long_MA)
                self.inds[d]['sma_short'] = bt.indicators.SMA(d.close, period=self.params.Short_MA)
    
                if i > 0:  # Check we are not on the first loop of data feed:
                    if self.p.oneplot == True:
                        d.plotinfo.plotmaster = self.datas[0]
    
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                return
    
            # Check if an order has been completed
            # Attention: broker could reject order if not enough cash
            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))
                    print(f'max prev = {self.max_prev} > max prev prev = {self.max_prev_prev}')
    
    
                    self.buyprice = order.executed.price
                    self.buycomm = order.executed.comm
                else:  # Sell
                    self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                             (order.executed.price,
                              order.executed.value,
                              order.executed.comm))
    
    
                self.bar_executed = len(self)
    
            # Write down: no pending order
            self.order = None
    
        def notify_trade(self, trade):
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
            if trade.pnl > 0:
                self.totalwin += trade.pnl
            if trade.pnl < 0:
                self.totalloss -= trade.pnl
            if trade.pnl > 0:
                self.wins += 1
            if trade.pnl < 0:
                self.losses += 1
    
        def next(self):
    
    
            for i, d in enumerate(self.datas):
    
                self.C = self.datas[i].close[0]
                self.O = self.datas[i].open[0]
                self.L = self.datas[i].low[0]
                self.H = self.datas[i].high[0]
    
                self.C_prev = self.datas[i].close[-1]
                self.O_prev = self.datas[i].open[-1]
                self.L_prev = self.datas[i].low[-1]
                self.H_prev = self.datas[i].high[-1]
                self.symbol = self.datas[i]._name
                self.date = self.datas[0].datetime.datetime(0)
                max_ = self.inds[d]['MAX'][0]
                self.max_prev = self.inds[d]['MAX'][-1]
                self.max_prev_prev = self.inds[d]['MAX'][-2]
                min_prev = self.inds[d]['MIN'][-1]
       
                if not self.position:
                    if self.max_prev > self.max_prev_prev:
                        print(f'{self.max_prev} is greater than {self.max_prev_prev}')
                        self.buy()
    
                else:
                    if self.L_prev <= min_prev:
                        self.sell()
    
        def stop(self):
            if self.losses + self.wins != 0:
                batting_average = self.wins / (self.losses + self.wins)
            percent_return = self.broker.getvalue() / self.broker.startingcash
            if self.wins != 0:
                average_win = self.totalwin / self.wins
            if self.losses != 0:
                average_loss = self.totalloss / self.losses
            print('==================================================')
            print('Starting Value - %.2f' % self.broker.startingcash)
            print('Ending   Value - %.2f' % self.broker.getvalue())
            print('Percent Return: %.2f' % percent_return)
            if self.losses + self.wins != 0:
                print('Batting average %.2f' % batting_average)
            print('Wins: %.0f' % self.wins)
            print('Losses: %.0f' % self.losses)
            if self.wins != 0:
                print('Average win: %.2f' % average_win)
            if self.losses != 0:
                print('Average loss: %.2f' % average_loss)
            print('==================================================')
    

    and here's a sample output:

    176.38 is greater than 175.37
    176.495 is greater than 176.38
    2020-04-16, BUY EXECUTED, Price: 175.25, Cost: 11505.49, Comm 0.00
    max prev = 176.495 > max prev prev = 176.38
    2020-04-21, SELL EXECUTED, Price: 172.00, Cost: 11505.49, Comm 0.00
    2020-04-21, OPERATION PROFIT, GROSS -213.51, NET -213.51
    175.89 is greater than 174.84
    175.94 is greater than 175.89
    176.07 is greater than 175.94
    176.215 is greater than 176.07
    176.23 is greater than 176.215
    176.41 is greater than 176.23
    176.93 is greater than 176.41
    177.415 is greater than 176.93
    177.51 is greater than 177.415
    2020-04-29, BUY EXECUTED, Price: 177.24, Cost: 11298.87, Comm 0.00
    max prev = 177.51 > max prev prev = 177.415
    2020-05-13, SELL EXECUTED, Price: 182.43, Cost: 11298.87, Comm 0.00
    2020-05-13, OPERATION PROFIT, GROSS 331.01, NET 331.01
    185.745 is greater than 185.57
    2020-05-18, BUY EXECUTED, Price: 186.16, Cost: 11629.25, Comm 0.00
    max prev = 185.745 > max prev prev = 185.57
    2020-05-21, SELL EXECUTED, Price: 183.79, Cost: 11629.25, Comm 0.00
    2020-05-21, OPERATION PROFIT, GROSS -147.68, NET -147.68
    184.175 is greater than 184.04
    2020-05-29, BUY EXECUTED, Price: 183.81, Cost: 11482.20, Comm 0.00
    max prev = 184.175 > max prev prev = 184.04
    ==================================================
    Starting Value - 10000.00
    Ending   Value - 12299.57
    Percent Return: 1.23
    Batting average 0.50
    Wins: 7
    Losses: 7
    Average win: 373.73
    Average loss: 161.99
    ==================================================
    

    you can see the code passes through the if/else block and prints the "XX is greater than XX", but does not execute the buy operation despite the code calling the function.



  • is there enough cash?

    def log(self, arg):
            print('{} {}'.format(self.datetime.date(), arg))
            
        # This section is for logging of orders in greater detail to figure out whether the strategy is actually having no problem with orders
        def notify_order(self, order):
            if order.status in [order.Accepted]:
                # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                return
            if order.status in [order.Submitted]:
                if order.isbuy():
                
                    dt, dn = self.datetime.date(), order.data._name
                    print('Buy {} {} {} Price {:.2f} Value {:.2f} Size {} Cash {:.2f}'.format(
                            order.getstatusname(), dt, dn, order.created.price, order.created.size * order.created.price , order.created.size, self.broker.getcash()))
                if order.issell():
                    dt, dn = self.datetime.date(), order.data._name
                    print('Sell {} {} {} Price {:.2f} Value {:.2f} Size {}'.format(
                            order.getstatusname(), dt, dn, order.created.price, order.created.size * order.created.price, order.created.size))
    
                # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                return
    
            # Check if an order has been completed
            # Attention: broker could reject order if not enough cash
            if order.status in [order.Completed]:
                if order.isbuy():
                    dt, dn = self.datetime.date(), order.data._name
                    print('Buy {} {} Price {:.2f} Value {:.2f} Size {}'.format(
                        dt, dn, order.executed.price, order.executed.value, order.executed.size))
    
                if order.issell():# Sell
                    dt, dn = self.datetime.date(), order.data._name
                    print('Sell {} {} Price {:.2f} Value {:.2f} Size {}'.format(
                        dt, dn, order.executed.price, order.executed.value, order.executed.size))
    
    
            elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                self.log('Order Canceled/Margin/Rejected')
    

    Code to have a closer look at the orders.



  • There is enough cash, here's a snip of the graph to visually demonstrate the issue I'm facing.https://imgur.com/a/QDIZpFU

    The intent is to make a purchase every time the 'max' indicator makes a local high. It's passing over a number of new highs and then making a purchase at a seemingly random time, but still a new high. This graph tells me its not something wrong with my data or the way I've implemented the indicators, but probably something wrong with the way I've implemented the backtrader buy functionality. I just cant wrap my head around what the issue might be



  • @Chris-Cappel Add a time stamp in front of print(f'{self.max_prev} is greater than {self.max_prev_prev}') and implement the code (def notify_order(self, order):) as suggested by @Jonny8 then share your log so we can see what's going on. Tough to tell without this information.



  • Good call. The issue was I didn't have enough cash. Odd because size should be determined by total cash/price.

    Even odder that this buy-order didn't fill.

    Buy Submitted 2018-11-26  Price 105.81 Value 9324.90 Size 88.12458567695634 Cash 9324.90
    

    Among the results I saw orders like this, which clearly, correctly shouldn't have filled (and didn't):

    Buy Submitted 2018-11-26  Price 105.81 Value 9328.43 Size 88.15791097525063 Cash 9324.90
    

    I've use a the workaround below to continue testing the efficiency of the strategy (previously 100%):

    cerebro.addsizer(bt.sizers.PercentSizer, percents=99)
    

    Thanks for the help everyone!


Log in to reply
 

});