Skip to content

ADX raises TypeError #194

Open
Open

Description

@liona24

Hey,

I was trying to use the ADX (Average Directional Index) Momentum Indicator.
For me this raises a TypeError:

import talib
from talib import abstract

adx = abstract.ADX
result = adx(dataset, time_period=14) # <-- TypeError here

The full stack trace:

TypeError                                 Traceback (most recent call last)
<ipython-input-90-36d2703ed47a> in <module>()
      2 
      3 adx = tabst.ADX
----> 4 result = adx(dataset, time_period=14)

talib/_abstract.pxi in talib._ta_lib.Function.__call__()

talib/_abstract.pxi in talib._ta_lib.Function.__call_function()

~/.local/share/virtualenvs/project/lib/python3.5/site-packages/talib/__init__.py in wrapper(*args, **kwargs)
     30                       for k, v in kwargs.items()}
     31 
---> 32             result = func(*args, **kwargs)
     33 
     34             # Series was passed in, Series gets out; re-apply index

talib/_func.pxi in talib._ta_lib.ADX()

TypeError: only size-1 arrays can be converted to Python scalars

I have tested with both pandas.DataFrame and a dictionary of numpy.ndarrays. Result is the same.

The other functions work fine though, this one seems to be the only one causing this behaviour for me.

I built TA Lib from source using the instructions provided. Running on Ubuntu 16.04 LTS.

Regards.

Activity

  1. mrjbq7 commented on Apr 5, 2018

    @mrjbq7
    Member

    What is dataset?

    It works for me:

    >>> import talib
    >>> import talib.abstract
    >>> import numpy
    
    >>> high = numpy.random.randn(50)
    >>> low = numpy.random.randn(50)
    >>> close = numpy.random.randn(50)
    
    >>> talib.ADX(high, low, close, timeperiod=14)
    
    >>> dataset = {'high': high, 'low': low, 'close': close}
    >>> talib.abstract.ADX(dataset, timeperiod=14)
  2. liona24 commented on Apr 6, 2018

    @liona24
    Author

    I tried your code and it works.. I did some further investigation and even though this does not produce the exact same error it will cause a similar one:

    import numpy as np
    import pandas as pd
    import talib
    from talib import abstract as tabst
    
    d = { i: np.random.randn(50) for i in [ 'high', 'low', 'close' ] }
    adx = tabst.ADX
    try:
        result = adx(d['high'], d['low'], d['close'], timeperiod=14)
    except TypeError:
        pass
    
    result = adx(d) # <-- TypeError here

    The stack trace of the error:

    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    ~/.local/share/virtualenvs/project/lib/python3.5/site-packages/talib/__init__.py in wrapper(*args, **kwargs)
         19                 index = next(arg.index
    ---> 20                              for arg in chain(args, kwargs.values())
         21                              if isinstance(arg, _pd_Series))
    
    StopIteration: 
    
    During handling of the above exception, another exception occurred:
    
    TypeError                                 Traceback (most recent call last)
    <ipython-input-3-3aa40928cfa5> in <module>()
    ----> 1 result = adx(d)
    
    talib/_abstract.pxi in talib._ta_lib.Function.__call__()
    
    talib/_abstract.pxi in talib._ta_lib.Function.__call_function()
    
    ~/.local/share/virtualenvs/project/lib/python3.5/site-packages/talib/__init__.py in wrapper(*args, **kwargs)
         22             except StopIteration:
         23                 # No pandas.Series passed in; short-circuit
    ---> 24                 return func(*args, **kwargs)
         25 
         26             # Use Series' float64 values if pandas, else use values as passed
    
    talib/_func.pxi in talib._ta_lib.ADX()
    
    TypeError: only size-1 arrays can be converted to Python scalars
    

    Apparently it was caused by me experimenting with the api and calling the function with wrong arguments (I was using a jupyter notebook)

  3. mrjbq7 commented on Apr 6, 2018

    @mrjbq7
    Member

    Hmm, so I can replicate your problem and will look into it.

    But, adding timeperiod as a keyword argument "fixes" it?

    adx(d, timeperiod=14)
  4. liona24 commented on Apr 6, 2018

    @liona24
    Author

    It seems to be caused by calling a function with the wrong parameters followed by a call which uses the default parameters.
    Adding all required parameters explicitly seems to "fix" it.

  5. mrjbq7 commented on Apr 6, 2018

    @mrjbq7
    Member

    @briancappello any interest in looking into this bug with abstract wrapper?

  6. briancappello commented on Apr 6, 2018

    @briancappello
    Contributor

    I guess I'm not clear on what the error is? I'm able to reproduce the exception by calling abstract.ADX(d['high'], d['low'], d['close']), but if I remember correctly, I think all the abstract functions expect the first argument to be a dictionary/dataframe of prices (as opposed to passing the price arrays as individual arguments).

    import numpy as np
    import talib
    from talib import abstract
    
    d = {i: np.random.randn(50) for i in ['open', 'high', 'low', 'close']}
    
    func_result = talib.ADX(d['high'], d['low'], d['close'])
    abstract_result = abstract.ADX(d)
    assert func_result.all() == abstract_result.all()
    
    func_result = talib.ADX(d['high'], d['low'], d['close'], timeperiod=20)
    abstract_result = abstract.ADX(d, timeperiod=20)
    assert func_result.all() == abstract_result.all()
    
    func_result = talib.ADX(d['high'], d['low'], d['open'])
    abstract_result = abstract.ADX(d, prices=['high', 'low', 'open'])
    assert func_result.all() == abstract_result.all()
  7. mrjbq7 commented on Apr 6, 2018

    @mrjbq7
    Member

    Seems like errors cause future calls to not work. I modified your code to have a bad initial call, that catches and ignores the exception, then the later calls don't work.

    import numpy as np
    import pandas as pd
    import talib
    from talib import abstract
    
    d = {i: np.random.randn(50) for i in ['open', 'high', 'low', 'close']}
    
    try:
        func_result = abstract.ADX(d['high'], d['low'], d['close'])
    except:
        pass
    
    func_result = talib.ADX(d['high'], d['low'], d['close'])
    abstract_result = abstract.ADX(d)
    assert func_result.all() == abstract_result.all()
    
    func_result = talib.ADX(d['high'], d['low'], d['close'], timeperiod=20)
    abstract_result = abstract.ADX(d, timeperiod=20)
    assert func_result.all() == abstract_result.all()
    
    func_result = talib.ADX(d['high'], d['low'], d['open'])
    abstract_result = abstract.ADX(d, prices=['high', 'low', 'open'])
    assert func_result.all() == abstract_result.all()
  8. briancappello commented on Apr 6, 2018

    @briancappello
    Contributor

    Gotchya. Skipped right over this:

    It seems to be caused by calling a function with the wrong parameters followed by a call which uses the default parameters.

    Certainly I agree it's not optimal. Looking through the code in Function.set_function_args it looks like it's setting the function parameters positionally using the arrays (in this case, I think it's setting timeperiod to be d['low']). (in other words, abstract.ADX(d, 20) is the same as abstract.ADX(d, timeperiod=20). I suppose i could put a type check in there that if the value for the function parameters isn't a "simple type" (str/bool/int/float) don't set it? In hind sight it would have been better to make those function parameters kwargs-only.

  9. mrjbq7 commented on Apr 6, 2018

    @mrjbq7
    Member

    Maybe the problem is saving keyword arguments?

    import numpy as np
    import talib
    from talib import abstract
    
    d = {i: np.random.randn(50) for i in ['open', 'high', 'low', 'close']}
    
    result1 = abstract.ADX(d, timeperiod=5)
    
    result2 = abstract.ADX(d)
    
    assert result1.all() == result2.all()

    Seems weird to me that the second call uses timeperiod=5 instead of the timeperiod=DEFAULT?

  10. briancappello commented on Apr 6, 2018

    @briancappello
    Contributor

    I mean, yea. That was a poor design decision on my part. And I'd be OK with changing it, but technically I think it would break BC. Which might not that big of a deal in this case - what do you think?

  11. briancappello commented on Apr 6, 2018

    @briancappello
    Contributor

    I'm not actually sure that would work. It could break a fair number of things, eg if you needed to determine how much data you needed based on the lookback:

    adx20 = abstract.ADX
    adx20.set_parameters({'timeperiod': 20})
    d = {i: np.random.randn(50 + adx20.lookback) for i in ['high', 'low', 'close']}
    adx20(d)
    

    The only thing I can think of at the moment that wouldn't break BC and would fix this is the hacky type check. Second best might be to only accept prices as a positional argument to __call__, but doing that could break code in the wild. Ultimately, and I admit the public interface/documentation perhaps isn't the greatest, but be that as it may, it seems like maybe this is really closer to new-user-error than a bug per se?

  12. mrjbq7 commented on Apr 7, 2018

    @mrjbq7
    Member

    Hmm, that's an excellent point.

    I'm not sure I'd say it's a design flaw. Seems like caching the parameters works nicely with lookback. Although we could have another version where lookback takes the parameters as function arguments, so no caching.

    I'm not sure which way to proceed. It's hard to know if breaking backwards compatibility would impact anyone, or if we could change it in such a way where we preserved it if set_parameters called, but did not save the arguments if the __call__ was made. Separating intention from accident. Dunno.

  13. mrjbq7 commented on Apr 7, 2018

    @mrjbq7
    Member

    Or we could just document it, and add a test case showing the exception clearing up next time you call it "correctly":

    import numpy as np
    import pandas as pd
    import talib
    from talib import abstract
    
    d = {i: np.random.randn(50) for i in ['open', 'high', 'low', 'close']}
    
    try:
        result1 = abstract.ADX(d, timeperiod="asdf")
    except:
        pass # assertRaises
    
    try:
        result2 = abstract.ADX(d)
    except:
        pass # assertRaises
    
    result3 = abstract.ADX(d, timeperiod=14)
    
    result4 = abstract.ADX(d)
    
    assert result3.all() == result4.all()
  14. mario4tier commented on Oct 23, 2026

    @mario4tier
    Member

    The original reports were fixed by #195. One path is still open: a call that fails part-way keeps the parameters it was given, so the next default call fails too (SMA(d, timeperiod=0) then SMA(d)). #779 restores them on every path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions