Strange behavior with cheat_on_open – or that might have nothing to do with it
-
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)
-
When using
cheat-on-open
:- The indicators have not been recalculated and hold the values that were last seen during the previous cycle in the equivalent xxx regular methods
From: Docs - Cheat-On-Open