'ItemCollection' object has no attribute 'broker'
-
I am trying to run a Cerebro instance backtest and am getting the following error:
File "<ipython-input-120-b8cab2f96cd8>", line 1, in <module> runfile('C:/Users/Google Drive/PPH/walkforward_opt/main_optim.py', wdir='C:/Users/Google Drive/PPH/walkforward_opt') File "C:\Users\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 678, in runfile execfile(filename, namespace) File "C:\Users\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 106, in execfile exec(compile(f.read(), filename, 'exec'), namespace) File "C:/Users/Google Drive/PPH/Marten/Jamieson2/walkforward_opt/main_optim.py", line 457, in <module> results = cerebro_wf.run() File "C:\Users\Anaconda3\lib\site-packages\backtrader\cerebro.py", line 1127, in run runstrat = self.runstrategies(iterstrat) File "C:\Users\Anaconda3\lib\site-packages\backtrader\cerebro.py", line 1298, in runstrategies strat._stop() File "C:\Users\Anaconda3\lib\site-packages\backtrader\strategy.py", line 462, in _stop analyzer._stop() File "C:\Users\Anaconda3\lib\site-packages\backtrader\analyzer.py", line 200, in _stop self.stop() File "C:\Users\Anaconda3\lib\site-packages\backtrader\analyzers\annualreturn.py", line 64, in stop value_cur = self.strategy.stats.broker.value[-i] AttributeError: 'ItemCollection' object has no attribute 'broker'
I am especially confused by this error as I believe the code that is causing the error is part of the built in "annualreturn" analyzer code which I have not changed.
Why would my strategy be creating an "ItemCollection" object where it is not supposed to?
Has anyone come across this before?
My full strategy class and other code is below if needed:
class SMACWalkForward(bt.Strategy): """The SMAC strategy but in a walk-forward analysis context""" params = {"start_dates": None, # Starting days for trading periods (a list) "end_dates": None, # Ending day for trading periods (a list) "fast": None, # List of fast moving average windows, corresponding to start dates (a list) "slow": None} # Like fast, but for slow moving average window (a list) # All the above lists must be of the same length, and they all line up def __init__(self): """Initialize the strategy""" print("STARTING WALKFORWARD STRATEGY") self.fastma = dict() self.slowma = dict() self.regime = dict() self.date_combos = [c for c in zip(self.p.start_dates, self.p.end_dates)] # Error checking if type(self.p.start_dates) is not list or type(self.p.end_dates) is not list or \ type(self.p.fast) is not list or type(self.p.slow) is not list: raise ValueError("Must past lists filled with numbers to params start_dates, end_dates, fast, slow.") elif len(self.p.start_dates) != len(self.p.end_dates) or \ len(self.p.fast) != len(self.p.start_dates) or len(self.p.slow) != len(self.p.start_dates): raise ValueError("All lists passed to params must have same length.") for d in self.getdatanames(): self.fastma[d] = dict() self.slowma[d] = dict() self.regime[d] = dict() # Additional indexing, allowing for differing start/end dates for sd, ed, f, s in zip(self.p.start_dates, self.p.end_dates, self.p.fast, self.p.slow): # More error checking if type(f) is not int or type(s) is not int: raise ValueError("Must include only integers in fast, slow.") elif f > s: raise ValueError("Elements in fast cannot exceed elements in slow.") elif f <= 0 or s <= 0: raise ValueError("Moving average windows must be positive.") if type(sd) is not dt.date or type(ed) is not dt.date: raise ValueError("Only datetime dates allowed in start_dates, end_dates.") elif ed - sd < dt.timedelta(0): raise ValueError("Start dates must always be before end dates.") # The moving averages # Notice that different moving averages are obtained for different combinations of # start/end dates self.fastma[d][(sd, ed)] = btind.SimpleMovingAverage(self.getdatabyname(d), period=f, plot=False) self.slowma[d][(sd, ed)] = btind.SimpleMovingAverage(self.getdatabyname(d), period=s, plot=False) # Get the regime self.regime[d][(sd, ed)] = self.fastma[d][(sd, ed)] - self.slowma[d][(sd, ed)] # In the future, use the backtrader indicator btind.CrossOver() def next(self): """Define what will be done in a single step, including creating and closing trades""" # Determine which set of moving averages to use curdate = self.datetime.date(0) dtidx = None # Will be index # Determine which period (if any) we are in for sd, ed in self.date_combos: # Debug output #print('{}: {} < {}: {}, {} < {}: {}'.format( # len(self), sd, curdate, (sd <= curdate), curdate, ed, (curdate <= ed))) if sd <= curdate and curdate <= ed: dtidx = (sd, ed) # Debug output #print('{}: the dtixdx is {}, and curdate is {};'.format(len(self), dtidx, curdate)) for d in self.getdatanames(): # Looping through all symbols pos = self.getpositionbyname(d).size or 0 if dtidx is None: # Not in any window break # Don't engage in trades if pos == 0: # Are we out of the market? # Consider the possibility of entrance # Notice the indexing; [0] always mens the present bar, and [-1] the bar immediately preceding # Thus, the condition below translates to: "If today the regime is bullish (greater than # 0) and yesterday the regime was not bullish" if self.regime[d][dtidx][0] > 0 and self.regime[d][dtidx][-1] <= 0: # A buy signal self.buy(data=self.getdatabyname(d)) else: # We have an open position if self.regime[d][dtidx][0] <= 0 and self.regime[d][dtidx][-1] > 0: # A sell signal self.sell(data=self.getdatabyname(d)) datafile = 'GETI-B.ST_daily.csv' market = datafile.split('-')[0] ohlc = pd.read_csv(datafile, index_col='date', parse_dates=True,dayfirst=True) ohlc = ohlc[ohlc['1. open']!= 0] ohlc.columns = ['open', 'high', 'low', 'close', 'volume'] ohlc = ohlc[['open', 'high', 'low', 'close']] ohlc= ohlc[ohlc.index > datetime.datetime(2000, 12, 31)] cerebro_wf = Cerebro(stdstats=False) cerebro_wf.broker.setcash(1000000) cerebro_wf.broker.setcommission(0.02) cerebro_wf.addstrategy(SMACWalkForward, # Give the results of the above optimization to SMACWalkForward (NOT OPTIONAL) fast=[int(f) for f in wfdf.fast], slow=[int(s) for s in wfdf.slow], start_dates=[sd.date() for sd in wfdf.start_date], end_dates=[ed.date() for ed in wfdf.end_date]) cerebro_wf.adddata(data) cerebro_wf.addobserver(AcctValue) cerebro_wf.addobservermulti(bt.observers.BuySell) # Plots up/down arrows cerebro_wf.addsizer(PropSizer) cerebro_wf.addanalyzer(AcctStats) results = cerebro_wf.run()
-
I have gone into "annualreturn.py" and changed:
value_cur = self.strategy.stats.broker.value[-i]
to:
try: value_cur = self.strategy.stats.broker.value[-i] except: value_cur = self.strategy.stats.acctvalue[-i]
and it now seems to work - but surely I should not be changing the backtrader source code!
Does anyone have any idea?
-
@stuj79 said in 'ItemCollection' object has no attribute 'broker':
cerebro_wf = Cerebro(stdstats=False)
You are removing the reference which the analyzer needs, hence the error.
@stuj79 said in 'ItemCollection' object has no attribute 'broker':
value_cur = self.strategy.stats.broker.value[-i] AttributeError: 'ItemCollection' object has no attribute 'broker'
In any case, you'd be better off using
TimeReturn
-
@backtrader said in 'ItemCollection' object has no attribute 'broker':
erebro_wf = Cerebro(stdstats=False)
Amazing! Thanks so much for your quick reply!! That has indeed solved the issue.