From 83c7ce54ca2d95d00ad629cde83991e9cc8b7743 Mon Sep 17 00:00:00 2001 From: didier Date: Sat, 13 Mar 2021 22:57:14 +0000 Subject: [PATCH] ARIMA backtesting --- .../prediction_techniques/arima.py | 215 +++++++++++++++++- 1 file changed, 206 insertions(+), 9 deletions(-) diff --git a/gamestonk_terminal/prediction_techniques/arima.py b/gamestonk_terminal/prediction_techniques/arima.py index a860a5ab00a..5ed5b4e45b7 100644 --- a/gamestonk_terminal/prediction_techniques/arima.py +++ b/gamestonk_terminal/prediction_techniques/arima.py @@ -1,4 +1,5 @@ import argparse +import datetime import matplotlib.pyplot as plt import pandas as pd from pandas.plotting import register_matplotlib_converters @@ -6,9 +7,15 @@ import pmdarima from statsmodels.tsa.arima.model import ARIMA 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 @@ -75,12 +82,54 @@ def arima(l_args, s_ticker, df_stock): default=False, help="results about ARIMA summary flag.", ) + 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] + # Machine Learning model if ns_parser.s_order: t_order = tuple([int(ord) for ord in list(ns_parser.s_order)]) @@ -122,13 +171,25 @@ def arima(l_args, s_ticker, df_stock): plt.figure() plt.plot(df_stock.index, df_stock["5. adjusted close"], lw=2) if ns_parser.s_order: - plt.title( - f"ARIMA {str(t_order)} on {s_ticker} - {ns_parser.n_days} days prediction" - ) + # BACKTESTING + if ns_parser.s_end_date: + plt.title( + f"BACKTESTING: ARIMA {str(t_order)} on {s_ticker} - {ns_parser.n_days} days prediction" + ) + else: + plt.title( + f"ARIMA {str(t_order)} on {s_ticker} - {ns_parser.n_days} days prediction" + ) else: - plt.title( - f"ARIMA {model.order} on {s_ticker} - {ns_parser.n_days} days prediction" - ) + # BACKTESTING + if ns_parser.s_end_date: + plt.title( + f"BACKTESTING: ARIMA {model.order} on {s_ticker} - {ns_parser.n_days} days prediction" + ) + else: + plt.title( + f"ARIMA {model.order} on {s_ticker} - {ns_parser.n_days} days prediction" + ) plt.xlim( df_stock.index[0], get_next_stock_market_days(df_pred.index[-1], 1)[-1] ) @@ -153,13 +214,149 @@ def arima(l_args, s_ticker, df_stock): df_stock.index[-1], ymin, ymax, linewidth=1, linestyle="--", 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.xticks( + [df_stock.index[-1], df_pred.index[-1] + datetime.timedelta(days=1)], + visible=True, + ) + 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.xticks( + [df_stock.index[-1], df_pred.index[-1] + datetime.timedelta(days=1)], + visible=True, + ) + 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("") except Exception as e: