diff --git a/gamestonk_terminal/common/prediction_techniques/mc_model.py b/gamestonk_terminal/common/prediction_techniques/mc_model.py new file mode 100644 index 00000000000..4b23e2b0047 --- /dev/null +++ b/gamestonk_terminal/common/prediction_techniques/mc_model.py @@ -0,0 +1,47 @@ +"""Monte Carlo Model""" +__docformat__ = "numpy" + +from typing import Union +import pandas as pd +import numpy as np +from scipy.stats import norm + + +def get_mc_brownian( + data: Union[pd.Series, np.array], n_future: int, n_sims: int, use_log=True +) -> np.array: + """Performs monte carlo forecasting for brownian motion with drift + + Parameters + ---------- + data : Union[pd.Series, np.array] + Input data. + n_future : int + Number of future steps + n_sims : int + Number of simulations to run + use_log : bool, optional + Flag to use log returns, by default True + + Returns + ------- + np.array + Array of predictions. Has shape (n_future, n_sims) + """ + changes = data.pct_change().dropna() + if use_log: + changes = np.log(1 + changes) + + dist_mean = changes.mean() + dist_var = changes.var() + dist_drift = dist_mean - 0.5 * dist_var + dist_std = np.sqrt(dist_var) + + random_steps = norm.ppf(np.random.rand(n_future, n_sims)) + predicted_change = np.exp(dist_drift + dist_std * random_steps) + possible_paths = np.zeros_like(predicted_change) + possible_paths[0] = data.values[-1] + + for t in range(1, n_future): + possible_paths[t] = possible_paths[t - 1] * predicted_change[t] + return possible_paths diff --git a/gamestonk_terminal/common/prediction_techniques/mc_view.py b/gamestonk_terminal/common/prediction_techniques/mc_view.py new file mode 100644 index 00000000000..3ec0e2c29ca --- /dev/null +++ b/gamestonk_terminal/common/prediction_techniques/mc_view.py @@ -0,0 +1,70 @@ +"""Monte Carlo View""" +__docformat__ = "numpy" + +from typing import Union +import os + +import pandas as pd +import numpy as np +import seaborn as sns +import matplotlib.pyplot as plt +import matplotlib.dates as mdates + +from gamestonk_terminal.common.prediction_techniques import mc_model +from gamestonk_terminal.helper_funcs import ( + plot_autoscale, + get_next_stock_market_days, + export_data, +) +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal import feature_flags as gtff + + +def display_mc_forecast( + data: Union[pd.Series, np.array], + n_future: int, + n_sims: int, + use_log=True, + export: str = "", +): + """Display monte carlo forecasting + + Parameters + ---------- + data : Union[pd.Series, np.array] + Data to forecast + n_future : int + Number of days to forecast + n_sims : int + Number of simulations to run + use_log : bool, optional + Flag to use lognormal, by default True + export: str + Format to export data + """ + predicted_values = mc_model.get_mc_brownian(data, n_future, n_sims, use_log) + future_index = get_next_stock_market_days(data.index[-1], n_next_days=n_future) + dateFmt = mdates.DateFormatter("%m/%d/%Y") + + fig, ax = plt.subplots(1, 2, figsize=plot_autoscale(), dpi=PLOT_DPI) + + ax[0].plot(data) + ax[0].plot(future_index, predicted_values, alpha=0.3) + ax[0].set_title("Data Predictions") + ax[0].xaxis.set_major_formatter(dateFmt) + ax[0].tick_params(axis="x", labelrotation=45) + ax[0].grid("on") + + sns.histplot(predicted_values[-1, :], ax=ax[1], kde=True) + ax[1].set_xlabel("Price") + ax[1].set_title(f"Distribution of data after {n_future} days.") + ax[1].set_xlim(np.min(predicted_values[-1, :]), np.max(predicted_values[-1, :])) + ax[1].grid("on") + + fig.tight_layout(pad=2) + + if gtff.USE_ION: + plt.ion() + plt.show() + export_data(export, os.path.dirname(os.path.abspath(__file__)), "mc") + print("") diff --git a/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py b/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py index 6cb7cfc8655..5c26251590b 100644 --- a/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py +++ b/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py @@ -27,6 +27,7 @@ from gamestonk_terminal.common.prediction_techniques import ( neural_networks_view, regression_view, pred_helper, + mc_view, ) from gamestonk_terminal.stocks.stocks_helper import load @@ -46,6 +47,7 @@ class PredictionTechniquesController: "rnn", "lstm", "conv1d", + "mc", ] CHOICES += CHOICES_MODELS @@ -105,6 +107,7 @@ Models: rnn Recurrent Neural Network lstm Long-Short Term Memory conv1d 1D Convolutional Neural Network + mc Monte-Carlo simulations """ print(help_string) @@ -724,6 +727,59 @@ Models: finally: pred_helper.restore_env() + def call_mc(self, other_args: List[str]): + """Process mc command""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + add_help=False, + prog="mc", + description=""" + Perform Monte Carlo forecasting + """, + ) + parser.add_argument( + "-d", + "--days", + help="Days to predict", + dest="n_days", + type=check_positive, + default=30, + ) + parser.add_argument( + "-n", + "--num", + help="Number of simulations to perform", + dest="n_sims", + default=100, + ) + parser.add_argument( + "--dist", + choices=["normal", "lognormal"], + default="lognormal", + dest="dist", + help="Whether to model returns or log returns", + ) + + try: + ns_parser = parse_known_args_and_warn( + parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED + ) + if not ns_parser: + return + if self.target != "AdjClose": + print("MC Prediction designed for AdjClose prices") + return + + mc_view.display_mc_forecast( + data=self.stock[self.target], + n_future=ns_parser.n_days, + n_sims=ns_parser.n_sims, + use_log=ns_parser.dist == "lognormal", + export=ns_parser.export, + ) + except Exception as e: + print(e, "\n") + def menu(ticker: str, start: datetime, interval: str, stock: pd.DataFrame): """Prediction Techniques Menu""" diff --git a/website/content/stocks/prediction_techniques/mc/_index.md b/website/content/stocks/prediction_techniques/mc/_index.md new file mode 100644 index 00000000000..583003a56ee --- /dev/null +++ b/website/content/stocks/prediction_techniques/mc/_index.md @@ -0,0 +1,20 @@ +``` +usage: mc [-d N_DAYS] [-n N_SIMS] [--dist {normal,lognormal}] [-h] [--export {png,jpg,pdf,svg}] +``` + +Perform Monte Carlo forecasting + +``` +optional arguments: + -d N_DAYS, --days N_DAYS + Days to predict (default: 30) + -n N_SIMS, --num N_SIMS + Number of simulations to perform (default: 100) + --dist {normal,lognormal} + Whether to model returns or log returns (default: lognormal) + -h, --help show this help message (default: False) + --export {png,jpg,pdf,svg} + Export figure into png, jpg, pdf, svg (default: ) +``` + +![MC_fig](https://user-images.githubusercontent.com/18151143/138540365-55a253f4-e02f-406d-9398-984d988888d7.png) diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index 0749762b21d..c99693f3137 100755 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -508,6 +508,8 @@ main: ref: "/stocks/prediction_techniques/lstm" - name: conv1d ref: "/stocks/prediction_techniques/conv1d" + - name: mc + ref: "/stocks/prediction_techniques/mc" - name: report ref: "/stocks/report" - name: forex