Trades don't happen until WAY late (NO, THEY DON'T)



  • I'm trying to implement a simple moving average crossover strategy that trades multiple symbols. Following this example, I came up with the following strategy:

    import datetime
    import backtrader as bt
    import backtrader.indicators as btind
    
    class PropSizer(bt.Sizer):
        """A position sizer that will buy as many stocks as necessary for a certain proportion of the portfolio
           to be committed to the position, while allowing stocks to be bought in batches (say, 100)"""
        params = {"prop": 0.1, "batch": 100}
        
        def _getsizing(self, comminfo, cash, data, isbuy):
            """Returns the proper sizing"""
            
            if isbuy:    # Buying
                target = self.broker.getvalue() * self.params.prop    # Ideal total value of the position
                price = data.close[0]
                shares_ideal = target / price    # How many shares are needed to get target
                batches = int(shares_ideal / self.params.batch)    # How many batches is this trade?
                shares = batches * self.params.batch    # The actual number of shares bought
                
                if shares * price > cash:
                    return 0    # Not enough money for this trade
                else:
                    return shares
                
            else:    # Selling
                return self.broker.getposition(data).size    # Clear the position
    
    class SMAC(bt.Strategy):
        """A simple moving average crossover strategy; crossing of a fast and slow moving average generates buy/sell
           signals"""
        params = {"slow": 50, "fast": 20}    # The windows for both fast and slow moving averages
        
        def __init__(self):
            """Initialize the strategy"""
            # Tracking pending orders
            # self.order = None
            
            self.fastma = dict()
            self.slowma = dict()
            self.regime = dict()
            
            self._addobserver(True, bt.observers.BuySell)
            
            for d in self.datas:
                
                # The moving averages
                self.fastma[d._name] = btind.SimpleMovingAverage(d, period=self.params.fast,    # Fast moving average
                                                                 plotname="FastMA: " + d._name)
                self.slowma[d._name] = btind.SimpleMovingAverage(d, period=self.params.slow,    # Slow moving average
                                                                 plotname="SlowMA: " + d._name)
    
                # Get the regime
                self.regime[d._name] = self.fastma[d._name] - self.slowma[d._name](-1)    # Positive when bullish
        
        def next(self):
            """Define what will be done in a single step, including creating and closing trades"""
            for d in self.datas:
                pos = self.getposition(d).size or 0
                if pos == 0:    # Are we out of the market?
                    # Consider the possibility of entrance
                    if self.regime[d._name][0] > 0 and self.regime[d._name][-1] <= 0:    # A buy signal
                        self.buy(data=d)
    
                else:    # We have an open position
                    if self.regime[d._name][0] <= 0 and self.regime[d._name][-1] <= 0:    # A sell signal
                        self.sell(data=d)
    
    cerebro = bt.Cerebro(stdstats=True)
    cerebro.broker.set_cash(1000000)    # Set our starting cash to $1,000,000
    cerebro.broker.setcommission(0.02)
    
    start = datetime.datetime(2010, 1, 1)
    end = datetime.datetime(2016, 10, 31)
    is_first = True
    symbols = ["AAPL", "GOOG", "MSFT", "FB", "TWTR", "NFLX", "AMZN", "YHOO",
               "SNY", "NTDOY", "IBM", "HPQ"]
    plot_symbols = ["AAPL"]
    #plot_symbols = []
    for s in symbols:
        data = bt.feeds.YahooFinanceData(dataname=s, fromdate=start, todate=end)
        if s in plot_symbols:
            if is_first:
                data_main_plot = data
                is_first = False
            else:
                data.plotinfo.plotmaster = data_main_plot
        else:
            data.plotinfo.plot = False
        cerebro.adddata(data)    # Give the data to cerebro
    cerebro.addstrategy(SMAC)
    cerebro.addsizer(PropSizer)
    cerebro.run()
    cerebro.plot(volume=False)
    

    Unfortunately, trades don't occur until WAY late, perhaps halfway through the period of interest. Trades should be happening as soon as 50 days in; that's the window length of the slow moving average. Why are trades happening so late? What's wrong with my strategy?

    EDIT: I think I know what the problem is; some symbols are new and don't exist, so the backtester must be not checking until all symbols have data. That said, is there a way to work with data like this, when some symbols don't exist all in the same time frame?



  • I revised the strategy to use the methods of the Strategy class for accessing data. No improvement. Here's the code of the new version of SMAC:

    class SMAC(bt.Strategy):
        """A simple moving average crossover strategy; crossing of a fast and slow moving average generates buy/sell
           signals"""
        params = {"slow": 50, "fast": 20}    # The windows for both fast and slow moving averages
        
        def __init__(self):
            """Initialize the strategy"""
            # Tracking pending orders
            # self.order = None
            
            self.fastma = dict()
            self.slowma = dict()
            self.regime = dict()
            
            self._addobserver(True, bt.observers.BuySell)
            
            for d in self.getdatanames():
                
                # The moving averages
                self.fastma[d] = btind.SimpleMovingAverage(self.getdatabyname(d), period=self.params.fast,    # Fast moving average
                                                                 plotname="FastMA: " + d)
                self.slowma[d] = btind.SimpleMovingAverage(self.getdatabyname(d), period=self.params.slow,    # Slow moving average
                                                                 plotname="SlowMA: " + d)
    
                # Get the regime
                self.regime[d] = self.fastma[d] - self.slowma[d](-1)    # Positive when bullish
        
        def next(self):
            """Define what will be done in a single step, including creating and closing trades"""
            for d in self.getdatanames():
                pos = self.getpositionbyname(d).size or 0
                if pos == 0:    # Are we out of the market?
                    # Consider the possibility of entrance
                    if self.regime[d][0] > 0 and self.regime[d][-1] <= 0:    # A buy signal
                        self.buy(data=self.getdatabyname(d))
    
                else:    # We have an open position
                    if self.regime[d][0] <= 0 and self.regime[d][-1] <= 0:    # A sell signal
                        self.sell(data=self.getdatabyname(d))
    


  • Okay, I think I figured out the issue, but I don't know how to address it. I edited the opening post in response.



  • I changed the list of symbols to the one below and the problem was "fixed":

    symbols = ["AAPL", "GOOG", "MSFT", "AMZN", "YHOO", "SNY", "NTDOY", "IBM", "HPQ", "QCOM", "NVDA"]
    

    That said, I hope there's a better solution to this problem than "Make sure all stocks have the same time frame," because I would prefer that, during periods certain symbols are not available/don't exist, they're just ignored and don't back up the whole backtest until they become available.


  • administrators

    @Curtis-Miller said in Trades don't happen until WAY late:

    EDIT: I think I know what the problem is; some symbols are new and don't exist, so the backtester must be not checking until all symbols have data. That said, is there a way to work with data like this, when some symbols don't exist all in the same time frame?

    All lines objects in backtrader have a minimum period and the strategy uses that to autocalculate when it can first go into next. If some of your data feeds are not delivering, nothing can be guaranteed with regards to accessing the data feeds and values of indicators calculated on them.

    There is no better solution (if you have one, feel free to propose it) If some of your data feeds are not delivering the following would break

    def next(self):
        # data0 is any of the above, it doesn't matter it just an example, the key: IT DOESN'T HAVE ANY DATA YET
        if self.data0.close[0]:  # breaking here
            print('something')
    

    So: NO. Trades ARE NOT HAPPENING WAY TOO LATE

    You may always override prenext, check if the data feeds have already delivered and act accordingly. See the following parts of the documentation:


  • administrators

    That's exactly the implemented behavior: no-op and that's why the strategy remains in the prenext phase, moving only to the next (yes-op) when data is available.

    You may forward each prenext call to next and always be in the yes-op mode.


Log in to reply
 

Looks like your connection to Backtrader Community was lost, please wait while we try to reconnect.