From 027789433764d5ef7e9700d67ddbcf6c49f82201 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Tue, 9 Mar 2021 12:47:13 -0800 Subject: [PATCH] ENH bbands #202 ENH core added ordered and chunksize args ENH chunksize optimizing --- README.md | 3 ++- pandas_ta/core.py | 30 ++++++++++++++++++++---------- pandas_ta/volatility/bbands.py | 11 ++++++----- setup.py | 2 +- tests/test_strategy.py | 10 ++++++++-- 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 96faa52..78f375c 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ $ pip install pandas_ta Latest Version -------------- -Best choice! Version: *0.2.45b* +Best choice! Version: *0.2.47b* ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta ``` @@ -715,6 +715,7 @@ article in the June, 1994 issue of Technical Analysis of Stocks & Commodities Ma ## **Updated Indicators** * _Average True Range_ (**atr**): The default ```mamode``` is now "**RMA**" and with the same ```mamode``` options as TradingView. See ```help(ta.atr)```. +* _Bollinger Bands_ (**bbands**): New argument ```ddoff``` to control the Degrees of Freedom. Default is 0. See ```help(ta.bbands)```. * _Decreasing_ (**decreasing**): New argument ```strict``` checks if the series is continuously decreasing over period ```length```. Default: ```False```. See ```help(ta.decreasing)```. * _Increasing_ (**increasing**): New argument ```strict``` checks if the series is continuously increasing over period ```length```. Default: ```False```. See ```help(ta.increasing)```. * _Trend Return_ (**trend_return**): Returns a DataFrame now instead of Series with pertinenet trade info for a _trend_. An example can be found in the [AI Example Notebook](https://github.com/twopirllc/pandas-ta/tree/master/examples/AIExample.ipynb). The notebook is still a work in progress and open to colloboration. diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 94b08ea..7193979 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from dataclasses import dataclass, field from datetime import datetime +from math import log as mlog from multiprocessing import cpu_count, Pool from time import perf_counter from typing import List, Tuple @@ -582,6 +583,8 @@ class AnalysisIndicators(BasePandasObject): # cpus = cpu_count() # Ensure indicators are appended to the DataFrame kwargs["append"] = True + all_ordered = kwargs.pop("ordered", False) + mp_chunksize = kwargs.pop("chunksize", self.cores) # Initialize initial_column_count = len(self._df.columns) @@ -657,11 +660,14 @@ class AnalysisIndicators(BasePandasObject): use_multiprocessing = False if use_multiprocessing: - if verbose: - print(f"[i] Multiprocessing: {self.cores} of {cpu_count()} cores.") - + _total_ta = len(ta) pool = Pool(self.cores) + # Some magic to optimize chunksize for speed based on total ta indicators + _chunksize = mp_chunksize - 1 if mp_chunksize > _total_ta else int(mlog(_total_ta)) + 1 + if verbose: + print(f"[i] Multiprocessing: {self.cores} of {cpu_count()} cores of {_total_ta} indicators.") + results = None if mode["custom"]: # Create a list of all the custom indicators into a list custom_ta = [( @@ -671,15 +677,21 @@ class AnalysisIndicators(BasePandasObject): ) for ind in ta] # Custom multiprocessing pool. Must be ordered for Chained Strategies # May fix this to cpus if Chaining/Composition if it remains - # inconsistent - results = pool.imap(self._mp_worker, custom_ta, self.cores) + results = pool.imap(self._mp_worker, custom_ta, _chunksize) else: default_ta = [(ind, tuple(), kwargs) for ind in ta] - # All and Categorical multiprocessing pool. Speed over Order. - results = pool.imap_unordered(self._mp_worker, default_ta, self.cores) + # All and Categorical multiprocessing pool. + if all_ordered: + results = pool.imap(self._mp_worker, default_ta, _chunksize) # Order over Speed + else: + results = pool.imap_unordered(self._mp_worker, default_ta, _chunksize) # Speed over Order + if results is None: + print(f"[X] ta.strategy('{name}') has no results.") + return pool.close() pool.join() + else: # Without multiprocessing: if verbose: @@ -705,9 +717,7 @@ class AnalysisIndicators(BasePandasObject): if verbose: print(f"[i] Total indicators: {len(ta)}") - print( - f"[i] Columns added: {len(self._df.columns) - initial_column_count}" - ) + print(f"[i] Columns added: {len(self._df.columns) - initial_column_count}") if timed: print(f"[i] Runtime: {ftime}") diff --git a/pandas_ta/volatility/bbands.py b/pandas_ta/volatility/bbands.py index 108e25c..149cb39 100644 --- a/pandas_ta/volatility/bbands.py +++ b/pandas_ta/volatility/bbands.py @@ -5,21 +5,21 @@ from pandas_ta.statistics import stdev from pandas_ta.utils import get_offset, verify_series -def bbands(close, length=None, std=None, mamode=None, offset=None, **kwargs): +def bbands(close, length=None, std=None, mamode=None, ddof=0, offset=None, **kwargs): """Indicator: Bollinger Bands (BBANDS)""" # Validate arguments close = verify_series(close) length = int(length) if length and length > 0 else 5 std = float(std) if std and std > 0 else 2.0 mamode = mamode if isinstance(mamode, str) else "sma" + ddof = int(ddof) if ddof >= 0 and ddof < length else 1 offset = get_offset(offset) # Calculate Result - standard_deviation = stdev(close=close, length=length) + standard_deviation = stdev(close=close, length=length, ddof=ddof) deviations = std * standard_deviation mid = ma(mamode, close, length=length, **kwargs) - lower = mid - deviations upper = mid + deviations @@ -74,11 +74,11 @@ Sources: Calculation: Default Inputs: - length=5, std=2, mamode="sma" + length=5, std=2, mamode="sma", ddof=0 EMA = Exponential Moving Average SMA = Simple Moving Average STDEV = Standard Deviation - stdev = STDEV(close, length) + stdev = STDEV(close, length, ddof) if "ema": MID = EMA(close, length) else: @@ -94,6 +94,7 @@ Args: length (int): The short period. Default: 5 std (int): The long period. Default: 2 mamode (str): Two options: "sma" or "ema". Default: "sma" + ddof (int): Degrees of Freedom to use. Default: 0 offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/setup.py b/setup.py index e7316b3..fb85358 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "2", "46b")), + version=".".join(("0", "2", "47b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_strategy.py b/tests/test_strategy.py index 57db8f4..7ec1939 100644 --- a/tests/test_strategy.py +++ b/tests/test_strategy.py @@ -67,6 +67,11 @@ class TestStrategyMethods(TestCase): self.category = "All" self.data.ta.strategy(verbose=verbose, timed=strategy_timed) + def test_all_ordered(self): + self.category = "All" + self.data.ta.strategy(ordered=True, verbose=verbose, timed=strategy_timed) + self.category = "All Ordered" # Rename for Speed Table + @skipUnless(verbose, "verbose mode only") def test_all_strategy(self): self.data.ta.strategy(pandas_ta.AllStrategy, verbose=verbose, timed=strategy_timed) @@ -76,12 +81,13 @@ class TestStrategyMethods(TestCase): self.category = "All" self.data.ta.strategy(self.category, verbose=verbose, timed=strategy_timed) - @skipUnless(verbose, "verbose mode only") + # @skipUnless(verbose, "verbose mode only") def test_all_multiparams_strategy(self): self.category = "All" self.data.ta.strategy(self.category, length=10, verbose=verbose, timed=strategy_timed) self.data.ta.strategy(self.category, length=50, verbose=verbose, timed=strategy_timed) - self.data.ta.strategy(self.category, fast=5, verbose=verbose, timed=strategy_timed) + self.data.ta.strategy(self.category, fast=5, slow=10, verbose=verbose, timed=strategy_timed) + self.category = "All Multiruns with diff Args" # Rename for Speed Table # @skip def test_candles_category(self):