Simple channel indicator



  • Hi
    I have tried to write a simple channel indicator. The indicator has 2 lines. The maximum of the last 20 Highs and the minimum of the last 20 lows.
    Here is my attempt.

    class channel20(bt.Indicator):
        lines = ('maxi','mini',)
        params = (('period', 20),)
        
        def __init__(self):
            self.l.maxi= math.max(self.data.high(p.period))
            self.l.mini= math.min(self.data.low(p.period))
    

    putting it into a simple strategy to see if it works, I tried this:

    from datetime import datetime
    import backtrader as bt
    
    
    class channel20(bt.Indicator):
        lines = ('maxi','mini',)
        params = (('period', 20),)
        
        def __init__(self):
            self.l.maxi= math.max(self.data.high(p.period))
            self.l.mini= math.min(self.data.low(p.period))
    
    class test(bt.SignalStrategy):
        def __init__(self):
            channel = bt.indicator.channel20
    
    cerebro = bt.Cerebro()
    cerebro.addstrategy(test)
    
    data0 = bt.feeds.YahooFinanceData(dataname='YHOO', fromdate=datetime(2011, 1, 1),
                                      todate=datetime(2012, 12, 31))
    cerebro.adddata(data0)
    
    cerebro.run()
    cerebro.plot()
    

    to get the following error.

    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-22-d93cd4afba9e> in <module>()
         23 cerebro.adddata(data0)
         24 
    ---> 25 cerebro.run()
         26 cerebro.plot()
    
    /home/rory/anaconda2/envs/backtrader/lib/python2.7/site-packages/backtrader/cerebro.pyc in run(self, **kwargs)
       1068             # let's skip process "spawning"
       1069             for iterstrat in iterstrats:
    -> 1070                 runstrat = self.runstrategies(iterstrat)
       1071                 self.runstrats.append(runstrat)
       1072         else:
    
    /home/rory/anaconda2/envs/backtrader/lib/python2.7/site-packages/backtrader/cerebro.pyc in runstrategies(self, iterstrat, predata)
       1144                 data._start()
       1145                 if self._dopreload:
    -> 1146                     data.preload()
       1147 
       1148         for stratcls, sargs, skwargs in iterstrat:
    
    /home/rory/anaconda2/envs/backtrader/lib/python2.7/site-packages/backtrader/feed.pyc in preload(self)
        687 
        688         # preloaded - no need to keep the object around - breaks multip in 3.x
    --> 689         self.f.close()
        690         self.f = None
        691 
    
    AttributeError: 'NoneType' object has no attribute 'close'
    
    

    I was hoping to simply get a printout of the indicator.
    I wonder if anyone could shed some light on what I have done wrong.
    Thanks

    RM



  • Hi @rorymack

    I ran your script and got the same error. Taking out your indicator from the strategy __init__ still returned the same error.

    After a bit of tinkering I found a few issues:

    1. The data feed is causing the first error you are seeing
    AttributeError: 'NoneType' object has no attribute 'close'
    

    I replaced your data feed with my own and got passed this.

    1. You do not need to load your indicator with bt.indicator.channel20 as it is in your script. You can initialize it with self.channel = channel20()

    2. You have not imported the math module. However if you do, you will get the following error

    module 'math' has no attribute 'max'
    
    1. If I understand you correctly, you want the indicator to display the highest high of the last 20 candles and the lowest low of the last 20 candles. This is no compatible with a signal strategy. For signal strategies, the lines need to alternate between 1 and -1 values for long and short signals.

    2. you are missing a few self declarations.

    Some other thoughts and a solution:

    1. You probably want to add a self.addminperiod(self.p.period) line to your indicators __init__ so that it doesn't try and do anything until it has 20 candles worth of data.

    Here is a working version of what I think you are looking for. Replace the data with your own. (Or investigate why the Yahoo call isn't working. I don't use Yahoo data so have not looked into it)

    from datetime import datetime
    import backtrader as bt
    
    
    class channel20(bt.Indicator):
        lines = ('maxi','mini',)
        params = (('period', 20),)
    
        def __init__(self):
            self.addminperiod(self.p.period)
    
        def next(self):
            highs = self.data.high.get(size=self.p.period)
            lows = self.data.low.get(size=self.p.period)
            self.lines.maxi[0] = max(highs)
            self.lines.mini[0] = min(lows)
    
    
    class test(bt.Strategy):
        def __init__(self):
            self.channel = channel20()
    
    cerebro = bt.Cerebro()
    cerebro.addstrategy(test)
    
    fromdate = datetime(2012,1,1)
    todate = datetime(2012,1,5)
    datapath = '../data/csv/commodities/XAUUSD/XAUUSD_m1_Ask_2012-2016.csv'
    data0 = bt.feeds.GenericCSVData(
        timeframe=bt.TimeFrame.Minutes,
        compression=1,
        dataname=datapath,
        nullvalue=0.0,
        dtformat=('%m/%d/%Y'),
        tmformat=('%H:%M:%S'),
        fromdate=fromdate,
        todate=todate,
        datetime=0,
        time=1,
        high=3,
        low=4,
        open=2,
        close=5,
        volume=6,
        openinterest=-1 #-1 means not used
        )
    
    cerebro.adddata(data0)
    
    cerebro.run()
    cerebro.plot()
    
    

    This resulted in the following

    0_1494744951775_figure_0.png

    Hope this helps!



  • Hi @ThatBlokeDave

    Thank you for your reply. I am blundering my way around here and appreciate you taking time to help me out.

    1. Interesting that is was the data feed, I hacked this attempt from the SMACrossover example.
      Looking at the DataFeeds reference https://www.backtrader.com/docu/dataautoref.html
      It appears that it has all the lines needed.
      Is it generally better to create csv's of the data ?
      I notice quickstart tutorial does that.

    I ran it again with your modifications and the yahoo data feed and it worked! Not sure if it is a version thing.

    1. AhHa ! Thank you my python fu is not what it should be...
    2. Ok, again poor Python skills on my side.
    3. Not sure if I follow you here, I had in mind that I would set up the indicator and then set up the signal in
     def next(self):
    

    with this logic :
    If previous period channel max is < current period high buy
    and
    If previous period channel low is > current period low sell
    Which would generate the long and short signals.

    I had it in my head that the indicator is separate from the signal, Is that conceptually correct?

    1. Again thanks bad python on my part.

    The solution

    1. Yes I do !

    Thank you for the solution, I will play around some more with it to get to grips with the platform.
    As an aside, how do you get the indicator to plot as an overlay on the data? As opposed to in a separate plot.

    Thanks again.
    RM


  • administrators

    @rorymack said in Simple channel indicator:

        def __init__(self):
            self.l.maxi= math.max(self.data.high(p.period))
            self.l.mini= math.min(self.data.low(p.period))
    

    Unfortunately math.max (actually max) doesn't generate a lines lazily evaluated object and self.data.high(period) gives you a delayed lines lazily evaluated object, but not an array, which is the expectation of max.

    The platform includes Highest (aka MaxN) and Lowest (aka MinN) indicators for that.

    self.lines.maxi = bt.ind.MaxN(self.data, period=self.p.period)
    

    This is also wrong:

    channel = bt.indicator.channel20
    

    because there is no instantiation. You are simply assigning a class (and not an instance of it) to channel



  • @rorymack said in Simple channel indicator:

    As an aside, how do you get the indicator to plot as an overlay on the data? As opposed to in a separate plot.

    If you want the indicator to appear on the price chart. (I agree, this indicator is a good one to appear with price). Then add subplot=False when initializing the indicator. This is a Backtrader keyword argument for Indicators.

    self.channel = channel20(subplot=False)
    

    It will result in something that looks like this:

    0_1494811551945_figure_0.png

    I ran it again with your modifications and the yahoo data feed and it worked! Not sure if it is a version thing.

    It also could have been a Yahoo server issue... It was the weekend.

    with this logic :
    If previous period channel max is < current period high buy
    and
    If previous period channel low is > current period low sell
    Which would generate the long and short signals.

    Ok - If I follow this correctly, the script I gave you would need a tweak because the current candles high / low would never be above the min/max for the period as the current candle is included in the calculation. You can see this in the above example image.

    To do this you can just pop the most recent result out of the list and you can check if the current candle is above or below the period max/min. Note that the period you are now comparing against is 19 candles instead of 20.


  • administrators

    @ThatBlokeDave said in Simple channel indicator:

    Then add subplot=False when initializing the indicator

    Or else add

    plotinfo = dict(subplot=False)
    

    to the indicator definition



  • Vote to add indicator into main package! It's great for Forex market.



  • @Maxim-Korobov

    Main package already has Highest and Lowest indicators.
    docs - indicators ref


Log in to reply
 

Looks like your connection to Backtrader Community was lost, please wait while we try to reconnect.