From 201aa0d1638930656058ca02ea87529bc507caf3 Mon Sep 17 00:00:00 2001 From: Cedric Zhuang Date: Fri, 7 Jan 2022 19:38:33 +0800 Subject: [PATCH] [GH-23] Add supertrend indicator. (#111) As title. Also fix some issues in readme. --- README.md | 136 ++++++++++++++++++++++++++++---------------------- stockstats.py | 83 +++++++++++++++++++++++++++++- test.py | 16 ++++++ 3 files changed, 175 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 2e3742c..b2de2f9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Supported statistics/indicators are: * ADXR: Smoothed Moving Average of ADX * TRIX: Triple Exponential Moving Average * TEMA: Another Triple Exponential Moving Average -* VR: Volatility Volume Ratio +* VR: Volume Variation Index * MFI: Money Flow Index * VWMA: Volume Weighted Moving Average * CHOP: Choppiness Index @@ -53,6 +53,7 @@ Supported statistics/indicators are: * PPO: Percentage Price Oscillator * StochRSI: Stochastic RSI * WT: LazyBear's Wave Trend +* Supertrend: with the Upper Band and Lower Band ## Installation @@ -232,22 +233,19 @@ date [2813 rows x 3 columns] ``` -#### RSI - Relative Strength Index +#### [RSI - Relative Strength Index](https://en.wikipedia.org/wiki/Relative_strength_index) -RSI stands -for [Relative Strength Index](https://en.wikipedia.org/wiki/Relative_strength_index) - -It has a configurable window. The default window size is 14 which is +RSI has a configurable window. The default window size is 14 which is configurable through `StockDataFrame.RSI`. e.g. * `df['rsi']`: 14 periods RSI * `df['rsi_6']`: 6 periods RSI -#### Log Return of the Close +#### [Log Return of the Close](https://en.wikipedia.org/wiki/Rate_of_return) Logarithmic return = ln( close / last close) -From [wiki](https://en.wikipedia.org/wiki/Rate_of_return): +From wiki: > For example, if a stock is priced at 3.570 USD per share at the close on > one day, and at 3.575 USD per share at the close the next day, then the @@ -327,10 +325,10 @@ Examples: RSV is essential for calculating KDJ. It takes a window parameter. Use `df['rsv']` or `df['rsv_6']` to access it. -#### RSI - Relative Strength Index +#### [RSI - Relative Strength Index](https://en.wikipedia.org/wiki/Relative_strength_index) -[RSI](https://en.wikipedia.org/wiki/Relative_strength_index) chart the current -and historical strength or weakness of a stock. It takes a window parameter. +RSI chart the current and historical strength or weakness of a stock. It takes +a window parameter. The default window is 14. Use `StockDataFrame.RSI` to tune it. @@ -339,11 +337,10 @@ Examples: * `df['rsi']`: retrieve the RSI of 14 periods * `df['rsi_6']`: retrieve the RSI of 6 periods -#### Stochastic RSI +#### [Stochastic RSI](https://www.investopedia.com/terms/s/stochrsi.asp) -[Stochastic RSI](https://www.investopedia.com/terms/s/stochrsi.asp) gives -traders an idea of whether the current RSI value is overbought or oversold. It -takes a window parameter. +Stochastic RSI gives traders an idea of whether the current RSI value is +overbought or oversold. It takes a window parameter. The default window is 14. Use `StockDataFrame.RSI` to tune it. @@ -352,11 +349,9 @@ Examples: * `df['stochrsi']`: retrieve the Stochastic RSI of 14 periods * `df['stochrsi_6']`: retrieve the Stochastic RSI of 6 periods -#### WT - Wave Trend +#### [WT - Wave Trend](https://medium.com/@samuel.mcculloch/lets-take-a-look-at-wavetrend-with-crosses-lazybear-s-indicator-2ece1737f72f) -Retrieve -the [LazyBear's Wave Trend](https://medium.com/@samuel.mcculloch/lets-take-a-look-at-wavetrend-with-crosses-lazybear-s-indicator-2ece1737f72f) -with `df['wt1']` and `df['wt2']`. +Retrieve the LazyBear's Wave Trend with `df['wt1']` and `df['wt2']`. Wave trend uses two parameters. You can tune them with `StockDataFrame.WAVE_TREND_1` and `StockDataFrame.WAVE_TREND_2`. @@ -368,10 +363,10 @@ It takes two parameters, column and window. For example, use `df['close_7_smma']` to retrieve the 7 periods smoothed moving average of the close price. -#### TRIX - Triple Exponential Average +#### [TRIX - Triple Exponential Average](https://www.investopedia.com/articles/technical/02/092402.asp) -[Trix, the triple exponential average](https://www.investopedia.com/articles/technical/02/092402.asp) -, is used to identify oversold and overbought markets. +The triple exponential average is used to identify oversold and overbought +markets. The algorithm is: @@ -391,12 +386,9 @@ Examples: * `df['trix']` stands for 12 periods Trix for the close price. * `df['middle_10_trix']` stands for the 10 periods Trix for the typical price. -#### TEMA - Another Triple Exponential Average +#### [TEMA - Another Triple Exponential Average](https://www.forextraders.com/forex-education/forex-technical-analysis/triple-exponential-moving-average-the-tema-indicator/) -Tema is another implementation for the triple exponential moving average. You -can find the -algorithm [here](https://www.forextraders.com/forex-education/forex-technical-analysis/triple-exponential-moving-average-the-tema-indicator/) -. +Tema is another implementation for the triple exponential moving average. ``` TEMA=(3 x EMA) - (3 x EMA of EMA) + (EMA of EMA of EMA) @@ -412,9 +404,19 @@ Examples: * `df['tema']` stands for 12 periods TEMA for the close price. * `df['middle_10_tema']` stands for the 10 periods TEMA for the typical price. -#### WR - Williams Overbought/Oversold Index +#### [VR - Volume Variation Index](https://help.eaglesmarkets.com/hc/en-us/articles/900002867026-Summary-of-volume-variation-index) + +It is the strength index of trading volume. + +It has a default window of 26. Change it with `StockDataFrame.VR`. + +Examples: +* `df['vr']` retrieves the 26 periods VR. +* `df['vr_6']` retrieves the 6 periods VR. + +#### [WR - Williams Overbought/Oversold Index](https://www.investopedia.com/terms/w/williamsr.asp) -[Williams Overbought/Oversold index](https://www.investopedia.com/terms/w/williamsr.asp) +Williams Overbought/Oversold index is a type of momentum indicator that moves between 0 and -100 and measures overbought and oversold levels. @@ -426,11 +428,9 @@ Examples: * `df['wr']` retrieves the 14 periods WR. * `df['wr_6']` retrieves the 6 periods WR. -#### CCI - Commodity Channel Index +#### [CCI - Commodity Channel Index](https://www.investopedia.com/terms/c/commoditychannelindex.asp) -CCI stands -for [Commodity Channel Index](https://www.investopedia.com/terms/c/commoditychannelindex.asp) -. +CCI stands for Commodity Channel Index. It requires a window parameter. The default window is 14. Use `StockDataFrame.CCI` to change it. @@ -445,9 +445,9 @@ Examples: TR is a measure of volatility of a High-Low-Close series. It is used for calculating the ATR. -#### ATR - Average True Range +#### [ATR - Average True Range](https://en.wikipedia.org/wiki/Average_true_range) -The [Average True Range](https://en.wikipedia.org/wiki/Average_true_range) is an +The Average True Range is an N-period smoothed moving average (SMMA) of the true range value. Default to 14 periods. @@ -458,14 +458,27 @@ Example: * `df['atr']` retrieves the 14 periods ATR. * `df['atr_5']` retrieves the 5 periods ATR. +#### [Supertrend](https://economictimes.indiatimes.com/markets/stocks/news/how-to-use-supertrend-indicator-to-find-buying-and-selling-opportunities-in-market/articleshow/54492970.cms) + +Supertrend indicates the current trend. +We use the [algorithm described here](https://medium.com/codex/step-by-step-implementation-of-the-supertrend-indicator-in-python-656aa678c111). +It includes 3 lines: +* `df['supertrend']` is the trend line. +* `df['supertrend_ub']` is the upper band of the trend +* `df['supertrend_lb']` is the lower band of the trend + +It has 2 parameters: +* `StockDataFrame.SUPERTREND_MUL` is the multiplier of the band, default to 3. +* `StockDataFrame.SUPERTREND_WINDOW` is the window size, default to 14. + #### DMA - Difference of Moving Average `df['dma']` retreives the difference of 10 periods SMA of the close price and the 50 periods SMA of the close price. -#### DMI - Directional Movement Index +#### [DMI - Directional Movement Index](https://www.investopedia.com/terms/d/dmi.asp) -The [directional movement index (DMI)](https://www.investopedia.com/terms/d/dmi.asp) +The directional movement index (DMI) identifies in which direction the price of an asset is moving. It has several lines: @@ -484,9 +497,12 @@ It has several parameters. * `StockDataFrame.ADX_EMA` - window for ADX * `StockDataFrame.ADXR_EMA` - window for ADXR -#### KDJ Indicator +#### [KDJ Indicator](https://en.wikipedia.org/wiki/Stochastic_oscillator) -It consists of three lines: +The stochastic oscillator is a momentum indicator that uses support and +resistance levels. + +It includes three lines: * `df['kdfk']` - K series * `df['kdfd']` - D series * `df['kdfj']` - J series @@ -497,9 +513,9 @@ Use `df['kdjk_6']` to retrieve the K series of 6 periods. KDJ also has two configurable parameter named `StockDataFrame.KDJ_PARAM`. The default value is `(2.0/3.0, 1.0/3.0)` -#### CR - Energy Index +#### [CR - Energy Index](https://support.futunn.com/en/topic167/?lang=en-us) -The [Energy Index (Intermediate Willingness Index)](https://support.futunn.com/en/topic167/?lang=en-us) +The Energy Index (Intermediate Willingness Index) uses the relationship between the highest price, the lowest price and yesterday's middle price to reflect the market's willingness to buy and sell. @@ -510,12 +526,12 @@ It contains 4 lines: * `df['cr-ma2']` - `StockDataFrame.CR_MA2` periods of the CR moving average * `df['cr-ma3']` - `StockDataFrame.CR_MA3` periods of the CR moving average -#### Typical Price +#### [Typical Price](https://en.wikipedia.org/wiki/Typical_price) It's the average of `high`, `low` and `close`. Use `df['middle']` to access this value. -#### Bollinger Bands +#### [Bollinger Bands](https://en.wikipedia.org/wiki/Bollinger_Bands) The Bollinger bands includes three lines * `df['boll']` is the baseline @@ -526,7 +542,7 @@ The default period of the Bollinger Band can be changed with `StockDataFrame.BOLL_PERIOD`. The width of the bands can be turned with `StockDataFrame.BOLL_STD_TIMES`. The default value is 2. -#### MACD - Moving Average Convergence Divergence +#### [MACD - Moving Average Convergence Divergence](https://en.wikipedia.org/wiki/MACD) We use the close price to calculate the MACD lines. * `df['macd']` is the difference between two exponential moving average. @@ -540,10 +556,9 @@ value are 12 and 26 The period of the signal line can be tuned with `StockDataFrame.MACD_EMA_SIGNAL`. The default value is 9. -#### PPO - Percentage Price Oscillator +#### [PPO - Percentage Price Oscillator](https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:price_oscillators_ppo) -The [Percentage Price Oscillator](https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:price_oscillators_ppo) -includes three lines. +The Percentage Price Oscillator includes three lines. * `df['ppo']` derives from the difference of 2 exponential moving average. * `df['ppos]` is the signal line. @@ -556,17 +571,21 @@ value are 12 and 26 The period of the signal line can be tuned with `StockDataFrame.PPO_EMA_SIGNAL`. The default value is 9. -#### Moving Standard Deviation +#### [Simple Moving Average](https://www.investopedia.com/terms/m/mean.asp) + +Follow the pattern `__sma` to retrieve simple moving average. + +#### [Moving Standard Deviation](https://www.investopedia.com/terms/s/standarddeviation.asp) Follow the pattern `__mstd` to retrieve the moving STD. -#### Moving Variance +#### [Moving Variance](https://www.investopedia.com/terms/v/variance.asp) Follow the pattern `__mvar` to retrieve the moving VAR. -#### Volume Weighted Moving Average +#### [Volume Weighted Moving Average](https://www.investopedia.com/articles/trading/11/trading-with-vwap-mvwap.asp) -It's the [moving average weighted by volume](https://www.investopedia.com/articles/trading/11/trading-with-vwap-mvwap.asp). +It's the moving average weighted by volume. It has a parameter for window size. The default window is 14. Change it with `StockDataFrame.VWMA`. @@ -575,10 +594,9 @@ Examples: * `df['vwma']` retrieves the 14 periods VWMA * `df['vwma_6']` retrieves the 6 periods VWMA -#### CHOP - Choppiness Index +#### [CHOP - Choppiness Index](https://www.tradingview.com/education/choppinessindex/) -The [Choppiness Index](https://www.tradingview.com/education/choppinessindex/) -determines if the market is choppy. +The Choppiness Index determines if the market is choppy. It has a parameter for window size. The default window is 14. Change it with `StockDataFrame.CHOP`. @@ -587,9 +605,9 @@ Examples: * `df['chop']` retrieves the 14 periods CHOP * `df['chop_6']` retrieves the 6 periods CHOP -#### MFI - Money Flow Index +#### [MFI - Money Flow Index](https://www.investopedia.com/terms/m/mfi.asp) -The [Money Flow Index](https://www.investopedia.com/terms/m/mfi.asp) +The Money Flow Index identifies overbought or oversold signals in an asset. It has a parameter for window size. The default window is 14. Change it with @@ -599,10 +617,10 @@ Examples: * `df['mfi']` retrieves the 14 periods MFI * `df['mfi_6']` retrieves the 6 periods MFI -#### KAMA - Kaufman's Adaptive Moving Average +#### [KAMA - Kaufman's Adaptive Moving Average](https://school.stockcharts.com/doku.php?id=technical_indicators:kaufman_s_adaptive_moving_average) -[Kaufman's Adaptive Moving Average](https://school.stockcharts.com/doku.php?id=technical_indicators:kaufman_s_adaptive_moving_average) -is designed to account for market noise or volatility. +Kaufman's Adaptive Moving Average is designed to account for market noise or +volatility. It has 2 optional parameter and 2 required parameter * fast - optional, the parameter for fast EMA smoothing, default to 5 diff --git a/stockstats.py b/stockstats.py index a0495b8..3c66b8e 100644 --- a/stockstats.py +++ b/stockstats.py @@ -82,6 +82,9 @@ class StockDataFrame(pd.DataFrame): ATR_SMMA = 14 + SUPERTREND_MUL = 3 + SUPERTREND_WINDOW = 14 + VWMA = 14 CHOP = 14 @@ -522,6 +525,81 @@ def _get_tr(self): """ self['tr'] = self._tr() + def _get_supertrend(self, window=None): + """ Supertrend + + Supertrend indicator shows trend direction. + It provides buy or sell indicators. + https://medium.com/codex/step-by-step-implementation-of-the-supertrend-indicator-in-python-656aa678c111 + + :param window: number of periods + :return: None + """ + if window is None: + window = self.SUPERTREND_WINDOW + window = self.get_int_positive(window) + + high = self['high'] + low = self['low'] + close = self['close'] + m_atr = self.SUPERTREND_MUL * self._atr(window) + hl_avg = (high + low) / 2.0 + # basic upper band + b_ub = hl_avg + m_atr + # basic lower band + b_lb = hl_avg - m_atr + + size = len(close) + ub = np.empty(size, dtype=np.float64) + lb = np.empty(size, dtype=np.float64) + st = np.empty(size, dtype=np.float64) + + for i in range(size): + if i == 0: + ub[i] = b_ub.iloc[i] + lb[i] = b_lb.iloc[i] + if close.iloc[i] <= ub[i]: + st[i] = ub[i] + else: + st[i] = lb[i] + continue + + last_close = close.iloc[i - 1] + curr_close = close.iloc[i] + last_ub = ub[i - 1] + last_lb = lb[i - 1] + last_st = st[i - 1] + curr_b_ub = b_ub.iloc[i] + curr_b_lb = b_lb.iloc[i] + + # calculate current upper band + if curr_b_ub < last_ub or last_close > last_ub: + ub[i] = curr_b_ub + else: + ub[i] = last_ub + + # calculate current lower band + if curr_b_lb > last_lb or last_close < last_lb: + lb[i] = curr_b_lb + else: + lb[i] = last_lb + + # calculate supertrend + if last_st == last_ub: + if curr_close <= ub[i]: + st[i] = ub[i] + else: + st[i] = lb[i] + elif last_st == last_lb: + if curr_close > lb[i]: + st[i] = lb[i] + else: + st[i] = ub[i] + + self['supertrend_ub'] = ub + self['supertrend_lb'] = lb + self['supertrend'] = st + def _atr(self, window): tr = self._tr() return self._smma(tr, window) @@ -533,6 +611,7 @@ def _get_atr(self, window=None): the true range values. Default to 14 periods. https://en.wikipedia.org/wiki/Average_true_range + :param window: number of periods :return: None """ if window is None: @@ -584,7 +663,6 @@ def _get_pdm(self, windows): If window is not 1, calculate the SMMA of +DM - :param self: data :param windows: range :return: """ @@ -1126,6 +1204,9 @@ def handler(self): ('mfi',): self._get_mfi, ('wt1', 'wt2'): self._get_wave_trend, ('wr',): self._get_wr, + ('supertrend', + 'supertrend_lb', + 'supertrend_ub'): self._get_supertrend, } def __init_not_exist_column(self, key): diff --git a/test.py b/test.py index b27a32a..2667452 100644 --- a/test.py +++ b/test.py @@ -624,3 +624,19 @@ def test_init_all(self): stock.init_all() columns = stock.columns assert_that(columns, has_items('macd', 'kdjj', 'mfi', 'boll')) + + def test_supertrend(self): + stock = self.get_stock_90day() + st = stock['supertrend'] + st_ub = stock['supertrend_ub'] + st_lb = stock['supertrend_lb'] + + idx = 20110302 + assert_that(st[idx], near_to(13.3430)) + assert_that(st_ub[idx], near_to(13.3430)) + assert_that(st_lb[idx], near_to(12.2541)) + + idx = 20110331 + assert_that(st[idx], near_to(12.9021)) + assert_that(st_ub[idx], near_to(14.6457)) + assert_that(st_lb[idx], near_to(12.9021))