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 ofSMAC
: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.
-
@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: -
That's exactly the implemented behavior: no-op and that's why the strategy remains in the
prenext
phase, moving only to thenext
(yes-op) when data is available.You may forward each
prenext
call tonext
and always be in the yes-op mode.