diff --git a/pandas_ta/custom.py b/pandas_ta/custom.py index 51a23a6d..18542491 100644 --- a/pandas_ta/custom.py +++ b/pandas_ta/custom.py @@ -1,24 +1,92 @@ # -*- coding: utf-8 -*- - +import importlib import os import sys from os.path import abspath, join, exists, basename, splitext from glob import glob import types -import importlib import pandas_ta import pandas as pd from pandas_ta import AnalysisIndicators -def import_dir(dir_path, verbose=True): + +def bind(function_name, function, method): + """ + Helper function to bind the function and class method defined in a custom + indicator module to the active pandas_ta instance. + + Args: + function_name (str): The name of the indicator within pandas_ta + function (fcn): The indicator function + method (fcn): The class method corresponding to the passed function + """ + setattr(pandas_ta, function_name, function) + setattr(AnalysisIndicators, function_name, method) + + +def create_dir(path, create_categories=True, verbose=True): + """ + Helper function to setup a suitable folder structure for working with + custom indicators. You only need to call this once whenever you want to + setup a new custom indicators folder. + + Args: + path (str): Full path to where you want your indicator tree + create_categories (bool): If True create category sub-folders + verbose (bool): If True print verbose output of results + """ # ensure that the passed directory exists / is readable - if not exists(dir_path): - print(f"[X] Unable to read the directory '{dir_path}'.") + if not exists(path): + os.makedirs(path) + if verbose: + print(f"[i] Created main directory '{path}'.") + + # list the contents of the directory + # dirs = glob(abspath(join(path, '*'))) + + # optionally add any missing category subdirectories + if create_categories: + for sd in [*pandas_ta.Category]: + d = abspath(join(path, sd)) + if not exists(d): + os.makedirs(d) + if verbose: + dirname = basename(d) + print(f"[i] Created an empty sub-directory '{dirname}'.") + + +def get_module_functions(module): + """ + Helper function to get the functions of an imported module as a dictionary. + + Args: + module: python module + + Returns: + dict: module functions mapping + { + "func1_name": func1, + "func2_name": func2,... + } + """ + module_functions = {} + + for name, item in vars(module).items(): + if isinstance(item, types.FunctionType): + module_functions[name] = item + + return module_functions + + +def import_dir(path, verbose=True): + # ensure that the passed directory exists / is readable + if not exists(path): + print(f"[X] Unable to read the directory '{path}'.") return # list the contents of the directory - dirs = glob(abspath(join(dir_path, '*'))) + dirs = glob(abspath(join(path, '*'))) # traverse full directory, importing all modules found there for d in dirs: @@ -31,20 +99,20 @@ def import_dir(dir_path, verbose=True): continue # for each module found in that category (directory)... - for module in glob(abspath(join(dir_path, dirname, '*.py'))): + for module in glob(abspath(join(path, dirname, '*.py'))): module_name = splitext(basename(module))[0] # ensure that the supplied path is included in our python path if d not in sys.path: sys.path.append(d) - # (re)load the indicator module + # (re)load the indicator module module_functions = load_indicator_module(module_name) # figure out which of the modules functions to bind to pandas_ta fcn_callable = module_functions.get(module_name, None) fcn_method_callable = module_functions.get(module_name + "_method", None) - + if fcn_callable == None: print(f"[X] Unable to find a function named '{module_name}' in the module '{module_name}.py'.") continue @@ -61,17 +129,18 @@ def import_dir(dir_path, verbose=True): if verbose: print(f"[i] Successfully imported the custom indicator '{module}' into category '{dirname}'.") + import_dir.__doc__ = \ """ Import a directory of custom indicators into pandas_ta Args: - dir_path (str): Full path to your indicator tree + path (str): Full path to your indicator tree verbose (bool): If True verbose output of results This method allows you to experiment and develop your own technical analysis -indicators in a separate local directory of your choice but use them seamlessly -together with the existing pandas_ta functions just like if they were part of +indicators in a separate local directory of your choice but use them seamlessly +together with the existing pandas_ta functions just like if they were part of pandas_ta. If you at some late point would like to push them into the pandas_ta library @@ -85,7 +154,7 @@ def import_dir(dir_path, verbose=True): >>> import pandas_ta as ta 2. Create an empty directory on your machine where you want to work with your -indicators. Invoke pandas_ta.custom.import_dir once to pre-populate it with +indicators. Invoke pandas_ta.custom.import_dir once to pre-populate it with sub-folders for all available indicator categories, e.g.: >>> import os @@ -94,22 +163,22 @@ def import_dir(dir_path, verbose=True): >>> my_dir = abspath(join(expanduser("~"), "my_indicators")) >>> create_dir(my_dir) -3. You can now create your own custom indicator e.g. by copying existing -ones from pandas_ta core module and modifying them. +3. You can now create your own custom indicator e.g. by copying existing +ones from pandas_ta core module and modifying them. -IMPORTANT: Each custom indicator should have a unique name and have both +IMPORTANT: Each custom indicator should have a unique name and have both a) a function named exactly as the module, e.g. 'ni' if the module is ni.py b) a matching method used by AnalysisIndicators named as the module but ending with '_method'. E.g. 'ni_method' -In essence these modules should look exactly like the standard indicators +In essence these modules should look exactly like the standard indicators available in categories under the pandas_ta-folder. The only difference will be an addition of a matching class method. -For an example of the correct structure, look at the example ni.py in the +For an example of the correct structure, look at the example ni.py in the examples folder. -The ni.py indicator is a trend indicator so therefore we drop it into the +The ni.py indicator is a trend indicator so therefore we drop it into the sub-folder named trend. Thus we have a folder structure like this: ~/my_indicators/ @@ -131,18 +200,6 @@ def import_dir(dir_path, verbose=True): like all other native indicators in pandas_ta, including help functions. """ -def bind(function_name, function, method): - """ - Helper function to bind the function and class method defined in a custom - indicator module to the active pandas_ta instance. - - Args: - function_name (str): The name of the indicator within pandas_ta - function (fcn): The indicator function - method (fcn): The class method corresponding to the passed function - """ - setattr(pandas_ta, function_name, function) - setattr(AnalysisIndicators, function_name, method) def load_indicator_module(module_name): """ @@ -165,58 +222,4 @@ def load_indicator_module(module_name): # reload to refresh previously loaded module module = importlib.reload(module) - return get_module_functions(module) - -def get_module_functions(module): - """ - Helper function to get the functions of an imported module as a dictionary. - - Args: - module: python module - - Returns: - dict: module functions mapping - { - "func1_name": func1, - "func2_name": func2,... - } - - """ - module_functions = {} - - for name, item in vars(module).items(): - if isinstance(item, types.FunctionType): - module_functions[name] = item - - return module_functions - -def create_dir(dir_path, create_categories=True, verbose=True): - """ - Helper function to setup a suitable folder structure for working with - custom indicators. You only need to call this once whenever you want to - setup a new custom indicators folder. - - Args: - dir_path (str): Full path to where you want your indicator tree - create_categories (bool): If True create category sub-folders - verbose (bool): If True print verbose output of results - """ - - # ensure that the passed directory exists / is readable - if not exists(dir_path): - os.makedirs(dir_path) - if verbose: - print(f"[i] Created main directory '{dir_path}'.") - - # list the contents of the directory - dirs = glob(abspath(join(dir_path, '*'))) - - # optionally add any missing category subdirectories - if create_categories: - for sd in [*pandas_ta.Category]: - d = abspath(join(dir_path, sd)) - if not exists(d): - os.makedirs(d) - if verbose: - dirname = basename(d) - print(f"[i] Created an empty sub-directory '{dirname}'.") \ No newline at end of file + return get_module_functions(module) \ No newline at end of file