diff --git a/gamestonk_terminal/prediction_techniques/ets.py b/gamestonk_terminal/prediction_techniques/ets.py index 9f15c595217..a802b0cc977 100644 --- a/gamestonk_terminal/prediction_techniques/ets.py +++ b/gamestonk_terminal/prediction_techniques/ets.py @@ -1,4 +1,5 @@ import argparse +import datetime import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -6,9 +7,15 @@ 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, + valid_date, + patch_pandas_text_adjustment, + get_next_stock_market_days, +) +from gamestonk_terminal.prediction_techniques.pred_helper import ( print_pretty_prediction, + price_prediction_backtesting_color, + print_prediction_kpis, ) from gamestonk_terminal import feature_flags as gtff @@ -94,12 +101,55 @@ def exponential_smoothing(l_args, s_ticker, df_stock): default=5, help="Seasonal periods.", ) + parser.add_argument( + "-e", + "--end", + action="store", + type=valid_date, + dest="s_end_date", + default=None, + help="The end date (format YYYY-MM-DD) to select - Backtesting", + ) try: ns_parser = parse_known_args_and_warn(parser, l_args) if not ns_parser: return + # BACKTESTING + if ns_parser.s_end_date: + + if ns_parser.s_end_date < df_stock.index[0]: + print( + "Backtesting not allowed, since End Date is older than Start Date of historical data\n" + ) + return + + if ( + ns_parser.s_end_date + < get_next_stock_market_days( + last_stock_day=df_stock.index[0], n_next_days=5 + ns_parser.n_days + )[-1] + ): + print( + "Backtesting not allowed, since End Date is too close to Start Date to train model\n" + ) + return + + future_index = get_next_stock_market_days( + last_stock_day=ns_parser.s_end_date, n_next_days=ns_parser.n_days + ) + + if future_index[-1] > datetime.datetime.now(): + print( + "Backtesting not allowed, since End Date + Prediction days is in the future\n" + ) + return + + df_future = df_stock[future_index[0] : future_index[-1]] + df_stock = df_stock[: ns_parser.s_end_date] + + # Get ETS model model, title = get_exponential_smoothing_model( df_stock["5. adjusted close"].values, ns_parser.trend, @@ -131,7 +181,11 @@ def exponential_smoothing(l_args, s_ticker, df_stock): # Plotting plt.figure() plt.plot(df_stock.index, df_stock["5. adjusted close"], lw=2) - plt.title(f"{title} on {s_ticker}") + # BACKTESTING + if ns_parser.s_end_date: + plt.title(f"BACKTESTING: {title} on {s_ticker}") + else: + plt.title(f"{title} on {s_ticker}") plt.xlim( df_stock.index[0], @@ -168,15 +222,161 @@ def exponential_smoothing(l_args, s_ticker, df_stock): color="k", ) + # BACKTESTING + if ns_parser.s_end_date: + plt.plot( + df_future.index, + df_future["5. adjusted close"], + lw=2, + c="tab:blue", + ls="--", + ) + plt.plot( + [df_stock.index[-1], df_future.index[0]], + [ + df_stock["5. adjusted close"].values[-1], + df_future["5. adjusted close"].values[0], + ], + lw=1, + c="tab:blue", + linestyle="--", + ) + if gtff.USE_ION: plt.ion() plt.show() - # Print prediction data - print_pretty_prediction( - df_pred, df_stock["5. adjusted close"].values[-1] - ) + # BACKTESTING + if ns_parser.s_end_date: + plt.figure() + plt.subplot(211) + plt.plot( + df_future.index, + df_future["5. adjusted close"], + lw=2, + c="tab:blue", + ls="--", + ) + plt.plot(df_pred.index, df_pred, lw=2, c="green") + plt.scatter( + df_future.index, + df_future["5. adjusted close"], + c="tab:blue", + lw=3, + ) + plt.plot( + [df_stock.index[-1], df_future.index[0]], + [ + df_stock["5. adjusted close"].values[-1], + df_future["5. adjusted close"].values[0], + ], + lw=2, + c="tab:blue", + ls="--", + ) + plt.scatter(df_pred.index, df_pred, c="green", lw=3) + plt.plot( + [df_stock.index[-1], df_pred.index[0]], + [df_stock["5. adjusted close"].values[-1], df_pred.values[0]], + lw=2, + c="green", + ls="--", + ) + plt.title("BACKTESTING: Real data price versus Prediction") + plt.xlim( + df_stock.index[-1], + df_pred.index[-1] + datetime.timedelta(days=1), + ) + 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.legend(["Real data", "Prediction data"]) + plt.xticks([]) + + plt.subplot(212) + plt.axhline(y=0, color="k", linestyle="--", linewidth=2) + plt.plot( + df_future.index, + 100 + * (df_pred.values - df_future["5. adjusted close"].values) + / df_future["5. adjusted close"].values, + lw=2, + c="red", + ) + plt.scatter( + df_future.index, + 100 + * (df_pred.values - df_future["5. adjusted close"].values) + / df_future["5. adjusted close"].values, + c="red", + lw=5, + ) + plt.title("BACKTESTING: Error between Real data and Prediction [%]") + plt.plot( + [df_stock.index[-1], df_future.index[0]], + [ + 0, + 100 + * ( + df_pred.values[0] + - df_future["5. adjusted close"].values[0] + ) + / df_future["5. adjusted close"].values[0], + ], + lw=2, + ls="--", + c="red", + ) + plt.xlim( + df_stock.index[-1], + df_pred.index[-1] + datetime.timedelta(days=1), + ) + plt.xlabel("Time") + plt.ylabel("Prediction Error (%)") + 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.legend(["Real data", "Prediction data"]) + + if gtff.USE_ION: + plt.ion() + + plt.show() + + # Refactor prediction dataframe for backtesting print + df_pred.name = "Prediction" + df_pred = df_pred.to_frame() + df_pred["Real"] = df_future["5. adjusted close"] + + if gtff.USE_COLOR: + + patch_pandas_text_adjustment() + + print("Time Real [$] x Prediction [$]") + print( + df_pred.apply( + price_prediction_backtesting_color, axis=1 + ).to_string() + ) + else: + print(df_pred[["Real", "Prediction"]].round(2).to_string()) + + print("") + print_prediction_kpis( + df_pred["Real"].values, df_pred["Prediction"].values + ) + + else: + # Print prediction data + print_pretty_prediction( + df_pred, df_stock["5. adjusted close"].values[-1] + ) print("") else: