diff --git a/.gitignore b/.gitignore index 1e31be3..dd90ffa 100644 --- a/.gitignore +++ b/.gitignore @@ -119,10 +119,13 @@ pandas_ta/_wrapper.py data/datas.csv data/SPY_5min.csv data/SPY_1min.csv +data/similang-ch.csv data/tulip.csv examples/taplot.py +examples/charting.ipynb examples/ib_trader.ipynb examples/example2.ipynb +examples/*.csv setup.cfg note.md driver.py diff --git a/README.md b/README.md index 87d9c98..3e87140 100644 --- a/README.md +++ b/README.md @@ -16,59 +16,21 @@ All the indicators return a named Series or a DataFrame in uppercase underscore * Has 100+ indicators and utility functions. * Option to use __multiprocessing__ when using df.ta.strategy(). See below. -* Example Jupyter Notebook under the examples directory. -* A new 'ta' method called 'strategy' that be default, runs __all__ the indicators. +* Example Jupyter Notebooks under the [examples](https://github.com/twopirllc/pandas-ta/tree/master/examples) directory, including how to create Custom Strategies using the new [__Strategy__ Class](https://github.com/twopirllc/pandas-ta/tree/master/examples/PandaTA_Strategy_Examples.ipynb) +* A new 'ta' method called 'strategy'. By default, it runs __all__ the indicators. * Abbreviated Indicator names as listed below. -* __Extended Pandas DataFrame__ as 'ta'. +* __Extended Pandas DataFrame__ as 'ta'. * Easily add prefixes or suffixes or both to columns names. * Categories similar to [TA-lib](https://github.com/mrjbq7/ta-lib/tree/master/docs/func_groups) and tightly correlated with TA Lib in testing. ## __Recent Changes__ +* A __Strategy__ Class to help name and group your favorite indicators. +* An experimental and independent __Watchlist__ Class located in the [Examples](https://github.com/twopirllc/pandas-ta/tree/master/examples/watchlist.py) Directory that can be used in conjunction with the new __Strategy__ Class. * Improved the calculation performance of indicators: _Exponential Moving Averagage_ and _Weighted Moving Average_. * Removed internal core optimizations when running ```df.ta.strategy('all')``` with multiprocessing. See the ```ta.strategy()``` method for more details. -### __New DataFrame Method:__ - strategy (strategy) - -### __Added indicators:__ - Bias (bias) - Choppiness Index (chop) - Chande Kroll Stop (cksp) - Doji (cdl_doji) - Entropy (entropy) - Heikin-Ashi Candles (ha) - Inertia (inertia) - KDJ (kdj) - Parabolic Stop and Reverse (psar) - Price Distance (pdist) - Psycholigical Line (psl) - Percentage Volume Oscillator (pvo) - Relative Volatility Index (rvi) - Supertrend (supertrend) - Weighted Closing Price (wcp) -### __Added utilities:__ - Above (above) - Above Value (above_value) - Below (below) - Below Value (below_value) - Cross Value (cross_value) -### __User Added Indicators:__ - Aberration (aberration) - BRAR (brar) -### __Corrected Indicators:__ - Absolute Price Oscillator (apo) - Aroon & Aroon Oscillator (aroon) - * Fixed indicator and included oscillator in returned dataframe - Bollinger Bands (bbands) - Commodity Channel Index (cci) - Chande Momentum Oscillator (cmo) - Exponential Moving Average (ema) - Moving Average Convergence Divergence (macd) - Relative Vigor Index (rvgi) - Symmetric Weighted Moving Average (swma) - Weighted Moving Average (wma) ## What is a Pandas DataFrame Extension? @@ -127,9 +89,63 @@ pd.DataFrame().ta.indicators() help(ta.log_return) ``` -## __New DataFrame Method__: _strategy_ with Multiprocessing +## New Class: __Strategy__ +### What is a Pandas TA Strategy? +A _Strategy_ is a simple way to name and group your favorite TA indicators. Technically, a _Strategy_ is a simple Data Class to contain list of indicators and their parameters. __Note__: _Strategy_ is experimental and subject to change. Pandas TA comes with two basic Strategies: __AllStrategy__ and __CommonStrategy__. -Strategy is a new __Pandas (TA)__ method to facilitate bulk indicator processing. By default, running ```df.ta.strategy()``` will append __all +* See the [Pandas TA Strategy Examples](https://github.com/twopirllc/pandas-ta/tree/master/examples/PandasTA_Strategy_Examples.ipynb) Notebook for more Examples including _Indicator Composition/Chaining_. + +### Strategy Requirements: +- _name_: Some short memorable string. _Note_: Case-insensitive "All" is reserved. +- _ta_: A list of dicts containing keyword arguments to identify the indicator and the indicator's arguments + +### Optional Requirements: +- _description_: A more detailed description of what the Strategy tries to capture. Default: None +- _created_: At datetime string of when it was created. Default: Automatically generated. + +#### Things to note: +- A Strategy will __fail__ when consumed by Pandas TA if there is no {"kind": "indicator name"} attribute. __Remember__ to check your spelling. + +#### Brief Examples +```python +# Builtin All Default Strategy +AllStrategy = Strategy( + name="All", + description="All the indicators with their default settings. Pandas TA default.", + ta=None +) + +# Builtin Default (Example) Strategy. +CommonStrategy = Strategy( + name="Common Price and Volume SMAs", + description="Common Price SMAs: 10, 20, 50, 200 and Volume SMA: 20.", + ta=[ + {"kind": "sma", "length": 10}, + {"kind": "sma", "length": 20}, + {"kind": "sma", "length": 50}, + {"kind": "sma", "length": 200}, + {"kind": "sma", "close": "volume", "length": 20, "prefix": "VOL"} + ] +) + +# Your Custom Strategy or whatever your TA composition +CustomStrategy = ta.Strategy( + name="Momo and Volatility", + description="SMA 50,200, BBANDS, RSI, MACD and Volume SMA 20", + ta=[ + {"kind": "sma", "length": 50}, + {"kind": "sma", "length": 200}, + {"kind": "bbands", "length": 20}, + {"kind": "rsi"}, + {"kind": "macd", "fast": 8, "slow": 21}, + {"kind": "sma", "close": "volume", "length": 20, "prefix": "VOLUME"}, + ] +) +``` + +## __DataFrame Method__: _strategy_ with Multiprocessing + +The new __Pandas (TA)__ method __strategy__ is used to facilitate bulk indicator processing. By default, running ```df.ta.strategy()``` will append __all applicable__ indicators to DataFrame ```df```. Utility methods like ```above```, ```below``` et al are not included. * The ```ta.strategy()``` method is still __under development__. Future iterations will allow you to load a ```ta.json``` config file with your specific strategy name and parameters to automatically run you bulk indicators. @@ -171,7 +187,44 @@ df.ta.strategy(fast=10, slow=50, verbose=True) df.columns ``` -## __New DataFrame kwargs__: _prefix_ and _suffix_ +### Running a Builtin, Categorical or Custom Strategy +While the _Strategy_ Class it has not been fully integrated with the __strategy__ method yet. For now, the following can be done to implement your Custom Strategy. + +```python +# Running the builtin CommonStrategy as mentioned above +df.ta.strategy(ta.CommonStrategy) + +# Available categories +ta.categories + +# Running a Categorical Strategy only requires the Category name +df.ta.strategy(name="Momentum") # Default values for all Momentum indicators +df.ta.strategy(name="overlap", length=27) # Override all 'length' attributes + +# Or create your own Custom Strategy +CustomStrategy = ta.Strategy( + name="Momo and Volatility", + description="SMA 50,200, BBANDS, RSI, MACD and Volume SMA 20", + ta=[ + {"kind": "sma", "length": 50}, + {"kind": "sma", "length": 200}, + {"kind": "bbands", "length": 20}, + {"kind": "rsi"}, + {"kind": "macd", "fast": 8, "slow": 21}, + {"kind": "sma", "close": "volume", "length": 20, "prefix": "VOLUME"}, + ] +) +# To run "Custom Strategy" +df.ta.strategy(CustomStrategy) + +# Or pass in the name and ta atributes of the "Custom Strategy" +df.ta.strategy(name=CustomStrategy.name, ta=CustomStrategy.ta) + +# Sanity check. Make sure all the columns are there +df.columns +``` + +## __DataFrame kwargs__: _prefix_ and _suffix_ ```python prehl2 = df.ta.hl2(prefix="pre") @@ -184,7 +237,7 @@ bothhl2 = df.ta.hl2(prefix="pre", suffix="post") print(bothhl2.name) # "pre_HL2_post" ``` -## __New DataFrame Properties__: _reverse_ & _datetime_ordered_ +## __DataFrame Properties__: _reverse_ & _datetime_ordered_ ```python # The 'reverse' is a helper property that returns the DataFrame @@ -193,7 +246,7 @@ df = df.ta.reverse # The 'datetime_ordered' property returns True if the DataFrame # index is of Pandas datetime64 and df.index[0] < df.index[-1] -# Otherwise it return False +# Otherwise it returns False time_series_in_order = df.ta.datetime_ordered ``` diff --git a/examples/PandasTA_Strategy_Examples.ipynb b/examples/PandasTA_Strategy_Examples.ipynb new file mode 100644 index 0000000..00e9b9e --- /dev/null +++ b/examples/PandasTA_Strategy_Examples.ipynb @@ -0,0 +1,3025 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pandas TA ([pandas_ta](https://github.com/twopirllc/pandas-ta)) Strategies for Custom Technical Analysis\n", + "\n", + "## Topics\n", + "- What is a Pandas TA Strategy?\n", + " - Builtin Strategies: __AllStrategy__ and __CommonStrategy__\n", + " - Creating Strategies\n", + "- Watchlist Class\n", + " - Strategy Management and Execution\n", + "- Indicator Composition/Chaining for more Complex Strategies\n", + " - Comprehensive Example: _MACD and RSI Momo with BBANDS and SMAs 50 & 200 and Cumulative Log Returns_" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%matplotlib inline\n", + "import datetime as dt\n", + "\n", + "# import matplotlib.pyplot as plt\n", + "# import mplfinance as mpf\n", + "\n", + "import pandas as pd\n", + "import pandas_ta as ta\n", + "from alphaVantageAPI.alphavantage import AlphaVantage # pip install alphaVantage-api\n", + "\n", + "from watchlist import Watchlist\n", + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# What is a Pandas TA Strategy?\n", + "A _Strategy_ is a simple way to name and group your favorite TA indicators. Technically, a _Strategy_ is a simple Data Class to contain list of indicators and their parameters. __Note__: _Strategy_ is experimental and subject to change. Pandas TA comes with two basic Strategies: __AllStrategy__ and __CommonStrategy__.\n", + "\n", + "## Strategy Requirements:\n", + "- _name_: Some short memorable string. _Note_: Case-insensitive \"All\" is reserved.\n", + "- _ta_: A list of dicts containing keyword arguments to identify the indicator and the indicator's arguments\n", + "\n", + "## Optional Requirements:\n", + "- _description_: A more detailed description of what the Strategy tries to capture. Default: None\n", + "- _created_: At datetime string of when it was created. Default: Automatically generated.\n", + "\n", + "### Things to note:\n", + "- A Strategy will __fail__ when consumed by Pandas TA if there is no {\"kind\": \"indicator name\"} attribute." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Builtin Examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### All" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name = All\n", + "description = All the indicators with their default settings. Pandas TA default.\n", + "created = 07/25/2020, 11:24:19\n", + "ta = None\n" + ] + } + ], + "source": [ + "AllStrategy = ta.AllStrategy\n", + "print(\"name =\", AllStrategy.name)\n", + "print(\"description =\", AllStrategy.description)\n", + "print(\"created =\", AllStrategy.created)\n", + "print(\"ta =\", AllStrategy.ta)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Common" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name = Common Price and Volume SMAs\n", + "description = Common Price SMAs: 10, 20, 50, 200 and Volume SMA: 20.\n", + "created = 07/25/2020, 11:24:19\n", + "ta = [{'kind': 'sma', 'length': 10}, {'kind': 'sma', 'length': 20}, {'kind': 'sma', 'length': 50}, {'kind': 'sma', 'length': 200}, {'kind': 'sma', 'close': 'volume', 'length': 20, 'prefix': 'VOL'}]\n" + ] + } + ], + "source": [ + "CommonStrategy = ta.CommonStrategy\n", + "print(\"name =\", CommonStrategy.name)\n", + "print(\"description =\", CommonStrategy.description)\n", + "print(\"created =\", CommonStrategy.created)\n", + "print(\"ta =\", CommonStrategy.ta)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating Strategies" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simple Strategy A" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='A', ta=[{'kind': 'sma', 'length': 50}, {'kind': 'sma', 'length': 200}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "custom_a = ta.Strategy(name=\"A\", ta=[{\"kind\": \"sma\", \"length\": 50}, {\"kind\": \"sma\", \"length\": 200}])\n", + "custom_a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simple Strategy B" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='B', ta=[{'kind': 'ema', 'length': 8}, {'kind': 'ema', 'length': 21}, {'kind': 'log_return', 'cumulative': True}, {'kind': 'rsi'}, {'kind': 'supertrend'}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "custom_b = ta.Strategy(name=\"B\", ta=[{\"kind\": \"ema\", \"length\": 8}, {\"kind\": \"ema\", \"length\": 21}, {\"kind\": \"log_return\", \"cumulative\": True}, {\"kind\": \"rsi\"}, {\"kind\": \"supertrend\"}])\n", + "custom_b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bad Strategy. (Misspelled Indicator)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='Runtime Failure', ta=[{'kind': 'percet_return'}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Misspelled indicator, will fail later when ran with Pandas\n", + "custom_run_failure = ta.Strategy(name=\"Runtime Failure\", ta=[{\"kind\": \"percet_return\"}])\n", + "custom_run_failure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Strategy Management and Execution with _Watchlist_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize AlphaVantage Data Source" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AlphaVantage(\n", + " end_point:str = https://www.alphavantage.co/query,\n", + " api_key:str = YOUR API KEY,\n", + " export:bool = True,\n", + " export_path:str = .,\n", + " output_size:str = full,\n", + " output:str = csv,\n", + " datatype:str = json,\n", + " clean:bool = True,\n", + " proxy:dict = {}\n", + ")" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "AV = AlphaVantage(\n", + " api_key=\"YOUR API KEY\", premium=False,\n", + " output_size='full', clean=True,\n", + " export_path=\".\", export=True\n", + ")\n", + "AV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Watchlist and set it's 'ds' to AlphaVantage" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "watch = Watchlist([\"SPY\", \"IWM\"], ds=AV)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Info about the Watchlist. Note, the default Strategy is \"All\"" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Watch(name='Watchlist: SPY, IWM', tickers[2]='SPY, IWM', tf='D', strategy[0]='All')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "watch" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Help about Watchlist" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on class Watchlist in module watchlist:\n", + "\n", + "class Watchlist(builtins.object)\n", + " | Watchlist(tickers: list, tf: str = None, name: str = None, strategy: pandas_ta.core.Strategy = None, ds: object = None, **kwargs)\n", + " | \n", + " | Watchlist Class (** This is subject to change! **)\n", + " | ============================================================================\n", + " | A simple Class to load/download financial market data and automatically\n", + " | apply Technical Analysis indicators with a Pandas TA Strategy. Default\n", + " | Strategy: pandas_ta.AllStrategy.\n", + " | \n", + " | Requirements:\n", + " | - Pandas TA (pip install pandas_ta)\n", + " | - AlphaVantage (pip install alphaVantage-api) for the Default Data Source.\n", + " | To use another Data Source, update the load() method after AV.\n", + " | \n", + " | Required Arguments:\n", + " | - tickers: A list of strings containing tickers. Example: ['SPY', 'AAPL']\n", + " | ============================================================================\n", + " | \n", + " | Methods defined here:\n", + " | \n", + " | __init__(self, tickers: list, tf: str = None, name: str = None, strategy: pandas_ta.core.Strategy = None, ds: object = None, **kwargs)\n", + " | Initialize self. See help(type(self)) for accurate signature.\n", + " | \n", + " | __repr__(self) -> str\n", + " | Return repr(self).\n", + " | \n", + " | indicators(self, *args, **kwargs) -> \n", + " | Returns the list of indicators that are available with Pandas Ta.\n", + " | \n", + " | load(self, ticker: str = None, tf: str = None, index: str = 'date', drop: list = ['dividend', 'split_coefficient'], file_path: str = '.', **kwargs) -> pandas.core.frame.DataFrame\n", + " | Loads or Downloads (if a local csv does not exist) the data from the\n", + " | Data Source. When successful, it returns a Data Frame for the requested\n", + " | ticker. If no tickers are given, it loads all the tickers.\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Data descriptors defined here:\n", + " | \n", + " | __dict__\n", + " | dictionary for instance variables (if defined)\n", + " | \n", + " | __weakref__\n", + " | list of weak references to the object (if defined)\n", + " | \n", + " | data\n", + " | When not None, it contains a dictionary of DataFrames keyed by ticker. data = {\"SPY\": pd.DataFrame, ...}\n", + " | \n", + " | name\n", + " | The name of the Watchlist. Default: \"Watchlist: {Watchlist.tickers}\".\n", + " | \n", + " | strategy\n", + " | Pandas TA Strategy Class. Default: pandas_ta.AllStrategy\n", + " | \n", + " | tf\n", + " | Alias for timeframe. Default: 'D'\n", + " | \n", + " | tickers\n", + " | tickers\n", + " | \n", + " | If a string, it it converted to a list. Example: 'AAPL' -> ['AAPL']\n", + " | * Does not accept, comma seperated strings.\n", + " | If a list, checks if it is a list of strings.\n", + " | \n", + " | verbose\n", + " | Toggle the verbose property. Default: False\n", + "\n" + ] + } + ], + "source": [ + "help(Watchlist)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Default Strategy is \"All\"" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[!] Loading All: SPY, IWM\n", + "\n", + "[+] Downloading['D']: SPY\n", + "[+] Strategy: All\n", + "[i] Indicators with the following arguments: {'append': True}\n", + "[i] Excluded[10]: above, above_value, below, below_value, cross, cross_value, long_run, short_run, trend_return, vp\n", + "[i] Total indicators: 101\n", + "[i] Columns added: 152\n", + "\n", + "[+] Downloading['D']: IWM\n", + "[+] Strategy: All\n", + "[i] Indicators with the following arguments: {'append': True}\n", + "[i] Excluded[10]: above, above_value, below, below_value, cross, cross_value, long_run, short_run, trend_return, vp\n", + "[i] Total indicators: 101\n", + "[i] Columns added: 152\n" + ] + } + ], + "source": [ + "# No arguments loads all the tickers and applies the Strategy to each ticker.\n", + "# The result can be accessed with Watchlist's 'data' property which returns a dictionary keyed by ticker and DataFrames as values \n", + "watch.load(verbose=True, timed=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'SPY': open high low close volume ABER_ZG_5_15 \\\n", + " date \n", + " 1999-11-01 136.5000 137.0000 135.5625 135.5625 4006500.0 NaN \n", + " 1999-11-02 135.9687 137.2500 134.5937 134.5937 6516900.0 NaN \n", + " 1999-11-03 136.0000 136.3750 135.1250 135.5000 7222300.0 NaN \n", + " 1999-11-04 136.7500 137.3593 135.7656 136.5312 7907500.0 NaN \n", + " 1999-11-05 138.6250 139.1093 136.7812 137.8750 7431500.0 136.332267 \n", + " ... ... ... ... ... ... ... \n", + " 2020-07-20 321.4300 325.1300 320.6200 324.3200 56150230.0 320.673333 \n", + " 2020-07-21 326.4500 326.9300 323.9400 325.0100 57245315.0 322.353333 \n", + " 2020-07-22 324.6200 327.2000 324.5000 326.8600 57792915.0 323.313333 \n", + " 2020-07-23 326.4700 327.2300 321.4800 322.9600 75737989.0 324.014000 \n", + " 2020-07-24 320.9500 321.9900 319.2460 320.8800 73766597.0 323.886400 \n", + " \n", + " ABER_SG_5_15 ABER_XG_5_15 ABER_ATR_5_15 ACCBL_20 ... \\\n", + " date ... \n", + " 1999-11-01 NaN NaN NaN NaN ... \n", + " 1999-11-02 NaN NaN NaN NaN ... \n", + " 1999-11-03 NaN NaN NaN NaN ... \n", + " 1999-11-04 NaN NaN NaN NaN ... \n", + " 1999-11-05 NaN NaN NaN NaN ... \n", + " ... ... ... ... ... ... \n", + " 2020-07-20 326.296123 315.050544 5.622790 300.906776 ... \n", + " 2020-07-21 327.800604 316.906063 5.447270 301.895656 ... \n", + " 2020-07-22 328.577452 318.049214 5.264119 302.558926 ... \n", + " 2020-07-23 329.310511 318.717489 5.296511 303.787477 ... \n", + " 2020-07-24 329.077410 318.695390 5.191010 305.041909 ... \n", + " \n", + " VAR_30 VTXP_14 VTXM_14 VWAP VWMA_10 WCP \\\n", + " date \n", + " 1999-11-01 NaN NaN NaN 136.041667 NaN 135.921875 \n", + " 1999-11-02 NaN NaN NaN 135.693303 NaN 135.257775 \n", + " 1999-11-03 NaN NaN NaN 135.682462 NaN 135.625000 \n", + " 1999-11-04 NaN NaN NaN 135.950504 NaN 136.546825 \n", + " 1999-11-05 NaN NaN NaN 136.393305 NaN 137.910125 \n", + " ... ... ... ... ... ... ... \n", + " 2020-07-20 45.091757 1.223611 0.623952 153.261128 318.474591 323.597500 \n", + " 2020-07-21 46.469833 1.211493 0.661493 153.278065 319.600245 325.222500 \n", + " 2020-07-22 50.919143 1.153997 0.689436 153.295250 320.677611 326.355000 \n", + " 2020-07-23 52.999190 1.085706 0.763560 153.317466 321.522738 323.657500 \n", + " 2020-07-24 48.773943 1.027629 0.904621 153.338694 321.846761 320.749000 \n", + " \n", + " WILLR_14 WMA_10 ZL_EMA_10 Z_30 \n", + " date \n", + " 1999-11-01 NaN NaN NaN NaN \n", + " 1999-11-02 NaN NaN NaN NaN \n", + " 1999-11-03 NaN NaN NaN NaN \n", + " 1999-11-04 NaN NaN NaN NaN \n", + " 1999-11-05 NaN NaN NaN NaN \n", + " ... ... ... ... ... \n", + " 2020-07-20 -3.801032 320.096545 323.215081 1.680556 \n", + " 2020-07-21 -10.750280 321.291636 324.115975 1.747819 \n", + " 2020-07-22 -2.058111 322.618909 325.718525 1.900614 \n", + " 2020-07-23 -25.800604 323.042909 325.442430 1.309102 \n", + " 2020-07-24 -38.368580 322.932727 323.987442 0.970050 \n", + " \n", + " [5216 rows x 157 columns],\n", + " 'IWM': open high low close volume ABER_ZG_5_15 \\\n", + " date \n", + " 2000-05-26 91.06 91.440 90.63 91.44 37400.0 NaN \n", + " 2000-05-30 92.75 94.810 92.75 94.81 28800.0 NaN \n", + " 2000-05-31 95.13 96.380 95.13 95.75 18000.0 NaN \n", + " 2000-06-01 97.11 97.310 97.11 97.31 3500.0 NaN \n", + " 2000-06-02 101.70 102.400 101.70 102.40 14700.0 96.091333 \n", + " ... ... ... ... ... ... ... \n", + " 2020-07-20 146.12 146.850 145.15 145.96 19581689.0 145.160667 \n", + " 2020-07-21 147.47 149.160 147.20 148.03 24467065.0 146.623333 \n", + " 2020-07-22 147.09 148.670 147.03 148.11 24424808.0 146.904000 \n", + " 2020-07-23 147.98 150.200 146.70 148.26 21704889.0 147.400667 \n", + " 2020-07-24 147.29 147.665 145.56 146.08 20015547.0 147.375000 \n", + " \n", + " ABER_SG_5_15 ABER_XG_5_15 ABER_ATR_5_15 ACCBL_20 ... \\\n", + " date ... \n", + " 2000-05-26 NaN NaN NaN NaN ... \n", + " 2000-05-30 NaN NaN NaN NaN ... \n", + " 2000-05-31 NaN NaN NaN NaN ... \n", + " 2000-06-01 NaN NaN NaN NaN ... \n", + " 2000-06-02 NaN NaN NaN NaN ... \n", + " ... ... ... ... ... ... \n", + " 2020-07-20 149.115307 141.206027 3.954640 133.720919 ... \n", + " 2020-07-21 150.527664 142.719003 3.904331 134.312771 ... \n", + " 2020-07-22 150.657375 143.150625 3.753375 134.568274 ... \n", + " 2020-07-23 151.137150 143.664183 3.736483 135.260831 ... \n", + " 2020-07-24 151.042385 143.707615 3.667385 135.937695 ... \n", + " \n", + " VAR_30 VTXP_14 VTXM_14 VWAP VWMA_10 WCP \\\n", + " date \n", + " 2000-05-26 NaN NaN NaN 91.170000 NaN 91.23750 \n", + " 2000-05-30 NaN NaN NaN 92.454834 NaN 94.29500 \n", + " 2000-05-31 NaN NaN NaN 93.159976 NaN 95.75250 \n", + " 2000-06-01 NaN NaN NaN 93.322938 NaN 97.26000 \n", + " 2000-06-02 NaN NaN NaN 94.592497 NaN 102.22500 \n", + " ... ... ... ... ... ... ... \n", + " 2020-07-20 14.347839 1.085970 0.934117 88.365517 143.089584 145.98000 \n", + " 2020-07-21 11.515402 1.030077 0.909147 88.373544 143.813948 148.10500 \n", + " 2020-07-22 10.497958 1.013525 0.935595 88.381529 144.438962 147.98000 \n", + " 2020-07-23 11.200568 0.993994 0.920849 88.388676 145.342850 148.35500 \n", + " 2020-07-24 9.687226 0.945133 0.982616 88.395051 145.807709 146.34625 \n", + " \n", + " WILLR_14 WMA_10 ZL_EMA_10 Z_30 \n", + " date \n", + " 2000-05-26 NaN NaN NaN NaN \n", + " 2000-05-30 NaN NaN NaN NaN \n", + " 2000-05-31 NaN NaN NaN NaN \n", + " 2000-06-01 NaN NaN NaN NaN \n", + " 2000-06-02 NaN NaN NaN NaN \n", + " ... ... ... ... ... \n", + " 2020-07-20 -17.267552 144.240000 146.914980 0.906142 \n", + " 2020-07-21 -9.479866 145.149091 147.299529 1.671168 \n", + " 2020-07-22 -8.808725 145.942909 147.801433 1.797089 \n", + " 2020-07-23 -14.969136 146.651818 148.188445 1.763914 \n", + " 2020-07-24 -31.790123 146.797273 147.826909 1.077936 \n", + " \n", + " [5072 rows x 157 columns]}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "watch.data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
openhighlowclosevolumeABER_ZG_5_15ABER_SG_5_15ABER_XG_5_15ABER_ATR_5_15ACCBL_20...VAR_30VTXP_14VTXM_14VWAPVWMA_10WCPWILLR_14WMA_10ZL_EMA_10Z_30
date
1999-11-01136.5000137.0000135.5625135.56254006500.0NaNNaNNaNNaNNaN...NaNNaNNaN136.041667NaN135.921875NaNNaNNaNNaN
1999-11-02135.9687137.2500134.5937134.59376516900.0NaNNaNNaNNaNNaN...NaNNaNNaN135.693303NaN135.257775NaNNaNNaNNaN
1999-11-03136.0000136.3750135.1250135.50007222300.0NaNNaNNaNNaNNaN...NaNNaNNaN135.682462NaN135.625000NaNNaNNaNNaN
1999-11-04136.7500137.3593135.7656136.53127907500.0NaNNaNNaNNaNNaN...NaNNaNNaN135.950504NaN136.546825NaNNaNNaNNaN
1999-11-05138.6250139.1093136.7812137.87507431500.0136.332267NaNNaNNaNNaN...NaNNaNNaN136.393305NaN137.910125NaNNaNNaNNaN
..................................................................
2020-07-20321.4300325.1300320.6200324.320056150230.0320.673333326.296123315.0505445.622790300.906776...45.0917571.2236110.623952153.261128318.474591323.597500-3.801032320.096545323.2150811.680556
2020-07-21326.4500326.9300323.9400325.010057245315.0322.353333327.800604316.9060635.447270301.895656...46.4698331.2114930.661493153.278065319.600245325.222500-10.750280321.291636324.1159751.747819
2020-07-22324.6200327.2000324.5000326.860057792915.0323.313333328.577452318.0492145.264119302.558926...50.9191431.1539970.689436153.295250320.677611326.355000-2.058111322.618909325.7185251.900614
2020-07-23326.4700327.2300321.4800322.960075737989.0324.014000329.310511318.7174895.296511303.787477...52.9991901.0857060.763560153.317466321.522738323.657500-25.800604323.042909325.4424301.309102
2020-07-24320.9500321.9900319.2460320.880073766597.0323.886400329.077410318.6953905.191010305.041909...48.7739431.0276290.904621153.338694321.846761320.749000-38.368580322.932727323.9874420.970050
\n", + "

5216 rows × 157 columns

\n", + "
" + ], + "text/plain": [ + " open high low close volume ABER_ZG_5_15 \\\n", + "date \n", + "1999-11-01 136.5000 137.0000 135.5625 135.5625 4006500.0 NaN \n", + "1999-11-02 135.9687 137.2500 134.5937 134.5937 6516900.0 NaN \n", + "1999-11-03 136.0000 136.3750 135.1250 135.5000 7222300.0 NaN \n", + "1999-11-04 136.7500 137.3593 135.7656 136.5312 7907500.0 NaN \n", + "1999-11-05 138.6250 139.1093 136.7812 137.8750 7431500.0 136.332267 \n", + "... ... ... ... ... ... ... \n", + "2020-07-20 321.4300 325.1300 320.6200 324.3200 56150230.0 320.673333 \n", + "2020-07-21 326.4500 326.9300 323.9400 325.0100 57245315.0 322.353333 \n", + "2020-07-22 324.6200 327.2000 324.5000 326.8600 57792915.0 323.313333 \n", + "2020-07-23 326.4700 327.2300 321.4800 322.9600 75737989.0 324.014000 \n", + "2020-07-24 320.9500 321.9900 319.2460 320.8800 73766597.0 323.886400 \n", + "\n", + " ABER_SG_5_15 ABER_XG_5_15 ABER_ATR_5_15 ACCBL_20 ... \\\n", + "date ... \n", + "1999-11-01 NaN NaN NaN NaN ... \n", + "1999-11-02 NaN NaN NaN NaN ... \n", + "1999-11-03 NaN NaN NaN NaN ... \n", + "1999-11-04 NaN NaN NaN NaN ... \n", + "1999-11-05 NaN NaN NaN NaN ... \n", + "... ... ... ... ... ... \n", + "2020-07-20 326.296123 315.050544 5.622790 300.906776 ... \n", + "2020-07-21 327.800604 316.906063 5.447270 301.895656 ... \n", + "2020-07-22 328.577452 318.049214 5.264119 302.558926 ... \n", + "2020-07-23 329.310511 318.717489 5.296511 303.787477 ... \n", + "2020-07-24 329.077410 318.695390 5.191010 305.041909 ... \n", + "\n", + " VAR_30 VTXP_14 VTXM_14 VWAP VWMA_10 WCP \\\n", + "date \n", + "1999-11-01 NaN NaN NaN 136.041667 NaN 135.921875 \n", + "1999-11-02 NaN NaN NaN 135.693303 NaN 135.257775 \n", + "1999-11-03 NaN NaN NaN 135.682462 NaN 135.625000 \n", + "1999-11-04 NaN NaN NaN 135.950504 NaN 136.546825 \n", + "1999-11-05 NaN NaN NaN 136.393305 NaN 137.910125 \n", + "... ... ... ... ... ... ... \n", + "2020-07-20 45.091757 1.223611 0.623952 153.261128 318.474591 323.597500 \n", + "2020-07-21 46.469833 1.211493 0.661493 153.278065 319.600245 325.222500 \n", + "2020-07-22 50.919143 1.153997 0.689436 153.295250 320.677611 326.355000 \n", + "2020-07-23 52.999190 1.085706 0.763560 153.317466 321.522738 323.657500 \n", + "2020-07-24 48.773943 1.027629 0.904621 153.338694 321.846761 320.749000 \n", + "\n", + " WILLR_14 WMA_10 ZL_EMA_10 Z_30 \n", + "date \n", + "1999-11-01 NaN NaN NaN NaN \n", + "1999-11-02 NaN NaN NaN NaN \n", + "1999-11-03 NaN NaN NaN NaN \n", + "1999-11-04 NaN NaN NaN NaN \n", + "1999-11-05 NaN NaN NaN NaN \n", + "... ... ... ... ... \n", + "2020-07-20 -3.801032 320.096545 323.215081 1.680556 \n", + "2020-07-21 -10.750280 321.291636 324.115975 1.747819 \n", + "2020-07-22 -2.058111 322.618909 325.718525 1.900614 \n", + "2020-07-23 -25.800604 323.042909 325.442430 1.309102 \n", + "2020-07-24 -38.368580 322.932727 323.987442 0.970050 \n", + "\n", + "[5216 rows x 157 columns]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "watch.data['SPY']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Easy to swap Strategies and run them" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running Simple Strategy A" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='A', ta=[{'kind': 'sma', 'length': 50}, {'kind': 'sma', 'length': 200}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load custom_a into Watchlist and verify\n", + "watch.strategy = custom_a\n", + "watch.strategy" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[i] Loaded['D']: IWM_D.csv\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
openhighlowclosevolumeSMA_50SMA_200
date
2000-05-2691.0691.44090.6391.4437400.0NaNNaN
2000-05-3092.7594.81092.7594.8128800.0NaNNaN
2000-05-3195.1396.38095.1395.7518000.0NaNNaN
2000-06-0197.1197.31097.1197.313500.0NaNNaN
2000-06-02101.70102.400101.70102.4014700.0NaNNaN
........................
2020-07-20146.12146.850145.15145.9619581689.0139.7062145.93605
2020-07-21147.47149.160147.20148.0324467065.0140.0196145.93750
2020-07-22147.09148.670147.03148.1124424808.0140.3478145.93235
2020-07-23147.98150.200146.70148.2621704889.0140.7736145.92925
2020-07-24147.29147.665145.56146.0820015547.0141.2408145.92735
\n", + "

5072 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " open high low close volume SMA_50 SMA_200\n", + "date \n", + "2000-05-26 91.06 91.440 90.63 91.44 37400.0 NaN NaN\n", + "2000-05-30 92.75 94.810 92.75 94.81 28800.0 NaN NaN\n", + "2000-05-31 95.13 96.380 95.13 95.75 18000.0 NaN NaN\n", + "2000-06-01 97.11 97.310 97.11 97.31 3500.0 NaN NaN\n", + "2000-06-02 101.70 102.400 101.70 102.40 14700.0 NaN NaN\n", + "... ... ... ... ... ... ... ...\n", + "2020-07-20 146.12 146.850 145.15 145.96 19581689.0 139.7062 145.93605\n", + "2020-07-21 147.47 149.160 147.20 148.03 24467065.0 140.0196 145.93750\n", + "2020-07-22 147.09 148.670 147.03 148.11 24424808.0 140.3478 145.93235\n", + "2020-07-23 147.98 150.200 146.70 148.26 21704889.0 140.7736 145.92925\n", + "2020-07-24 147.29 147.665 145.56 146.08 20015547.0 141.2408 145.92735\n", + "\n", + "[5072 rows x 7 columns]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "watch.load('IWM')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running Simple Strategy B" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='B', ta=[{'kind': 'ema', 'length': 8}, {'kind': 'ema', 'length': 21}, {'kind': 'log_return', 'cumulative': True}, {'kind': 'rsi'}, {'kind': 'supertrend'}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load custom_b into Watchlist and verify\n", + "watch.strategy = custom_b\n", + "watch.strategy" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[i] Loaded['D']: SPY_D.csv\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
openhighlowclosevolumeEMA_8EMA_21CUMLOGRET_1RSI_14SUPERT_7_3.0SUPERTd_7_3.0SUPERTl_7_3.0SUPERTs_7_3.0
date
1999-11-01136.5000137.0000135.5625135.56254006500.0NaNNaNNaNNaN0.0000001NaNNaN
1999-11-02135.9687137.2500134.5937134.59376516900.0NaNNaN-0.0071720.000000NaN1NaNNaN
1999-11-03136.0000136.3750135.1250135.50007222300.0NaNNaN-0.00046150.185503NaN1NaNNaN
1999-11-04136.7500137.3593135.7656136.53127907500.0NaNNaN0.00712069.153995NaN1NaNNaN
1999-11-05138.6250139.1093136.7812137.87507431500.0NaNNaN0.01691579.896816NaN1NaNNaN
..........................................
2020-07-20321.4300325.1300320.6200324.320056150230.0319.783320315.0764500.87229863.569724307.8904391307.890439NaN
2020-07-21326.4500326.9300323.9400325.010057245315.0320.944805315.9795000.87442364.179426311.3096621311.309662NaN
2020-07-22324.6200327.2000324.5000326.860057792915.0322.259293316.9686360.88009965.830627312.5854251312.585425NaN
2020-07-23326.4700327.2300321.4800322.960075737989.0322.415005317.5133060.86809659.594031312.5854251312.585425NaN
2020-07-24320.9500321.9900319.2460320.880073766597.0322.073893317.8193690.86163456.518677312.5854251312.585425NaN
\n", + "

5216 rows × 13 columns

\n", + "
" + ], + "text/plain": [ + " open high low close volume EMA_8 \\\n", + "date \n", + "1999-11-01 136.5000 137.0000 135.5625 135.5625 4006500.0 NaN \n", + "1999-11-02 135.9687 137.2500 134.5937 134.5937 6516900.0 NaN \n", + "1999-11-03 136.0000 136.3750 135.1250 135.5000 7222300.0 NaN \n", + "1999-11-04 136.7500 137.3593 135.7656 136.5312 7907500.0 NaN \n", + "1999-11-05 138.6250 139.1093 136.7812 137.8750 7431500.0 NaN \n", + "... ... ... ... ... ... ... \n", + "2020-07-20 321.4300 325.1300 320.6200 324.3200 56150230.0 319.783320 \n", + "2020-07-21 326.4500 326.9300 323.9400 325.0100 57245315.0 320.944805 \n", + "2020-07-22 324.6200 327.2000 324.5000 326.8600 57792915.0 322.259293 \n", + "2020-07-23 326.4700 327.2300 321.4800 322.9600 75737989.0 322.415005 \n", + "2020-07-24 320.9500 321.9900 319.2460 320.8800 73766597.0 322.073893 \n", + "\n", + " EMA_21 CUMLOGRET_1 RSI_14 SUPERT_7_3.0 SUPERTd_7_3.0 \\\n", + "date \n", + "1999-11-01 NaN NaN NaN 0.000000 1 \n", + "1999-11-02 NaN -0.007172 0.000000 NaN 1 \n", + "1999-11-03 NaN -0.000461 50.185503 NaN 1 \n", + "1999-11-04 NaN 0.007120 69.153995 NaN 1 \n", + "1999-11-05 NaN 0.016915 79.896816 NaN 1 \n", + "... ... ... ... ... ... \n", + "2020-07-20 315.076450 0.872298 63.569724 307.890439 1 \n", + "2020-07-21 315.979500 0.874423 64.179426 311.309662 1 \n", + "2020-07-22 316.968636 0.880099 65.830627 312.585425 1 \n", + "2020-07-23 317.513306 0.868096 59.594031 312.585425 1 \n", + "2020-07-24 317.819369 0.861634 56.518677 312.585425 1 \n", + "\n", + " SUPERTl_7_3.0 SUPERTs_7_3.0 \n", + "date \n", + "1999-11-01 NaN NaN \n", + "1999-11-02 NaN NaN \n", + "1999-11-03 NaN NaN \n", + "1999-11-04 NaN NaN \n", + "1999-11-05 NaN NaN \n", + "... ... ... \n", + "2020-07-20 307.890439 NaN \n", + "2020-07-21 311.309662 NaN \n", + "2020-07-22 312.585425 NaN \n", + "2020-07-23 312.585425 NaN \n", + "2020-07-24 312.585425 NaN \n", + "\n", + "[5216 rows x 13 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "watch.load('SPY')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running Bad Strategy. (Misspelled indicator)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='Runtime Failure', ta=[{'kind': 'percet_return'}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load custom_run_failure into Watchlist and verify\n", + "watch.strategy = custom_run_failure\n", + "watch.strategy" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[i] Loaded['D']: IWM_D.csv\n", + "[X] Oops! 'AnalysisIndicators' object has no attribute 'percet_return'\n" + ] + } + ], + "source": [ + "try:\n", + " iwm = watch.load('IWM')\n", + "except AttributeError as error:\n", + " print(f\"[X] Oops! {error}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Indicator Composition/Chaining\n", + "- When you need an indicator to depend on the value of a prior indicator\n", + "- Utilitze _prefix_ or _suffix_ to help identify unique columns or avoid column name clashes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Volume MAs and MA chains" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='Volume MAs and Price MA chain', ta=[{'kind': 'ema', 'close': 'volume', 'length': 10, 'prefix': 'VOLUME'}, {'kind': 'sma', 'close': 'volume', 'length': 20, 'prefix': 'VOLUME'}, {'kind': 'ema', 'length': 5}, {'kind': 'linreg', 'close': 'EMA_5', 'length': 8, 'prefix': 'EMA_5'}], description=None, created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Set EMA's and SMA's 'close' to 'volume' to create Volume MAs, prefix 'volume' MAs with 'VOLUME' so easy to identify the column\n", + "# Take a price EMA and apply LINREG from EMA's output\n", + "volmas_price_ma_chain = [\n", + " {\"kind\":\"ema\", \"close\": \"volume\", \"length\": 10, \"prefix\": \"VOLUME\"},\n", + " {\"kind\":\"sma\", \"close\": \"volume\", \"length\": 20, \"prefix\": \"VOLUME\"},\n", + " {\"kind\":\"ema\", \"length\": 5},\n", + " {\"kind\":\"linreg\", \"close\": \"EMA_5\", \"length\": 8, \"prefix\": \"EMA_5\"},\n", + "]\n", + "vp_ma_chain_ta = ta.Strategy(\"Volume MAs and Price MA chain\", volmas_price_ma_chain)\n", + "vp_ma_chain_ta" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Volume MAs and Price MA chain'" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Update the Watchlist\n", + "watch.strategy = vp_ma_chain_ta\n", + "watch.strategy.name" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[i] Loaded['D']: SPY_D.csv\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
openhighlowclosevolumeVOLUME_EMA_10VOLUME_SMA_20EMA_5EMA_5_LR_8
date
1999-11-01136.5000137.0000135.5625135.56254006500.0NaNNaNNaNNaN
1999-11-02135.9687137.2500134.5937134.59376516900.0NaNNaNNaNNaN
1999-11-03136.0000136.3750135.1250135.50007222300.0NaNNaNNaNNaN
1999-11-04136.7500137.3593135.7656136.53127907500.0NaNNaNNaNNaN
1999-11-05138.6250139.1093136.7812137.87507431500.0NaNNaN136.012480NaN
..............................
2020-07-20321.4300325.1300320.6200324.320056150230.07.254859e+0781019145.80321.398416320.058000
2020-07-21326.4500326.9300323.9400325.010057245315.06.976618e+0780181050.95322.602277321.307000
2020-07-22324.6200327.2000324.5000326.860057792915.06.758922e+0779667351.70324.021518322.687127
2020-07-23326.4700327.2300321.4800322.960075737989.06.907082e+0776850881.55323.667679323.406458
2020-07-24320.9500321.9900319.2460320.880073766597.06.992459e+0776090907.45322.738452323.487321
\n", + "

5216 rows × 9 columns

\n", + "
" + ], + "text/plain": [ + " open high low close volume VOLUME_EMA_10 \\\n", + "date \n", + "1999-11-01 136.5000 137.0000 135.5625 135.5625 4006500.0 NaN \n", + "1999-11-02 135.9687 137.2500 134.5937 134.5937 6516900.0 NaN \n", + "1999-11-03 136.0000 136.3750 135.1250 135.5000 7222300.0 NaN \n", + "1999-11-04 136.7500 137.3593 135.7656 136.5312 7907500.0 NaN \n", + "1999-11-05 138.6250 139.1093 136.7812 137.8750 7431500.0 NaN \n", + "... ... ... ... ... ... ... \n", + "2020-07-20 321.4300 325.1300 320.6200 324.3200 56150230.0 7.254859e+07 \n", + "2020-07-21 326.4500 326.9300 323.9400 325.0100 57245315.0 6.976618e+07 \n", + "2020-07-22 324.6200 327.2000 324.5000 326.8600 57792915.0 6.758922e+07 \n", + "2020-07-23 326.4700 327.2300 321.4800 322.9600 75737989.0 6.907082e+07 \n", + "2020-07-24 320.9500 321.9900 319.2460 320.8800 73766597.0 6.992459e+07 \n", + "\n", + " VOLUME_SMA_20 EMA_5 EMA_5_LR_8 \n", + "date \n", + "1999-11-01 NaN NaN NaN \n", + "1999-11-02 NaN NaN NaN \n", + "1999-11-03 NaN NaN NaN \n", + "1999-11-04 NaN NaN NaN \n", + "1999-11-05 NaN 136.012480 NaN \n", + "... ... ... ... \n", + "2020-07-20 81019145.80 321.398416 320.058000 \n", + "2020-07-21 80181050.95 322.602277 321.307000 \n", + "2020-07-22 79667351.70 324.021518 322.687127 \n", + "2020-07-23 76850881.55 323.667679 323.406458 \n", + "2020-07-24 76090907.45 322.738452 323.487321 \n", + "\n", + "[5216 rows x 9 columns]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "spy = watch.load('SPY')\n", + "spy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### MACD BBANDS" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='MACD BBands', ta=[{'kind': 'macd'}, {'kind': 'bbands', 'close': 'MACD_12_26_9', 'length': 20, 'prefix': 'MACD'}], description='BBANDS_20 applied to MACD', created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# MACD is the initial indicator that BBANDS depends on.\n", + "# Set BBANDS's 'close' to MACD's main signal, in this case 'MACD_12_26_9' and add a prefix (or suffix) so it's easier to identify\n", + "macd_bands_ta = [\n", + " {\"kind\":\"macd\"},\n", + " {\"kind\":\"bbands\", \"close\": \"MACD_12_26_9\", \"length\": 20, \"prefix\": \"MACD\"}\n", + "]\n", + "macd_bands_ta = ta.Strategy(\"MACD BBands\", macd_bands_ta, f\"BBANDS_{macd_bands_ta[1]['length']} applied to MACD\")\n", + "macd_bands_ta" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'MACD BBands'" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Update the Watchlist\n", + "watch.strategy = macd_bands_ta\n", + "watch.strategy.name" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[i] Loaded['D']: SPY_D.csv\n", + "[i] Set 'df.ta.mp = True' to enable multiprocessing. This computer has 4 cores. Default: False\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
openhighlowclosevolumeMACD_12_26_9MACDh_12_26_9MACDs_12_26_9MACD_BBL_20_2.0MACD_BBM_20_2.0MACD_BBU_20_2.0
date
1999-11-01136.5000137.0000135.5625135.56254006500.0NaNNaNNaNNaNNaNNaN
1999-11-02135.9687137.2500134.5937134.59376516900.0NaNNaNNaNNaNNaNNaN
1999-11-03136.0000136.3750135.1250135.50007222300.0NaNNaNNaNNaNNaNNaN
1999-11-04136.7500137.3593135.7656136.53127907500.0NaNNaNNaNNaNNaNNaN
1999-11-05138.6250139.1093136.7812137.87507431500.0NaNNaNNaNNaNNaNNaN
....................................
2020-07-20321.4300325.1300320.6200324.320056150230.04.4228270.7136953.7091311.3511683.1532964.955423
2020-07-21326.4500326.9300323.9400325.010057245315.04.6519740.7542743.8977001.3415503.1573204.973090
2020-07-22324.6200327.2000324.5000326.860057792915.04.9260700.8226964.1033741.2790923.1835635.088035
2020-07-23326.4700327.2300321.4800322.960075737989.04.7735690.5361564.2374131.2156023.2431105.270617
2020-07-24320.9500321.9900319.2460320.880073766597.04.4337630.1570804.2766831.2113603.3067705.402179
\n", + "

5216 rows × 11 columns

\n", + "
" + ], + "text/plain": [ + " open high low close volume MACD_12_26_9 \\\n", + "date \n", + "1999-11-01 136.5000 137.0000 135.5625 135.5625 4006500.0 NaN \n", + "1999-11-02 135.9687 137.2500 134.5937 134.5937 6516900.0 NaN \n", + "1999-11-03 136.0000 136.3750 135.1250 135.5000 7222300.0 NaN \n", + "1999-11-04 136.7500 137.3593 135.7656 136.5312 7907500.0 NaN \n", + "1999-11-05 138.6250 139.1093 136.7812 137.8750 7431500.0 NaN \n", + "... ... ... ... ... ... ... \n", + "2020-07-20 321.4300 325.1300 320.6200 324.3200 56150230.0 4.422827 \n", + "2020-07-21 326.4500 326.9300 323.9400 325.0100 57245315.0 4.651974 \n", + "2020-07-22 324.6200 327.2000 324.5000 326.8600 57792915.0 4.926070 \n", + "2020-07-23 326.4700 327.2300 321.4800 322.9600 75737989.0 4.773569 \n", + "2020-07-24 320.9500 321.9900 319.2460 320.8800 73766597.0 4.433763 \n", + "\n", + " MACDh_12_26_9 MACDs_12_26_9 MACD_BBL_20_2.0 MACD_BBM_20_2.0 \\\n", + "date \n", + "1999-11-01 NaN NaN NaN NaN \n", + "1999-11-02 NaN NaN NaN NaN \n", + "1999-11-03 NaN NaN NaN NaN \n", + "1999-11-04 NaN NaN NaN NaN \n", + "1999-11-05 NaN NaN NaN NaN \n", + "... ... ... ... ... \n", + "2020-07-20 0.713695 3.709131 1.351168 3.153296 \n", + "2020-07-21 0.754274 3.897700 1.341550 3.157320 \n", + "2020-07-22 0.822696 4.103374 1.279092 3.183563 \n", + "2020-07-23 0.536156 4.237413 1.215602 3.243110 \n", + "2020-07-24 0.157080 4.276683 1.211360 3.306770 \n", + "\n", + " MACD_BBU_20_2.0 \n", + "date \n", + "1999-11-01 NaN \n", + "1999-11-02 NaN \n", + "1999-11-03 NaN \n", + "1999-11-04 NaN \n", + "1999-11-05 NaN \n", + "... ... \n", + "2020-07-20 4.955423 \n", + "2020-07-21 4.973090 \n", + "2020-07-22 5.088035 \n", + "2020-07-23 5.270617 \n", + "2020-07-24 5.402179 \n", + "\n", + "[5216 rows x 11 columns]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "spy = watch.load('SPY')\n", + "spy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comprehensive Strategy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### MACD and RSI Momentum with BBANDS and SMAs and Cumulative Log Returns" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Strategy(name='Momo, Bands and SMAs and Cumulative Log Returns', ta=[{'kind': 'sma', 'length': 50}, {'kind': 'sma', 'length': 200}, {'kind': 'bbands', 'length': 20}, {'kind': 'macd'}, {'kind': 'rsi'}, {'kind': 'log_return', 'cumulative': True}, {'kind': 'sma', 'close': 'CUMLOGRET_1', 'length': 5, 'suffix': 'CUMLOGRET'}], description='MACD and RSI Momo with BBANDS and SMAs 50 & 200 and Cumulative Log Returns', created='07/25/2020, 11:24:19', last_run=None, run_time=None)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "momo_bands_sma_ta = [\n", + " {\"kind\":\"sma\", \"length\": 50},\n", + " {\"kind\":\"sma\", \"length\": 200},\n", + " {\"kind\":\"bbands\", \"length\": 20},\n", + " {\"kind\":\"macd\"},\n", + " {\"kind\":\"rsi\"},\n", + " {\"kind\":\"log_return\", \"cumulative\": True},\n", + " {\"kind\":\"sma\", \"close\": \"CUMLOGRET_1\", \"length\": 5, \"suffix\": \"CUMLOGRET\"},\n", + "]\n", + "momo_bands_sma_strategy = ta.Strategy(\n", + " \"Momo, Bands and SMAs and Cumulative Log Returns\", # name\n", + " momo_bands_sma_ta, # ta\n", + " \"MACD and RSI Momo with BBANDS and SMAs 50 & 200 and Cumulative Log Returns\" # description\n", + ")\n", + "momo_bands_sma_strategy" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Momo, Bands and SMAs and Cumulative Log Returns'" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Update the Watchlist\n", + "watch.strategy = momo_bands_sma_strategy\n", + "watch.strategy.name" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[i] Loaded['D']: SPY_D.csv\n", + "[i] Runtime: 34.4336 ms (0.0344 s)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
openhighlowclosevolumeSMA_50SMA_200BBL_20_2.0BBM_20_2.0BBU_20_2.0MACD_12_26_9MACDh_12_26_9MACDs_12_26_9RSI_14CUMLOGRET_1SMA_5_CUMLOGRET03070
date
1999-11-01136.5000137.0000135.5625135.56254006500.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN03070
1999-11-02135.9687137.2500134.5937134.59376516900.0NaNNaNNaNNaNNaNNaNNaNNaN0.000000-0.007172NaN03070
1999-11-03136.0000136.3750135.1250135.50007222300.0NaNNaNNaNNaNNaNNaNNaNNaN50.185503-0.000461NaN03070
1999-11-04136.7500137.3593135.7656136.53127907500.0NaNNaNNaNNaNNaNNaNNaNNaN69.1539950.007120NaN03070
1999-11-05138.6250139.1093136.7812137.87507431500.0NaNNaNNaNNaNNaNNaNNaNNaN79.8968160.016915NaN03070
1999-11-08137.0000138.3750136.7500138.00004649200.0NaNNaNNaNNaNNaNNaNNaNNaN80.5745370.0178210.00684503070
1999-11-09138.5000138.6875136.2812136.70314533700.0NaNNaNNaNNaNNaNNaNNaNNaN58.5283520.0083790.00995503070
1999-11-10136.2500138.3906136.0781137.71876405600.0NaNNaNNaNNaNNaNNaNNaNNaN66.3036840.0157800.01320303070
1999-11-11138.1875138.5000137.4687138.50004794100.0NaNNaNNaNNaNNaNNaNNaN0.070.8339620.0214380.01606603070
1999-11-12139.2500139.9843137.1250139.750011802900.0NaNNaNNaNNaNNaNNaNNaN0.076.3194080.0304220.01876803070
1999-11-15139.8437140.2500139.4062140.07812187500.0NaNNaNNaNNaNNaNNaNNaN0.077.5148040.0327670.02175703070
1999-11-16140.5625143.0000140.0937141.25007544800.0NaNNaNNaNNaNNaNNaNNaN0.081.1709040.0410990.02830103070
1999-11-17142.2500142.9375141.3125141.62509459000.0NaNNaNNaNNaNNaNNaNNaN0.082.1699800.0437500.03389503070
1999-11-18142.4375143.0000141.6250142.62504491000.0NaNNaNNaNNaNNaNNaNNaN0.084.5276300.0507860.03976503070
1999-11-19142.4062142.9687142.0000142.50004832100.0NaNNaNNaNNaNNaNNaNNaN0.083.0493440.0499090.04366203070
1999-11-22142.4375143.0000141.5000142.46874155400.0NaNNaNNaNNaNNaNNaNNaN0.082.6595170.0496900.04704703070
1999-11-23142.8437142.8437140.3750141.21875918000.0NaNNaNNaNNaNNaNNaNNaN0.068.7753850.0408770.04700203070
1999-11-24140.7500142.4375140.0000141.96874459700.0NaNNaNNaNNaNNaNNaNNaN0.071.8324890.0461740.04748703070
1999-11-26142.4687142.8750141.2500141.43751693900.0NaNNaNNaNNaNNaNNaNNaN0.066.8409200.0424250.04581503070
1999-11-29140.8750141.9218140.4375140.93757348600.0NaNNaN134.086598139.34217144.597742NaNNaN0.062.4425340.0388840.04361003070
\n", + "
" + ], + "text/plain": [ + " open high low close volume SMA_50 \\\n", + "date \n", + "1999-11-01 136.5000 137.0000 135.5625 135.5625 4006500.0 NaN \n", + "1999-11-02 135.9687 137.2500 134.5937 134.5937 6516900.0 NaN \n", + "1999-11-03 136.0000 136.3750 135.1250 135.5000 7222300.0 NaN \n", + "1999-11-04 136.7500 137.3593 135.7656 136.5312 7907500.0 NaN \n", + "1999-11-05 138.6250 139.1093 136.7812 137.8750 7431500.0 NaN \n", + "1999-11-08 137.0000 138.3750 136.7500 138.0000 4649200.0 NaN \n", + "1999-11-09 138.5000 138.6875 136.2812 136.7031 4533700.0 NaN \n", + "1999-11-10 136.2500 138.3906 136.0781 137.7187 6405600.0 NaN \n", + "1999-11-11 138.1875 138.5000 137.4687 138.5000 4794100.0 NaN \n", + "1999-11-12 139.2500 139.9843 137.1250 139.7500 11802900.0 NaN \n", + "1999-11-15 139.8437 140.2500 139.4062 140.0781 2187500.0 NaN \n", + "1999-11-16 140.5625 143.0000 140.0937 141.2500 7544800.0 NaN \n", + "1999-11-17 142.2500 142.9375 141.3125 141.6250 9459000.0 NaN \n", + "1999-11-18 142.4375 143.0000 141.6250 142.6250 4491000.0 NaN \n", + "1999-11-19 142.4062 142.9687 142.0000 142.5000 4832100.0 NaN \n", + "1999-11-22 142.4375 143.0000 141.5000 142.4687 4155400.0 NaN \n", + "1999-11-23 142.8437 142.8437 140.3750 141.2187 5918000.0 NaN \n", + "1999-11-24 140.7500 142.4375 140.0000 141.9687 4459700.0 NaN \n", + "1999-11-26 142.4687 142.8750 141.2500 141.4375 1693900.0 NaN \n", + "1999-11-29 140.8750 141.9218 140.4375 140.9375 7348600.0 NaN \n", + "\n", + " SMA_200 BBL_20_2.0 BBM_20_2.0 BBU_20_2.0 MACD_12_26_9 \\\n", + "date \n", + "1999-11-01 NaN NaN NaN NaN NaN \n", + "1999-11-02 NaN NaN NaN NaN NaN \n", + "1999-11-03 NaN NaN NaN NaN NaN \n", + "1999-11-04 NaN NaN NaN NaN NaN \n", + "1999-11-05 NaN NaN NaN NaN NaN \n", + "1999-11-08 NaN NaN NaN NaN NaN \n", + "1999-11-09 NaN NaN NaN NaN NaN \n", + "1999-11-10 NaN NaN NaN NaN NaN \n", + "1999-11-11 NaN NaN NaN NaN NaN \n", + "1999-11-12 NaN NaN NaN NaN NaN \n", + "1999-11-15 NaN NaN NaN NaN NaN \n", + "1999-11-16 NaN NaN NaN NaN NaN \n", + "1999-11-17 NaN NaN NaN NaN NaN \n", + "1999-11-18 NaN NaN NaN NaN NaN \n", + "1999-11-19 NaN NaN NaN NaN NaN \n", + "1999-11-22 NaN NaN NaN NaN NaN \n", + "1999-11-23 NaN NaN NaN NaN NaN \n", + "1999-11-24 NaN NaN NaN NaN NaN \n", + "1999-11-26 NaN NaN NaN NaN NaN \n", + "1999-11-29 NaN 134.086598 139.34217 144.597742 NaN \n", + "\n", + " MACDh_12_26_9 MACDs_12_26_9 RSI_14 CUMLOGRET_1 \\\n", + "date \n", + "1999-11-01 NaN NaN NaN NaN \n", + "1999-11-02 NaN NaN 0.000000 -0.007172 \n", + "1999-11-03 NaN NaN 50.185503 -0.000461 \n", + "1999-11-04 NaN NaN 69.153995 0.007120 \n", + "1999-11-05 NaN NaN 79.896816 0.016915 \n", + "1999-11-08 NaN NaN 80.574537 0.017821 \n", + "1999-11-09 NaN NaN 58.528352 0.008379 \n", + "1999-11-10 NaN NaN 66.303684 0.015780 \n", + "1999-11-11 NaN 0.0 70.833962 0.021438 \n", + "1999-11-12 NaN 0.0 76.319408 0.030422 \n", + "1999-11-15 NaN 0.0 77.514804 0.032767 \n", + "1999-11-16 NaN 0.0 81.170904 0.041099 \n", + "1999-11-17 NaN 0.0 82.169980 0.043750 \n", + "1999-11-18 NaN 0.0 84.527630 0.050786 \n", + "1999-11-19 NaN 0.0 83.049344 0.049909 \n", + "1999-11-22 NaN 0.0 82.659517 0.049690 \n", + "1999-11-23 NaN 0.0 68.775385 0.040877 \n", + "1999-11-24 NaN 0.0 71.832489 0.046174 \n", + "1999-11-26 NaN 0.0 66.840920 0.042425 \n", + "1999-11-29 NaN 0.0 62.442534 0.038884 \n", + "\n", + " SMA_5_CUMLOGRET 0 30 70 \n", + "date \n", + "1999-11-01 NaN 0 30 70 \n", + "1999-11-02 NaN 0 30 70 \n", + "1999-11-03 NaN 0 30 70 \n", + "1999-11-04 NaN 0 30 70 \n", + "1999-11-05 NaN 0 30 70 \n", + "1999-11-08 0.006845 0 30 70 \n", + "1999-11-09 0.009955 0 30 70 \n", + "1999-11-10 0.013203 0 30 70 \n", + "1999-11-11 0.016066 0 30 70 \n", + "1999-11-12 0.018768 0 30 70 \n", + "1999-11-15 0.021757 0 30 70 \n", + "1999-11-16 0.028301 0 30 70 \n", + "1999-11-17 0.033895 0 30 70 \n", + "1999-11-18 0.039765 0 30 70 \n", + "1999-11-19 0.043662 0 30 70 \n", + "1999-11-22 0.047047 0 30 70 \n", + "1999-11-23 0.047002 0 30 70 \n", + "1999-11-24 0.047487 0 30 70 \n", + "1999-11-26 0.045815 0 30 70 \n", + "1999-11-29 0.043610 0 30 70 " + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "spy = watch.load('SPY', timed=True)\n", + "# Apply constants to the DataFrame for indicators\n", + "spy.ta.constants(True, 0, 0, 1) # 0\n", + "spy.ta.constants(True, 30, 30, 1) # 30\n", + "spy.ta.constants(True, 70, 70, 1) # 70\n", + "spy.head(20)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/watchlist.py b/examples/watchlist.py new file mode 100644 index 0000000..c33aa26 --- /dev/null +++ b/examples/watchlist.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +from functools import lru_cache +from pathlib import Path +from random import random + +import pandas as pd + +from alphaVantageAPI.alphavantage import AlphaVantage # pip install alphaVantage-api +import pandas_ta as ta + + +class Watchlist(object): + """Watchlist Class (** This is subject to change! **) + ============================================================================ + A simple Class to load/download financial market data and automatically + apply Technical Analysis indicators with a Pandas TA Strategy. Default + Strategy: pandas_ta.AllStrategy. + + Requirements: + - Pandas TA (pip install pandas_ta) + - AlphaVantage (pip install alphaVantage-api) for the Default Data Source. + To use another Data Source, update the load() method after AV. + + Required Arguments: + - tickers: A list of strings containing tickers. Example: ['SPY', 'AAPL'] + ============================================================================ + """ + def __init__( + self, + tickers: list, + tf: str = None, + name: str = None, + strategy: ta.Strategy = None, + ds: object = None, + **kwargs + ): + self.tickers = tickers + self.tf = tf + self.verbose = kwargs.pop("verbose", False) + self.name = name + self.data = None + self.kwargs = kwargs + + self.ds = ds if ds is not None else None + self.strategy = strategy + + + def _drop_columns(self, df: pd.DataFrame, cols: list = ['Unnamed: 0', 'date', 'split_coefficient', 'dividend']): + """Helper methods to drop columns silently.""" + df_columns = list(df.columns) + if any(_ in df_columns for _ in cols): + if self.verbose: + print(f"[i] Possible columns dropped: {', '.join(cols)}") + df = df.drop(cols, axis=1, errors='ignore') + return df + + def _load_all(self, **kwargs) -> dict: + """Updates the Watchlist's data property with a dictionary of DataFrames + keyed by ticker.""" + if self.tickers is not None and isinstance(self.tickers, list) and len(self.tickers): + self.data = {ticker: self.load(ticker, **kwargs) for ticker in self.tickers} + return self.data + + def load( + self, + ticker: str = None, + tf: str = None, + index: str = 'date', + drop: list = ['dividend', 'split_coefficient'], + file_path: str = ".", + **kwargs + ) -> pd.DataFrame: + """Loads or Downloads (if a local csv does not exist) the data from the + Data Source. When successful, it returns a Data Frame for the requested + ticker. If no tickers are given, it loads all the tickers.""" + + tf = self.tf if tf is None else tf.upper() + if ticker is not None and isinstance(ticker, str): + ticker = str(ticker).upper() + else: + print(f"[!] Loading All: {', '.join(self.tickers)}") + self._load_all(**kwargs) + return + + filename_ = f"{ticker}_{tf}.csv" + current_file = Path(file_path) / filename_ + + # Load local or from Data Source + if current_file.exists(): + df = pd.read_csv(filename_, index_col=index) + if not df.ta.datetime_ordered: + df = df.set_index(pd.DatetimeIndex(df.index)) + print(f"\n[i] Loaded['{tf}']: {filename_}") + else: + if self.ds is not None and isinstance(self.ds, AlphaVantage): + df = self.ds.data(tf, ticker) + if not df.ta.datetime_ordered: + df = df.set_index(pd.DatetimeIndex(df[index])) + print(f"\n[+] Downloading['{tf}']: {ticker}") + + df = self._drop_columns(df) # Remove select columns + + if kwargs.pop("analyze", True): + df.ta.strategy(name=self.strategy.name, ta=self.strategy.ta, **kwargs) + + df.ticker = ticker # Attach ticker to the DataFrame + return df + + @property + def data(self) -> dict: + """When not None, it contains a dictionary of DataFrames keyed by ticker. data = {"SPY": pd.DataFrame, ...}""" + return self._data + + @data.setter + def data(self, value: dict) -> None: + # Later check dict has string keys and DataFrame values + if value is not None and isinstance(value, dict): + if self.verbose: print(f"[+] New data") + self._data = value + else: + self._data = None + + @property + def name(self) -> str: + """The name of the Watchlist. Default: "Watchlist: {Watchlist.tickers}".""" + return self._name + + @name.setter + def name(self, value: str) -> None: + if isinstance(value, str): + self._name = str(value) + else: + self._name = f"Watchlist: {', '.join(self.tickers)}" + + @property + def strategy(self) -> ta.Strategy: + """Pandas TA Strategy Class. Default: pandas_ta.AllStrategy""" + return self._strategy + + @strategy.setter + def strategy(self, value: ta.Strategy) -> None: + if value is not None and isinstance(value, ta.Strategy): + self._strategy = value + else: + self._strategy = ta.AllStrategy + + @property + def tf(self) -> str: + """Alias for timeframe. Default: 'D'""" + return self._tf + + @tf.setter + def tf(self, value: str) -> None: + if isinstance(value, str): + value = str(value) + self._tf = value + else: + self._tf = "D" + + @property + def tickers(self) -> list: + """tickers + + If a string, it it converted to a list. Example: 'AAPL' -> ['AAPL'] + * Does not accept, comma seperated strings. + If a list, checks if it is a list of strings. + """ + return self._tickers + + @tickers.setter + def tickers(self, value: (list, str)) -> None: + if value is None: + print(f"[X] {value} is not a valie Watchlist ticker.") + return + elif isinstance(value, list) and [isinstance(_, str) for _ in value]: + self._tickers = list(map(str.upper, value)) + elif isinstance(value, str): + self._tickers = [value.upper()] + self.name = self._tickers + + @property + def verbose(self) -> bool: + """Toggle the verbose property. Default: False""" + return self._verbose + + @verbose.setter + def verbose(self, value: bool) -> None: + if isinstance(value, bool): + self._verbose = bool(value) + else: + self._verbose = False + + def indicators(self, *args, **kwargs) -> any: + """Returns the list of indicators that are available with Pandas Ta.""" + pd.DataFrame().ta.indicators(*args, **kwargs) + + def __repr__(self) -> str: + s = f"Watch(name='{self.name}', tickers[{len(self.tickers)}]='{', '.join(self.tickers)}', tf='{self.tf}', strategy[{self.strategy.total_ta()}]='{self.strategy.name}'" + if self.data is not None: + s += f", data[{len(self.data.keys())}])" + return s + return s + ")" \ No newline at end of file diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 19aa92e..a47b57e 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -18,4 +18,6 @@ except DistributionNotFound: else: __version__ = _dist.version +categories = ["candles", "momentum", "overlap", "performance", "statistics", "trend", "volatility", "volume"] + from pandas_ta.core import * \ No newline at end of file diff --git a/pandas_ta/candles/__init__.py b/pandas_ta/candles/__init__.py index 57e6728..d92eb5e 100644 --- a/pandas_ta/candles/__init__.py +++ b/pandas_ta/candles/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- -from .candle import * from .ha import ha from .cdl_doji import cdl_doji \ No newline at end of file diff --git a/pandas_ta/candles/candle.py b/pandas_ta/candles/candle.py deleted file mode 100644 index bd29f58..0000000 --- a/pandas_ta/candles/candle.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -from pandas_ta.utils import get_offset, non_zero_range, verify_series - -def candle_color(open_, close): - color = close.copy().astype(int) - color[close >= open_] = 1 - color[close < open_] = -1 - return color - -def real_body(open_, close): - return non_zero_range(open_, close) - -def high_low_range(high, low): - return non_zero_range(high, low) \ No newline at end of file diff --git a/pandas_ta/candles/cdl_doji.py b/pandas_ta/candles/cdl_doji.py index 5a3c5ff..8f724ed 100644 --- a/pandas_ta/candles/cdl_doji.py +++ b/pandas_ta/candles/cdl_doji.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- from pandas_ta.overlap import sma -from pandas_ta.utils import get_offset, is_percent, non_zero_range, verify_series -from .candle import * - +from pandas_ta.utils import get_offset, high_low_range, is_percent +from pandas_ta.utils import non_zero_range, real_body, verify_series def cdl_doji(open_, high, low, close, length=None, factor=None, scalar=None, asint=True, offset=None, **kwargs): """Candle Type: Doji""" diff --git a/pandas_ta/core.py b/pandas_ta/core.py index f544cba..68140c9 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- +from dataclasses import dataclass, field +from datetime import datetime from functools import wraps from multiprocessing import cpu_count, Pool from random import random from time import perf_counter +from typing import List import pandas as pd +from pandas_ta import categories from pandas.core.base import PandasObject from pandas_ta.candles import * @@ -17,7 +21,10 @@ from pandas_ta.volatility import * from pandas_ta.volume import * from pandas_ta.utils import * -version = ".".join(("0", "1", "75b")) +version = ".".join(("0", "1", "78b")) + +# Dictionary of files for each category, used in df.ta.strategy() +Category = {name: category_files(name) for name in categories} def mp_worker(args): df, method, kwargs = args @@ -40,11 +47,91 @@ def finalize(method): return _wrapper +@dataclass +class Strategy: + """Strategy (Data)Class + A way to name and group your favorite indicators + + Args: + name (str): Some short memorable string. Note: Case-insensitive "All" is reserved. + ta (list of dicts): A list of dicts containing keyword arguments where "kind" is the indicator. + description (str): A more detailed description of what the Strategy tries to capture. Default: None + created (str): At datetime string of when it was created. Default: Automatically generated. *Subject to change* + + Example TA: + ta = [ + {"kind": "sma", "length": 200}, + {"kind": "sma", "close": "volume", "length": 50}, + {"kind": "bbands", "length": 20}, + {"kind": "rsi"}, + {"kind": "macd", "fast": 8, "slow": 21}, + {"kind": "sma", "close": "volume", "length": 20, "prefix": "VOLUME"}, + ] + """ + name: str# = None # Required. + ta: List = field(default_factory=list) # Required. + description: str = None # Helpful. More descriptive version or notes or w/e. + created: str = datetime.now().strftime("%m/%d/%Y, %H:%M:%S") # Optional. May change type later to datetime + last_run: str = None # Auto filled + run_time: str = None # Auto filled + + def __post_init__(self): + has_name = True + is_ta = False + required_args = ["[X] Strategy requires the following argument(s):"] + + name_is_str = isinstance(self.name, str) + ta_is_list = isinstance(self.ta, list) + + if self.name is None or not name_is_str: + required_args.append(" - name. Must be a string. Example: \"My TA\". Note: \"all\" is reserved.") + has_name != has_name + + if self.ta is None: + self.ta = None + elif self.ta is not None and ta_is_list and self.total_ta() > 0: + # Check that all elements of the list are dicts. + # Does not check if the dicts values are valid indicator kwargs + # User must check indicator documentation for all indicators args. + is_ta = all([isinstance(_, dict) and len(_.keys()) > 0 for _ in self.ta]) + else: + s = " - ta. Format is a list of dicts. Example: [{'kind': 'sma', 'length': 10}]" + s += "\n Check the indicator for the correct arguments if you receive this error." + required_args.append(s) + + if len(required_args) > 1: + [print(_) for _ in required_args] + return None + + def total_ta(self): + return len(self.ta) if self.ta is not None else 0 + +# All Default Strategy +AllStrategy = Strategy( + name="All", + description="All the indicators with their default settings. Pandas TA default.", + ta=None +) + +# Default (Example) Strategy. +CommonStrategy = Strategy( + name="Common Price and Volume SMAs", + description="Common Price SMAs: 10, 20, 50, 200 and Volume SMA: 20.", + ta=[ + {"kind": "sma", "length": 10}, + {"kind": "sma", "length": 20}, + {"kind": "sma", "length": 50}, + {"kind": "sma", "length": 200}, + {"kind": "sma", "close": "volume", "length": 20, "prefix": "VOL"} + ] +) + + class BasePandasObject(PandasObject): """Simple PandasObject Extension - Ensures the DataFrame is not empty and has columns. It would be a - sad Panda otherwise. + Ensures the DataFrame is not empty and has columns. + It would be a sad Panda otherwise. Args: df (pd.DataFrame): Extends Pandas DataFrame @@ -140,30 +227,37 @@ class AnalysisIndicators(BasePandasObject): _adjusted = None _mp = False - def __call__(self, kind=None, alias=None, timed=False, verbose=False, **kwargs): - try: - if isinstance(kind, str): - kind = kind.lower() - fn = getattr(self, kind) + def __call__( + self, + kind: str= None, + alias: str = None, + timed = False, + verbose = False, + **kwargs + ): + try: + if isinstance(kind, str): + kind = kind.lower() + fn = getattr(self, kind) - if timed: stime = perf_counter() + if timed: stime = perf_counter() - # Run the indicator - result = fn(**kwargs) # = getattr(self, kind)(**kwargs) + # Run the indicator + result = fn(**kwargs) # = getattr(self, kind)(**kwargs) - # Add an alias if passed - if alias: result.alias = f"{alias}" + if timed: + result.timed = final_time(stime) + alias_str = alias + ':' if alias is not None else '' + print(f"[+] {kind}:{alias_str} {result.timed}") - if timed: - result.timed = final_time(stime) - print(f"[+] {kind}:{alias + ':' if alias is not None else ''} {result.timed}") + # Add an alias if passed + if alias: result.alias = f"{alias}" - return result - else: - self.help() - - except: pass + return result + else: + self.help() + except: pass @property def adjusted(self) -> str: @@ -257,11 +351,11 @@ class AnalysisIndicators(BasePandasObject): match = [i for i, x in enumerate(matches) if x] # If found, awesome. Return it or return the 'series'. cols = ', '.join(list(df.columns)) - NOT_FOUND = f" [X] Ooops!!!: It's {series not in df.columns}, the series '{series}' not in {cols}" + NOT_FOUND = f"[X] Ooops!!!: It's {series not in df.columns}, the series '{series}' was not found in {cols}" return df.iloc[:,match[0]] if len(match) else print(NOT_FOUND) - def constants(self, append, lower_bound=-100, upper_bound=100, every=1): + def constants(self, append, lower_bound=-100, upper_bound=100, every=10): """Constants Useful for creating indicator levels or if you need some constant value @@ -332,58 +426,7 @@ class AnalysisIndicators(BasePandasObject): s = f"{header}\nTotal Indicators: {total_indicators}\n" print(f"{s}Abbreviations:\n {', '.join(ta_indicators)}") if total_indicators > 0 else print(s) - - # ALL Features - def _all(self, **kwargs): - """Appends by default all non-excluded indicators to the DataFrame. Used by ta.strategy(**kwargs)""" - cpus = cpu_count() - cores = int(kwargs.pop("cores", cpus)) - timed = kwargs.pop("timed", False) - verbose = kwargs.pop("verbose", False) - user_excluded = kwargs.pop("exclude", []) - append = kwargs.setdefault("append", True) - - excluded = ["above", "above_value", "below", "below_value", - "cross", "cross_value", "long_run", "short_run", "trend_return", "vp"] - excluded += user_excluded - - current_columns = len(self._df.columns) - indicators = self.indicators(as_list=True, exclude=excluded) - - print('[+] Strategy "All"') - if verbose: - print(f'[i] Indicators with the following arguments: {kwargs}') - print(f"[i] excluded[{len(excluded)}]: {', '.join(excluded)}") - - if timed: stime = perf_counter() - - if not self.mp: - # Display multiprocessing tip 10% of the time. - if random() < 0.1: - print(f"[i] Set 'df.ta.mp = True' to enable multiprocessing. This computer has {cpus} cores. Default: False") - - methods = [getattr(self, kind) for kind in indicators] - [f(**kwargs) for f in methods] - - else: - print(f"[i] multiprocessing: {cores} of {cpu_count()} cores") - pool = Pool(cores) - result = pool.imap_unordered( - mp_worker, ((self._df, ind, kwargs) for ind in indicators), cores - ) - pool.close() - pool.join() - - # Apply prefixes/suffixes and append to the DataFrame - for r in result: - self._add_prefix_suffix(r, **kwargs) - self._append(r, **kwargs) - - print(f"[i] total indicators: {len(indicators)}, columns added: {len(self._df.columns) - current_columns}") - print(f"[i] runtime: {final_time(stime)}\n") if timed else None - - - def strategy(self, **kwargs): + def strategy(self, *args, **kwargs): """Strategy Method An experimental method that by default runs all applicable indicators. @@ -393,17 +436,94 @@ class AnalysisIndicators(BasePandasObject): Args: name (str, optional): Default: 'all' exclude (list, optional): Default: []. List of indicator names to exclude. - verbose (bool): Default: False kwargs: (optional) Default: {}. Any indicator argument you want to modify. For example, length=20 or offset=-1 or high=df['High'] ... - """ - name = kwargs.pop("name", "all") - if name is None or name == "" or not isinstance(name, str): # Extra check - name = "all" - self._all(**kwargs) if name == "all" else None + cpus = cpu_count() + kwargs["append"] = True # Ensure indicators are appended to the DataFrame + + name = kwargs.pop("name", None) + # removing before sending the rest of kwargs to the indicators + ta = kwargs.pop("ta", None) + mp = kwargs.pop("mp", False) + cores = int(kwargs.pop("cores", cpus)) + timed = kwargs.pop("timed", False) + verbose = kwargs.pop("verbose", False) + user_excluded = kwargs.pop("exclude", []) + + # Filter if Strategy, Category or by Strategy name and TA + if len(args) and isinstance(args[0], Strategy): + name, ta = args[0].name, args[0].ta + elif name is None or name.lower() == "all": + name = "All" + elif ta is None and name.lower() in categories: + ta = Category[name.lower()] + + is_all = True if name is None or name.lower() == "all" else False + has_ta = True if ta is not None else False + initial_column_count = len(self._df.columns) + + excluded = [] + excluded_functions = ["above", "above_value", "below", "below_value", "cross", "cross_value", "long_run", "short_run", "trend_return", "vp"] + excluded += user_excluded # Exclude user excluded ta if listed + + print(f"[+] Strategy: {name}") if verbose else None + if name.lower() in categories or is_all: + # Exclude special functions + excluded += excluded_functions + if is_all: + ta = self.indicators(as_list=True, exclude=excluded) + else: + ta = Category[name.lower()] + else: + for kwds in ta: + kwds["append"] = True + + if verbose: + print(f'[i] Indicators with the following arguments: {kwargs}') + if is_all and len(excluded) > 0: + print(f"[i] Excluded[{len(excluded)}]: {', '.join(excluded)}") + + # Enable multiprocessing if user sets: mp=True + if mp: self.mp = not self.mp + + if self.mp: + print(f"[i] Multiprocessing: {cores} of {cpu_count()} cores") + pool = Pool(cores) + + if timed: stime = perf_counter() + result = pool.imap_unordered( + mp_worker, ((self._df, ind, kwargs) for ind in ta), cores + ) + pool.close() + pool.join() + + # Apply prefixes/suffixes and appends indicator result to the DataFrame + for r in result: + self._add_prefix_suffix(r, **kwargs) + self._append(r, **kwargs) + if timed: ftime = final_time(stime) + + else: + # Display multiprocessing tip 10% of the time. + if random() < 0.1: + print(f"[i] Set 'df.ta.mp = True' to enable multiprocessing. This computer has {cpus} cores. Default: False") + + if timed: stime = perf_counter() + if name.lower() in categories or is_all: + indicators = [getattr(self, kind) for kind in ta] + [f(**kwargs) for f in indicators] + else: + [getattr(self, kwds["kind"])(**kwds) for kwds in ta] + + if timed: ftime = final_time(stime) + + if verbose: + print(f"[i] Total indicators: {len(ta)}") + print(f"[i] Columns added: {len(self._df.columns) - initial_column_count}") + print(f"[i] Runtime: {ftime}") if timed else None # Candles @@ -872,11 +992,13 @@ class AnalysisIndicators(BasePandasObject): @finalize def trend_return(self, close=None, trend=None, log=True, cumulative=None, offset=None, trend_reset=None, **kwargs): - close = self._get_column(close, 'close') - trend = self._get_column(trend, f"{trend}") + if trend is None: return self._df + else: + close = self._get_column(close, 'close') + trend = self._get_column(trend, f"{trend}") - result = trend_return(close=close, trend=trend, log=log, cumulative=cumulative, offset=offset, trend_reset=trend_reset, **kwargs) - return result + result = trend_return(close=close, trend=trend, log=log, cumulative=cumulative, offset=offset, trend_reset=trend_reset, **kwargs) + return result # Statistics Indicators @@ -1173,10 +1295,11 @@ class AnalysisIndicators(BasePandasObject): return result @finalize - def donchian(self, close=None, length=None, offset=None, **kwargs): - close = self._get_column(close, 'close') + def donchian(self, high=None, low=None, lower_length=None, upper_length=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') - result = donchian(close=close, length=length, offset=offset, **kwargs) + result = donchian(high=high, low=low, lower_length=lower_length, upper_length=upper_length, offset=offset, **kwargs) return result @finalize @@ -1354,7 +1477,4 @@ class AnalysisIndicators(BasePandasObject): volume = self._get_column(volume, 'volume') result = vp(close=close, volume=volume, width=width, percent=percent, **kwargs) - return result - -# if __name__ == "__main__": -# freeze_support() \ No newline at end of file + return result \ No newline at end of file diff --git a/pandas_ta/momentum/rsi.py b/pandas_ta/momentum/rsi.py index 87cbea1..777b128 100644 --- a/pandas_ta/momentum/rsi.py +++ b/pandas_ta/momentum/rsi.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from pandas import DataFrame, concat +from pandas_ta.overlap import rma from pandas_ta.utils import get_drift, get_offset, verify_series, signals def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): @@ -18,10 +19,10 @@ def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): positive[positive < 0] = 0 # Make negatives 0 for the postive series negative[negative > 0] = 0 # Make postives 0 for the negative series - positive_avg = positive.ewm(com=length, adjust=False).mean() - negative_avg = negative.ewm(com=length, adjust=False).mean().abs() + positive_avg = rma(positive, length=length) + negative_avg = rma(negative, length=length) - rsi = scalar * positive_avg / (positive_avg + negative_avg) + rsi = scalar * positive_avg / (positive_avg + negative_avg.abs()) # Offset if offset != 0: diff --git a/pandas_ta/overlap/rma.py b/pandas_ta/overlap/rma.py index ddbc917..5ed998a 100644 --- a/pandas_ta/overlap/rma.py +++ b/pandas_ta/overlap/rma.py @@ -6,12 +6,11 @@ def rma(close, length=None, offset=None, **kwargs): # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 10 - min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length offset = get_offset(offset) alpha = (1.0 / length) if length > 0 else 0.5 # Calculate Result - rma = close.ewm(alpha=alpha, min_periods=min_periods).mean() + rma = close.ewm(alpha=alpha).mean() # Offset if offset != 0: diff --git a/pandas_ta/statistics/entropy.py b/pandas_ta/statistics/entropy.py index 9b383af..216833d 100644 --- a/pandas_ta/statistics/entropy.py +++ b/pandas_ta/statistics/entropy.py @@ -20,7 +20,7 @@ def entropy(close, length=None, base=None, offset=None, **kwargs): # Name & Category entropy.name = f"ENTP_{length}" - entropy.category = 'statistics' + entropy.category = "statistics" return entropy diff --git a/pandas_ta/statistics/kurtosis.py b/pandas_ta/statistics/kurtosis.py index 261e748..4b6fa82 100644 --- a/pandas_ta/statistics/kurtosis.py +++ b/pandas_ta/statistics/kurtosis.py @@ -18,7 +18,7 @@ def kurtosis(close, length=None, offset=None, **kwargs): # Name & Category kurtosis.name = f"KURT_{length}" - kurtosis.category = 'statistics' + kurtosis.category = "statistics" return kurtosis diff --git a/pandas_ta/statistics/mad.py b/pandas_ta/statistics/mad.py index 1b9fc7a..a355717 100644 --- a/pandas_ta/statistics/mad.py +++ b/pandas_ta/statistics/mad.py @@ -23,7 +23,7 @@ def mad(close, length=None, offset=None, **kwargs): # Name & Category mad.name = f"MAD_{length}" - mad.category = 'statistics' + mad.category = "statistics" return mad diff --git a/pandas_ta/statistics/median.py b/pandas_ta/statistics/median.py index 61503db..dcf43c6 100644 --- a/pandas_ta/statistics/median.py +++ b/pandas_ta/statistics/median.py @@ -18,7 +18,7 @@ def median(close, length=None, offset=None, **kwargs): # Name & Category median.name = f"MEDIAN_{length}" - median.category = 'statistics' + median.category = "statistics" return median diff --git a/pandas_ta/statistics/quantile.py b/pandas_ta/statistics/quantile.py index 515af6e..30a81b0 100644 --- a/pandas_ta/statistics/quantile.py +++ b/pandas_ta/statistics/quantile.py @@ -19,7 +19,7 @@ def quantile(close, length=None, q=None, offset=None, **kwargs): # Name & Category quantile.name = f"QTL_{length}_{q}" - quantile.category = 'statistics' + quantile.category = "statistics" return quantile diff --git a/pandas_ta/statistics/skew.py b/pandas_ta/statistics/skew.py index 8705589..dbed0d4 100644 --- a/pandas_ta/statistics/skew.py +++ b/pandas_ta/statistics/skew.py @@ -24,7 +24,7 @@ def skew(close, length=None, offset=None, **kwargs): # Name & Category skew.name = f"SKEW_{length}" - skew.category = 'statistics' + skew.category = "statistics" return skew diff --git a/pandas_ta/statistics/stdev.py b/pandas_ta/statistics/stdev.py index 1f92b3f..d89561d 100644 --- a/pandas_ta/statistics/stdev.py +++ b/pandas_ta/statistics/stdev.py @@ -3,15 +3,16 @@ from numpy import sqrt as npsqrt from .variance import variance from ..utils import get_offset, verify_series -def stdev(close, length=None, offset=None, **kwargs): +def stdev(close, length=None, ddof=1, offset=None, **kwargs): """Indicator: Standard Deviation""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 30 + ddof = int(ddof) if ddof >= 0 and ddof < length else 1 offset = get_offset(offset) # Calculate Result - stdev = variance(close=close, length=length).apply(npsqrt) + stdev = variance(close=close, length=length, ddof=ddof).apply(npsqrt) # Offset if offset != 0: @@ -19,7 +20,7 @@ def stdev(close, length=None, offset=None, **kwargs): # Name & Category stdev.name = f"STDEV_{length}" - stdev.category = 'statistics' + stdev.category = "statistics" return stdev @@ -39,6 +40,9 @@ Calculation: Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 30 + ddof (int): Delta Degrees of Freedom. + The divisor used in calculations is N - ddof, + where N represents the number of elements. Default: 1 offset (int): How many periods to offset the result. Default: 0 Kwargs: @@ -47,4 +51,4 @@ Kwargs: Returns: pd.Series: New feature generated. -""" \ No newline at end of file +""" diff --git a/pandas_ta/statistics/variance.py b/pandas_ta/statistics/variance.py index 7d5fd26..5dc182d 100644 --- a/pandas_ta/statistics/variance.py +++ b/pandas_ta/statistics/variance.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- from ..utils import get_offset, verify_series -def variance(close, length=None, offset=None, **kwargs): +def variance(close, length=None, ddof=1, offset=None, **kwargs): """Indicator: Variance""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 1 else 30 + ddof = int(ddof) if ddof >= 0 and ddof < length else 1 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length offset = get_offset(offset) # Calculate Result - variance = close.rolling(length, min_periods=min_periods).var() + variance = close.rolling(length, min_periods=min_periods).var(ddof) # Offset if offset != 0: @@ -18,7 +20,7 @@ def variance(close, length=None, offset=None, **kwargs): # Name & Category variance.name = f"VAR_{length}" - variance.category = 'statistics' + variance.category = "statistics" return variance @@ -37,6 +39,9 @@ Calculation: Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 30 + ddof (int): Delta Degrees of Freedom. + The divisor used in calculations is N - ddof, + where N represents the number of elements. Default: 1 offset (int): How many periods to offset the result. Default: 0 Kwargs: @@ -45,4 +50,4 @@ Kwargs: Returns: pd.Series: New feature generated. -""" \ No newline at end of file +""" diff --git a/pandas_ta/statistics/zscore.py b/pandas_ta/statistics/zscore.py index 848e49b..09165ed 100644 --- a/pandas_ta/statistics/zscore.py +++ b/pandas_ta/statistics/zscore.py @@ -22,7 +22,7 @@ def zscore(close, length=None, std=None, offset=None, **kwargs): # Name & Category zscore.name = f"Z_{length}" - zscore.category = 'statistics' + zscore.category = "statistics" return zscore diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 77be0e0..b9580ed 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import math +from pathlib import Path from time import perf_counter import numpy as np @@ -9,7 +10,7 @@ from functools import reduce from operator import mul from sys import float_info as sflt -TRADING_DAYS_PER_YEAR = 250 +TRADING_DAYS_PER_YEAR = 251 TRADING_HOURS_PER_DAY = 6.5 MINUTES_PER_HOUR = 60 @@ -17,9 +18,9 @@ MINUTES_PER_HOUR = 60 def _above_below( series_a: pd.Series, series_b: pd.Series, - above: bool =True, - asint: bool =True, - offset: int =None, + above: bool = True, + asint: bool = True, + offset: int = None, **kwargs ): series_a = verify_series(series_a) @@ -52,8 +53,8 @@ def _above_below( def above( series_a: pd.Series, series_b: pd.Series, - asint: bool =True, - offset: int =None, + asint: bool = True, + offset: int = None, **kwargs ): return _above_below(series_a, series_b, above=True, asint=asint, offset=offset, **kwargs) @@ -97,6 +98,12 @@ def below_value( return _above_below(series_a, series_b, above=False, asint=asint, offset=offset, **kwargs) +def category_files(category: str) -> list: + """Helper function to return all filenames in the category directory.""" + files = [x.stem for x in list(Path(f"pandas_ta/{category}/").glob("*.py")) if x.stem != "__init__"] + return files + + def combination(**kwargs): """https://stackoverflow.com/questions/4941753/is-there-a-math-ncr-function-in-python""" n = int(math.fabs(kwargs.pop('n', 1))) @@ -433,4 +440,18 @@ def weights(w): def zero(x: [int, float]) -> [int, float]: """If the value is close to zero, then return zero. Otherwise return the value.""" - return 0 if -sflt.epsilon < x and x < sflt.epsilon else x \ No newline at end of file + return 0 if -sflt.epsilon < x and x < sflt.epsilon else x + +# Candle Functions + +def candle_color(open_, close): + color = close.copy().astype(int) + color[close >= open_] = 1 + color[close < open_] = -1 + return color + +def real_body(open_, close): + return non_zero_range(open_, close) + +def high_low_range(high, low): + return non_zero_range(high, low) \ No newline at end of file diff --git a/pandas_ta/volatility/bbands.py b/pandas_ta/volatility/bbands.py index a9d01c8..83b1005 100644 --- a/pandas_ta/volatility/bbands.py +++ b/pandas_ta/volatility/bbands.py @@ -44,15 +44,15 @@ def bbands(close, length=None, std=None, mamode=None, offset=None, **kwargs): upper.fillna(method=kwargs['fill_method'], inplace=True) # Name and Categorize it - lower.name = f"BBL_{length}" - mid.name = f"BBM_{length}" - upper.name = f"BBU_{length}" + lower.name = f"BBL_{length}_{std}" + mid.name = f"BBM_{length}_{std}" + upper.name = f"BBU_{length}_{std}" mid.category = upper.category = lower.category = 'volatility' # Prepare DataFrame to return data = {lower.name: lower, mid.name: mid, upper.name: upper} bbandsdf = DataFrame(data) - bbandsdf.name = f"BBANDS_{length}" + bbandsdf.name = f"BBANDS_{length}_{std}" bbandsdf.category = 'volatility' return bbandsdf diff --git a/pandas_ta/volatility/donchian.py b/pandas_ta/volatility/donchian.py index 7e892a9..cad0777 100644 --- a/pandas_ta/volatility/donchian.py +++ b/pandas_ta/volatility/donchian.py @@ -2,19 +2,20 @@ from pandas import DataFrame from ..utils import get_offset, verify_series -def donchian(close, lower_length=None, upper_length=None, offset=None, **kwargs): +def donchian(high, low, lower_length=None, upper_length=None, offset=None, **kwargs): """Indicator: Donchian Channels (DC)""" # Validate arguments - close = verify_series(close) - lower_length = int(lower_length) if lower_length and lower_length > 0 else 10 + high = verify_series(high) + low = verify_series(low) + lower_length = int(lower_length) if lower_length and lower_length > 0 else 20 upper_length = int(upper_length) if upper_length and upper_length > 0 else 20 lower_min_periods = int(kwargs['lower_min_periods']) if 'lower_min_periods' in kwargs and kwargs['lower_min_periods'] is not None else lower_length upper_min_periods = int(kwargs['upper_min_periods']) if 'upper_min_periods' in kwargs and kwargs['upper_min_periods'] is not None else upper_length offset = get_offset(offset) # Calculate Result - lower = close.rolling(lower_length, min_periods=lower_min_periods).min() - upper = close.rolling(upper_length, min_periods=upper_min_periods).max() + lower = low.rolling(lower_length, min_periods=lower_min_periods).min() + upper = high.rolling(upper_length, min_periods=upper_min_periods).max() mid = 0.5 * (lower + upper) # Handle fills @@ -60,15 +61,17 @@ Sources: Calculation: Default Inputs: - length=20 - LOWER = close.rolling(length).min() - UPPER = close.rolling(length).max() + lower_length=upper_length=20 + LOWER = low.rolling(lower_length).min() + UPPER = high.rolling(upper_length).max() MID = 0.5 * (LOWER + UPPER) Args: - close (pd.Series): Series of 'close's - length (int): The short period. Default: 20 - offset (int): How many periods to offset the result. Default: 0 + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + lower_length (int): The short period. Default: 20 + upper_length (int): The short period. Default: 20 + offset (int): How many periods to offset the result. Default: 0 Kwargs: fillna (value, optional): pd.DataFrame.fillna(value) diff --git a/pandas_ta/volume/cmf.py b/pandas_ta/volume/cmf.py index 03ad4cd..47e35d2 100644 --- a/pandas_ta/volume/cmf.py +++ b/pandas_ta/volume/cmf.py @@ -68,8 +68,8 @@ Args: high (pd.Series): Series of 'high's low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's - open (pd.Series): Series of 'open's volume (pd.Series): Series of 'volume's + open_ (pd.Series): Series of 'open's. Default: None length (int): The short period. Default: 20 offset (int): How many periods to offset the result. Default: 0 @@ -79,4 +79,4 @@ Kwargs: Returns: pd.Series: New feature generated. -""" \ No newline at end of file +""" diff --git a/pandas_ta/volume/vp.py b/pandas_ta/volume/vp.py index 770fe6e..8302299 100644 --- a/pandas_ta/volume/vp.py +++ b/pandas_ta/volume/vp.py @@ -83,12 +83,12 @@ Calculation: Args: close (pd.Series): Series of 'close's volume (pd.Series): Series of 'volume's - width (int): How many ranges to distrubute price into. Default: 10 + width (int): How many ranges to distrubute price into. Default: 10 Kwargs: fillna (value, optional): pd.DataFrame.fillna(value) fill_method (value, optional): Type of fill method - sort_close (value, optional): Whether to sort by close before splitting into ranges. Default: False + sort_close (value, optional): Whether to sort by close before splitting into ranges. Default: False Returns: pd.DataFrame: New feature generated. diff --git a/tests/config.py b/tests/config.py index 3c4534f..dde7f77 100644 --- a/tests/config.py +++ b/tests/config.py @@ -6,7 +6,7 @@ VERBOSE = True ALERT = f"[!]" INFO = f"[i]" -CORRELATION = 'corr' #'sem' +CORRELATION = "corr" #"sem" CORRELATION_THRESHOLD = 0.99 # Less than 0.99 is undesirable sample_data = read_csv( diff --git a/tests/context.py b/tests/context.py index a237851..11f5534 100644 --- a/tests/context.py +++ b/tests/context.py @@ -1,5 +1,5 @@ import os import sys -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) import pandas_ta \ No newline at end of file diff --git a/tests/test_indicator_candle.py b/tests/test_indicator_candle.py index 1297164..f0a58dc 100644 --- a/tests/test_indicator_candle.py +++ b/tests/test_indicator_candle.py @@ -14,11 +14,11 @@ class TestCandle(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -26,7 +26,7 @@ class TestCandle(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume + if hasattr(cls, "volume"): del cls.volume del cls.data diff --git a/tests/test_indicator_candle_ext.py b/tests/test_indicator_candle_ext.py index 561a768..ebb3035 100644 --- a/tests/test_indicator_candle_ext.py +++ b/tests/test_indicator_candle_ext.py @@ -26,9 +26,9 @@ class TestCandleExtension(TestCase): def test_ha_ext(self): self.data.ta.ha(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-4:]), ['HA_open', 'HA_high', 'HA_low', 'HA_close']) + self.assertEqual(list(self.data.columns[-4:]), ["HA_open", "HA_high", "HA_low", "HA_close"]) def test_cdl_doji_ext(self): self.data.ta.cdl_doji(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CDL_DOJI_10_0.1') \ No newline at end of file + self.assertEqual(self.data.columns[-1], "CDL_DOJI_10_0.1") \ No newline at end of file diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 6a9ad00..32ab89a 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -14,11 +14,11 @@ class TestMomentum(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -26,7 +26,7 @@ class TestMomentum(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume + if hasattr(cls, "volume"): del cls.volume del cls.data @@ -62,12 +62,12 @@ class TestMomentum(TestCase): def test_ao(self): result = pandas_ta.ao(self.high, self.low) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'AO_5_34') + self.assertEqual(result.name, "AO_5_34") def test_apo(self): result = pandas_ta.apo(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'APO_12_26') + self.assertEqual(result.name, "APO_12_26") try: expected = tal.APO(self.close) @@ -82,12 +82,12 @@ class TestMomentum(TestCase): def test_bias(self): result = pandas_ta.bias(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'BIAS_SMA_26') + self.assertEqual(result.name, "BIAS_SMA_26") def test_bop(self): result = pandas_ta.bop(self.open, self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'BOP') + self.assertEqual(result.name, "BOP") try: expected = tal.BOP(self.open, self.high, self.low, self.close) @@ -102,12 +102,12 @@ class TestMomentum(TestCase): def test_brar(self): result = pandas_ta.brar(self.open, self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'BRAR_26') + self.assertEqual(result.name, "BRAR_26") def test_cci(self): result = pandas_ta.cci(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'CCI_14_0.015') + self.assertEqual(result.name, "CCI_14_0.015") try: expected = tal.CCI(self.high, self.low, self.close) @@ -122,12 +122,12 @@ class TestMomentum(TestCase): def test_cg(self): result = pandas_ta.cg(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'CG_10') + self.assertEqual(result.name, "CG_10") def test_cmo(self): result = pandas_ta.cmo(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'CMO_14') + self.assertEqual(result.name, "CMO_14") try: expected = tal.CMO(self.close) @@ -142,45 +142,45 @@ class TestMomentum(TestCase): def test_coppock(self): result = pandas_ta.coppock(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'COPC_11_14_10') + self.assertEqual(result.name, "COPC_11_14_10") def test_fisher(self): result = pandas_ta.fisher(self.high, self.low) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'FISHERT_5') + self.assertEqual(result.name, "FISHERT_5") def test_inertia(self): result = pandas_ta.inertia(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'INERTIA_20_14') + self.assertEqual(result.name, "INERTIA_20_14") result = pandas_ta.inertia(self.close, self.high, self.low, refined=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'INERTIAr_20_14') + self.assertEqual(result.name, "INERTIAr_20_14") result = pandas_ta.inertia(self.close, self.high, self.low, thirds=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'INERTIAt_20_14') + self.assertEqual(result.name, "INERTIAt_20_14") def test_kdj(self): result = pandas_ta.kdj(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'KDJ_9_3') + self.assertEqual(result.name, "KDJ_9_3") def test_kst(self): result = pandas_ta.kst(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'KST_10_15_20_30_10_10_10_15_9') + self.assertEqual(result.name, "KST_10_15_20_30_10_10_10_15_9") def test_macd(self): result = pandas_ta.macd(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'MACD_12_26_9') + self.assertEqual(result.name, "MACD_12_26_9") try: expected = tal.MACD(self.close) - expecteddf = DataFrame({'MACD_12_26_9': expected[0], 'MACDh_12_26_9': expected[2], 'MACDs_12_26_9': expected[1]}) + expecteddf = DataFrame({"MACD_12_26_9": expected[0], "MACDh_12_26_9": expected[2], "MACDs_12_26_9": expected[1]}) pdt.assert_frame_equal(result, expecteddf) except AssertionError as ae: try: @@ -204,7 +204,7 @@ class TestMomentum(TestCase): def test_mom(self): result = pandas_ta.mom(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MOM_10') + self.assertEqual(result.name, "MOM_10") try: expected = tal.MOM(self.close) @@ -219,32 +219,32 @@ class TestMomentum(TestCase): def test_ppo(self): result = pandas_ta.ppo(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'PPO_12_26_9') + self.assertEqual(result.name, "PPO_12_26_9") try: expected = tal.PPO(self.close) - pdt.assert_series_equal(result['PPO_12_26_9'], expected, check_names=False) + pdt.assert_series_equal(result["PPO_12_26_9"], expected, check_names=False) except AssertionError as ae: try: - corr = pandas_ta.utils.df_error_analysis(result['PPO_12_26_9'], expected, col=CORRELATION) + corr = pandas_ta.utils.df_error_analysis(result["PPO_12_26_9"], expected, col=CORRELATION) self.assertGreater(corr, CORRELATION_THRESHOLD) except Exception as ex: - error_analysis(result['PPO_12_26_9'], CORRELATION, ex) + error_analysis(result["PPO_12_26_9"], CORRELATION, ex) def test_psl(self): result = pandas_ta.psl(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PSL_12') + self.assertEqual(result.name, "PSL_12") def test_pvo(self): result = pandas_ta.pvo(self.volume) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'PVO_12_26_9') + self.assertEqual(result.name, "PVO_12_26_9") def test_roc(self): result = pandas_ta.roc(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ROC_10') + self.assertEqual(result.name, "ROC_10") try: expected = tal.ROC(self.close) @@ -259,7 +259,7 @@ class TestMomentum(TestCase): def test_rsi(self): result = pandas_ta.rsi(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'RSI_14') + self.assertEqual(result.name, "RSI_14") try: expected = tal.RSI(self.close) @@ -274,37 +274,37 @@ class TestMomentum(TestCase): def test_rvgi(self): result = pandas_ta.rvgi(self.open, self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'RVGI_14_4') + self.assertEqual(result.name, "RVGI_14_4") def test_slope(self): result = pandas_ta.slope(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'SLOPE_1') + self.assertEqual(result.name, "SLOPE_1") def test_slope_as_angle(self): result = pandas_ta.slope(self.close, as_angle=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ANGLEr_1') + self.assertEqual(result.name, "ANGLEr_1") def test_slope_as_angle_to_degrees(self): result = pandas_ta.slope(self.close, as_angle=True, to_degrees=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ANGLEd_1') + self.assertEqual(result.name, "ANGLEd_1") def test_stoch(self): result = pandas_ta.stoch(self.high, self.low, self.close, fast_k=14, slow_k=14, slow_d=14) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'STOCH_14_14_14') + self.assertEqual(result.name, "STOCH_14_14_14") self.assertEqual(len(result.columns), 4) result = pandas_ta.stoch(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'STOCH_14_5_3') + self.assertEqual(result.name, "STOCH_14_5_3") try: tal_stochf = tal.STOCHF(self.high, self.low, self.close) tal_stoch = tal.STOCH(self.high, self.low, self.close) - tal_stochdf = DataFrame({'STOCHF_14': tal_stochf[0], 'STOCHF_3': tal_stochf[1], 'STOCH_5': tal_stoch[0], 'STOCH_3': tal_stoch[1]}) + tal_stochdf = DataFrame({"STOCHF_14": tal_stochf[0], "STOCHF_3": tal_stochf[1], "STOCH_5": tal_stoch[0], "STOCH_3": tal_stoch[1]}) pdt.assert_frame_equal(result, tal_stochdf) except AssertionError as ae: try: @@ -334,17 +334,17 @@ class TestMomentum(TestCase): def test_trix(self): result = pandas_ta.trix(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'TRIX_30_9') + self.assertEqual(result.name, "TRIX_30_9") def test_tsi(self): result = pandas_ta.tsi(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'TSI_13_25') + self.assertEqual(result.name, "TSI_13_25") def test_uo(self): result = pandas_ta.uo(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'UO_7_14_28') + self.assertEqual(result.name, "UO_7_14_28") try: expected = tal.ULTOSC(self.high, self.low, self.close) @@ -359,7 +359,7 @@ class TestMomentum(TestCase): def test_willr(self): result = pandas_ta.willr(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'WILLR_14') + self.assertEqual(result.name, "WILLR_14") try: expected = tal.WILLR(self.high, self.low, self.close) diff --git a/tests/test_indicator_momentum_ext.py b/tests/test_indicator_momentum_ext.py index deabd83..87ec2db 100644 --- a/tests/test_indicator_momentum_ext.py +++ b/tests/test_indicator_momentum_ext.py @@ -26,152 +26,152 @@ class TestMomentumExtension(TestCase): def test_ao_ext(self): self.data.ta.ao(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'AO_5_34') + self.assertEqual(self.data.columns[-1], "AO_5_34") def test_apo_ext(self): self.data.ta.apo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'APO_12_26') + self.assertEqual(self.data.columns[-1], "APO_12_26") def test_bias_ext(self): self.data.ta.bias(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'BIAS_SMA_26') + self.assertEqual(self.data.columns[-1], "BIAS_SMA_26") def test_bop_ext(self): self.data.ta.bop(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'BOP') + self.assertEqual(self.data.columns[-1], "BOP") def test_brar_ext(self): self.data.ta.brar(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['AR_26', 'BR_26']) + self.assertEqual(list(self.data.columns[-2:]), ["AR_26", "BR_26"]) def test_cci_ext(self): self.data.ta.cci(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CCI_14_0.015') + self.assertEqual(self.data.columns[-1], "CCI_14_0.015") def test_cg_ext(self): self.data.ta.cg(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CG_10') + self.assertEqual(self.data.columns[-1], "CG_10") def test_cmo_ext(self): self.data.ta.cmo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CMO_14') + self.assertEqual(self.data.columns[-1], "CMO_14") def test_coppock_ext(self): self.data.ta.coppock(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'COPC_11_14_10') + self.assertEqual(self.data.columns[-1], "COPC_11_14_10") def test_fisher_ext(self): self.data.ta.fisher(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'FISHERT_5') + self.assertEqual(self.data.columns[-1], "FISHERT_5") def test_inertia_ext(self): self.data.ta.inertia(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'INERTIA_20_14') + self.assertEqual(self.data.columns[-1], "INERTIA_20_14") def test_inertia_refined_ext(self): self.data.ta.inertia(refined=True, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'INERTIAr_20_14') + self.assertEqual(self.data.columns[-1], "INERTIAr_20_14") def test_inertia_thirds_ext(self): self.data.ta.inertia(thirds=True, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'INERTIAt_20_14') + self.assertEqual(self.data.columns[-1], "INERTIAt_20_14") def test_kdj_ext(self): self.data.ta.kdj(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['K_9_3', 'D_9_3', 'J_9_3']) + self.assertEqual(list(self.data.columns[-3:]), ["K_9_3", "D_9_3", "J_9_3"]) def test_kst_ext(self): self.data.ta.kst(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['KST_10_15_20_30_10_10_10_15', 'KSTs_9']) + self.assertEqual(list(self.data.columns[-2:]), ["KST_10_15_20_30_10_10_10_15", "KSTs_9"]) def test_macd_ext(self): self.data.ta.macd(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['MACD_12_26_9', 'MACDh_12_26_9', 'MACDs_12_26_9']) + self.assertEqual(list(self.data.columns[-3:]), ["MACD_12_26_9", "MACDh_12_26_9", "MACDs_12_26_9"]) def test_mom_ext(self): self.data.ta.mom(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MOM_10') + self.assertEqual(self.data.columns[-1], "MOM_10") def test_ppo_ext(self): self.data.ta.ppo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['PPO_12_26_9', 'PPOh_12_26_9', 'PPOs_12_26_9']) + self.assertEqual(list(self.data.columns[-3:]), ["PPO_12_26_9", "PPOh_12_26_9", "PPOs_12_26_9"]) def test_psl_ext(self): self.data.ta.psl(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PSL_12') + self.assertEqual(self.data.columns[-1], "PSL_12") def test_pvo_ext(self): self.data.ta.pvo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['PVO_12_26_9', 'PVOh_12_26_9', 'PVOs_12_26_9']) + self.assertEqual(list(self.data.columns[-3:]), ["PVO_12_26_9", "PVOh_12_26_9", "PVOs_12_26_9"]) def test_roc_ext(self): self.data.ta.roc(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ROC_10') + self.assertEqual(self.data.columns[-1], "ROC_10") def test_rsi_ext(self): self.data.ta.rsi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'RSI_14') + self.assertEqual(self.data.columns[-1], "RSI_14") def test_rvgi_ext(self): self.data.ta.rvgi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['RVGI_14_4', 'RVGIs_14_4']) + self.assertEqual(list(self.data.columns[-2:]), ["RVGI_14_4", "RVGIs_14_4"]) def test_slope_ext(self): self.data.ta.slope(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'SLOPE_1') + self.assertEqual(self.data.columns[-1], "SLOPE_1") self.data.ta.slope(append=True, as_angle=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ANGLEr_1') + self.assertEqual(self.data.columns[-1], "ANGLEr_1") self.data.ta.slope(append=True, as_angle=True, to_degrees=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ANGLEd_1') + self.assertEqual(self.data.columns[-1], "ANGLEd_1") def test_stoch_ext(self): self.data.ta.stoch(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-4:]), ['STOCHFk_14', 'STOCHFd_3', 'STOCHk_5', 'STOCHd_3']) + self.assertEqual(list(self.data.columns[-4:]), ["STOCHFk_14", "STOCHFd_3", "STOCHk_5", "STOCHd_3"]) def test_trix_ext(self): self.data.ta.trix(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['TRIX_30_9', 'TRIXs_30_9']) + self.assertEqual(list(self.data.columns[-2:]), ["TRIX_30_9", "TRIXs_30_9"]) def test_tsi_ext(self): self.data.ta.tsi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'TSI_13_25') + self.assertEqual(self.data.columns[-1], "TSI_13_25") def test_uo_ext(self): self.data.ta.uo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'UO_7_14_28') + self.assertEqual(self.data.columns[-1], "UO_7_14_28") def test_willr_ext(self): self.data.ta.willr(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'WILLR_14') + self.assertEqual(self.data.columns[-1], "WILLR_14") diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index 401c816..902b3d0 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -15,11 +15,11 @@ class TestOverlap(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -27,7 +27,7 @@ class TestOverlap(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume + if hasattr(cls, "volume"): del cls.volume del cls.data @@ -39,7 +39,7 @@ class TestOverlap(TestCase): def test_dema(self): result = pandas_ta.dema(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'DEMA_10') + self.assertEqual(result.name, "DEMA_10") try: expected = tal.DEMA(self.close, 10) @@ -54,7 +54,7 @@ class TestOverlap(TestCase): def test_ema(self): result = pandas_ta.ema(self.close, presma=False) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'EMA_10') + self.assertEqual(result.name, "EMA_10") try: expected = tal.EMA(self.close, 10) @@ -69,17 +69,17 @@ class TestOverlap(TestCase): def test_fwma(self): result = pandas_ta.fwma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'FWMA_10') + self.assertEqual(result.name, "FWMA_10") def test_hl2(self): result = pandas_ta.hl2(self.high, self.low) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'HL2') + self.assertEqual(result.name, "HL2") def test_hlc3(self): result = pandas_ta.hlc3(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'HLC3') + self.assertEqual(result.name, "HLC3") try: expected = tal.TYPPRICE(self.high, self.low, self.close) @@ -94,24 +94,24 @@ class TestOverlap(TestCase): def test_hma(self): result = pandas_ta.hma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'HMA_10') + self.assertEqual(result.name, "HMA_10") def test_kama(self): result = pandas_ta.kama(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'KAMA_10_2_30') + self.assertEqual(result.name, "KAMA_10_2_30") def test_ichimoku(self): ichimoku, span = pandas_ta.ichimoku(self.high, self.low, self.close) self.assertIsInstance(ichimoku, DataFrame) self.assertIsInstance(span, DataFrame) - self.assertEqual(ichimoku.name, 'ICHIMOKU_9_26_52') - self.assertEqual(span.name, 'ICHISPAN_9_26') + self.assertEqual(ichimoku.name, "ICHIMOKU_9_26_52") + self.assertEqual(span.name, "ICHISPAN_9_26") def test_linreg(self): result = pandas_ta.linreg(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LR_14') + self.assertEqual(result.name, "LR_14") try: expected = tal.LINEARREG(self.close) @@ -126,7 +126,7 @@ class TestOverlap(TestCase): def test_linreg_angle(self): result = pandas_ta.linreg(self.close, angle=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LRa_14') + self.assertEqual(result.name, "LRa_14") try: expected = tal.LINEARREG_ANGLE(self.close) @@ -141,7 +141,7 @@ class TestOverlap(TestCase): def test_linreg_intercept(self): result = pandas_ta.linreg(self.close, intercept=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LRb_14') + self.assertEqual(result.name, "LRb_14") try: expected = tal.LINEARREG_INTERCEPT(self.close) @@ -156,12 +156,12 @@ class TestOverlap(TestCase): def test_linreg_r(self): result = pandas_ta.linreg(self.close, r=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LRr_14') + self.assertEqual(result.name, "LRr_14") def test_linreg_slope(self): result = pandas_ta.linreg(self.close, slope=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LRm_14') + self.assertEqual(result.name, "LRm_14") try: expected = tal.LINEARREG_SLOPE(self.close) @@ -176,7 +176,7 @@ class TestOverlap(TestCase): def test_midpoint(self): result = pandas_ta.midpoint(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MIDPOINT_2') + self.assertEqual(result.name, "MIDPOINT_2") try: expected = tal.MIDPOINT(self.close, 2) @@ -191,7 +191,7 @@ class TestOverlap(TestCase): def test_midprice(self): result = pandas_ta.midprice(self.high, self.low) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MIDPRICE_2') + self.assertEqual(result.name, "MIDPRICE_2") try: expected = tal.MIDPRICE(self.high, self.low, 2) @@ -206,27 +206,27 @@ class TestOverlap(TestCase): def test_ohlc4(self): result = pandas_ta.ohlc4(self.open, self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'OHLC4') + self.assertEqual(result.name, "OHLC4") def test_pwma(self): result = pandas_ta.pwma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PWMA_10') + self.assertEqual(result.name, "PWMA_10") def test_rma(self): result = pandas_ta.rma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'RMA_10') + self.assertEqual(result.name, "RMA_10") def test_sinwma(self): result = pandas_ta.sinwma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'SINWMA_14') + self.assertEqual(result.name, "SINWMA_14") def test_sma(self): result = pandas_ta.sma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'SMA_10') + self.assertEqual(result.name, "SMA_10") try: expected = tal.SMA(self.close, 10) @@ -241,17 +241,17 @@ class TestOverlap(TestCase): def test_swma(self): result = pandas_ta.swma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'SWMA_10') + self.assertEqual(result.name, "SWMA_10") def test_supertrend(self): result = pandas_ta.supertrend(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'SUPERT_7_3.0') + self.assertEqual(result.name, "SUPERT_7_3.0") def test_t3(self): result = pandas_ta.t3(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'T3_10_0.7') + self.assertEqual(result.name, "T3_10_0.7") try: expected = tal.T3(self.close, 10) @@ -266,7 +266,7 @@ class TestOverlap(TestCase): def test_tema(self): result = pandas_ta.tema(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'TEMA_10') + self.assertEqual(result.name, "TEMA_10") try: expected = tal.TEMA(self.close, 10) @@ -281,7 +281,7 @@ class TestOverlap(TestCase): def test_trima(self): result = pandas_ta.trima(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'TRIMA_10') + self.assertEqual(result.name, "TRIMA_10") try: expected = tal.TRIMA(self.close, 10) @@ -296,17 +296,17 @@ class TestOverlap(TestCase): def test_vwap(self): result = pandas_ta.vwap(self.high, self.low, self.close, self.volume) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'VWAP') + self.assertEqual(result.name, "VWAP") def test_vwma(self): result = pandas_ta.vwma(self.close, self.volume) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'VWMA_10') + self.assertEqual(result.name, "VWMA_10") def test_wcp(self): result = pandas_ta.wcp(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'WCP') + self.assertEqual(result.name, "WCP") try: expected = tal.WCLPRICE(self.high, self.low, self.close) @@ -321,7 +321,7 @@ class TestOverlap(TestCase): def test_wma(self): result = pandas_ta.wma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'WMA_10') + self.assertEqual(result.name, "WMA_10") try: expected = tal.WMA(self.close, 10) @@ -336,4 +336,4 @@ class TestOverlap(TestCase): def test_zlma(self): result = pandas_ta.zlma(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ZL_EMA_10') + self.assertEqual(result.name, "ZL_EMA_10") diff --git a/tests/test_indicator_overlap_ext.py b/tests/test_indicator_overlap_ext.py index 47ee84e..9ce1967 100644 --- a/tests/test_indicator_overlap_ext.py +++ b/tests/test_indicator_overlap_ext.py @@ -26,87 +26,87 @@ class TestOverlapExtension(TestCase): def test_dema_ext(self): self.data.ta.dema(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'DEMA_10') + self.assertEqual(self.data.columns[-1], "DEMA_10") def test_ema_ext(self): self.data.ta.ema(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'EMA_10') + self.assertEqual(self.data.columns[-1], "EMA_10") def test_fwma_ext(self): self.data.ta.fwma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'FWMA_10') + self.assertEqual(self.data.columns[-1], "FWMA_10") def test_hl2_ext(self): self.data.ta.hl2(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'HL2') + self.assertEqual(self.data.columns[-1], "HL2") def test_hlc3_ext(self): self.data.ta.hlc3(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'HLC3') + self.assertEqual(self.data.columns[-1], "HLC3") def test_hma_ext(self): self.data.ta.hma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'HMA_10') + self.assertEqual(self.data.columns[-1], "HMA_10") def test_kama_ext(self): self.data.ta.kama(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'KAMA_10_2_30') + self.assertEqual(self.data.columns[-1], "KAMA_10_2_30") def test_ichimoku_ext(self): self.data.ta.ichimoku(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-5:]), ['ISA_9', 'ISB_26', 'ITS_9', 'IKS_26', 'ICS_26']) + self.assertEqual(list(self.data.columns[-5:]), ["ISA_9", "ISB_26", "ITS_9", "IKS_26", "ICS_26"]) def test_linreg_ext(self): self.data.ta.linreg(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'LR_14') + self.assertEqual(self.data.columns[-1], "LR_14") def test_midpoint_ext(self): self.data.ta.midpoint(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MIDPOINT_2') + self.assertEqual(self.data.columns[-1], "MIDPOINT_2") def test_midprice_ext(self): self.data.ta.midprice(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MIDPRICE_2') + self.assertEqual(self.data.columns[-1], "MIDPRICE_2") def test_ohlc4_ext(self): self.data.ta.ohlc4(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'OHLC4') + self.assertEqual(self.data.columns[-1], "OHLC4") def test_pwma_ext(self): self.data.ta.pwma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PWMA_10') + self.assertEqual(self.data.columns[-1], "PWMA_10") def test_rma_ext(self): self.data.ta.rma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'RMA_10') + self.assertEqual(self.data.columns[-1], "RMA_10") def test_sinwma_ext(self): self.data.ta.sinwma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'SINWMA_14') + self.assertEqual(self.data.columns[-1], "SINWMA_14") def test_sma_ext(self): self.data.ta.sma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'SMA_10') + self.assertEqual(self.data.columns[-1], "SMA_10") def test_swma_ext(self): self.data.ta.swma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'SWMA_10') + self.assertEqual(self.data.columns[-1], "SWMA_10") def test_supertrend_ext(self): self.data.ta.supertrend(append=True) @@ -116,39 +116,39 @@ class TestOverlapExtension(TestCase): def test_t3_ext(self): self.data.ta.t3(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'T3_10_0.7') + self.assertEqual(self.data.columns[-1], "T3_10_0.7") def test_tema_ext(self): self.data.ta.tema(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'TEMA_10') + self.assertEqual(self.data.columns[-1], "TEMA_10") def test_trima_ext(self): self.data.ta.trima(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'TRIMA_10') + self.assertEqual(self.data.columns[-1], "TRIMA_10") def test_vwap_ext(self): self.data.ta.vwap(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'VWAP') + self.assertEqual(self.data.columns[-1], "VWAP") def test_vwma_ext(self): self.data.ta.vwma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'VWMA_10') + self.assertEqual(self.data.columns[-1], "VWMA_10") def test_wcp_ext(self): self.data.ta.wcp(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'WCP') + self.assertEqual(self.data.columns[-1], "WCP") def test_wma_ext(self): self.data.ta.wma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'WMA_10') + self.assertEqual(self.data.columns[-1], "WMA_10") def test_zlma_ext(self): self.data.ta.zlma(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ZL_EMA_10') \ No newline at end of file + self.assertEqual(self.data.columns[-1], "ZL_EMA_10") \ No newline at end of file diff --git a/tests/test_indicator_performance.py b/tests/test_indicator_performance.py index 0fea777..d25e6ca 100644 --- a/tests/test_indicator_performance.py +++ b/tests/test_indicator_performance.py @@ -10,7 +10,7 @@ class TestPerformace(TestCase): @classmethod def setUpClass(cls): cls.data = sample_data - cls.close = cls.data['close'] + cls.close = cls.data["close"] cls.islong = cls.close > pandas_ta.sma(cls.close, length=50) @classmethod @@ -27,42 +27,42 @@ class TestPerformace(TestCase): def test_log_return(self): result = pandas_ta.log_return(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LOGRET_1') + self.assertEqual(result.name, "LOGRET_1") def test_cum_log_return(self): result = pandas_ta.log_return(self.close, cumulative=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'CUMLOGRET_1') + self.assertEqual(result.name, "CUMLOGRET_1") def test_percent_return(self): result = pandas_ta.percent_return(self.close, cumulative=False) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PCTRET_1') + self.assertEqual(result.name, "PCTRET_1") def test_cum_percent_return(self): result = pandas_ta.percent_return(self.close, cumulative=True) - self.assertEqual(result.name, 'CUMPCTRET_1') + self.assertEqual(result.name, "CUMPCTRET_1") def test_log_trend_return(self): result = pandas_ta.trend_return(self.close, self.islong, log=True, cumulative=False) - self.assertEqual(result.name, 'LTR') + self.assertEqual(result.name, "LTR") def test_cum_log_trend_return(self): result = pandas_ta.trend_return(self.close, self.islong, log=True, cumulative=True) - self.assertEqual(result.name, 'CLTR') + self.assertEqual(result.name, "CLTR") def test_variable_cum_log_trend_return(self): result = pandas_ta.trend_return(self.close, self.islong, log=True, cumulative=True, variable=True) - self.assertEqual(result.name, 'CLTR') + self.assertEqual(result.name, "CLTR") def test_pct_trend_return(self): result = pandas_ta.trend_return(self.close, self.islong, log=False, cumulative=False) - self.assertEqual(result.name, 'PTR') + self.assertEqual(result.name, "PTR") def test_cum_pct_trend_return(self): result = pandas_ta.trend_return(self.close, self.islong, log=False, cumulative=True) - self.assertEqual(result.name, 'CPTR') + self.assertEqual(result.name, "CPTR") def test_variable_pct_log_trend_return(self): result = pandas_ta.trend_return(self.close, self.islong, log=False, cumulative=True, variable=True) - self.assertEqual(result.name, 'CPTR') \ No newline at end of file + self.assertEqual(result.name, "CPTR") \ No newline at end of file diff --git a/tests/test_indicator_performance_ext.py b/tests/test_indicator_performance_ext.py index 8ea3e59..af3a0e3 100644 --- a/tests/test_indicator_performance_ext.py +++ b/tests/test_indicator_performance_ext.py @@ -10,7 +10,7 @@ class TestPerformaceExtension(TestCase): @classmethod def setUpClass(cls): cls.data = sample_data - cls.islong = cls.data['close'] > pandas_ta.sma(cls.data['close'], length=50) + cls.islong = cls.data["close"] > pandas_ta.sma(cls.data["close"], length=50) @classmethod def tearDownClass(cls): @@ -25,39 +25,39 @@ class TestPerformaceExtension(TestCase): def test_log_return_ext(self): self.data.ta.log_return(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'LOGRET_1') + self.assertEqual(self.data.columns[-1], "LOGRET_1") def test_cum_log_return_ext(self): self.data.ta.log_return(append=True, cumulative=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CUMLOGRET_1') + self.assertEqual(self.data.columns[-1], "CUMLOGRET_1") def test_percent_return_ext(self): self.data.ta.percent_return(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PCTRET_1') + self.assertEqual(self.data.columns[-1], "PCTRET_1") def test_cum_percent_return_ext(self): self.data.ta.percent_return(append=True, cumulative=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CUMPCTRET_1') + self.assertEqual(self.data.columns[-1], "CUMPCTRET_1") def test_log_trend_return_ext(self): self.data.ta.trend_return(trend=self.islong, log=True, cumulative=False, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'LTR') + self.assertEqual(self.data.columns[-1], "LTR") def test_cum_log_trend_return_ext(self): self.data.ta.trend_return(trend=self.islong, log=True, cumulative=True, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CLTR') + self.assertEqual(self.data.columns[-1], "CLTR") def test_pct_trend_return_ext(self): self.data.ta.trend_return(trend=self.islong, log=False, cumulative=False, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PTR') + self.assertEqual(self.data.columns[-1], "PTR") def test_cum_pct_trend_return_ext(self): self.data.ta.trend_return(trend=self.islong, log=False, cumulative=True, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CPTR') \ No newline at end of file + self.assertEqual(self.data.columns[-1], "CPTR") \ No newline at end of file diff --git a/tests/test_indicator_statistics.py b/tests/test_indicator_statistics.py index 57f2adc..5e58dce 100644 --- a/tests/test_indicator_statistics.py +++ b/tests/test_indicator_statistics.py @@ -14,11 +14,11 @@ class TestStatistics(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -26,7 +26,7 @@ class TestStatistics(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume + if hasattr(cls, "volume"): del cls.volume del cls.data @@ -36,37 +36,37 @@ class TestStatistics(TestCase): def test_entropy(self): result = pandas_ta.entropy(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ENTP_10') + self.assertEqual(result.name, "ENTP_10") def test_kurtosis(self): result = pandas_ta.kurtosis(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'KURT_30') + self.assertEqual(result.name, "KURT_30") def test_mad(self): result = pandas_ta.mad(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MAD_30') + self.assertEqual(result.name, "MAD_30") def test_median(self): result = pandas_ta.median(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MEDIAN_30') + self.assertEqual(result.name, "MEDIAN_30") def test_quantile(self): result = pandas_ta.quantile(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'QTL_30_0.5') + self.assertEqual(result.name, "QTL_30_0.5") def test_skew(self): result = pandas_ta.skew(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'SKEW_30') + self.assertEqual(result.name, "SKEW_30") def test_stdev(self): result = pandas_ta.stdev(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'STDEV_30') + self.assertEqual(result.name, "STDEV_30") try: expected = tal.STDDEV(self.close, 30) @@ -81,7 +81,7 @@ class TestStatistics(TestCase): def test_variance(self): result = pandas_ta.variance(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'VAR_30') + self.assertEqual(result.name, "VAR_30") try: expected = tal.VAR(self.close, 30) @@ -96,4 +96,4 @@ class TestStatistics(TestCase): def test_zscore(self): result = pandas_ta.zscore(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'Z_30') \ No newline at end of file + self.assertEqual(result.name, "Z_30") \ No newline at end of file diff --git a/tests/test_indicator_statistics_ext.py b/tests/test_indicator_statistics_ext.py index 4a3727c..33b33ab 100644 --- a/tests/test_indicator_statistics_ext.py +++ b/tests/test_indicator_statistics_ext.py @@ -26,39 +26,39 @@ class TestStatisticsExtension(TestCase): def test_entropy_ext(self): self.data.ta.entropy(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ENTP_10') + self.assertEqual(self.data.columns[-1], "ENTP_10") def test_kurtosis_ext(self): self.data.ta.kurtosis(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'KURT_30') + self.assertEqual(self.data.columns[-1], "KURT_30") def test_mad_ext(self): self.data.ta.mad(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MAD_30') + self.assertEqual(self.data.columns[-1], "MAD_30") def test_median_ext(self): self.data.ta.median(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MEDIAN_30') + self.assertEqual(self.data.columns[-1], "MEDIAN_30") def test_quantile_ext(self): self.data.ta.quantile(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'QTL_30_0.5') + self.assertEqual(self.data.columns[-1], "QTL_30_0.5") def test_skew_ext(self): self.data.ta.skew(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'SKEW_30') + self.assertEqual(self.data.columns[-1], "SKEW_30") def test_stdev_ext(self): self.data.ta.stdev(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'STDEV_30') + self.assertEqual(self.data.columns[-1], "STDEV_30") def test_variance_ext(self): self.data.ta.variance(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'VAR_30') + self.assertEqual(self.data.columns[-1], "VAR_30") diff --git a/tests/test_indicator_trend.py b/tests/test_indicator_trend.py index 90d04ff..f6a7152 100644 --- a/tests/test_indicator_trend.py +++ b/tests/test_indicator_trend.py @@ -14,11 +14,11 @@ class TestTrend(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -26,7 +26,7 @@ class TestTrend(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume + if hasattr(cls, "volume"): del cls.volume del cls.data @@ -37,7 +37,7 @@ class TestTrend(TestCase): def test_adx(self): result = pandas_ta.adx(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'ADX_14') + self.assertEqual(result.name, "ADX_14") try: expected = tal.ADX(self.high, self.low, self.close) @@ -52,16 +52,16 @@ class TestTrend(TestCase): def test_amat(self): result = pandas_ta.amat(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'AMAT_EMA_8_21_2') + self.assertEqual(result.name, "AMAT_EMA_8_21_2") def test_aroon(self): result = pandas_ta.aroon(self.high, self.low) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'AROON_14') + self.assertEqual(result.name, "AROON_14") try: expected = tal.AROON(self.high, self.low) - expecteddf = DataFrame({'AROOND_14': expected[0], 'AROONU_14': expected[1]}) + expecteddf = DataFrame({"AROOND_14": expected[0], "AROONU_14": expected[1]}) pdt.assert_frame_equal(result, expecteddf) except AssertionError as ae: try: @@ -92,44 +92,44 @@ class TestTrend(TestCase): def test_chop(self): result = pandas_ta.chop(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'CHOP_14_1_100') + self.assertEqual(result.name, "CHOP_14_1_100") def test_cksp(self): result = pandas_ta.cksp(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'CKSP_10_1_9') + self.assertEqual(result.name, "CKSP_10_1_9") def test_decreasing(self): result = pandas_ta.decreasing(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'DEC_1') + self.assertEqual(result.name, "DEC_1") def test_dpo(self): result = pandas_ta.dpo(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'DPO_20') + self.assertEqual(result.name, "DPO_20") def test_increasing(self): result = pandas_ta.increasing(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'INC_1') + self.assertEqual(result.name, "INC_1") def test_linear_decay(self): result = pandas_ta.linear_decay(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LDECAY_5') + self.assertEqual(result.name, "LDECAY_5") def test_long_run(self): result = pandas_ta.long_run(self.close, self.open) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'LR_2') + self.assertEqual(result.name, "LR_2") def test_psar(self): result = pandas_ta.psar(self.high, self.low) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'PSAR_0.02_0.2') + self.assertEqual(result.name, "PSAR_0.02_0.2") - # Combine Long and Short SAR's into one SAR value + # Combine Long and Short SAR"s into one SAR value psar = result[result.columns[:2]].fillna(0) psar = psar[psar.columns[0]] + psar[psar.columns[1]] psar.name = result.name @@ -147,14 +147,14 @@ class TestTrend(TestCase): def test_qstick(self): result = pandas_ta.qstick(self.open, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'QS_10') + self.assertEqual(result.name, "QS_10") def test_short_run(self): result = pandas_ta.short_run(self.close, self.open) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'SR_2') + self.assertEqual(result.name, "SR_2") def test_vortex(self): result = pandas_ta.vortex(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'VTX_14') \ No newline at end of file + self.assertEqual(result.name, "VTX_14") \ No newline at end of file diff --git a/tests/test_indicator_trend_ext.py b/tests/test_indicator_trend_ext.py index 5f06337..a3df15f 100644 --- a/tests/test_indicator_trend_ext.py +++ b/tests/test_indicator_trend_ext.py @@ -26,79 +26,79 @@ class TestTrendExtension(TestCase): def test_adx_ext(self): self.data.ta.adx(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['ADX_14', 'DMP_14', 'DMN_14']) + self.assertEqual(list(self.data.columns[-3:]), ["ADX_14", "DMP_14", "DMN_14"]) def test_amat_ext(self): self.data.ta.amat(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['AMAT_LR_2', 'AMAT_SR_2']) + self.assertEqual(list(self.data.columns[-2:]), ["AMAT_LR_2", "AMAT_SR_2"]) def test_aroon_ext(self): self.data.ta.aroon(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['AROOND_14', 'AROONU_14', 'AROONOSC_14']) + self.assertEqual(list(self.data.columns[-3:]), ["AROOND_14", "AROONU_14", "AROONOSC_14"]) def test_chop_ext(self): self.data.ta.chop(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CHOP_14_1_100') + self.assertEqual(self.data.columns[-1], "CHOP_14_1_100") def test_cksp_ext(self): self.data.ta.cksp(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CKSPs_10_1_9') + self.assertEqual(self.data.columns[-1], "CKSPs_10_1_9") def test_decreasing_ext(self): self.data.ta.decreasing(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'DEC_1') + self.assertEqual(self.data.columns[-1], "DEC_1") def test_dpo_ext(self): self.data.ta.dpo(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'DPO_20') + self.assertEqual(self.data.columns[-1], "DPO_20") def test_increasing_ext(self): self.data.ta.increasing(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'INC_1') + self.assertEqual(self.data.columns[-1], "INC_1") def test_linear_decay_ext(self): self.data.ta.linear_decay(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'LDECAY_5') + self.assertEqual(self.data.columns[-1], "LDECAY_5") def test_long_run_ext(self): # Nothing passed, return self self.assertEqual(self.data.ta.long_run(append=True).shape, self.data.shape) - fast = self.data.ta.ema('close', 8) - slow = self.data.ta.ema('close', 21) + fast = self.data.ta.ema("close", 8) + slow = self.data.ta.ema("close", 21) self.data.ta.long_run(fast, slow, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'LR_2') + self.assertEqual(self.data.columns[-1], "LR_2") def test_psar_ext(self): self.data.ta.psar(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-4:]), ['PSARl_0.02_0.2', 'PSARs_0.02_0.2', 'PSARaf_0.02_0.2', 'PSARr_0.02_0.2']) + self.assertEqual(list(self.data.columns[-4:]), ["PSARl_0.02_0.2", "PSARs_0.02_0.2", "PSARaf_0.02_0.2", "PSARr_0.02_0.2"]) def test_qstick_ext(self): self.data.ta.qstick(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'QS_10') + self.assertEqual(self.data.columns[-1], "QS_10") def test_short_run_ext(self): # Nothing passed, return self self.assertEqual(self.data.ta.short_run(append=True).shape, self.data.shape) - fast = self.data.ta.ema('close', 8) - slow = self.data.ta.ema('close', 21) + fast = self.data.ta.ema("close", 8) + slow = self.data.ta.ema("close", 21) self.data.ta.short_run(fast, slow, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'SR_2') + self.assertEqual(self.data.columns[-1], "SR_2") def test_vortext_ext(self): self.data.ta.vortex(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['VTXP_14', 'VTXM_14']) + self.assertEqual(list(self.data.columns[-2:]), ["VTXP_14", "VTXM_14"]) diff --git a/tests/test_indicator_volatility.py b/tests/test_indicator_volatility.py index 15585cb..a526c44 100644 --- a/tests/test_indicator_volatility.py +++ b/tests/test_indicator_volatility.py @@ -14,11 +14,11 @@ class TestVolatility(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -26,7 +26,7 @@ class TestVolatility(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume + if hasattr(cls, "volume"): del cls.volume del cls.data @@ -37,17 +37,17 @@ class TestVolatility(TestCase): def test_aberration(self): result = pandas_ta.aberration(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'ABER_5_15') + self.assertEqual(result.name, "ABER_5_15") def test_accbands(self): result = pandas_ta.accbands(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'ACCBANDS_20') + self.assertEqual(result.name, "ACCBANDS_20") def test_atr(self): result = pandas_ta.atr(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ATR_14') + self.assertEqual(result.name, "ATR_14") try: expected = tal.ATR(self.high, self.low, self.close) @@ -62,11 +62,11 @@ class TestVolatility(TestCase): def test_bbands(self): result = pandas_ta.bbands(self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'BBANDS_5') + self.assertEqual(result.name, "BBANDS_5_2.0") try: expected = tal.BBANDS(self.close) - expecteddf = DataFrame({'BBL_5': expected[0], 'BBM_5': expected[1], 'BBU_5': expected[2]}) + expecteddf = DataFrame({"BBL_5_2.0": expected[0], "BBM_5_2.0": expected[1], "BBU_5_2.0": expected[2]}) pdt.assert_frame_equal(result, expecteddf) except AssertionError as ae: try: @@ -88,29 +88,29 @@ class TestVolatility(TestCase): error_analysis(result.iloc[:,2], CORRELATION, ex, newline=False) def test_donchian(self): - result = pandas_ta.donchian(self.close) + result = pandas_ta.donchian(self.high, self.low) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'DC_10_20') + self.assertEqual(result.name, "DC_20_20") - result = pandas_ta.donchian(self.close, lower_length=20, upper_length=5) + result = pandas_ta.donchian(self.high, self.low, lower_length=20, upper_length=5) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'DC_20_5') + self.assertEqual(result.name, "DC_20_5") def test_kc(self): result = pandas_ta.kc(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'KC_20') + self.assertEqual(result.name, "KC_20") def test_massi(self): result = pandas_ta.massi(self.high, self.low) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MASSI_9_25') + self.assertEqual(result.name, "MASSI_9_25") def test_natr(self): result = pandas_ta.natr(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'NATR_14') + self.assertEqual(result.name, "NATR_14") try: expected = tal.NATR(self.high, self.low, self.close) @@ -125,25 +125,25 @@ class TestVolatility(TestCase): def test_pdist(self): result = pandas_ta.pdist(self.open, self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PDIST') + self.assertEqual(result.name, "PDIST") def test_rvi(self): result = pandas_ta.rvi(self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'RVI_14') + self.assertEqual(result.name, "RVI_14") result = pandas_ta.rvi(self.close, self.high, self.low, refined=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'RVIr_14') + self.assertEqual(result.name, "RVIr_14") result = pandas_ta.rvi(self.close, self.high, self.low, thirds=True) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'RVIt_14') + self.assertEqual(result.name, "RVIt_14") def test_true_range(self): result = pandas_ta.true_range(self.high, self.low, self.close) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'TRUERANGE_1') + self.assertEqual(result.name, "TRUERANGE_1") try: expected = tal.TRANGE(self.high, self.low, self.close) diff --git a/tests/test_indicator_volatility_ext.py b/tests/test_indicator_volatility_ext.py index 14d743f..ae828ee 100644 --- a/tests/test_indicator_volatility_ext.py +++ b/tests/test_indicator_volatility_ext.py @@ -23,64 +23,64 @@ class TestVolatilityExtension(TestCase): def test_aberration_ext(self): self.data.ta.aberration(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-4:]), ['ABER_ZG_5_15', 'ABER_SG_5_15', 'ABER_XG_5_15', 'ABER_ATR_5_15']) + self.assertEqual(list(self.data.columns[-4:]), ["ABER_ZG_5_15", "ABER_SG_5_15", "ABER_XG_5_15", "ABER_ATR_5_15"]) def test_accbands_ext(self): self.data.ta.accbands(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['ACCBL_20', 'ACCBM_20', 'ACCBU_20']) + self.assertEqual(list(self.data.columns[-3:]), ["ACCBL_20", "ACCBM_20", "ACCBU_20"]) def test_atr_ext(self): self.data.ta.atr(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ATR_14') + self.assertEqual(self.data.columns[-1], "ATR_14") def test_bbands_ext(self): self.data.ta.bbands(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['BBL_5', 'BBM_5', 'BBU_5']) + self.assertEqual(list(self.data.columns[-3:]), ["BBL_5_2.0", "BBM_5_2.0", "BBU_5_2.0"]) def test_donchian_ext(self): self.data.ta.donchian(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['DCL_10_20', 'DCM_10_20', 'DCU_10_20']) + self.assertEqual(list(self.data.columns[-3:]), ["DCL_20_20", "DCM_20_20", "DCU_20_20"]) def test_kc_ext(self): self.data.ta.kc(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-3:]), ['KCL_20', 'KCB_20', 'KCU_20']) + self.assertEqual(list(self.data.columns[-3:]), ["KCL_20", "KCB_20", "KCU_20"]) def test_massi_ext(self): self.data.ta.massi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MASSI_9_25') + self.assertEqual(self.data.columns[-1], "MASSI_9_25") def test_natr_ext(self): self.data.ta.natr(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'NATR_14') + self.assertEqual(self.data.columns[-1], "NATR_14") def test_pdist_ext(self): self.data.ta.pdist(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PDIST') + self.assertEqual(self.data.columns[-1], "PDIST") def test_rvi_ext(self): self.data.ta.rvi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'RVI_14') + self.assertEqual(self.data.columns[-1], "RVI_14") def test_rvi_refined_ext(self): self.data.ta.rvi(refined=True, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'RVIr_14') + self.assertEqual(self.data.columns[-1], "RVIr_14") def test_rvi_thirds_ext(self): self.data.ta.rvi(thirds=True, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'RVIt_14') + self.assertEqual(self.data.columns[-1], "RVIt_14") def test_true_range_ext(self): self.data.ta.true_range(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'TRUERANGE_1') \ No newline at end of file + self.assertEqual(self.data.columns[-1], "TRUERANGE_1") \ No newline at end of file diff --git a/tests/test_indicator_volume.py b/tests/test_indicator_volume.py index 9e30d6d..7dc1b47 100644 --- a/tests/test_indicator_volume.py +++ b/tests/test_indicator_volume.py @@ -14,11 +14,11 @@ class TestVolume(TestCase): def setUpClass(cls): cls.data = sample_data cls.data.columns = cls.data.columns.str.lower() - cls.open = cls.data['open'] - cls.high = cls.data['high'] - cls.low = cls.data['low'] - cls.close = cls.data['close'] - if 'volume' in cls.data.columns: cls.volume_ = cls.data['volume'] + cls.open = cls.data["open"] + cls.high = cls.data["high"] + cls.low = cls.data["low"] + cls.close = cls.data["close"] + if "volume" in cls.data.columns: cls.volume_ = cls.data["volume"] @classmethod def tearDownClass(cls): @@ -26,7 +26,7 @@ class TestVolume(TestCase): del cls.high del cls.low del cls.close - if hasattr(cls, 'volume'): del cls.volume_ + if hasattr(cls, "volume"): del cls.volume_ del cls.data @@ -37,7 +37,7 @@ class TestVolume(TestCase): def test_ad(self): result = pandas_ta.ad(self.high, self.low, self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'AD') + self.assertEqual(result.name, "AD") try: expected = tal.AD(self.high, self.low, self.close, self.volume_) @@ -52,12 +52,12 @@ class TestVolume(TestCase): def test_ad_open(self): result = pandas_ta.ad(self.high, self.low, self.close, self.volume_, self.open) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ADo') + self.assertEqual(result.name, "ADo") def test_adosc(self): result = pandas_ta.adosc(self.high, self.low, self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'ADOSC_3_10') + self.assertEqual(result.name, "ADOSC_3_10") try: expected = tal.ADOSC(self.high, self.low, self.close, self.volume_) @@ -72,27 +72,27 @@ class TestVolume(TestCase): def test_aobv(self): result = pandas_ta.aobv(self.close, self.volume_) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'AOBV_EMA_2_4_2_2_2') + self.assertEqual(result.name, "AOBV_EMA_2_4_2_2_2") def test_cmf(self): result = pandas_ta.cmf(self.high, self.low, self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'CMF_20') + self.assertEqual(result.name, "CMF_20") def test_efi(self): result = pandas_ta.efi(self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'EFI_13') + self.assertEqual(result.name, "EFI_13") def test_eom(self): result = pandas_ta.eom(self.high, self.low, self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'EOM_14_100000000') + self.assertEqual(result.name, "EOM_14_100000000") def test_mfi(self): result = pandas_ta.mfi(self.high, self.low, self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'MFI_14') + self.assertEqual(result.name, "MFI_14") try: expected = tal.MFI(self.high, self.low, self.close, self.volume_) @@ -107,12 +107,12 @@ class TestVolume(TestCase): def test_nvi(self): result = pandas_ta.nvi(self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'NVI_1') + self.assertEqual(result.name, "NVI_1") def test_obv(self): result = pandas_ta.obv(self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'OBV') + self.assertEqual(result.name, "OBV") try: expected = tal.OBV(self.close, self.volume_) @@ -127,19 +127,19 @@ class TestVolume(TestCase): def test_pvi(self): result = pandas_ta.pvi(self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PVI_1') + self.assertEqual(result.name, "PVI_1") def test_pvol(self): result = pandas_ta.pvol(self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PVOL') + self.assertEqual(result.name, "PVOL") def test_pvt(self): result = pandas_ta.pvt(self.close, self.volume_) self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'PVT') + self.assertEqual(result.name, "PVT") def test_vp(self): result = pandas_ta.vp(self.close, self.volume_) self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'VP_10') + self.assertEqual(result.name, "VP_10") diff --git a/tests/test_indicator_volume_ext.py b/tests/test_indicator_volume_ext.py index da7efac..2f4e99d 100644 --- a/tests/test_indicator_volume_ext.py +++ b/tests/test_indicator_volume_ext.py @@ -10,7 +10,7 @@ class TestVolumeExtension(TestCase): @classmethod def setUpClass(cls): cls.data = sample_data - cls.open = cls.data['open'] + cls.open = cls.data["open"] @classmethod def tearDownClass(cls): @@ -25,49 +25,49 @@ class TestVolumeExtension(TestCase): def test_ad_ext(self): self.data.ta.ad(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'AD') + self.assertEqual(self.data.columns[-1], "AD") def test_ad_open_ext(self): self.data.ta.ad(open_=self.open, append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ADo') + self.assertEqual(self.data.columns[-1], "ADo") def test_adosc_ext(self): self.data.ta.adosc(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'ADOSC_3_10') + self.assertEqual(self.data.columns[-1], "ADOSC_3_10") def test_aobv_ext(self): self.data.ta.aobv(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-7:]), ['OBV', 'OBV_min_2', 'OBV_max_2', 'OBV_EMA_2', 'OBV_EMA_4', 'AOBV_LR_2', 'AOBV_SR_2']) - # Remove 'OBV' so it does not interfere with test_obv_ext() - self.data.drop('OBV', axis=1, inplace=True) + self.assertEqual(list(self.data.columns[-7:]), ["OBV", "OBV_min_2", "OBV_max_2", "OBV_EMA_2", "OBV_EMA_4", "AOBV_LR_2", "AOBV_SR_2"]) + # Remove "OBV" so it does not interfere with test_obv_ext() + self.data.drop("OBV", axis=1, inplace=True) def test_cmf_ext(self): self.data.ta.cmf(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CMF_20') + self.assertEqual(self.data.columns[-1], "CMF_20") def test_efi_ext(self): self.data.ta.efi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'EFI_13') + self.assertEqual(self.data.columns[-1], "EFI_13") def test_eom_ext(self): self.data.ta.eom(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'EOM_14_100000000') + self.assertEqual(self.data.columns[-1], "EOM_14_100000000") def test_mfi_ext(self): self.data.ta.mfi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'MFI_14') + self.assertEqual(self.data.columns[-1], "MFI_14") def test_nvi_ext(self): self.data.ta.nvi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'NVI_1') + self.assertEqual(self.data.columns[-1], "NVI_1") # print(f"\nNVI: {self.data.columns[-1]}") # print(f"NVI: {self.data.columns}") @@ -76,24 +76,24 @@ class TestVolumeExtension(TestCase): self.assertIsInstance(self.data, DataFrame) # print(f"\nOBV: {self.data.columns[-1]}") # print(f"OBV: {self.data.columns}") - self.assertEqual(self.data.columns[-1], 'OBV') + self.assertEqual(self.data.columns[-1], "OBV") def test_pvi_ext(self): self.data.ta.pvi(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PVI_1') + self.assertEqual(self.data.columns[-1], "PVI_1") def test_pvol_ext(self): self.data.ta.pvol(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PVOL') + self.assertEqual(self.data.columns[-1], "PVOL") def test_pvt_ext(self): self.data.ta.pvt(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'PVT') + self.assertEqual(self.data.columns[-1], "PVT") def test_vp_ext(self): result = self.data.ta.vp() self.assertIsInstance(result, DataFrame) - self.assertEqual(result.name, 'VP_10') \ No newline at end of file + self.assertEqual(result.name, "VP_10") \ No newline at end of file