Creating an indicator for multiple data feeds
-
Hello, I am trying to figure out how to make an indicator that can be used for multiple stocks. In the following example I have two stocks that I wish to produce a moving average for based on their volume. The trouble is that im not sure how to specify to the SMA indicator that i want it to make the SMA based on the volume.
Below this code are other attempts iv tried.
import datetime from datetime import timedelta import pandas as pd import backtrader.feeds as btfeeds import pandas_datareader.data as web import backtrader as bt import backtrader.indicators as btind import backtrader.analyzers as btanalyzers import matplotlib.pyplot as plt class PandasclassFilter(btfeeds.PandasData): lines = ('volume',) params =(('volume', -1),) datafields = btfeeds.PandasData.datafields + ([ 'volume']) class Universe_filter(bt.Strategy): params = dict(period=14) def __init__(self): self.newSMA = btind.MovingAverageSimple(period = self.params.period) def next(self): for i, d in enumerate(self.datas): day_open = self.datas[i].open day_close = self.datas[i].close day_low = self.datas[i].low day_high = self.datas[i].high date = self.datas[i].datetime.date day_vol = self.datas[i].volume symbol = self.datas[i]._name Vol_14P_SMA = self.newSMA if Vol_14P_SMA[0] > 0 : Vol_percent = (day_vol[0]/Vol_14P_SMA[0])*100 else: Vol_percent = .00001 if date(0) == run().Filter_start: return if Vol_percent < 300: return if day_low[0] < ((day_close[-1])*.90): print("Date: %s Close: %s High: %s Low: %s Open: %s Vol: %s" %(date(0), day_close[0], day_high[0], day_low[0],day_open[0], day_vol[0])) print('Vol SMA: %s' % Vol_14P_SMA[0]) print('Vol Percent: %.2f%%' % Vol_percent) class run: Stocklist = ['OSTK','GME'] Filter_start = datetime.date(2017,4,27) Filter_end = datetime.date(2018,5,9) def runstrat(self): #Filter Cerebro start + add strat + data + run cerebro = bt.Cerebro() cerebro.addstrategy(Universe_filter) for i in (run.Stocklist): DF_1 = web.DataReader([i], "morningstar", run.Filter_start, run.Filter_end) DF_1= pd.DataFrame(DF_1) DF_1.reset_index(level = 'Symbol', inplace=True,drop=False) data1 = PandasclassFilter(dataname = DF_1) cerebro.adddata(data1, name = i) cerebro.run() cerebro.plot(style = 'candle',barup = 'green') if __name__ == '__main__': strat = run() strat.runstrat()
Below this text is another attempt i made by specifying in the init method that I want the SMA to refer to volume, however when i use a loop within the init function the next method seems to only take in one of the two stocks.
The code can be copy and pasted right into the init in the code above to replicate the issue.
def __init__(self): for i,d in enumerate(self.datas): print(i,d) day_vol = self.datas[i].volume self.newSMA = btind.MovingAverageSimple(day_vol, period = self.params.period)
-
This may be like a dumb question but:
- Where is code for the Indicator you are trying to create?
You say you are trying to create an indicator but there is no indicator in that code.
An example (amongst others) of an Indicator which uses multiple data feeds is
CrossOver
. It's in the sources. Furthermore you can even force the minimum number of data feeds which the indicator is bound to receive.You have a strategy and the extra code you say one can paste to reproduce an issue. Which issue?
Can you:
- Provide a complete example
- Indicate what's actually not working and why it is not working
- What's the input
- What's the output
-
@backtrader I am just trying to use the SMA. As i understand it, that is already built into backtrader.indicators or am i mistaken?
What i posted is the complete example. I am trying to compare the volume of the [0] day with SMA of the [0] value of a 14 period SMA on volume.
If the volume of the [0] day is over 300% of the [0] value on the SMA and the low of the day is over 10% lower than the previous day close, then I have the print functions tell me the OHLC, Volume and Vol_percent of those days.
Im not home now, but il post an additional reply with the outputs that this code makes. You should be able to just copy and paste it into any python interpreter though.
-
@sagittarius19 said in Creating an indicator for multiple data feeds:
I am just trying to use the SMA
The SMA is not an indicator for multiple data feeds. You are therefore NOT creating an indicator for multiple data feeds.
It seems that what you want to do is instantiate an indicator multiple times, each time with a single data feed.
The volume today is:
myvolumes[i] = self.datas[i].volume[0]
. The SMA is:mysmas[i] = bt.ind.SMA(self.datas[i].volume, period=14)
and the comparisonmyvolumes[i] > Factor_X * mysmas[i]
It is still unclear what your actual problem is. Instantiating a
SimpleMovingAverage
on one data feed or on 1,000,000 data feeds is simply a repetitive (ie: a loop) task. -
@backtrader I edited the code i posted as there was a typo. None the less i am still getting the errors i will show below. When i try and run the loop, I am only getting results for a single stock.
When I run:
import datetime from datetime import timedelta import pandas as pd import backtrader.feeds as btfeeds import pandas_datareader.data as web import backtrader as bt import backtrader.indicators as btind import backtrader.analyzers as btanalyzers import matplotlib.pyplot as plt class PandasclassFilter(btfeeds.PandasData): lines = ('volume',) params =(('volume', -1),) datafields = btfeeds.PandasData.datafields + ([ 'volume']) class Universe_filter(bt.Strategy): params = dict(period=14) def __init__(self): for i,d in enumerate(self.datas): day_vol = self.datas[i].volume self.newSMA = btind.MovingAverageSimple(day_vol, period = self.params.period) def next(self): for i, d in enumerate(self.datas): day_open = self.datas[i].open day_close = self.datas[i].close day_low = self.datas[i].low day_high = self.datas[i].high date = self.datas[i].datetime.date day_vol = self.datas[i].volume symbol = self.datas[i]._name Vol_14P_SMA = self.newSMA if Vol_14P_SMA[0] > 0 : Vol_percent = (day_vol[0]/Vol_14P_SMA[0])*100 else: Vol_percent = .00001 if date(0) == run().Filter_start: return if Vol_percent < 300: return if day_low[0] < ((day_close[-1])*.90): print(" %s %s Close: %s High: %s Low: %s Open: %s Vol: %s" %(date(0),symbol, day_close[0], day_high[0], day_low[0],day_open[0], day_vol[0])) print('Vol SMA: %s' % Vol_14P_SMA[0]) print('Vol Percent: %.2f%%' % Vol_percent) class run: Stocklist = ['OSTK', 'GME'] Filter_start = datetime.date(2017,4,27) Filter_end = datetime.date(2018,5,9) def runstrat(self): #Filter Cerebro start + add strat + data + run cerebro = bt.Cerebro() cerebro.addstrategy(Universe_filter) for i in (run.Stocklist): DF_1 = web.DataReader([i], "morningstar", run.Filter_start, run.Filter_end) DF_1= pd.DataFrame(DF_1) DF_1.reset_index(level = 'Symbol', inplace=True,drop=False) data1 = PandasclassFilter(dataname = DF_1) cerebro.adddata(data1, name = i) cerebro.run() cerebro.plot(style = 'candle',barup = 'green') if __name__ == '__main__': strat = run() strat.runstrat()
I get:
[Running] python "c:\Python36\.vscode\Strats\Filter2.py" 2017-10-10 OSTK Close: 28.1 High: 33.3 Low: 27.575 Open: 31.0 Vol: 5122374.0 Vol SMA: 1538266.71429 Vol Percent: 333.00% 2017-12-19 OSTK Close: 73.625 High: 82.7 Low: 68.05 Open: 73.45 Vol: 10734603.0 Vol SMA: 3326904.85714 Vol Percent: 322.66% [Done] exited with code=0 in 4.773 seconds
I should be seeing results for both OSTK and GME but am only seeing results for OSTK.
When I run GME alone in the "Stocklist" I get 4 different dates that the filter printed:
[Running] python "c:\Python36\.vscode\Strats\Filter2.py" 2017-05-26 GME Close: 22.22 High: 22.41 Low: 21.25 Open: 22.12 Vol: 14211097.0 Vol SMA: 3749496.35714 Vol Percent: 379.01% 2017-08-25 GME Close: 19.4 High: 19.94 Low: 18.72 Open: 19.9 Vol: 20495518.0 Vol SMA: 3517752.5 Vol Percent: 582.63% 2018-01-12 GME Close: 17.76 High: 18.54 Low: 17.63 Open: 18.5 Vol: 15445837.0 Vol SMA: 3750032.57143 Vol Percent: 411.89% 2018-03-29 GME Close: 12.62 High: 13.0 Low: 12.2 Open: 12.65 Vol: 25461816.0 Vol SMA: 5696925.71429 Vol Percent: 446.94% [Done] exited with code=0 in 4.181 seconds
So in short, there is some issue with having a loop in the init method that is preventing next from receiving both datafeeds. I tried using prenext but that didnt solve the problem either. I should be getting all the filtered dates for both OSTK and GME.
I tried putting the [i] after self.newSMA but it was throwing errors as well. If that is a critical point let me know.
Thanks again for your patience and help on this.
EDIT
For completeness here is the code and output with the [i] at the end of self.newSMA:import datetime from datetime import timedelta import pandas as pd import backtrader.feeds as btfeeds import pandas_datareader.data as web import backtrader as bt import backtrader.indicators as btind import backtrader.analyzers as btanalyzers import matplotlib.pyplot as plt class PandasclassFilter(btfeeds.PandasData): lines = ('volume',) params =(('volume', -1),) datafields = btfeeds.PandasData.datafields + ([ 'volume']) class Universe_filter(bt.Strategy): params = dict(period=14) def __init__(self): for i,elm in enumerate(self.datas): day_vol = self.datas[i].volume self.newSMA[i] = btind.MovingAverageSimple(day_vol, period = self.params.period) def next(self): for i, elm in enumerate(self.datas): day_open = self.datas[i].open day_close = self.datas[i].close day_low = self.datas[i].low day_high = self.datas[i].high date = self.datas[i].datetime.date day_vol = self.datas[i].volume symbol = self.datas[i]._name Vol_14P_SMA = self.newSMA[i] if Vol_14P_SMA[0] > 0 : Vol_percent = (day_vol[0]/Vol_14P_SMA[0])*100 else: Vol_percent = .00001 if date(0) == run().Filter_start: return if Vol_percent < 300: return if day_low[0] < ((day_close[-1])*.90): print(' ') print("%s %s Close: %s High: %s Low: %s Open: %s Vol: %s" %(date(0),symbol, day_close[0], day_high[0], day_low[0],day_open[0], day_vol[0])) print('Vol SMA: %s' % Vol_14P_SMA[0]) print('Vol Percent: %.2f%%' % Vol_percent) class run: Stocklist = ["OSTK", 'GME'] Filter_start = datetime.date(2017,4,27) Filter_end = datetime.date(2018,5,9) def runstrat(self): #Filter Cerebro start + add strat + data + run cerebro = bt.Cerebro() cerebro.addstrategy(Universe_filter) for i in (run.Stocklist): DF_1 = web.DataReader([i], "morningstar", run.Filter_start, run.Filter_end) DF_1= pd.DataFrame(DF_1) DF_1.reset_index(level = 'Symbol', inplace=True,drop=False) data1 = PandasclassFilter(dataname = DF_1) cerebro.adddata(data1, name = i) cerebro.run() #cerebro.plot(style = 'candle',barup = 'green') if __name__ == '__main__': strat = run() strat.runstrat()
[Running] python "c:\Python36\.vscode\Strats\Filter2.py" Traceback (most recent call last): File "c:\Python36\.vscode\Strats\Filter2.py", line 76, in <module> strat.runstrat() File "c:\Python36\.vscode\Strats\Filter2.py", line 71, in runstrat cerebro.run() File "C:\Python27_path\lib\site-packages\backtrader\cerebro.py", line 1127, in run runstrat = self.runstrategies(iterstrat) File "C:\Python27_path\lib\site-packages\backtrader\cerebro.py", line 1217, in runstrategies strat = stratcls(*sargs, **skwargs) File "C:\Python27_path\lib\site-packages\backtrader\metabase.py", line 88, in __call__ _obj, args, kwargs = cls.doinit(_obj, *args, **kwargs) File "C:\Python27_path\lib\site-packages\backtrader\metabase.py", line 78, in doinit _obj.__init__(*args, **kwargs) File "c:\Python36\.vscode\Strats\Filter2.py", line 22, in __init__ self.newSMA[i] = btind.MovingAverageSimple(day_vol, period = self.params.period) File "C:\Python27_path\lib\site-packages\backtrader\lineseries.py", line 461, in __getattr__ return getattr(self.lines, name) AttributeError: 'Lines_LineSeries_LineIterator_DataAccessor_Strateg' object has no attribute 'newSMA' [Done] exited with code=1 in 2.811 seconds
-
@sagittarius19 said in Creating an indicator for multiple data feeds:
So in short, there is some issue with having a loop in the init
There is for sure no issue, because there are several tens of scripts doing a loop and accessing all data feeds.
I fail to understand the complexities of the script. If you want to diagnose a problem, you should remove everything but the data feeds that you are inputting and then see if that feeds and the values produced by them are available to you. The rest of the code is simply bothering your ability to diagnose any mistake you are making.
@sagittarius19 said in Creating an indicator for multiple data feeds:
For completeness here is the code and output with the [i] at the end of self.newSMA:
@sagittarius19 said in Creating an indicator for multiple data feeds:
self.newSMA[i] = btind.MovingAverageSimple(day_vol, period = self.params.period)
@sagittarius19 said in Creating an indicator for multiple data feeds:
AttributeError: 'Lines_LineSeries_LineIterator_DataAccessor_Strateg' object has no attribute 'newSMA'
It that's one of the major problems you are facing, I can only very sincerely suggest: master the basics of programming and Python before carrying on with algotrading. This is not meant with arrogance and not meant as patronizing. It is meant as sincere advice.
-
@backtrader The loop in the init function is accessing all the data feeds just fine.
The trouble is that when i have a loop in the init method, the next method is only running one of the data feeds. I have concluded this issue by doing exactly what you described. I stripped the code of everything and then ran it with the loop in init and then without it. That's what iv found to be the issue.
I understand that the issue most likely lies somewhere else and that's why i'm posting on the forums to see if anyone can point out where the problem is. Iv searched for examples where people try to use a SMA using a specific column from a datafeed but failed to find any.
I wasn't totally sure if when you put [i] at the end of mysmas[i], if you were suggesting I write my code like that or were just referring to mysmas when it was running through any of the datafeeds.
If my questions sound too elementary for your liking then I apologize. Iv done the basic python tutorials but I learn best by getting involved and trying to make a strategy in my head work, rather than spending countless hours doing theoretical exercises. I suspect many more people than you think also are looking for the same questions I have. These forums don't have people posting with a super high frequency and the documentation can be confusing to new people, so its good for the community to be ok with answering questions that seem basic/silly to you. If not you get people who are scared to post something in the fear of being told to master programming before they peruse algotrading.
-
I found a solution.
import datetime from datetime import timedelta import pandas as pd import backtrader.feeds as btfeeds import pandas_datareader.data as web import backtrader as bt import backtrader.indicators as btind import backtrader.analyzers as btanalyzers import matplotlib.pyplot as plt class PandasclassFilter(btfeeds.PandasData): lines = ('volume',) params =(('volume', -1),) datafields = btfeeds.PandasData.datafields + ([ 'volume']) class Universe_filter(bt.Strategy): params = dict(period=14) def __init__(self): self.SMAlist = dict() for i, elm in enumerate(self.datas): day_vol = self.datas[i].volume symbol = self.datas[i]._name self.SMAlist[symbol]= [btind.MovingAverageSimple(day_vol, period = self.params.period)] def next(self): for i, elm in enumerate(self.datas): day_open = self.datas[i].open day_close = self.datas[i].close day_low = self.datas[i].low day_high = self.datas[i].high date = self.datas[i].datetime.date day_vol = self.datas[i].volume symbol = self.datas[i]._name Vol_14P_SMA = self.SMAlist[symbol][0] Vol_percent = (day_vol[0]/Vol_14P_SMA[0])*100 if date(0) == run().Filter_start: return if Vol_14P_SMA[0] != Vol_14P_SMA[0] : return if Vol_percent > 300: if day_low[0] < ((day_close[-1])*.90): print(' ') print("%s %s Close: %s High: %s Low: %s Open: %s Vol: %s" %(date(0),symbol, day_close[0], day_high[0], day_low[0],day_open[0], day_vol[0])) print('Vol SMA: %s' % Vol_14P_SMA[0]) print('Vol Percent: %.2f%%' % Vol_percent) class run: Stocklist = ['OSTK', 'GME'] Filter_start = datetime.date(2017,4,27) Filter_end = datetime.date(2018,5,9) def runstrat(self): #Filter Cerebro start + add strat + data + run cerebro = bt.Cerebro() cerebro.addstrategy(Universe_filter) for i in (run.Stocklist): DF_1 = web.DataReader([i], "morningstar", run.Filter_start, run.Filter_end) DF_1= pd.DataFrame(DF_1) DF_1.reset_index(level = 'Symbol', inplace=True,drop=False) data1 = PandasclassFilter(dataname = DF_1) cerebro.adddata(data1, name = i) cerebro.run() #cerebro.plot(style = 'candle',barup = 'green') if __name__ == '__main__': strat = run() strat.runstrat()
In my conditonal statements before I had:
if Vol_percent < 300: return
I changed it to:
if Vol_percent > 300: if day_low[0] < ((day_close[-1])*.90): print(' ') print("%s %s Close: %s High: %s Low: %s Open: %s Vol: %s" %(date(0),symbol, day_close[0], day_high[0], day_low[0],day_open[0], day_vol[0])) print('Vol SMA: %s' % Vol_14P_SMA[0]) print('Vol Percent: %.2f%%' % Vol_percent)
I also had each SMA added to a dictionary when it was made:
def __init__(self): self.SMAlist = dict() for i, elm in enumerate(self.datas): day_vol = self.datas[i].volume symbol = self.datas[i]._name self.SMAlist[symbol]= [btind.MovingAverageSimple(day_vol, period = self.params.period)]