backtrader2 / backtrader

Python Backtesting library for trading strategies
https://www.backtrader.com
GNU General Public License v3.0
221 stars 52 forks source link

Fix #46 -- TA-Lib functions which require inputs besides close do not work properly #50

Closed lukeplausin closed 3 years ago

lukeplausin commented 3 years ago

Hi, I believe this fixes the broken TA-Lib functions.

The process is -

Check the _tabstract object to see which inputs are required
If more than one input is required, build the inputs using the named variables
If not, use the old code

Tested with these functions:

SMA
RSI
MACD
ULTOSC (non trivial)
MFI (non trivial)

Closed #46 because I want to add other changes to master in my fork

neilsmurphy commented 3 years ago

Hi, I made a copy of your pull request and tried to run:

self.sma = bt.talib.MFI(
            self.data.high,
            self.data.low,
            self.data.close,
            self.data.volume,
            timeperiod=15,
        )

I get the following error message:

/home/runout/a_bt_env/backtrader/venv/bin/python3 /home/runout/a_bt_env/backtrader/talib_fix.py
Traceback (most recent call last):
  File "/home/runout/a_bt_env/backtrader/talib_fix.py", line 55, in <module>
    cerebro.run()
  File "/home/runout/a_bt_env/backtrader/backtrader/cerebro.py", line 1127, in run
    runstrat = self.runstrategies(iterstrat)
  File "/home/runout/a_bt_env/backtrader/backtrader/cerebro.py", line 1293, in runstrategies
    self._runonce(runstrats)
  File "/home/runout/a_bt_env/backtrader/backtrader/cerebro.py", line 1652, in _runonce
    strat._once()
  File "/home/runout/a_bt_env/backtrader/backtrader/lineiterator.py", line 297, in _once
    indicator._once()
  File "/home/runout/a_bt_env/backtrader/backtrader/lineiterator.py", line 318, in _once
    self.once(self._minperiod, self.buflen())
  File "/home/runout/a_bt_env/backtrader/backtrader/talib.py", line 197, in once
    x_inputs = [
  File "/home/runout/a_bt_env/backtrader/backtrader/talib.py", line 198, in <listcomp>
    np.array(getattr(x, field).array)
  File "/home/runout/a_bt_env/backtrader/backtrader/lineseries.py", line 461, in __getattr__
    return getattr(self.lines, name)
AttributeError: 'Lines_LineSeries_LineSeriesStub' object has no attribute **'high'**

I tried to dig around the code to find out how you were trying to reference the lines but I couldn't figure it out. The code is definitely getting stuck here:

x_inputs = [
                        np.array(getattr(x, field).array)
                        for field in self._tabstract.info['input_names']['prices']
                    ]

And then line 457 in the lineseries.py .

    def __getattr__(self, name):
        # to refer to line by name directly if the attribute was not found
        # in this object if we set an attribute in this object it will be
        # found before we end up here
        return getattr(self.lines, name)

When it tries to find the 'high' line name it doesn't. Again, sorry but I couldn't see where you were trying to get this. Let me know if I can help.

Thanks.

neilsmurphy commented 3 years ago

I just ran the following on the standard backtrader install without error in both runonce=True and False

self.mfi = bt.talib.MFI(
    self.data.high,
    self.data.low,
    self.data.open,
    self.data.volume,
    timeperiod=15,
)

self.ultosc = bt.talib.ULTOSC(self.data.high, self.data.low, self.data.close)
self.sma = bt.talib.SMA(self.data.close, period=30)

...
OUTPUT

2006-02-10, close 3729.00 ultosc    51 mfi    73 sma  3657
2006-02-13, close 3730.00 ultosc    59 mfi    74 sma  3661
2006-02-14, close 3763.00 ultosc    63 mfi    74 sma  3664
2006-02-15, close 3741.00 ultosc    62 mfi    74 sma  3667
2006-02-16, close 3773.00 ultosc    69 mfi    73 sma  3670
2006-02-17, close 3777.00 ultosc    66 mfi    72 sma  3673
2006-02-20, close 3775.00 ultosc    70 mfi    71 sma  3676
2006-02-21, close 3775.00 ultosc    64 mfi    71 sma  3680

Also, after looking at the code extensively, it looks like the standard implementation should work fine.

I'm wondering if you have anymore information to help reproduce the error?

neilsmurphy commented 3 years ago

@lukeplausin @vladisld I would like to close this with no further action if agreed. I could not replicate the error and the code looks like it should work as coded.

lukeplausin commented 3 years ago

Hi Neils,

Sorry for a late reply. The indicator works if you use it like this:

self.mfi = bt.talib.MFI(
    self.data
    timeperiod=15,
)

Thanks

neilsmurphy commented 3 years ago
self.mfi = bt.talib.MFI(
    self.data
    timeperiod=15,
)

Thanks for bringing this up like this. I think what is happening is if you put in self.data then backtrader will seek out the appropriate high, low, open, close, volume lines.

However, it is possible you wish to switch the lines for whatever reason, or put in manually, then you just have to use the key value.

self.mfi = bt.talib.MFI(
            high=self.data.high,
            low=self.data.low,
            close=self.data.close,
            volume=self.data.volume,
            timeperiod=15,
        )

So for example changing close to open works fine as well.

elf.mfi = bt.talib.MFI(
            high=self.data.high,
            low=self.data.low,
            close=self.data.open,
            volume=self.data.volume,
            timeperiod=15,
        )