From a33bc05e8d1822c80b39bf187b74b98961a42faf Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Wed, 11 Oct 2023 19:50:03 +0530 Subject: [PATCH 1/8] Adds exponential moving average algorithm --- financial/exponential_moving_average.py | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 financial/exponential_moving_average.py diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py new file mode 100644 index 000000000000..5d602b923c4d --- /dev/null +++ b/financial/exponential_moving_average.py @@ -0,0 +1,67 @@ +""" + Calculates exponential moving average (EMA) on the series of numbers + Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing + Reference: https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential-moving-average-ema + + Exponential moving average is used in financial field to analyze changes stock prices. + EMA is used in conjuction with Simple moving average (SMA), EMA reacts to the changes in + the value quicker than SMA, which is one of the advantages of using EMA. +""" + +import numpy as np + +def exponential_moving_average(series : list[float], window_size : int) -> list [float]: + ''' + :param series: Array of numbers (Time series data) + :param window_size: Window size for calculating average (window_size > 0) + :return: Resulting array of exponentially averaged numbers + + Formula: + + st = alpha * xt + (1 - alpha) * st_prev + alpha = 2/(1 + window_size) - smoothing factor + + Exponential moving average (EMA) is a rule of thumb technique for + smoothing time series data using the exponential window function. + ''' + + if (window_size <= 0): + raise ValueError("window_size must be > 0") + elif (window_size >= len(series)): + raise ValueError("window_size must be < length of series") + + # Resultent array + exp_averaged_arr : list[float] = [] + + # Calculating smoothing factor + alpha = 2 / (1 + window_size) + + # Exponential average at timestamp t + st = series[0] + + for t in range(len(series)): + if t <= window_size: + # Assigning simple moving average till the window_size for the first time + # is reached + st = (st + series[t]) * 0.5 + exp_averaged_arr.append(st) + else: + # Calculating exponential moving average based on current timestamp data + # point and previous exponential average value + st = (alpha * series[t]) + ((1 - alpha) * st) + exp_averaged_arr.append(st) + + return exp_averaged_arr + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + test_series = [2,5,3,8.2,6,9,10] + test_window_size = 3 + result = exponential_moving_average(test_series, test_window_size) + print("Test series: ", test_series) + print("Window size: ", test_window_size) + print("Result: ", result) + From acdc0a2fddff23a3149e3d04c72b5a5dccd6fbb0 Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:21:21 +0530 Subject: [PATCH 2/8] code clean up --- financial/exponential_moving_average.py | 45 +++++++++++++------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 5d602b923c4d..1afb96e3b9c4 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -3,35 +3,38 @@ Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing Reference: https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential-moving-average-ema - Exponential moving average is used in financial field to analyze changes stock prices. - EMA is used in conjuction with Simple moving average (SMA), EMA reacts to the changes in - the value quicker than SMA, which is one of the advantages of using EMA. + Exponential moving average is used in finance to analyze changes stock prices. + EMA is used in conjuction with Simple moving average (SMA), EMA reacts to the + changes inthe value quicker than SMA, which is one of the advantages of using EMA. """ -import numpy as np -def exponential_moving_average(series : list[float], window_size : int) -> list [float]: - ''' - :param series: Array of numbers (Time series data) - :param window_size: Window size for calculating average (window_size > 0) - :return: Resulting array of exponentially averaged numbers +def exponential_moving_average(series: list[float], window_size: int) -> list[float]: + """ + Returns the exponential moving average of the given array list + >>> exponential_moving_average([2, 5, 3, 8.2, 6, 9, 10], 3) + [2.0, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625] - Formula: + :param series: Array of numbers (Time series data) + :param window_size: Window size for calculating average (window_size > 0) + :return: Resulting array of exponentially averaged numbers - st = alpha * xt + (1 - alpha) * st_prev - alpha = 2/(1 + window_size) - smoothing factor + Formula: - Exponential moving average (EMA) is a rule of thumb technique for - smoothing time series data using the exponential window function. - ''' + st = alpha * xt + (1 - alpha) * st_prev + alpha = 2/(1 + window_size) - smoothing factor - if (window_size <= 0): + Exponential moving average (EMA) is a rule of thumb technique for + smoothing time series data using the exponential window function. + """ + + if window_size <= 0: raise ValueError("window_size must be > 0") - elif (window_size >= len(series)): + elif window_size >= len(series): raise ValueError("window_size must be < length of series") # Resultent array - exp_averaged_arr : list[float] = [] + exp_averaged_arr: list[float] = [] # Calculating smoothing factor alpha = 2 / (1 + window_size) @@ -50,18 +53,18 @@ def exponential_moving_average(series : list[float], window_size : int) -> list # point and previous exponential average value st = (alpha * series[t]) + ((1 - alpha) * st) exp_averaged_arr.append(st) - + return exp_averaged_arr + if __name__ == "__main__": import doctest doctest.testmod() - test_series = [2,5,3,8.2,6,9,10] + test_series = [2, 5, 3, 8.2, 6, 9, 10] test_window_size = 3 result = exponential_moving_average(test_series, test_window_size) print("Test series: ", test_series) print("Window size: ", test_window_size) print("Result: ", result) - From 1ce781fef3a68d017b1a4f0dbc1c5188169c0a8c Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:26:10 +0530 Subject: [PATCH 3/8] spell correction --- financial/exponential_moving_average.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 1afb96e3b9c4..7d903ee48937 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -4,8 +4,8 @@ Reference: https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential-moving-average-ema Exponential moving average is used in finance to analyze changes stock prices. - EMA is used in conjuction with Simple moving average (SMA), EMA reacts to the - changes inthe value quicker than SMA, which is one of the advantages of using EMA. + EMA is used in conjunction with Simple moving average (SMA), EMA reacts to the + changes in the value quicker than SMA, which is one of the advantages of using EMA. """ From 7b09aeb1566cf12291ee9fa1c29a2a964b39a6ef Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:12:58 +0530 Subject: [PATCH 4/8] Modifies I/O types of function --- financial/exponential_moving_average.py | 45 ++++++++++++++----------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 7d903ee48937..2014639fc150 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -8,16 +8,21 @@ changes in the value quicker than SMA, which is one of the advantages of using EMA. """ +from collections.abc import Iterator -def exponential_moving_average(series: list[float], window_size: int) -> list[float]: + +def exponential_moving_average( + series_generator: Iterator[float], window_size: int +) -> Iterator[float]: """ - Returns the exponential moving average of the given array list - >>> exponential_moving_average([2, 5, 3, 8.2, 6, 9, 10], 3) + Returns the generator which generates exponential moving average of the given + series generator + >>> list(exponential_moving_average((ele for ele in [2, 5, 3, 8.2, 6, 9, 10]), 3)) [2.0, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625] - :param series: Array of numbers (Time series data) + :param series_generator: Generator which generates numbers :param window_size: Window size for calculating average (window_size > 0) - :return: Resulting array of exponentially averaged numbers + :return: Returns generator of which returns exponentially averaged numbers Formula: @@ -30,31 +35,27 @@ def exponential_moving_average(series: list[float], window_size: int) -> list[fl if window_size <= 0: raise ValueError("window_size must be > 0") - elif window_size >= len(series): - raise ValueError("window_size must be < length of series") - - # Resultent array - exp_averaged_arr: list[float] = [] # Calculating smoothing factor alpha = 2 / (1 + window_size) + # Defining timestamp t + t = 0 + # Exponential average at timestamp t - st = series[0] + st = None - for t in range(len(series)): + for xt in series_generator: if t <= window_size: # Assigning simple moving average till the window_size for the first time # is reached - st = (st + series[t]) * 0.5 - exp_averaged_arr.append(st) + st = float(xt) if st is None else (st + xt) * 0.5 else: # Calculating exponential moving average based on current timestamp data # point and previous exponential average value - st = (alpha * series[t]) + ((1 - alpha) * st) - exp_averaged_arr.append(st) - - return exp_averaged_arr + st = (alpha * xt) + ((1 - alpha) * st) + t += 1 + yield st if __name__ == "__main__": @@ -62,9 +63,13 @@ def exponential_moving_average(series: list[float], window_size: int) -> list[fl doctest.testmod() + def test_gen_func(arr: list[float]): + yield from arr + test_series = [2, 5, 3, 8.2, 6, 9, 10] + test_generator = test_gen_func(test_series) test_window_size = 3 - result = exponential_moving_average(test_series, test_window_size) + result = exponential_moving_average(test_generator, test_window_size) print("Test series: ", test_series) print("Window size: ", test_window_size) - print("Result: ", result) + print("Result: ", list(result)) From 040379f72fe0fb84b966281a0534d141ed045a43 Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:18:45 +0530 Subject: [PATCH 5/8] Replaces generator function --- financial/exponential_moving_average.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 2014639fc150..506dc607b64f 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -63,11 +63,8 @@ def exponential_moving_average( doctest.testmod() - def test_gen_func(arr: list[float]): - yield from arr - test_series = [2, 5, 3, 8.2, 6, 9, 10] - test_generator = test_gen_func(test_series) + test_generator = (ele for ele in test_series) test_window_size = 3 result = exponential_moving_average(test_generator, test_window_size) print("Test series: ", test_series) From 6edf6e5395202ef4830057c4712cb97391340ec8 Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:36:07 +0530 Subject: [PATCH 6/8] Resolved mypy type error --- financial/exponential_moving_average.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 506dc607b64f..104ddc10252d 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -43,13 +43,13 @@ def exponential_moving_average( t = 0 # Exponential average at timestamp t - st = None + st = 0.0 for xt in series_generator: if t <= window_size: # Assigning simple moving average till the window_size for the first time # is reached - st = float(xt) if st is None else (st + xt) * 0.5 + st = float(xt) if t == 0 else (st + xt) * 0.5 else: # Calculating exponential moving average based on current timestamp data # point and previous exponential average value From 68c4bb87d24081238a2a7c5ddc942f8a35243cb8 Mon Sep 17 00:00:00 2001 From: Poojan Smart <44301271+PoojanSmart@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:28:50 +0530 Subject: [PATCH 7/8] readibility of code and documentation --- financial/exponential_moving_average.py | 47 ++++++++++++++++--------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 104ddc10252d..91c46687ca51 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -1,7 +1,8 @@ """ - Calculates exponential moving average (EMA) on the series of numbers + Calculate the exponential moving average (EMA) on the series of numbers. Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing - Reference: https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential-moving-average-ema + https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential + -moving-average-ema Exponential moving average is used in finance to analyze changes stock prices. EMA is used in conjunction with Simple moving average (SMA), EMA reacts to the @@ -12,22 +13,27 @@ def exponential_moving_average( - series_generator: Iterator[float], window_size: int + series: Iterator[float], window_size: int ) -> Iterator[float]: """ Returns the generator which generates exponential moving average of the given - series generator - >>> list(exponential_moving_average((ele for ele in [2, 5, 3, 8.2, 6, 9, 10]), 3)) + series + >>> list(exponential_moving_average(iter([2, 5, 3, 8.2, 6, 9, 10]), 3)) [2.0, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625] - :param series_generator: Generator which generates numbers + :param series: Generator which generates numbers :param window_size: Window size for calculating average (window_size > 0) :return: Returns generator of which returns exponentially averaged numbers Formula: st = alpha * xt + (1 - alpha) * st_prev - alpha = 2/(1 + window_size) - smoothing factor + + Where, + st : Exponential moving average at timestamp t + xt : Datapoint in series at timestamp t + st_prev : Exponential moving average at timestamp t-1 + alpha : 2/(1 + window_size) - smoothing factor Exponential moving average (EMA) is a rule of thumb technique for smoothing time series data using the exponential window function. @@ -40,22 +46,28 @@ def exponential_moving_average( alpha = 2 / (1 + window_size) # Defining timestamp t - t = 0 + timestamp = 0 # Exponential average at timestamp t - st = 0.0 + exponential_average = 0.0 - for xt in series_generator: - if t <= window_size: + for datapoint in series: + if timestamp <= window_size: # Assigning simple moving average till the window_size for the first time # is reached - st = float(xt) if t == 0 else (st + xt) * 0.5 + exponential_average = ( + float(datapoint) + if timestamp == 0 + else (exponential_average + datapoint) * 0.5 + ) else: # Calculating exponential moving average based on current timestamp data # point and previous exponential average value - st = (alpha * xt) + ((1 - alpha) * st) - t += 1 - yield st + exponential_average = (alpha * datapoint) + ( + (1 - alpha) * exponential_average + ) + timestamp += 1 + yield exponential_average if __name__ == "__main__": @@ -64,9 +76,10 @@ def exponential_moving_average( doctest.testmod() test_series = [2, 5, 3, 8.2, 6, 9, 10] - test_generator = (ele for ele in test_series) test_window_size = 3 - result = exponential_moving_average(test_generator, test_window_size) + result = exponential_moving_average( + series=iter(test_series), window_size=test_window_size + ) print("Test series: ", test_series) print("Window size: ", test_window_size) print("Result: ", list(result)) From ce8ecce5000d78d6b6f44f457309e98705e0ece8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 12 Oct 2023 15:51:18 +0200 Subject: [PATCH 8/8] Update exponential_moving_average.py --- financial/exponential_moving_average.py | 58 ++++++++++--------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/financial/exponential_moving_average.py b/financial/exponential_moving_average.py index 91c46687ca51..0b6cea3b4c91 100644 --- a/financial/exponential_moving_average.py +++ b/financial/exponential_moving_average.py @@ -1,5 +1,5 @@ """ - Calculate the exponential moving average (EMA) on the series of numbers. + Calculate the exponential moving average (EMA) on the series of stock prices. Wikipedia Reference: https://en.wikipedia.org/wiki/Exponential_smoothing https://www.investopedia.com/terms/e/ema.asp#toc-what-is-an-exponential -moving-average-ema @@ -13,17 +13,17 @@ def exponential_moving_average( - series: Iterator[float], window_size: int + stock_prices: Iterator[float], window_size: int ) -> Iterator[float]: """ - Returns the generator which generates exponential moving average of the given - series - >>> list(exponential_moving_average(iter([2, 5, 3, 8.2, 6, 9, 10]), 3)) - [2.0, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625] + Yields exponential moving averages of the given stock prices. + >>> tuple(exponential_moving_average(iter([2, 5, 3, 8.2, 6, 9, 10]), 3)) + (2, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625) - :param series: Generator which generates numbers - :param window_size: Window size for calculating average (window_size > 0) - :return: Returns generator of which returns exponentially averaged numbers + :param stock_prices: A stream of stock prices + :param window_size: The number of stock prices that will trigger a new calculation + of the exponential average (window_size > 0) + :return: Yields a sequence of exponential moving averages Formula: @@ -31,12 +31,12 @@ def exponential_moving_average( Where, st : Exponential moving average at timestamp t - xt : Datapoint in series at timestamp t + xt : stock price in from the stock prices at timestamp t st_prev : Exponential moving average at timestamp t-1 alpha : 2/(1 + window_size) - smoothing factor Exponential moving average (EMA) is a rule of thumb technique for - smoothing time series data using the exponential window function. + smoothing time series data using an exponential window function. """ if window_size <= 0: @@ -45,29 +45,19 @@ def exponential_moving_average( # Calculating smoothing factor alpha = 2 / (1 + window_size) - # Defining timestamp t - timestamp = 0 - # Exponential average at timestamp t - exponential_average = 0.0 + moving_average = 0.0 - for datapoint in series: - if timestamp <= window_size: + for i, stock_price in enumerate(stock_prices): + if i <= window_size: # Assigning simple moving average till the window_size for the first time # is reached - exponential_average = ( - float(datapoint) - if timestamp == 0 - else (exponential_average + datapoint) * 0.5 - ) + moving_average = (moving_average + stock_price) * 0.5 if i else stock_price else: # Calculating exponential moving average based on current timestamp data # point and previous exponential average value - exponential_average = (alpha * datapoint) + ( - (1 - alpha) * exponential_average - ) - timestamp += 1 - yield exponential_average + moving_average = (alpha * stock_price) + ((1 - alpha) * moving_average) + yield moving_average if __name__ == "__main__": @@ -75,11 +65,9 @@ def exponential_moving_average( doctest.testmod() - test_series = [2, 5, 3, 8.2, 6, 9, 10] - test_window_size = 3 - result = exponential_moving_average( - series=iter(test_series), window_size=test_window_size - ) - print("Test series: ", test_series) - print("Window size: ", test_window_size) - print("Result: ", list(result)) + stock_prices = [2.0, 5, 3, 8.2, 6, 9, 10] + window_size = 3 + result = tuple(exponential_moving_average(iter(stock_prices), window_size)) + print(f"{stock_prices = }") + print(f"{window_size = }") + print(f"{result = }")