Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decorator to register arbitrary user defined funcs for use with pta.Strategy API #707

Open
arainboldt opened this issue Aug 2, 2023 · 5 comments
Labels
enhancement New feature or request

Comments

@arainboldt
Copy link

arainboldt commented Aug 2, 2023

Is there a decorator that can be used to register arbitrary functions so that they can be used with the pta.Strategy API?

I'm thinking something like this:

import pandas_ta as pta

@pta.register
def my_cool_indicator(close: pd.Series, length: int, **kwargs):
    return close.rolling(length).apply(something_cool, **kwargs)

CoolStrategy = pta.Strategy(
    name="CoolStrategy",
    ta= [
      {"kind":"my_cool_indicator", "length":length, "another_kwarg":42}
]
)

df.ta.strategy(CoolStrategy)

If the library isn't already setup for something like this, it might take a bit of refactoring. It would, however, greatly improve the extensibility of the library for the user.

Thanks!

Andrew

@arainboldt arainboldt added the enhancement New feature or request label Aug 2, 2023
@arainboldt
Copy link
Author

arainboldt commented Aug 2, 2023

Here's a hacky yet working sketch given the current design of the library:

from pandas_ta.custom import bind
from functools import wraps
import pandas_ta

def _get_args_dict(f):
    args_names = f.__code__.co_varnames[:f.__code__.co_argcount]
    return args_names

def create_class_method(f):
    @wraps(f)
    def method(self, *args, **kwargs):
        kwarg_names = _get_args_dict(f)
        valid_kwargs = {k: v for k,v in kwargs.items() if k in kwarg_names}
        return f(*args, **valid_kwargs)
    
    return method

def pta_register(f):
    class_method = create_class_method(f)
    bind(function_name=f.__name__, function=f, method=class_method)

    @wraps(f)
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    
    return wrapper

And in use:

@pta_register
def pivot_support_resistance(ohlcv, length: int):
    adjusted_high = ohlcv.High.rolling(length).max()
    adjusted_low = ohlcv.Low.rolling(length).min()
    adjusted_close = ohlcv.Close.rolling(length).mean()
    pivot_point = (adjusted_high + adjusted_low + adjusted_close) / 3

    ohlcv[f'PIVOT_SUPPORT_{length}'] = (pivot_point.rolling(length).min()  * 2) - adjusted_high
    ohlcv[f'PIVOT_RESISTANCE_{length}'] = (pivot_point.rolling(length).max() * 2) - adjusted_low
    ohlcv[f'PIVOT_POINT_{length}'] = pivot_point
    ohlcv[f'PIVOT_RESISTANCE_{length}_RATIO_CL'] = ohlcv.Close / ohlcv[f'PIVOT_RESISTANCE_{length}']
    ohlcv[f'PIVOT_SUPPORT_{length}_RATIO_CL'] = ohlcv.Close / ohlcv[f'PIVOT_SUPPORT_{length}']
    ohlcv[f'PIVOT_SPOINT_{length}_RATIO_CL'] = ohlcv.Close / ohlcv[f'PIVOT_POINT_{length}']
    
    return ohlcv
import pandas_ta as pta

Pivot = pta.Strategy(
    name = "Pivot",
    ta = [
        {"kind":"pivot_support_resistance", "length":20, "ohlcv": df}
    ]
)

df.ta.strategy(Pivot)

@twopirllc
Copy link
Owner

Hello @arainboldt,

Is there a decorator that can be used to register arbitrary functions so that they can be used with the pta.Strategy API?

Currently there is no way to do this. However I do think this is a nice way extend the functionality of the library for external user defined indicators. 😎

.. it might take a bit of refactoring.

Based upon your test code so far, how much refactoring are we talking about? Before answering, please try it out with the development version so we are on the same page.

Kind Regards,
KJ

@arainboldt
Copy link
Author

@twopirllc the test code works, with one limitation.

Strategies with funcs decorated with this will run as expected, except that the ALL strategy breaks after using the decorator due to the way it gathers or calls funcs. I'll look back at my example notebook to see what the exact error is.

Generally, however, the explicit definition of each function in the DataFrame subclass is a problematic pattern. A more abstract and generalized approach would provide flexibility and extensibility down the line. Perhaps this is something to consider refactoring.

@twopirllc
Copy link
Owner

@arainboldt,

There have been some changes with strategy() beyond being renamed to study() on the development branch that you should check out. If I recall, it fixes the ALL study/strategy issue.

Generally, however, the explicit definition of each function in the DataFrame subclass is a problematic pattern.

Yes, it has been a thorn in my side and I unfortunately have not had the time rebuild and test a possible better working DataFrame Extension with less explicit indicator definitions.

When you get a chance, please test your code on the development branch and not the main branch.

KJ

@arainboldt
Copy link
Author

Awesome. Will do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants