Merge pull request #132 from DidierRLopes/ets

This commit is contained in:
DidierRLopes
2021-03-08 09:20:30 +00:00
committed by GitHub
3 changed files with 340 additions and 16 deletions

View File

@@ -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:
![sma](https://user-images.githubusercontent.com/25267873/108604945-d29aea80-73a8-11eb-8dac-6a545b9c52b9.png)
## ets <a name="ets"></a>
```
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.
![ets_pltr](https://user-images.githubusercontent.com/25267873/110266847-97a6d280-7fb6-11eb-997e-0b598abc713b.png)
## knn <a name="knn"></a>
```
usage: knn [-i N_INPUTS] [-d N_DAYS] [-j N_JUMPS] [-n N_NEIGHBORS]
@@ -125,7 +139,7 @@ Facebook's Prophet:
## mlp <a name="mlp"></a>
```
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 <a name="rnn"></a>
```
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 <a name="lstm"></a>
```
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'} }]
```

View File

@@ -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

View File

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