From b316e1064c5738a0e4dd4a3e2e268881f72de914 Mon Sep 17 00:00:00 2001 From: Urban Ottosson Date: Fri, 16 Jul 2021 13:43:43 +0200 Subject: [PATCH] Initial tests ok --- pandas_ta/custom.py | 186 ++++++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 83 deletions(-) diff --git a/pandas_ta/custom.py b/pandas_ta/custom.py index d3f0a09..c5ede24 100644 --- a/pandas_ta/custom.py +++ b/pandas_ta/custom.py @@ -2,131 +2,151 @@ import os import sys -from os.path import dirname, abspath, join +from os.path import abspath, join, exists, basename, splitext from glob import glob import importlib import pandas_ta +import pandas as pd -def import_dir(dir_path, create_categories=True, verbose=True): +def create_dir(dir_path, create_categories=True, verbose=True): + """ + Helper function to setup a suitable folder structure for working with + custom indicators. + + 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 verbose output of results + """ # ensure that the passed directory exists / is readable - if not os.path.exists(dir_path): - print(f"[X] Unable to read the directory '{dir_path}'.") - return - + 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 os.path.exists(d): - os.makedirs(directory + 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}'.") - # traverse the directory, importing all modules found there +def import_dir(dir_path, verbose=True): + + # ensure that the passed directory exists / is readable + if not exists(dir_path): + print(f"[X] Unable to read the directory '{dir_path}'.") + return + + # obtain a list of all reserved pandas_ta indicator names + df = pd.DataFrame() + names_already_in_use = df.ta.indicators(as_list=True) + + # list the contents of the directory + dirs = glob(abspath(join(dir_path, '*'))) + + # traverse full directory, importing all modules found there for d in dirs: - dirname = os.path.basename(d) + dirname = basename(d) + # only look in directories which are valid pandas_ta categories if dirname not in [*pandas_ta.Category]: - print(f"[i] Skipping the sub-directory '{dirname}' since it's not a pandas_ta category.") + if verbose: + print(f"[i] Skipping the sub-directory '{dirname}' since it's not a valid pandas_ta category.") continue + # for each module found in that category (directory)... for module in glob(abspath(join(dir_path, dirname, '*.py'))): - module = os.path.splitext(os.path.basename(module))[0] + module = splitext(basename(module))[0] - if module in pandas_ta.Category[dirname]: - print(f"[X] Skipping the custom module '{module}' since a function with that name already exists in pandas_ta.") + # check that we only load modules not already loaded in pandas_ta + if module in names_already_in_use: + print(f"[i] Warning: the custom module '{module}' will replace a module in pandas_ta.") continue - # import the module and add it to the correct category - pandas_ta.Category[dirname].append(module) + # ensure that the supplied path is included in our python path if d not in sys.path: sys.path.append(d) + + # import the module and add it to the correct category importlib.import_module(module, d) + pandas_ta.Category[dirname].append(module) if verbose: - print(f"[i] Successfully imported the module '{module}' into category '{dirname}'.") + print(f"[i] Successfully imported the 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 + verbose (bool): If True verbose output of results + This method allows you to experiment and develop your own technical analysis -indicators independantly in a separate local directory of your choice but -still use them seamlessly together with the existing pandas_ta functions just -like if they were part of pandas_ta. +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 you can do so very easily by following the step by step instruction here https://github.com/twopirllc/pandas-ta/issues/264. ----------------------------------- - -By default, the 'ta' extension uses lower case column names: open, high, -low, close, and volume. You can override the defaults by providing the it's -replacement name when calling the indicator. For example, to call the -indicator hl2(). - -With 'default' columns: open, high, low, close, and volume. ->>> df.ta.hl2() ->>> df.ta(kind="hl2") - -With DataFrame columns: Open, High, Low, Close, and Volume. ->>> df.ta.hl2(high="High", low="Low") ->>> df.ta(kind="hl2", high="High", low="Low") - -If you do not want to use a DataFrame Extension, just call it normally. ->>> sma10 = ta.sma(df["Close"]) # Default length=10 ->>> sma50 = ta.sma(df["Close"], length=50) ->>> ichimoku, span = ta.ichimoku(df["High"], df["Low"], df["Close"]) - -Args: - kind (str, optional): Default: None. Kind is the 'name' of the indicator. - It converts kind to lowercase before calling. - timed (bool, optional): Default: False. Curious about the execution - speed? - kwargs: Extension specific modifiers. - append (bool, optional): Default: False. When True, it appends the - resultant column(s) to the DataFrame. - -Returns: - Most Indicators will return a Pandas Series. Others like MACD, BBANDS, - KC, et al will return a Pandas DataFrame. Ichimoku on the other hand - will return two DataFrames, the Ichimoku DataFrame for the known period - and a Span DataFrame for the future of the Span values. - Let's get started! 1. Loading the 'ta' module: >>> import pandas as pd >>> import ta as ta -2. Load some data: +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 +sub-folders for all available indicator categories, e.g.: + +>>> ta.custom.create_dir('~/my_indicators') + +3. You can now create your own custom indicator e.g. by copying existing +ones from pandas_ta core module and modifying them. Each custom indicator +should have a unique name and have both a function and a method defined. +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 we drop it into the sub-folder +named trend. Thus we have a folder structure like this: + +~/my_indicators/ +│ +├── candles/ +. +. +└── trend/ +. └── ni.py +. +└── volume/ + +4. We can now dynamically load all our custom indicators located in our +designated indicators directory like this: + +>>> ta.custom.import_dir('~/my_indicators') + +If your custom indicator loaded succesfully then it should behave exactly +like all other native indicators in pandas_ta. E.g. + +>>> help(ta.ni) +>>> help(df.ta.ni) >>> df = pd.read_csv("AAPL.csv", index_col="date", parse_dates=True) - -3. Help! -3a. General Help: ->>> help(df.ta) ->>> df.ta() -3b. Indicator Help: ->>> help(ta.apo) -3c. Indicator Extension Help: ->>> help(df.ta.apo) - -4. Ways of calling an indicator. -4a. Standard: Calling just the APO indicator without "ta" DataFrame extension. ->>> ta.apo(df["close"]) -4b. DataFrame Extension: Calling just the APO indicator with "ta" DataFrame extension. ->>> df.ta.apo() -4c. DataFrame Extension (kind): Calling APO using 'kind' ->>> df.ta(kind="apo") -4d. Strategy: +>>> ta.ni(df["close"]) +>>> df.ta.ni() +>>> df.ta(kind="ni") >>> df.ta.strategy("All") # Default ->>> df.ta.strategy(ta.Strategy("My Strat", ta=[{"kind": "apo"}])) # Custom - -5. Working with kwargs -5a. Append the result to the working df. ->>> df.ta.apo(append=True) -5b. Timing an indicator. ->>> apo = df.ta(kind="apo", timed=True) ->>> print(apo.timed) +>>> df.ta.strategy(ta.Strategy("My Strat", ta=[{"kind": "ni"}])) # Custom +>>> df.ta.ni(append=True) +>>> ni = df.ta(kind="ni", timed=True) +>>> print(ni.timed) """