From 7523fd7901fb1b4bb7771eea394cabcd0fa5f676 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 5 May 2021 15:20:14 -0400 Subject: [PATCH] make general "backtest" function / change default stock load date --- gamestonk_terminal/main_helper.py | 2 +- .../neural_networks_helper.py | 12 ++ .../prediction_techniques/pred_helper.py | 202 +++++++++++++++++- .../prediction_techniques/sma_view.py | 33 +-- 4 files changed, 218 insertions(+), 31 deletions(-) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 30ef54f0af0..8cd282145f6 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -145,7 +145,7 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock): "-s", "--start", type=valid_date, - default="2015-01-01", + default="2019-01-01", dest="s_start_date", help="The starting date (format YYYY-MM-DD) of the stock", ) diff --git a/gamestonk_terminal/prediction_techniques/neural_networks_helper.py b/gamestonk_terminal/prediction_techniques/neural_networks_helper.py index e69de29bb2d..125105080ea 100644 --- a/gamestonk_terminal/prediction_techniques/neural_networks_helper.py +++ b/gamestonk_terminal/prediction_techniques/neural_networks_helper.py @@ -0,0 +1,12 @@ +"""Helper functions""" +__docformat__ = "numpy" +from typing import List, Tuple +from datetime import datetime +import pandas as pd +import numpy as np +from matplotlib import pyplot as plt +from gamestonk_terminal.helper_funcs import ( + get_next_stock_market_days, + patch_pandas_text_adjustment, + plot_autoscale, +) diff --git a/gamestonk_terminal/prediction_techniques/pred_helper.py b/gamestonk_terminal/prediction_techniques/pred_helper.py index fd80ded4824..6cd4624863e 100644 --- a/gamestonk_terminal/prediction_techniques/pred_helper.py +++ b/gamestonk_terminal/prediction_techniques/pred_helper.py @@ -1,16 +1,216 @@ """ Prediction helper functions """ __docformat__ = "numpy" +from typing import Tuple +from datetime import datetime import numpy as np import pandas as pd +from pandas.plotting import register_matplotlib_converters +import matplotlib.pyplot as plt from colorama import Fore, Style from sklearn.metrics import ( mean_absolute_error, r2_score, mean_squared_error, ) - +from gamestonk_terminal.helper_funcs import ( + get_next_stock_market_days, + patch_pandas_text_adjustment, + plot_autoscale, +) from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.config_plot import PLOT_DPI + +register_matplotlib_converters() + + +def get_backtesting_data( + df_stock: pd.DataFrame, parsed_end_date: datetime, pred_days: int +) -> Tuple[pd.DataFrame, pd.DataFrame, bool]: + """ + + Parameters + ---------- + df_stock: pd.DataFrame + Dataframe of stock prices + parsed_end_date: datetime + Date that seperates test from prediction + pred_days: int + Number of days to predict + + Returns + ------- + df_future: pd.DataFrame + Dataframe of future prices + df_stock: pd.DataFrame + Dataframe of past prices + Flag: bool + Returns True if backtesting meets requirements, False if there are errors + + """ + if parsed_end_date < df_stock.index[0]: + print( + "Backtesting not allowed, since End Date is older than Start Date of historical data\n" + ) + return pd.DataFrame(), pd.DataFrame(), False + + if ( + parsed_end_date + < get_next_stock_market_days( + last_stock_day=df_stock.index[0], n_next_days=5 + pred_days + )[-1] + ): + print( + "Backtesting not allowed, since End Date is too close to Start Date to train model\n" + ) + return pd.DataFrame(), pd.DataFrame(), False + + future_index = get_next_stock_market_days( + last_stock_day=parsed_end_date, n_next_days=pred_days + ) + + if future_index[-1] > datetime.now(): + print( + "Backtesting not allowed, since End Date + Prediction days is in the future\n" + ) + return pd.DataFrame(), pd.DataFrame(), False + + df_future = df_stock[future_index[0] : future_index[-1]] + df_stock = df_stock[:parsed_end_date] + return df_future, df_stock, True + + +def plot_pred( + df_stock: pd.DataFrame, + df_future: pd.DataFrame, + df_pred: pd.DataFrame, + bt_flag: bool, +): + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + plt.plot(df_stock.index, df_stock["5. adjusted close"], lw=2) + # BACKTESTING + if ns_parser.s_end_date: + plt.title( + f"BACKTESTING: {ns_parser.n_length} Moving Average on {s_ticker} - {ns_parser.n_days} days prediction" + ) + else: + plt.title( + f"{ns_parser.n_length} Moving Average 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]) + 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) + df_ma = df_stock["5. adjusted close"].rolling(window=ns_parser.n_length).mean() + plt.plot(df_ma.index, df_ma, lw=2, linestyle="--", c="tab:orange") + 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") + + if bt_flag: + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + 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() + + pass def price_prediction_color(val: float, last_val: float) -> str: diff --git a/gamestonk_terminal/prediction_techniques/sma_view.py b/gamestonk_terminal/prediction_techniques/sma_view.py index 23fd35fb486..902939018a9 100644 --- a/gamestonk_terminal/prediction_techniques/sma_view.py +++ b/gamestonk_terminal/prediction_techniques/sma_view.py @@ -22,6 +22,7 @@ from gamestonk_terminal.prediction_techniques.pred_helper import ( print_pretty_prediction, price_prediction_backtesting_color, print_prediction_kpis, + get_backtesting_data, ) from gamestonk_terminal.config_plot import PLOT_DPI @@ -30,7 +31,6 @@ from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -# pylint: disable=unused-argument def simple_moving_average(other_args: List[str], s_ticker: str, df_stock: pd.DataFrame): """ Run an SMA model @@ -96,37 +96,12 @@ def simple_moving_average(other_args: List[str], s_ticker: str, df_stock: pd.Dat # 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 + df_future, df_stock, bt_flag = get_backtesting_data( + df_stock, ns_parser.s_end_date, 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" - ) + if not bt_flag: return - df_future = df_stock[future_index[0] : future_index[-1]] - df_stock = df_stock[: ns_parser.s_end_date] - # Prediction data l_predictions = list() # type: List for pred_day in range(ns_parser.n_days):