Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

    Problem with running Bollinger Bands Optimisation

    General Code/Help
    2
    3
    211
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • F
      FrankieC last edited by

      I'm trying to run a simple Bollinger strategy that buys when close crossover bottom BBand and sells when cross under BBand.

      Unfortunately I get this.

      Traceback (most recent call last):
      File "/usr/local/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3437, in run_code
      exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-2-c7f43e7322da>", line 1, in <module>
      runfile('/Users/frankie/PycharmProjects/pythonProject3/BollingerOPT.py', wdir='/Users/frankie/PycharmProjects/pythonProject3')
      File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
      pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
      File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
      exec(compile(contents+"\n", file, 'exec'), glob, loc)
      File "/Users/frankie/PycharmProjects/pythonProject3/BollingerOPT.py", line 353, in <module>
      opt_runs = cerebro.run(maxcpus=12)
      File "/usr/local/lib/python3.9/site-packages/backtrader/cerebro.py", line 1143, in run
      for r in pool.imap(self, iterstrats):
      File "/usr/local/Cellar/python@3.9/3.9.2_4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/pool.py", line 870, in next
      raise value
      TypeError: only size-1 arrays can be converted to Python scalars

      Any hint?

      I had problems by changing the parameter "devfactor" to "multiplier" but as soon as I changed that I had another problem.

      I post here the code.

      from __future__ import (absolute_import, division, print_function,
                              unicode_literals)
      import time
      start_time = time.time()
      import backtrader as bt
      import backtrader.feeds as btfeeds
      import backtrader.indicators as btind
      import pandas as pd
      import matplotlib
      import os
      import datetime  # For datetime objects
      import os.path  # To manage paths
      import sys  # To find out the script name (in argv[0])
      import pytz
      import numpy as np
      from pandas import DataFrame
      import backtrader as bt
      from collections import OrderedDict
      import tabulate
      
      
      
      # DataImportVar
      fromdate = datetime.datetime(2021, 2, 7, 12, 00)
      todate = datetime.datetime(2021, 4, 9, 19, 00)
      # SessionTimeFilter
      Session_begin = datetime.time(12, 0)
      Session_end = datetime.time(19, 0)
      
      if os.getcwd()=='/Users/frankie/PycharmProjects/pythonProject3':
          Directory = '/Users/frankie'
      else:
          Directory='/Users/francescocerutti_imac_hs/Desktop/FrankieHD27'
      
      class TotalReturns(bt.Analyzer):
          '''This analyzer reports the total pnl for strategy
      
          Params:
      
            - timeframe (default: ``None``)
              If ``None`` then the timeframe of the 1st data of the system will be
              used
      
            - compression (default: ``None``)
      
              Only used for sub-day timeframes to for example work on an hourly
              timeframe by specifying "TimeFrame.Minutes" and 60 as compression
      
              If ``None`` then the compression of the 1st data of the system will be
              used
      
          Methods:
      
            - get_analysis
      
              Returns a dictionary with returns as values and the datetime points for
              each return as keys
          '''
      
          def __init__(self):
              self.total_pnl = 0
              self.unrealized = 0  # Unrealized pnl for all positions all strategies
              self.positions = OrderedDict.fromkeys([d._name or 'Data%d' % i
                                                     for i, d in enumerate(self.datas)], 0)  # Current strategy positions
      
          def start(self):
              tf = min(d._timeframe for d in self.datas)
              self._usedate = tf >= bt.TimeFrame.Minutes
      
          def notify_order(self, order):
      
              if order.status in [order.Completed, order.Partial]:
                  self.total_pnl += order.executed.pnl - order.executed.comm
      
                  self.positions[order.data._name] += order.executed.size
      
          def next(self):
              if self._usedate:
                  self.rets[self.strategy.datetime.date()] = self.total_pnl
              else:
                  self.rets[self.strategy.datetime.datetime()] = self.total_pnl
      
          def stop(self):
      
              for dname in self.positions:
                  self.unrealized += (self.strategy.dnames[dname].close[0] -
                                      self.strategy.positionsbyname[dname].price) * \
                                     self.strategy.positionsbyname[dname].size
      
          def get_analysis(self):
              return self.rets
      
      '''
      class BOLLStrat(bt.Strategy):
          
          This is a simple mean reversion bollinger band strategy.
      
          Entry Criteria:
              - Long:
                  - Price closes below the lower band
                  - Stop Order entry when price crosses back above the lower band
              - Short:
                  - Price closes above the upper band
                  - Stop order entry when price crosses back below the upper band
          Exit Critria
              - Long/Short: Price touching the median line
          
      
          params = (
              ("period", 20),
              ("devfactor", 2),
              ("size", 20),
              ("debug", False)
          )
      
          def __init__(self):
              self.boll = bt.indicators.BollingerBands(period=self.p.period, devfactor=self.p.devfactor)
              # self.sx = bt.indicators.CrossDown(self.data.close, self.boll.lines.top)
              # self.lx = bt.indicators.CrossUp(self.data.close, self.boll.lines.bot)
      
          def next(self):
      
              orders = self.broker.get_orders_open()
      
              # Cancel open orders so we can track the median line
              if orders:
                  for order in orders:
                      self.broker.cancel(order)
      
              if not self.position:
      
                  if self.data.close > self.boll.lines.top:
                      self.sell(exectype=bt.Order.Stop, price=self.boll.lines.top[0], size=self.p.size)
      
                  if self.data.close < self.boll.lines.bot:
                      self.buy(exectype=bt.Order.Stop, price=self.boll.lines.bot[0], size=self.p.size)
      
      
              else:
      
                  if self.position.size > 0:
                      self.sell(exectype=bt.Order.Limit, price=self.boll.lines.mid[0], size=self.p.size)
      
                  else:
                      self.buy(exectype=bt.Order.Limit, price=self.boll.lines.mid[0], size=self.p.size)
      
              if self.p.debug:
                  print('---------------------------- NEXT ----------------------------------')
                  print("1: Data Name:                            {}".format(data._name))
                  print("2: Bar Num:                              {}".format(len(data)))
                  print("3: Current date:                         {}".format(data.datetime.datetime()))
                  print('4: Open:                                 {}'.format(data.open[0]))
                  print('5: High:                                 {}'.format(data.high[0]))
                  print('6: Low:                                  {}'.format(data.low[0]))
                  print('7: Close:                                {}'.format(data.close[0]))
                  print('8: Volume:                               {}'.format(data.volume[0]))
                  print('9: Position Size:                       {}'.format(self.position.size))
                  print('--------------------------------------------------------------------')
      
          def notify_trade(self, trade):
              if trade.isclosed:
                  dt = self.data.datetime.date()
      
                  print('---------------------------- TRADE ---------------------------------')
                  print("1: Data Name:                            {}".format(trade.data._name))
                  print("2: Bar Num:                              {}".format(len(trade.data)))
                  print("3: Current date:                         {}".format(dt))
                  print('4: Status:                               Trade Complete')
                  print('5: Ref:                                  {}'.format(trade.ref))
                  print('6: PnL:                                  {}'.format(round(trade.pnl, 2)))
                  print('--------------------------------------------------------------------')
      
      '''
      # Create a Strategy
      class TestStrategy(bt.Strategy):
          params = (
              ("period", 20),
              ("devfactor", 2.0)
          )
      
          # The first data in the list self.datas[0] is the default data for trading operations and to keep all strategy elements synchronized (it’s the system clock)
          def log(self, txt, dt=None):
              ''' Logging function fot this strategy'''
              dt = dt or self.datas[0].datetime.date(0)
              dt1 = self.data0.datetime.time(0)
              print('%s,%s, %s' % (dt.isoformat(), dt1.isoformat(), txt))
      
          '''
          This is a simple mean reversion bollinger band strategy.
      
          Entry Critria:
              - Long:
                  - Price closes below the lower band
                  - Stop Order entry when price crosses back above the lower band
              - Short:
                  - Price closes above the upper band
                  - Stop order entry when price crosses back below the upper band
          Exit Critria
              - Long/Short: Price touching the median line
          '''
          def __init__(self):
              init_time = time.time()
              self.startcash = self.broker.getvalue()
              # Keep a reference to the "close" line in the data[0] dataseries
              self.dataclose = self.datas[0].close
              # Keep a reference to the "close" line in the data[0] dataseries
              self.boll = btind.BollingerBands(period=self.p.period, devfactor=self.p.devfactor)
              # Signal Booleans
              self.sx = btind.CrossDown(self.data.close, self.boll.lines.top)
              self.lx = btind.CrossUp(self.data.close, self.boll.lines.bot)
      
              # Indicators for the plotting show
              # bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
              # bt.indicators.WeightedMovingAverage(self.datas[0], period=25,subplot=True)
      
          def notify_order(self, order):
              if order.status in [order.Submitted, order.Accepted]:
                  # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                  return
      
              # Check if an order has been completed
              # Attention: broker could reject order if not enough cash
              if order.status in [order.Completed]:
      
                  if order.isbuy():
                      self.log(
                          'BUY EXECUTED, Size: %.2f, OpenPrice: %.2f, Closeprice: %.2f, Comm %.2f' %
                          (order.executed.size,
                           order.executed.price,
                           order.executed.value,
                           order.executed.comm))
      
                      self.buyprice = order.executed.price
                      self.buycomm = order.executed.comm
                      self.opsize = order.executed.size
                  else:  # Sell
                      self.log(
                          'SELL EXECUTED, Size: %.2f, OpenPrice: %.2f, Closeprice: %.2f, Comm %.2f' %
                          (order.executed.size,
                           order.executed.price,
                           order.executed.value,
                           order.executed.comm))
      
      
              elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                  self.log('Order Canceled/Margin/Rejected')
      
              # Write down: no pending order
              self.order = None
      
          def notify_trade(self, trade):
      
              if not trade.isclosed:
                  return
      
              self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                       (trade.pnl, trade.pnlcomm))
      
          def next(self):
      
              if self.data.datetime.time() < Session_begin:
                  self.close()  # don't operate until x
                  return  #
              if self.data.datetime.time() > Session_end:
                  self.close()  # don't operate after y
                  return  #
      
              if self.lx == 1:
      
                  # BUY, BUY, BUY!!! (with all possible default parameters)
                  self.close()
                  self.log('BUY CREATE, %.2f' % self.dataclose[0])
      
                  # Keep track of the created order to avoid a 2nd order
                  self.order = self.buy()
      
      
              elif self.sx == 1:
      
                  # SELL, SELL, SELL!!! (with all possible default parameters)
                  self.close()
                  self.log('SELL CREATE, %.2f' % self.dataclose[0])
      
                  # Keep track of the created order to avoid a 2nd order
                  self.order = self.sell()
      
          def stop(self):
              pnl = round(self.broker.getvalue() - self.startcash, 3)
              print('SuTR Period: {} SuTR Mult: {}Final PnL: {}'.format(
                  self.params.period, self.params.devfactor, pnl))
      
      
      if __name__ == '__main__':
          # Variable for our starting cash
          startcash = 100000
      
          # Create a cerebro entity
          cerebro = bt.Cerebro(optreturn=False)
      
          # Add a strategy
          cerebro.optstrategy(TestStrategy, period=range(10, 41, 1), devfactor=np.linspace(1.0, 4.0, 31))
      
          data = btfeeds.GenericCSVData(
              dataname='CME_MINI_NQ1!Actual.csv',
      
              fromdate=fromdate,
              todate=todate,
              sessionstart=datetime.time(13, 00),
              sessionend=datetime.time(18, 00),
      
              dtformat=('%Y-%m-%d %H:%M:%S'),
              datetime=1,
              high=3,
              low=4,
              open=2,
              close=5,
              volume=6,
              openinterest=-1,
      
              timeframe=bt.TimeFrame.Minutes,
              compression=1
          )
      
          # Add the Data Feed to Cerebro
          cerebro.adddata(data)
      
          cerebro.addanalyzer(TotalReturns, _name='PNLS')
          cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
          cerebro.addanalyzer(bt.analyzers.SQN, _name='SQN')
          cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DrawDown')
      
          # Set our desired cash start
          cerebro.broker.setcash(startcash)
          # Set the commission
          cerebro.broker.setcommission(commission=0.00014, mult=20)
          # Run over everything
          print('Run Begin')
      
          #  #  -------------------------------- SET the cpus --------------------------------  #  #
          opt_runs = cerebro.run(maxcpus=20)
          print('Runs completed: ' + str(len(opt_runs)))
      

      Thanks in advance

      1 Reply Last reply Reply Quote 0
      • F
        FrankieC last edited by

        Tried using a list of float for devfactor.

        if __name__ == '__main__':
            # Variable for our starting cash
            startcash = 100000
        
            # Create a cerebro entity
            cerebro = bt.Cerebro(optreturn=False)
        
            # Add a strategy.  ADDED A LIST OF FLOAT INSTEAD OF NUMPY
            cerebro.optstrategy(TestStrategy, period=range(10, 41, 1), devfactor=[1.0, 2.0, 3.0])
        
            data = btfeeds.GenericCSVData(
                dataname='CME_MINI_NQ1!Actual.csv',
        
                fromdate=fromdate,
                todate=todate,
                sessionstart=datetime.time(13, 00),
                sessionend=datetime.time(18, 00),
        
                dtformat=('%Y-%m-%d %H:%M:%S'),
                datetime=1,
                high=3,
                low=4,
                open=2,
                close=5,
                volume=6,
                openinterest=-1,
        
                timeframe=bt.TimeFrame.Minutes,
                compression=1
            )
        
            # Add the Data Feed to Cerebro
            cerebro.adddata(data)
        
            cerebro.addanalyzer(TotalReturns, _name='PNLS')
            cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
            cerebro.addanalyzer(bt.analyzers.SQN, _name='SQN')
            cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DrawDown')
        
            # Set our desired cash start
            cerebro.broker.setcash(startcash)
            # Set the commission
            cerebro.broker.setcommission(commission=0.00014, mult=20)
            # Run over everything
            print('Run Begin')
        
            #  #  -------------------------------- SET the cpus --------------------------------  #  #
            opt_runs = cerebro.run(maxcpus=12)
            print('Runs completed: ' + str(len(opt_runs)))
        

        Now I got this.

        The above exception was the direct cause of the following exception:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3437, in run_code
            exec(code_obj, self.user_global_ns, self.user_ns)
          File "<ipython-input-2-c7f43e7322da>", line 1, in <module>
            runfile('/Users/frankie/PycharmProjects/pythonProject3/BollingerOPT.py', wdir='/Users/frankie/PycharmProjects/pythonProject3')
          File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
            pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
          File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
            exec(compile(contents+"\n", file, 'exec'), glob, loc)
          File "/Users/frankie/PycharmProjects/pythonProject3/BollingerOPT.py", line 353, in <module>
            opt_runs = cerebro.run(maxcpus=12)
          File "/usr/local/lib/python3.9/site-packages/backtrader/cerebro.py", line 1143, in run
            for r in pool.imap(self, iterstrats):
          File "/usr/local/Cellar/python@3.9/3.9.2_4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/pool.py", line 870, in next
            raise value
        AttributeError: 'Lines_LineSeries_LineIterator_DataAccessor_Strateg' object has no attribute 'order'
        
        run-out 1 Reply Last reply Reply Quote 0
        • run-out
          run-out @FrankieC last edited by

          @frankiec I ran your code and the only error I get is that your linspace is not a list. Try changing it to:

          devfactor=np.linspace(1.0, 4.0, 31).tolist()
          

          RunBacktest.com

          1 Reply Last reply Reply Quote 1
          • 1 / 1
          • First post
            Last post
          Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors