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
-
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
-
@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 thisma_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 anotherSMA
(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