diff --git a/gamestonk_terminal/prediction_techniques/README.md b/gamestonk_terminal/prediction_techniques/README.md
index 209ff96bf41..abf2bde299d 100644
--- a/gamestonk_terminal/prediction_techniques/README.md
+++ b/gamestonk_terminal/prediction_techniques/README.md
@@ -4,6 +4,8 @@ This menu aims to predict the share price of a pre-loaded stock, and the usage o
* [sma](#sma)
- simple moving average
+ * [ets](#ets)
+ - Exponential Smoothing (e.g. Holt-Winters)
* [knn](#knn)
- k-Nearest Neighbors
* [linear](#linear)
@@ -38,6 +40,18 @@ Simple Moving Average:

+## ets
+```
+usage: ets [-t TREND] [-s SEASONAL] [-p SEASONAL_PERIODS] [-d N_DAYS]
+```
+Exponential Smoothing (based on trend+seasonality, see https://otexts.com/fpp2/taxonomy.html):
+ * -t : trend component: N: None, A: Additive, Ad: Additive Damped. Default N.
+ * -s : seasonality component: N: None, A: Additive, M: Multiplicative. Default N.
+ * -p : seasonal periods. Default 5.
+ * -d : prediciton days. Default 5.
+
+
+
## knn
```
usage: knn [-i N_INPUTS] [-d N_DAYS] [-j N_JUMPS] [-n N_NEIGHBORS]
@@ -125,7 +139,7 @@ Facebook's Prophet:
## mlp
```
-usage: mlp [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [-e N_EPOCHS] [-p {normalization,standardization,none}]
+usage: mlp [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [-e N_EPOCHS] [-p {normalization,standardization,none}]
[-o {adam,adagrad,adadelta,adamax,ftrl,nadam,optimizer,rmsprop,sgd}] [-l {mae,mape,mse,msle}]
```
MulitLayer Perceptron:
@@ -140,15 +154,15 @@ MulitLayer Perceptron:
Due to the complexity of defining a model through command line, one can define it in: [config_neural_network_models.txt](/config_neural_network_models.py)
```
MultiLayer_Perceptron \
- = [ {'Dense':
+ = [ {'Dense':
{'units':50, 'activation':'relu'} },
- {'Dense':
+ {'Dense':
{'units':100, 'activation':'relu'} },
- {'Dense':
+ {'Dense':
{'units':80, 'activation':'relu'} },
- {'Dense':
+ {'Dense':
{'units':30, 'activation':'relu'} },
- {'Dense':
+ {'Dense':
{'activation':'linear'} }]
```
@@ -156,7 +170,7 @@ MultiLayer_Perceptron \
## rnn
```
-usage: rnn [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [-e N_EPOCHS] [-p {normalization,standardization,none}]
+usage: rnn [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [-e N_EPOCHS] [-p {normalization,standardization,none}]
[-o {adam,adagrad,adadelta,adamax,ftrl,nadam,optimizer,rmsprop,sgd}] [-l {mae,mape,mse,msle}]
```
Recurrent Neural Network:
@@ -171,15 +185,15 @@ Recurrent Neural Network:
Due to the complexity of defining a model through command line, one can define it in: [config_neural_network_models.txt](/config_neural_network_models.py)
```
Recurrent_Neural_Network \
- = [ {'SimpleRNN':
+ = [ {'SimpleRNN':
{'units':100, 'activation':'linear', 'return_sequences':True} },
- {'SimpleRNN':
+ {'SimpleRNN':
{'units':50, 'activation':'linear', 'return_sequences':True} },
- {'Dropout':
+ {'Dropout':
{'rate':0.2} },
- {'SimpleRNN':
+ {'SimpleRNN':
{'units':21, 'activation':'linear', 'return_sequences':False} },
- {'Dense':
+ {'Dense':
{'activation':'linear'} }]
```
@@ -187,7 +201,7 @@ Recurrent_Neural_Network \
## lstm
```
-usage: lstm [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [-e N_EPOCHS] [-p {normalization,standardization,none}]
+usage: lstm [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [-e N_EPOCHS] [-p {normalization,standardization,none}]
[-o {adam,adagrad,adadelta,adamax,ftrl,nadam,optimizer,rmsprop,sgd}] [-l {mae,mape,mse,msle}]
```
Long-Short Term Memory:
@@ -202,11 +216,11 @@ Long-Short Term Memory:
Due to the complexity of defining a model through command line, one can define it in: [config_neural_network_models.txt](/config_neural_network_models.py)
```
Long_Short_Term_Memory \
- = [ {'LSTM':
+ = [ {'LSTM':
{'units':25, 'activation':'tanh', 'return_sequences':True} },
- {'LSTM':
+ {'LSTM':
{'units':15, 'activation':'tanh', 'return_sequences':False} },
- {'Dense':
+ {'Dense':
{'activation':'linear'} }]
```
diff --git a/gamestonk_terminal/prediction_techniques/ets.py b/gamestonk_terminal/prediction_techniques/ets.py
new file mode 100644
index 00000000000..b52314f43ec
--- /dev/null
+++ b/gamestonk_terminal/prediction_techniques/ets.py
@@ -0,0 +1,304 @@
+import argparse
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+from pandas.plotting import register_matplotlib_converters
+from statsmodels.tsa.holtwinters import ExponentialSmoothing
+from gamestonk_terminal.helper_funcs import (
+ check_positive,
+ get_next_stock_market_days,
+ parse_known_args_and_warn,
+ print_pretty_prediction,
+)
+
+register_matplotlib_converters()
+
+trend = "Ad" # Additive damped, A Additive, N None
+seasonal = "N" # None, A Additive, M Multiplicative
+seasonal_periods = 5
+
+
+def check_valid_trend(trend: str) -> str:
+ if trend == "N" or trend == "A" or trend == "Ad":
+ return trend
+ raise argparse.ArgumentTypeError(
+ "Invalid trend selected. Choose between 'N', 'A', and 'Ad'"
+ )
+
+
+def check_valid_seasonal(seasonal: str) -> str:
+ if seasonal == "N" or seasonal == "A" or seasonal == "M":
+ return seasonal
+ raise argparse.ArgumentTypeError(
+ "Invalid seasonal selected. Choose between 'N', 'A', and 'M'"
+ )
+
+
+def exponential_smoothing(l_args, s_ticker, df_stock):
+ parser = argparse.ArgumentParser(
+ prog="ets",
+ description="""
+ Exponential Smoothing, see https://otexts.com/fpp2/taxonomy.html
+
+ Trend='N', Seasonal='N': Simple Exponential Smoothing
+ Trend='N', Seasonal='A': Exponential Smoothing
+ Trend='N', Seasonal='M': Exponential Smoothing
+ Trend='A', Seasonal='N': Holt’s linear method
+ Trend='A', Seasonal='A': Additive Holt-Winters’ method
+ Trend='A', Seasonal='M': Multiplicative Holt-Winters’ method
+ Trend='Ad', Seasonal='N': Additive damped trend method
+ Trend='Ad', Seasonal='A': Exponential Smoothing
+ Trend='Ad', Seasonal='M': Holt-Winters’ damped method
+
+ Trend component: N: None, A: Additive, Ad: Additive Damped
+ Seasonality component: N: None, A: Additive, M: Multiplicative
+ """,
+ )
+
+ parser.add_argument(
+ "-d",
+ "--days",
+ action="store",
+ dest="n_days",
+ type=check_positive,
+ default=5,
+ help="prediction days.",
+ )
+ parser.add_argument(
+ "-t",
+ "--trend",
+ action="store",
+ dest="trend",
+ type=check_valid_trend,
+ default="N",
+ help="Trend component: N: None, A: Additive, Ad: Additive Damped.",
+ )
+ parser.add_argument(
+ "-s",
+ "--seasonal",
+ action="store",
+ dest="seasonal",
+ type=check_valid_seasonal,
+ default="N",
+ help="Seasonality component: N: None, A: Additive, M: Multiplicative.",
+ )
+ parser.add_argument(
+ "-p",
+ "--periods",
+ action="store",
+ dest="seasonal_periods",
+ type=check_positive,
+ default=5,
+ help="Seasonal periods.",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, l_args)
+
+ model, title = get_exponential_smoothing_model(
+ df_stock["5. adjusted close"].values,
+ ns_parser.trend,
+ ns_parser.seasonal,
+ ns_parser.seasonal_periods,
+ )
+
+ if model.mle_retvals.success:
+ forecast = model.forecast(ns_parser.n_days)
+
+ l_pred_days = get_next_stock_market_days(
+ last_stock_day=df_stock["5. adjusted close"].index[-1],
+ n_next_days=ns_parser.n_days,
+ )
+ df_pred = pd.Series(forecast, index=l_pred_days, name="Price")
+
+ if ~np.isnan(forecast).any():
+
+ print(f"\n{title}")
+ print("\nFit model parameters:")
+ for key, value in model.params.items():
+ print(f"{key} {' '*(18-len(key))}: {value}")
+
+ print("\nAssess fit model:")
+ print(f"AIC: {round(model.aic, 2)}")
+ print(f"BIC: {round(model.bic, 2)}")
+ print(f"SSE: {round(model.sse, 2)}\n")
+
+ # Plotting
+ plt.figure()
+ plt.plot(df_stock.index, df_stock["5. adjusted close"], lw=2)
+ plt.title(f"{title} on {s_ticker}")
+
+ plt.xlim(
+ df_stock.index[0],
+ get_next_stock_market_days(df_pred.index[-1], 1)[-1],
+ )
+ plt.xlabel("Time")
+ plt.ylabel("Share Price ($)")
+ plt.grid(b=True, which="major", color="#666666", linestyle="-")
+ plt.minorticks_on()
+ plt.grid(
+ b=True, which="minor", color="#999999", linestyle="-", alpha=0.2
+ )
+ plt.plot(
+ [df_stock.index[-1], df_pred.index[0]],
+ [df_stock["5. adjusted close"].values[-1], df_pred.values[0]],
+ lw=1,
+ c="tab:green",
+ linestyle="--",
+ )
+ plt.plot(df_pred.index, df_pred, lw=2, c="tab:green")
+ plt.axvspan(
+ df_stock.index[-1],
+ df_pred.index[-1],
+ facecolor="tab:orange",
+ alpha=0.2,
+ )
+ _, _, ymin, ymax = plt.axis()
+ plt.vlines(
+ df_stock.index[-1],
+ ymin,
+ ymax,
+ linewidth=1,
+ linestyle="--",
+ color="k",
+ )
+ plt.ion()
+ plt.show()
+
+ # Print prediction data
+ print_pretty_prediction(
+ df_pred, df_stock["5. adjusted close"].values[-1]
+ )
+ print("")
+
+ else:
+ print("RuntimeWarning: invalid value encountered in double_scalars.")
+ else:
+ print("ConvergenceWarning: Optimization failed to converge.")
+
+ except Exception as e:
+ print(e)
+ print("")
+
+
+def get_exponential_smoothing_model(data, trend, seasonal, seasonal_periods):
+ if trend == "N": # None
+ if seasonal == "N": # None
+ title = "Trend='N', Seasonal='N': Simple Exponential Smoothing"
+ ETS = ExponentialSmoothing(
+ data,
+ trend=None,
+ damped_trend=False,
+ seasonal=None,
+ )
+ model = ETS.fit(
+ smoothing_level=None, smoothing_trend=None, damping_trend=None
+ )
+
+ elif seasonal == "A": # Additive
+ title = "Trend='N', Seasonal='A': Exponential Smoothing"
+ ETS = ExponentialSmoothing(
+ data,
+ trend=None,
+ damped_trend=False,
+ seasonal="add",
+ seasonal_periods=seasonal_periods,
+ )
+ model = ETS.fit(smoothing_level=None, smoothing_seasonal=None)
+
+ elif seasonal == "M": # Multiplicative
+ title = "Trend='N', Seasonal='M': Exponential Smoothing"
+ ETS = ExponentialSmoothing(
+ data,
+ trend=None,
+ damped_trend=False,
+ seasonal="mul",
+ seasonal_periods=seasonal_periods,
+ )
+ model = ETS.fit(smoothing_level=None, smoothing_seasonal=None)
+
+ elif trend == "A": # Additive
+ if seasonal == "N": # None
+ title = "Trend='A', Seasonal='N': Holt’s linear method"
+ ETS = ExponentialSmoothing(
+ data,
+ trend="add",
+ damped_trend=False,
+ seasonal=None,
+ )
+ model = ETS.fit(
+ smoothing_level=None, smoothing_trend=None, damping_trend=None
+ )
+
+ elif seasonal == "A": # Additive
+ title = "Trend='A', Seasonal='A': Additive Holt-Winters’ method"
+ ETS = ExponentialSmoothing(
+ data,
+ trend="add",
+ damped_trend=False,
+ seasonal="add",
+ seasonal_periods=seasonal_periods,
+ )
+ model = ETS.fit(
+ smoothing_level=None, smoothing_trend=None, smoothing_seasonal=None
+ )
+
+ elif seasonal == "M": # Multiplicative
+ title = "Trend='A', Seasonal='M': Multiplicative Holt-Winters’ method"
+ ETS = ExponentialSmoothing(
+ data,
+ trend="add",
+ damped_trend=False,
+ seasonal="mul",
+ seasonal_periods=seasonal_periods,
+ )
+ model = ETS.fit(
+ smoothing_level=None, smoothing_trend=None, smoothing_seasonal=None
+ )
+
+ elif trend == "Ad": # Additive damped
+ if seasonal == "N": # None
+ title = "Trend='Ad', Seasonal='N': Additive damped trend method"
+ ETS = ExponentialSmoothing(
+ data,
+ trend="add",
+ damped_trend=True,
+ seasonal=None,
+ )
+ model = ETS.fit(
+ smoothing_level=None, smoothing_trend=None, damping_trend=None
+ )
+
+ elif seasonal == "A": # Additive
+ title = "Trend='Ad', Seasonal='A': Exponential Smoothing"
+ ETS = ExponentialSmoothing(
+ data,
+ trend="add",
+ damped_trend=True,
+ seasonal="add",
+ seasonal_periods=seasonal_periods,
+ )
+ model = ETS.fit(
+ smoothing_level=None,
+ smoothing_trend=None,
+ damping_trend=None,
+ smoothing_seasonal=None,
+ )
+
+ elif seasonal == "M": # Multiplicative
+ title = "Trend='Ad', Seasonal='M': Holt-Winters’ damped method"
+ ETS = ExponentialSmoothing(
+ data,
+ trend="add",
+ damped_trend=True,
+ seasonal="mul",
+ seasonal_periods=seasonal_periods,
+ )
+ model = ETS.fit(
+ smoothing_level=None,
+ smoothing_trend=None,
+ damping_trend=None,
+ smoothing_seasonal=None,
+ )
+
+ return model, title
diff --git a/gamestonk_terminal/prediction_techniques/pred_menu.py b/gamestonk_terminal/prediction_techniques/pred_menu.py
index 336570ca73e..ae7d82e38b2 100644
--- a/gamestonk_terminal/prediction_techniques/pred_menu.py
+++ b/gamestonk_terminal/prediction_techniques/pred_menu.py
@@ -1,6 +1,7 @@
import argparse
from gamestonk_terminal.prediction_techniques import sma
+from gamestonk_terminal.prediction_techniques import ets
from gamestonk_terminal.prediction_techniques import knn
from gamestonk_terminal.prediction_techniques import regression
from gamestonk_terminal.prediction_techniques import arima
@@ -27,6 +28,7 @@ def print_prediction(s_ticker, s_start, s_interval):
print(" quit quit to abandon program")
print("")
print(" sma simple moving average")
+ print(" ets exponential smoothing (e.g. Holt-Winters)")
print(" knn k-Nearest Neighbors")
print(" linear linear regression (polynomial 1)")
print(" quadratic quadratic regression (polynomial 2)")
@@ -51,6 +53,7 @@ def pred_menu(df_stock, s_ticker, s_start, s_interval):
"q",
"quit",
"sma",
+ "ets",
"knn",
"linear",
"quadratic",
@@ -96,6 +99,9 @@ def pred_menu(df_stock, s_ticker, s_start, s_interval):
elif ns_known_args.cmd == "sma":
sma.simple_moving_average(l_args, s_ticker, df_stock)
+ elif ns_known_args.cmd == "ets":
+ ets.exponential_smoothing(l_args, s_ticker, df_stock)
+
elif ns_known_args.cmd == "knn":
knn.k_nearest_neighbors(l_args, s_ticker, df_stock)