Implementing a simple strategy that looks at the ROC(10) of SPY monthly data. If ROC xAbove 0 go long at the open of the next month (via daily data), otherwise if ROC xBelow 0 go short at the open of the next month (via daily data).
Monthly data from Yahoo is tagged against the first of the month, not the last day. To achieve what I want I provide two data feeds, daily SPY (to do the trades against) and Monthly SPY to trigger the trades. I have set cheat_on_open to true and configured the broker as well. In <strategy>.next_open I determine if the month is different from the last month (i.e., the first trading day of a new month). At this point I look at the current and previous value of ROC(10, monthly) to determine if any trade needs to be done.
The weird thing is the values that are being served up for ROC. Sometimes they match my calculated values, and sometimes they repeat or skip forward. I can’t understand what is happening. The blue heading indicates data that came out of Backtrader versus the Yahoo data and my calculated ROC value in grey/orange.
Any ideas?

Here is the main setup
print('---------- Start: %s - End: %s ----------' % (fromdate.date(), todate.date()))
# Create a cerebro entity, runonce handle mixed timeBase indicators
# cerebro0 is strategy, cerebro_bnh is Buy and hold to compare against
cerebro0 = CerebroAnalyzer.CerebroAnalyzer(runonce=False, cheat_on_open=True)
cerebro_bnh = CerebroAnalyzer.CerebroAnalyzer(runonce=False, cheat_on_open=True)
cerebros = (cerebro0, cerebro_bnh)
# Add a strategy
if 1:
cerebro0.addstrategy(sRocSpx.RocCross, rocPeriod=10, emaRocPeriod=1, eLim=0.0)
else:
cerebro0.optstrategy(sRocSpx.RocCross, rocPeriod=range(4, 20, 2))
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=os.path.join(datapath, 'SPY-1D.csv'),
fromdate=fromdate, # Do not pass values before this date
todate=todate, # Do not pass values after this date
reverse=False, #
)
data1 = bt.feeds.YahooFinanceCSVData(
dataname=os.path.join(datapath, 'SPY-1M.csv'),
fromdate=fromdate, # Do not pass values before this date
todate=todate, # Do not pass values after this date
reverse=False, #
timeframe=bt.TimeFrame.Months,
)
stake = 100
comm = 5.0/stake
for c in cerebros:
# Add the Data Feed to Cerebro
c.adddata(data)
c.adddata(data1)
# Set our desired cash start
c.broker.setcash(100000.0)
c.broker.set_coo(True)
Here is the strategy
params = {
('emaRocPeriod', 19),
('eLim', 0.003),
('rocPeriod', 10),
}
def __init__(self, *args, **kwArgs):
super().__init__(*args, **kwArgs)
self.dailyClose = self.datas[0].close
self.monthClose = self.datas[1].close
self.roc = bt.ind.ROC(self.monthClose, period=self.p.rocPeriod)
#self.roc1 = self.roc(-1)
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
self.tradeOpen = False
self.rocCurrent = None
bt.Indicator.csv = True
self.lastMonth = None # what was the last month that we looked at the close
self.monthEnd = None # what day 1-31 is the last trading day of the month
#------------------------------------------------------------------------
def next_open(self):
""" We decide on the 1st day of the month if we need to trade based on how the monthly ROC
finsihed
"""
nowIs = self.datas[0].datetime.date(0)
if self.lastMonth != nowIs.month:
self.lastMonth = nowIs.month
rocCurrent = self.roc.roc[0]
self.rocCurrent = rocCurrent
rocLast = self.roc.roc[-1] #self.roc1[0]
#value1 = self.ema_roc1.ema[0]
xa = rocCurrent > 0 and rocLast <= 0
xb = rocCurrent < 0 and rocLast >= 0
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
self.log('>>> %7.3f,%7.4f,%7.4f,%5s,%5s' % (self.monthClose[0],
rocCurrent, rocLast, xa, xb))
# Check if we are not in the market (no open)
if self.tradeSize <= 0 and xa:
# Not yet ... we MIGHT BUY if ...
#if xa: # and (emaRoc0 <= -self.p.eLim or self.p.eLim == 0.0):
# BUY, BUY, BUY!!! (with all possible default parameters)
self.log('BUY CREATE, %.2f' % self.dailyClose[0])
# Simply log the closing price of the series from the reference
self.log('Close, %.2f, %.2f, %.2f, xa:%5s xb:%5s' % (
self.monthClose[-1], rocCurrent, rocLast, xa, xb))
# Keep track of the created order to avoid a 2nd order
size = 100 if self.tradeSize == 0 else 200
self.order = self.buy(size=size)
elif self.tradeSize >= 0 and xb:
#if xb: # and (emaRoc0 >= self.p.eLim or self.p.eLim == 0.0):
#if self.dataclose[0] < self.sma[0]:
# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE, %.2f' % self.dailyClose[0])
# Simply log the closing price of the series from the reference
self.log('Close, %.2f, %.2f, %.2f, xa:%s xb:%s' % (
self.monthClose[0], rocCurrent, rocLast, xa, xb))
# Keep track of the created order to avoid a 2nd order
size = 100 if self.tradeSize == 0 else 200
# go short
self.order = self.sell(size=size)