From bf12490634cd5b644b82daf99a742f7fb2abee23 Mon Sep 17 00:00:00 2001 From: Cedric Zhuang Date: Wed, 14 Jun 2023 23:27:03 +0800 Subject: [PATCH] [GH-139] Add Awesome Oscillator The AO indicator is a good indicator for measuring the market dynamics, it reflects specific changes in the driving force of the market, which helps to identify the strength of the trend, including the points of its formation and reversal. Awesome Oscillator Formula * MEDIAN PRICE = (HIGH+LOW)/2 * AO = SMA(MEDIAN PRICE, 5)-SMA(MEDIAN PRICE, 34) Examples: * `df['ao']` returns the Awesome Oscillator with default windows (5, 34) * `df['ao_3,10']` returns the Awesome Oscillator with a window of 3 and 10 --- README.md | 19 +++++++++++++++- stockstats.py | 63 +++++++++++++++++++++++++++++++++++++++------------ test.py | 10 ++++++++ 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8ea4809..3e681d8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![codecov](https://codecov.io/gh/jealous/stockstats/branch/master/graph/badge.svg?token=IFMD1pVJ7T)](https://codecov.io/gh/jealous/stockstats) [![pypi](https://img.shields.io/pypi/v/stockstats.svg)](https://pypi.python.org/pypi/stockstats) -VERSION: 0.5.3 +VERSION: 0.5.4 ## Introduction @@ -56,6 +56,7 @@ Supported statistics/indicators are: * Supertrend: with the Upper Band and Lower Band * Aroon: Aroon Oscillator * Z: Z-Score +* AO: Awesome Oscillator ## Installation @@ -693,6 +694,22 @@ Where: Examples: * `df['close_75_z']` returns the Z-Score of close price with a window of 75 +#### [Awesome Oscillator](https://www.ifcm.co.uk/ntx-indicators/awesome-oscillator) + +The AO indicator is a good indicator for measuring the market dynamics, +it reflects specific changes in the driving force of the market, which +helps to identify the strength of the trend, including the points of +its formation and reversal. + +Awesome Oscillator Formula + +* MEDIAN PRICE = (HIGH+LOW)/2 +* AO = SMA(MEDIAN PRICE, 5)-SMA(MEDIAN PRICE, 34) + +Examples: +* `df['ao']` returns the Awesome Oscillator with default windows (5, 34) +* `df['ao_3,10']` returns the Awesome Oscillator with a window of 3 and 10 + ## Issues We use [Github Issues](https://github.com/jealous/stockstats/issues) to track diff --git a/stockstats.py b/stockstats.py index e1bd045..b0641f0 100644 --- a/stockstats.py +++ b/stockstats.py @@ -105,6 +105,9 @@ class StockDataFrame(pd.DataFrame): KAMA_SLOW = 34 KAMA_FAST = 5 + AO_SLOW = 34 + AO_FAST = 5 + MULTI_SPLIT_INDICATORS = ("kama",) # End of options @@ -548,32 +551,33 @@ def _get_supertrend(self, window=None): m_atr = self.SUPERTREND_MUL * self._atr(window) hl_avg = (high + low) / 2.0 # basic upper band - b_ub = hl_avg + m_atr + b_ub = list(hl_avg + m_atr) # basic lower band - b_lb = hl_avg - m_atr + b_lb = list(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) + close = list(close) 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]: + ub[i] = b_ub[i] + lb[i] = b_lb[i] + if close[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_close = close[i - 1] + curr_close = close[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] + curr_b_ub = b_ub[i] + curr_b_lb = b_lb[i] # calculate current upper band if curr_b_ub < last_ub or last_close > last_ub: @@ -1170,6 +1174,35 @@ def _get_mfi(self, window=None): mfi.iloc[:window] = 0.5 self[column_name] = mfi + def _get_ao(self, windows=None): + """ get awesome oscillator + + The AO indicator is a good indicator for measuring the market dynamics, + it reflects specific changes in the driving force of the market, which + helps to identify the strength of the trend, including the points of + its formation and reversal. + + + Awesome Oscillator Formula + * MEDIAN PRICE = (HIGH+LOW)/2 + * AO = SMA(MEDIAN PRICE, 5)-SMA(MEDIAN PRICE, 34) + + https://www.ifcm.co.uk/ntx-indicators/awesome-oscillator + """ + if windows is None: + fast = self.AO_FAST + slow = self.AO_SLOW + column_name = 'ao' + else: + n0, n1 = self.to_ints(windows) + fast = min(n0, n1) + slow = max(n0, n1) + column_name = 'ao_{},{}'.format(fast, slow) + + median_price = (self['high'] + self['low']) * 0.5 + ao = self._sma(median_price, fast) - self._sma(median_price, slow) + self[column_name] = ao + def _get_kama(self, column, windows, fasts=None, slows=None): """ get Kaufman's Adaptive Moving Average. Implemented after @@ -1200,17 +1233,18 @@ def _get_kama(self, column, windows, fasts=None, slows=None): slow_ema_smoothing = 2.0 / (slow + 1) smoothing_2 = fast_ema_smoothing - slow_ema_smoothing efficient_smoothing = efficiency_ratio * smoothing_2 - smoothing = 2 * (efficient_smoothing + slow_ema_smoothing) + smoothing = list(2 * (efficient_smoothing + slow_ema_smoothing)) # start with simple moving average - kama = self._sma(col, window) + kama = list(self._sma(col, window)) + col_list = list(col) if len(kama) >= window: - last_kama = kama.iloc[window - 1] + last_kama = kama[window - 1] else: last_kama = 0.0 for i in range(window, len(kama)): - cur = smoothing.iloc[i] * (col.iloc[i] - last_kama) + last_kama - kama.iloc[i] = cur + cur = smoothing[i] * (col_list[i] - last_kama) + last_kama + kama[i] = cur last_kama = cur self[column_name] = kama @@ -1347,6 +1381,7 @@ def handler(self): 'supertrend_lb', 'supertrend_ub'): self._get_supertrend, ('aroon',): self._get_aroon, + ('ao',): self._get_ao, } def __init_not_exist_column(self, key): diff --git a/test.py b/test.py index f623936..f2368f3 100644 --- a/test.py +++ b/test.py @@ -671,6 +671,16 @@ def test_supertrend(self): assert_that(st_ub[idx], near_to(14.6457)) assert_that(st_lb[idx], near_to(12.9021)) + def test_ao(self): + stock = self.get_stock_90day() + ao = stock['ao'] + ao1 = stock['ao_5,34'] + ao2 = stock['ao_5,10'] + idx = 20110302 + assert_that(ao[idx], near_to(-0.112)) + assert_that(ao1[idx], equal_to(ao[idx])) + assert_that(ao2[idx], near_to(-0.071)) + def test_drop_column_inplace(self): stock = self._supor[:20] stock.columns.name = 'Luke'