Thanks for the toolset, really quite indispensable.
I wrote up a Strategy and would like to have a customer Indicator class instead of integrating it directly into the Strategy (so I can use it elsewhere possibly). It seems to me like I made the correct classes and also instantiated them correctly, but there seems to be an error, upon running Cerebro, with the indicator's internals.
The reason I'm writing about it here and not in pyfinance is because I use the ols.PandasRollingOLS
from pyfinance by itself without issue. It's only the way I've written it into my Indicator class that throws an error, and the error ends up in a backtrader module (lineseries.py
). Sorry for length, shortened it as much as feasible:
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds
import pandas
from pyfinance import ols
# Create an Indicator
class FloatingWindowResiduals(bt.Indicator):
_mindatas = 2
lines = ('zscore',)
params = (
('wndw', 65),
)
def __init__(self):
self.addminperiod(self.params.wndw)
def next(self):
rolling_beta = ols.PandasRollingOLS(y=self.data0, x=self.data1,
window=self.params.wndw)
spread = self.data1 - (rolling_beta.beta['feature1'] *
self.data0 + rolling_beta.alpha)
# Get the 1 day moving average of the price spread
spread_mavg1 = spread.rolling(window=1, center=False).mean()
# Get the X day moving average
spread_mavgx = spread.rolling(window=self.params.wndw, center=False).mean()
# Take a rolling X day standard deviation
std_x = spread.rolling(window=self.params.wndw, center=False).std()
# Compute the z score for each day
self.lines.zscore[0] = (spread_mavg1 - spread_mavgx)/std_x
# Create a Stratey
class ResidualModel(bt.Strategy):
params = (
('wndw', 65),
...
)
# Initialise upon class instantiation
def __init__(self):
...
# Add an indicator
self.fwResid = FloatingWindowResiduals(self.data0, self.data1)
...
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(ResidualModel)
# Get a pandas dataframe
datapath0 = ('ABC.csv')
dataframe0 = pandas.read_csv(datapath0,
names=['date', 'open', 'high', 'low', 'close'],
skiprows=1,
header=None,
index_col='date',
usecols=['date','close'],
parse_dates=True)
data0 = bt.feeds.PandasData(dataname=dataframe0)
cerebro.adddata(data0)
datapath1 = ('XYZ.csv')
dataframe1 = pandas.read_csv(datapath1,
names=['date', 'open', 'high', 'low', 'close'],
skiprows=1,
header=None,
index_col='date',
usecols=['date','close'],
parse_dates=True)
data1 = bt.feeds.PandasData(dataname=dataframe1)
cerebro.adddata(data1)
cerebro.run()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-3938288c90b6> in <module>()
261 cerebro.adddata(data1)
262
--> 263 cerebro.run()
264
~\Anaconda3\lib\site-packages\backtrader\cerebro.py in run(self, **kwargs)
1125 # let's skip process "spawning"
1126 for iterstrat in iterstrats:
-> 1127 runstrat = self.runstrategies(iterstrat)
1128 self.runstrats.append(runstrat)
1129 if self._dooptimize:
~\Anaconda3\lib\site-packages\backtrader\cerebro.py in runstrategies(self, iterstrat, predata)
1291 self._runonce_old(runstrats)
1292 else:
-> 1293 self._runonce(runstrats)
1294 else:
1295 if self.p.oldsync:
~\Anaconda3\lib\site-packages\backtrader\cerebro.py in _runonce(self, runstrats)
1650
1651 for strat in runstrats:
-> 1652 strat._once()
1653 strat.reset() # strat called next by next - reset lines
1654
~\Anaconda3\lib\site-packages\backtrader\lineiterator.py in _once(self)
290
291 for indicator in self._lineiterators[LineIterator.IndType]:
--> 292 indicator._once()
293
294 for observer in self._lineiterators[LineIterator.ObsType]:
~\Anaconda3\lib\site-packages\backtrader\lineiterator.py in _once(self)
310 # indicators are each called with its min period
311 self.preonce(0, self._minperiod - 1)
--> 312 self.oncestart(self._minperiod - 1, self._minperiod)
313 self.once(self._minperiod, self.buflen())
314
~\Anaconda3\lib\site-packages\backtrader\indicator.py in oncestart_via_nextstart(self, start, end)
122
123 self.advance()
--> 124 self.nextstart()
125
126 def once_via_next(self, start, end):
~\Anaconda3\lib\site-packages\backtrader\lineiterator.py in nextstart(self)
340
341 # Called once for 1st full calculation - defaults to regular next
--> 342 self.next()
343
344 def next(self):
<ipython-input-3-3938288c90b6> in next(self)
34
35 rolling_beta = ols.PandasRollingOLS(y=self.data0, x=self.data1,
---> 36 window=self.params.wndw)
37
38 spread = self.data1 - (rolling_beta.beta['feature1'] *
~\Anaconda3\lib\site-packages\pyfinance\ols.py in __init__(self, y, x, window, has_const, use_const, names)
703 k = x.shape[-1] - 1
704 else:
--> 705 if x.ndim == 1:
706 k = 1
707 else:
~\Anaconda3\lib\site-packages\backtrader\lineseries.py in __getattr__(self, name)
459 # in this object if we set an attribute in this object it will be
460 # found before we end up here
--> 461 return getattr(self.lines, name)
462
463 def __len__(self):
AttributeError: 'Lines_LineSeries_DataSeries_OHLC_OHLCDateTime_Abst' object has no attribute 'ndim'
So what I'm not sure is why is it looking for a numpy ndmin
attribute in what looks like the window
parameter in my rolling_beta = ols.PandasRollingOLS(y=self.data0, x=self.data1, window=self.params.wndw)
calculation? This PandasRollingOLS
method takes 3 parameters, and I figure I'm assigning them more or less as intended in the Strategy parameters, right?