Skip to content

Adds exponential moving average algorithm #10273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 12, 2023
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions financial/exponential_moving_average.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
Calculates exponential moving average (EMA) on the series of numbers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Active tense

Suggested change
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines of Python code should be max 88 characters so let's not overdo it.

Suggested change
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
changes in the value quicker than SMA, which is one of the advantages of using EMA.
"""


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)
Copy link
Member

@cclauss cclauss Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The financial calculations seem artificial and trivial when they are supplied with all the data upfront.

Could we convert series: list[float] --> series: Iterable[float] so that we could continuously create/add to an EMA as new data came in from some generator expression? We will no longer be able to do len(series) but will other issues arise?

Suggested change
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)
from collections.abc import Iterable
def exponential_moving_average(series: Iteratable[float], window_size: int) -> list[float]:
"""
Returns the exponential moving average of the given array list
>>> exponential_moving_average(iter([2, 5, 3, 8.2, 6, 9, 10]), 3)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of return list[float] could we consider yielding floats as we finish windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion!
I have added generator as parameter instead of an iterator which acts as mock for file or data stream.

[2.0, 3.5, 3.25, 5.725, 5.8625, 7.43125, 8.715625]

: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)