For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

'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?


  • administrators

    @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.