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

indicator with dynamically changing data



  • Hi,

    I am really new to backtrader, so apologies if this is trivial.

    If I create a custom dummy indicator, I can change its data feeds dynamically inside the strategy and if I run the strategy with runonce=False, it works fine (look below for the code). However, if I use an existing indicator instead e.g. bt.indicators.SimpleMovingAverage, the same approach does not work.

    Many thanks for your help.

    class DynamicIndicator(bt.Indicator):
        lines = ('indicator1',)
    
        def next(self):
            self.lines.indicator1[0] = self.datas[0][0]
    
    class TestStrategy(bt.Strategy):
        def __init__(self):
        self.ind = DynamicIndicator(self.datas[1].lines.close)
    
        def next(self):
            if <condition>:
                self.ind.datas[0] = self.datas[2].lines.close
    

  • administrators

    The indicators use a generic notation to refer to the data with which they operate and don't care about indexing the array.

    In any case I feel you are using the wrong approach imho. Instead of changing the data feeds, you should change what the data feed delivers, which at the end of the day delivers the same result, but is compatible with any way an indicator can be written.



  • Many thanks for the reply and the help.

    Do you mean instead of changing the data of the indicator, change the data feed itself?

    I tried that below and although the data feed changes, the moving average is not updated.

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    import backtrader as bt
    import pandas as pd
    import datetime
    
    # Create a Strategy
    class TestStrategy(bt.Strategy):
    
        params = (
            ('maperiod', 2),
        )
    
        def log(self, txt, dt=None):
            ''' Logging function for this strategy'''
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))
    
        def __init__(self):
            self.ind = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
    
        def next(self):
            if self.datas[0].datetime.date(0) == datetime.datetime(2018, 1, 6).date():
                self.datas[0] = self.datas[1]
                # del self.datas[0]
            self.log("{}, {}".format(self.datas[0].lines.close[0], self.ind.lines.sma[0]))
    
    
    if __name__ == '__main__':
        # Create a cerebro entity
        cerebro = bt.Cerebro()
    
        # Add a strategy with parameters
        cerebro.addstrategy(TestStrategy, maperiod=2)
    
        df1 = pd.DataFrame({'Close': list(range(1,11))}, index=pd.date_range(start='1/1/2018', periods=10))
        df2 = pd.DataFrame({'Close': list(range(2,12))}, index=pd.date_range(start='1/1/2018', periods=10))
        cerebro.adddata(bt.feeds.PandasData(dataname=df1, nocase=True))
        cerebro.adddata(bt.feeds.PandasData(dataname=df2, nocase=True))
    
        # Set our desired cash start
        cerebro.broker.setcash(100000.0)
    
        # Print out the starting conditions
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
        # Run over everything
        cerebro.run(runonce=False, preload=False)
    
        # Print out the final result
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
    Starting Portfolio Value: 100000.00
    2018-01-02, 2.0, 1.5
    2018-01-03, 3.0, 2.5
    2018-01-04, 4.0, 3.5
    2018-01-05, 5.0, 4.5
    2018-01-06, 7.0, 5.5
    2018-01-07, 8.0, 6.5
    2018-01-08, 9.0, 7.5
    2018-01-09, 10.0, 8.5
    2018-01-10, 11.0, 9.5
    Final Portfolio Value: 100000.00
    

  • administrators

    @momentum said in indicator with dynamically changing data:

    Do you mean instead of changing the data of the indicator, change the data feed itself?

    No, I said that you create a data feed which dynamically changes the data it serves.

    You fail to understand that the self.datas array in each object is a different one. This is obvious if you consider that you can do this

    ma_on_ma = bt.ind.SMA(bt.ind.SMA(self.data, period=5), period=5)
    

    The outer SMA has, obviously, only 1 data feed which happens to be another SMA (the inner one)

    Futhermore, you expect things to use the array self.datas and you manipulate it with that expectation. As noted above, this is not supported, given that other notations and references are used in the internals.



  • I am trying to generate a dynamically changing data feed based on the RollOver class but it does not work always correctly when trying to update the history at the same time (self.params.update_history_upon_roll set to True). For example when moving to a data feed with longer history it gives wrong results. Any ideas?

    Thanks

        def _roll(self):
            if self._ds:
                clock1 = self.lines[0].lencount
                self._dexp = self._d
                self._d = self._ds.pop(0)
                self._dts.pop(0)
                clock3 = self._d.lines[0].lencount
                if self.params.update_history_upon_roll:
                    if clock3 < clock1:
                        self._d.lines.rewind()
                    for i in range(len(self.lines.lines)):
                        self.lines[i].array = self._d.lines[i].array[:]
                        self.lines[i].lencount = self._d.lines[i].lencount
                        self.lines[i].idx = self._d.lines[i].idx
                    if clock3 < clock1:
                        self.lines.advance()
                return True
            else:
                return False
    
        def _load(self):
            while self._d is not None:
                if self._doroll:
                    self._roll()
                    self._doroll = False
                _next = self._d.next()
                if _next is None:  # no values yet, more will come
                    continue
                if _next is False:  # no values from current data src
                    if self._ds:
                        self._d = self._ds.pop(0)
                        self._dts.pop(0)
                    else:
                        self._d = None
                    continue
    
                dt0 = self._d.datetime.datetime()  # current dt for active data
    
                # Synchronize other datas using dt0
                for i, d_dt in enumerate(zip(self._ds, self._dts)):
                    d, dt = d_dt
                    while dt < dt0:
                        if d.next() is None:
                            continue
                        self._dts[i] = dt = d.datetime.datetime()
    
                # Move expired future as much as needed
                while self._dexp is not None:
                    if not self._dexp.next():
                        self._dexp = None
                        break
    
                    if self._dexp.datetime.datetime() < dt0:
                        continue
    
                if self._dexp is None and self._checkdate(dt0, self._d):
                    # rule has been met ... check other factors only if 2 datas
                    # still there
                    if self._ds and self._checkcondition(self._d, self._ds[0]):
                        if self.params.upon_condition:
                            self._roll()
                        else:
                            self._doroll = True
    
                # Fill the line and tell we die
                for i in range(len(self.lines.lines)):
                    self.lines[i][0] = self._d.lines[i][0]
    
                return True
    
            # Out of the loop -> self._d is None, no data feed to return from
            return False
    

Log in to reply
 

});