diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50d750018fa..7f7dacfa7ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -238,6 +238,7 @@ Yahoo Finance: 5. Parse known args from list of arguments and values provided by the user. 6. Call the function contained in a `_view.py` file with the arguments parsed by argparse. ``` +@try_except def call_shorted(self, other_args: List[str]): """Process shorted command""" parser = argparse.ArgumentParser( @@ -263,22 +264,19 @@ def call_shorted(self, other_args: List[str]): dest="export", help="Export dataframe data to csv,json,xlsx file", ) - try: - if other_args: - if "-" not in other_args[0]: - other_args.insert(0, "-n") + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-n") - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return - yahoofinance_view.display_most_shorted( - num_stocks=ns_parser.num, - export=ns_parser.export, - ) + yahoofinance_view.display_most_shorted( + num_stocks=ns_parser.num, + export=ns_parser.export, + ) - except Exception as e: - print(e, "\n") ``` diff --git a/README.md b/README.md index 8a2cb6829c9..35752162faa 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,6 @@ To read more on adding the environment keys and how to configure your X-server t If you'd like to see a video recording of the installation process, @JohnnyDankseed has made one available [here](https://www.youtube.com/watch?v=-DJJ-cfquDA). -The user @mchow01 has made available a tutorial on [how to run the terminal on an Apple M1](https://mchow01.github.io/macos/system/gamestonk/2021/05/27/gamestonk-terminal-m1.html). - This project supports Python 3.7, 3.8 and 3.9. Our current recommendation is to use this project with Anaconda's Python distribution - either full [__Anaconda3 Latest__](https://repo.anaconda.com/archive/) or [__Miniconda3 Latest__](https://repo.anaconda.com/archive/). Several features in this project utilize Machine Learning. Machine Learning Python dependencies are optional. If you decided to add Machine Learning features at a later point, you will likely have better user experience with Anaconda's Python distribution. diff --git a/gamestonk_terminal/portfolio/portfolio_controller.py b/gamestonk_terminal/portfolio/portfolio_controller.py index 0e7714651cb..41ba5161945 100644 --- a/gamestonk_terminal/portfolio/portfolio_controller.py +++ b/gamestonk_terminal/portfolio/portfolio_controller.py @@ -3,8 +3,13 @@ __docformat__ = "numpy" import argparse import os +from os import listdir +from os.path import isfile, join +from typing import List +from datetime import datetime from prompt_toolkit.completion import NestedCompleter +import pandas as pd from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import ( @@ -12,13 +17,23 @@ from gamestonk_terminal.helper_funcs import ( MENU_GO_BACK, MENU_QUIT, MENU_RESET, + try_except, + valid_date, + check_positive_float, ) from gamestonk_terminal.menu import session from gamestonk_terminal.portfolio.brokers import bro_controller from gamestonk_terminal.portfolio.portfolio_analysis import pa_controller from gamestonk_terminal.portfolio.portfolio_optimization import po_controller +from gamestonk_terminal.portfolio import ( + portfolio_view, + portfolio_model, + yfinance_model, + portfolio_helper, +) +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn -# pylint: disable=R1710 +# pylint: disable=R1710,E1101 class PortfolioController: @@ -37,6 +52,13 @@ class PortfolioController: "bro", "pa", "po", + "load", + "save", + "show", + "add", + "rmv", + "ar", + "rmr", ] CHOICES += CHOICES_MENUS @@ -51,6 +73,18 @@ class PortfolioController: self.completer = NestedCompleter.from_nested_dict( {c: None for c in self.CHOICES} ) + self.portfolio = pd.DataFrame( + columns=[ + "Name", + "Type", + "Quantity", + "Date", + "Price", + "Fees", + "Premium", + "Side", + ] + ) def print_help(self): """Print help""" @@ -67,6 +101,19 @@ What do you want to do? > bro brokers holdings, \t\t supports: robinhood, alpaca, ally, degiro > pa portfolio analysis, \t\t analyses your custom portfolio > po portfolio optimization, \t optimal portfolio weights from pyportfolioopt + +Portfolio: + load load data into the portfolio + save save your portfolio for future use + show show existing transactions + add add a security to your portfolio + rmv remove a security from your portfolio + +Reports: + ar annual report for performance of a given portfolio + +Graphs: + rmr graph your returns versus the market's returns """ print(help_text) @@ -143,6 +190,259 @@ What do you want to do? else: return True + @try_except + def call_load(self, other_args: List[str]): + """Process load command""" + path = os.path.dirname(os.path.abspath(__file__)) + path = os.path.abspath(os.path.join(path, "portfolios")) + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="load", + description="Load your portfolio", + ) + parser.add_argument( + "-n", + "--name", + type=str, + choices=[f for f in listdir(path) if isfile(join(path, f))], + dest="name", + required="-h" not in other_args, + help="Name of file to be saved", + ) + if other_args: + if "-n" not in other_args and "-h" not in other_args: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + self.portfolio = portfolio_model.load_df(ns_parser.name) + print("") + + @try_except + def call_save(self, other_args: List[str]): + """Process save command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="save", + description="Save your portfolio", + ) + parser.add_argument( + "-n", + "--name", + type=str, + dest="name", + required="-h" not in other_args, + help="Name of file to be saved", + ) + if other_args and "-n" not in other_args and "-h" not in other_args: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + if ( + ".csv" not in ns_parser.name + and ".xlsx" not in ns_parser.name + and ".json" not in ns_parser.name + ): + print( + "Please submit as 'filename.filetype' with filetype being csv, xlsx, or json\n" + ) + + portfolio_model.save_df(self.portfolio, ns_parser.name) + + def call_show(self, _): + """Process show command""" + portfolio_view.show_df(self.portfolio, False) + + def call_add(self, other_args: List[str]): + """Process add command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="add", + description="Adds an item to your portfolio", + ) + parser.add_argument( + "-n", + "--name", + type=str, + dest="name", + required="-h" not in other_args, + help="Name of item to be added (for example a ticker for a stock)", + ) + parser.add_argument( + "-t", + "--type", + dest="type", + type=str, + choices=["stock", "cash"], # "bond", "option", "crypto", + default="stock", + help="Type of asset to add", + ) + parser.add_argument( + "-q", + "--quantity", + dest="quantity", + type=check_positive_float, + default=1, + help="Amounts of the asset owned", + ) + parser.add_argument( + "-d", + "--date", + dest="date", + type=valid_date, + default=datetime.now(), + help="Date: yyyy/mm/dd", + ) + parser.add_argument( + "-p", + "--price", + dest="price", + type=check_positive_float, + required="-h" not in other_args, + help="Price purchased for asset", + ) + parser.add_argument( + "-f", + "--fees", + dest="fees", + type=check_positive_float, + help="Fees paid for transaction", + ) + # parser.add_argument( + # "-r", + # "--premium", + # dest="premium", + # type=check_positive_float, + # help="Premium paid/received for the option", + # ) + parser.add_argument( + "-a", + "--action", + type=str, + dest="action", + choices=["buy", "sell", "interest", "deposit", "withdrawal"], + default="buy", + help="Select what you did in the transaction", + ) + if other_args: + if "-n" not in other_args and "-h" not in other_args: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + if ns_parser.type == "cash" and ns_parser.action not in [ + "deposit", + "withdrawal", + ]: + print("Cash can only be deposited or withdrew\n") + return + if ns_parser.type != "cash" and ns_parser.action in ["deposit", "withdrawal"]: + print("Only cash can be deposited or withdrew\n") + return + + if ns_parser.type == "stock": + if not portfolio_helper.is_ticker(ns_parser.name): + print("Invalid ticker\n") + return + + data = { + "Name": ns_parser.name, + "Type": ns_parser.type, + "Quantity": ns_parser.quantity, + "Date": ns_parser.date, + "Price": ns_parser.price, + "Fees": ns_parser.fees, + "Premium": None, # ns_parser.premium + "Side": ns_parser.action, + } + self.portfolio = self.portfolio.append([data]) + self.portfolio.index = list(range(0, len(self.portfolio.values))) + print(f"{ns_parser.name.upper()} successfully added\n") + + def call_rmv(self, _): + """Process rmv command""" + portfolio_view.show_df(self.portfolio, True) + to_rmv = int(input("\nType the index number you want to remove:\n")) + if 0 <= to_rmv < len(self.portfolio.index): + self.portfolio = self.portfolio.drop(self.portfolio.index[to_rmv]) + self.portfolio.index = list(range(0, len(self.portfolio.values))) + else: + print( + f"Invalid index please use an integer between 0 and {len(self.portfolio.index)-1}\n" + ) + + @try_except + def call_ar(self, other_args: List[str]): + """Process ar command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ar", + description="Create an annual report based on given portfolio", + ) + parser.add_argument( + "-m", + "--market", + type=str, + dest="market", + default="SPY", + help="Choose a ticker to be the market asset", + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + if self.portfolio.empty: + print("Please add items to the portfolio\n") + return + + val, hist = portfolio_model.convert_df(self.portfolio) + if not val.empty: + portfolio_view.annual_report(val, hist, ns_parser.market) + + @try_except + def call_rmr(self, other_args: List[str]): + """Process rmr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ret", + description="Graph of portfolio returns versus market returns", + ) + parser.add_argument( + "-m", + "--market", + type=str, + dest="market", + default="SPY", + help="Choose a ticker to be the market asset", + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + if self.portfolio.empty: + print("Please add items to the portfolio\n") + return + + val, _ = portfolio_model.convert_df(self.portfolio) + if not val.empty: + df_m = yfinance_model.get_market(val.index[0], ns_parser.market) + portfolio_view.plot_overall_return(val, df_m, 365, ns_parser.market, True) + else: + print("Cannot generate a graph from an empty dataframe\n") + def menu(): """Portfolio Menu""" diff --git a/gamestonk_terminal/portfolio/portfolio_helper.py b/gamestonk_terminal/portfolio/portfolio_helper.py new file mode 100644 index 00000000000..05c79ea8aff --- /dev/null +++ b/gamestonk_terminal/portfolio/portfolio_helper.py @@ -0,0 +1,21 @@ +"""Portfolio Helper""" +__docformat__ = "numpy" + +import yfinance as yf + + +def is_ticker(ticker: str) -> bool: + """Determine whether a string is a valid ticker + + Parameters + ---------- + ticker : str + The string to be tested + + Returns + ---------- + answer : bool + Whether the string is a ticker + """ + item = yf.Ticker(ticker) + return "previousClose" in item.info diff --git a/gamestonk_terminal/portfolio/portfolio_model.py b/gamestonk_terminal/portfolio/portfolio_model.py new file mode 100644 index 00000000000..775dd9583d5 --- /dev/null +++ b/gamestonk_terminal/portfolio/portfolio_model.py @@ -0,0 +1,351 @@ +"""Portfolio Model""" +__docformat__ = "numpy" + +import os +import math +from datetime import date, timedelta, datetime +from typing import List + +import numpy as np +import pandas as pd + +from gamestonk_terminal.portfolio import portfolio_view, yfinance_model + +# pylint: disable=E1136 +# pylint: disable=unsupported-assignment-operation + + +def save_df(df: pd.DataFrame, name: str) -> None: + """Saves the portfolio as a csv + + Parameters + ---------- + df : pd.DataFrame + The dataframe to be saved + name : str + The name of the string + """ + path = os.path.dirname(os.path.abspath(__file__)) + path = os.path.abspath(os.path.join(path, "portfolios", name)) + if ".csv" in name: + df.to_csv(path, index=False) + elif ".json" in name: + df.to_json(path, index=False) + elif ".xlsx" in name: + df = df.to_excel(path, index=False, engine="openpyxl") + + +def load_df(name: str) -> pd.DataFrame: + """Load the portfolio from a csv + + Parameters + ---------- + name : str + The name of the string + + Returns + ---------- + data : pd.DataFrame + A DataFrame with historical trading information + """ + if ".csv" not in name and ".xlsx" not in name and ".json" not in name: + print( + "Please submit as 'filename.filetype' with filetype being csv, xlsx, or json\n" + ) + return pd.DataFrame() + + try: + if ".csv" in name: + df = pd.read_csv(f"gamestonk_terminal/portfolio/portfolios/{name}") + elif ".json" in name: + df = pd.read_json(f"gamestonk_terminal/portfolio/portfolios/{name}") + elif ".xlsx" in name: + df = pd.read_excel( + f"gamestonk_terminal/portfolio/portfolios/{name}", engine="openpyxl" + ) + + df.index = list(range(0, len(df.values))) + df["Name"] = df["Name"].str.lower() + df["Type"] = df["Type"].str.lower() + df["Date"] = pd.to_datetime(df["Date"], format="%Y/%m/%d") + return df + except FileNotFoundError: + portfolio_view.load_info() + return pd.DataFrame() + + +def add_values( + log: pd.DataFrame, changes: pd.DataFrame, cashes: pd.DataFrame +) -> pd.DataFrame: + """Creates a new df with performance results + + Parameters + ---------- + log : pd.DataFrame + The dataframe that will have daily holdings + changes : pd.DataFrame + Transactions that changed holdings + cashes : pd.DataFrame + Cash changing transactions + + Returns + ---------- + log : pd.DataFrame + A dataframe with daily holdings + """ + for index, _ in log.iterrows(): + # Add stocks to dataframe + values = changes[changes["Date"] == index] + if len(values.index) > 0: + for _, sub_row in values.iterrows(): + ticker = sub_row["Name"] + quantity = sub_row["Quantity"] + price = sub_row["Price"] + fees = sub_row["Fees"] + if math.isnan(fees): + fees = 0 + sign = -1 if sub_row["Side"].lower() == "sell" else 1 + pos1 = log.cumsum().at[index, ("Quantity", ticker)] > 0 + pos2 = (quantity * sign) > 0 + + if sub_row["Side"].lower() == "interest": + log.at[index, ("Cost Basis", ticker)] = ( + log.at[index, ("Cost Basis", ticker)] + quantity * price + ) + log.at[index, ("Cash", "Cash")] = log.at[ + index, ("Cash", "Cash") + ] - (quantity * price) + + elif ( + pos1 == pos2 + or log.cumsum().at[index, ("Quantity", ticker)] == 0 + or (quantity * sign) == 0 + ): + log.at[index, ("Quantity", ticker)] = ( + log.at[index, ("Quantity", ticker)] + quantity * sign + ) + log.at[index, ("Cost Basis", ticker)] = ( + log.at[index, ("Cost Basis", ticker)] + + fees + + quantity * sign * price + ) + log.at[index, ("Cash", "Cash")] = log.at[ + index, ("Cash", "Cash") + ] - (fees + quantity * sign * price) + else: + rev = ( + log.at[index, ("Profit", ticker)] + quantity * sign * price * -1 + ) + wa_cost = ( + quantity / log.cumsum().at[index, ("Quantity", ticker)] + ) * log.cumsum().at[index, ("Cost Basis", ticker)] + log.at[index, ("Profit", ticker)] = rev - wa_cost - fees + log.at[index, ("Cash", "Cash")] = ( + log.at[index, ("Cash", "Cash")] + rev - fees + ) + log.at[index, ("Quantity", ticker)] = ( + log.at[index, ("Quantity", ticker)] + quantity * sign + ) + log.at[index, ("Cost Basis", ticker)] = ( + log.at[index, ("Cost Basis", ticker)] - wa_cost + ) + cash_vals = cashes[cashes["Date"] == index] + if len(cash_vals.index) > 0: + for _, sub_row in cash_vals.iterrows(): + amount = sub_row["Price"] + if sub_row["Side"] == "deposit": + d = 1 + elif sub_row["Side"] == "withdrawal": + d = -1 + else: + raise ValueError("Cash type must be deposit or withdrawal") + log.at[index, ("Cash", "Cash")] = ( + log.at[index, ("Cash", "Cash")] + d * amount + ) + log.at[index, ("Cash", "User")] = ( + log.at[index, ("Cash", "User")] + d * amount + ) + return log + + +def merge_dataframes( + log: pd.DataFrame, + hist: pd.DataFrame, + changes: pd.DataFrame, + divs: pd.DataFrame, + uniques: List[str], +) -> pd.DataFrame: + """Merge dataframes to create final dataframe + + Parameters + ---------- + log : pd.DataFrame + The new dataframe with daily holdings + hist : pd.DataFrame + Historical returns for stocks in portfolio + changes : pd.DataFrame + A log of past transactions + divs : pd.DataFrame + The dividend history for stocks in portfolio + unqiues: List[str] + A list of stocks in the portfolio + + Returns + ---------- + comb : pd.DataFrame + Thew new aggregated dataframe + """ + comb = pd.merge(log, hist, how="left", left_index=True, right_index=True) + comb = comb.fillna(method="ffill") + comb = pd.merge(comb, divs, how="left", left_index=True, right_index=True) + comb = comb.fillna(0) + + for uni in uniques: + comb[("Quantity", uni)] = comb[("Quantity", uni)].cumsum() + comb[("Cost Basis", uni)] = comb[("Cost Basis", uni)].cumsum() + comb[("Cash", "Cash")] = comb[("Cash", "Cash")] + ( + comb[("Quantity", uni)] * comb[("Dividend", uni)] + ) + comb[("Holding", uni)] = ( + np.where( + comb[("Quantity", uni)] > 0, + comb[("Close", uni)], + (2 * comb[("Close", uni)][0] - comb[("Close", uni)]), + ) + * comb[("Quantity", uni)] + ) + comb[("Profit", uni)] = comb[("Profit", uni)].cumsum() + comb[("Cash", "Cash")] = comb[("Cash", "Cash")].cumsum() + if len(changes["Date"]) > 0: + comb["holdings"] = comb.sum(level=0, axis=1)["Holding"] + comb["profits"] = comb.sum(level=0, axis=1)["Profit"] + comb["total_prof"] = comb["holdings"] + comb["profits"] + comb["total_cost"] = comb.sum(level=0, axis=1)["Cost Basis"] + return comb + + +def convert_df(portfolio: pd.DataFrame) -> pd.DataFrame: + """Converts a df from activity to daily holdings + + Parameters + ---------- + portfolio : pd.DataFrame + The dataframe of transactions + + Returns + ---------- + data : pd.DataFrame + A dataframe with daily holdings of portfolio + hist : pd.DataFrame + The historical performance of tickers in portfolio + """ + changes = portfolio.copy() + # Transactions sorted for only stocks + cashes = changes[changes["Type"] == "cash"] + if cashes.empty: + raise ValueError("Brokers require cash, input cash deposits") + changes = changes[changes["Type"] == "stock"] + uniques = list(set(changes["Name"].tolist())) + if uniques: + hist = yfinance_model.get_stocks(uniques, min(changes["Date"])) + divs = yfinance_model.get_dividends(uniques) + else: + hist, divs = pd.DataFrame(), pd.DataFrame() + + divs = divs.fillna(0) + mini = min(cashes["Date"].to_list() + changes["Date"].to_list()) + days = pd.date_range(mini, date.today() - timedelta(days=1), freq="d") + zeros = [0 for _ in uniques] + data = [zeros + zeros + zeros + [0] for _ in days] + vals = ["Quantity", "Cost Basis", "Profit"] + arrays = [[x for _ in uniques for x in vals] + ["Cash"], uniques * 3 + ["Cash"]] + tuples = list(zip(*arrays)) + headers = pd.MultiIndex.from_tuples(tuples, names=["first", "second"]) + log = pd.DataFrame(data, columns=headers, index=days) + log[("Cash", "User")] = 0 + log = add_values(log, changes, cashes) + comb = merge_dataframes(log, hist, changes, divs, uniques) + + return comb, hist + + +def get_return(df: pd.DataFrame, df_m: pd.DataFrame, n: int) -> pd.DataFrame: + """Adds cumulative returns to a holdings df + + Parameters + ---------- + df : pd.DataFrame + The dataframe of daily holdings + df_m : pd.DataFrame + The dataframe of market performance + n : int + The period to get returns for + + Returns + ---------- + comb : pd.DataFrame + Dataframe with holdings and returns + """ + df = df.copy() + df = df[df.index >= datetime.now() - timedelta(days=n + 1)] + comb = pd.merge(df, df_m, how="left", left_index=True, right_index=True) + comb = comb.fillna(method="ffill") + comb = comb.dropna() + comb["return"] = ( + comb[("Cash", "Cash")] + + (0 if "holdings" not in comb.columns else comb["holdings"]) + ) / ( + comb[("Cash", "Cash")].shift(1) + + comb[("Cash", "User")] + + (0 if "holdings" not in comb.columns else comb["holdings"].shift(1)) + ) + comb["return"] = comb["return"].fillna(1) + comb["return"] = comb["return"].cumprod() - 1 + return comb + + +def get_rolling_beta( + df: pd.DataFrame, hist: pd.DataFrame, mark: pd.DataFrame, n: pd.DataFrame +) -> pd.DataFrame: + """Turns a holdings portfolio into a rolling beta dataframe + + Parameters + ---------- + df : pd.DataFrame + The dataframe of daily holdings + hist : pd.DataFrame + A dataframe of historical returns + mark : pd.DataFrame + The dataframe of market performance + n : int + The period to get returns for + + Returns + ---------- + final : pd.DataFrame + Dataframe with rolling beta + """ + df = df["Holding"] + uniques = df.columns.tolist() + res = df.div(df.sum(axis=1), axis=0) + res = res.fillna(0) + comb = pd.merge( + hist["Close"], mark["Market"], how="left", left_index=True, right_index=True + ) + comb = comb.fillna(method="ffill") + df_var = comb.rolling(252).var().unstack()["Close"].to_frame(name="var") + for col in hist["Close"].columns: + df1 = ( + comb.rolling(252).cov().unstack()[col]["Close"].to_frame(name=f"cov_{col}") + ) + df_var = pd.merge(df_var, df1, how="left", left_index=True, right_index=True) + df_var[f"beta_{col}"] = df_var[f"cov_{col}"] / df_var["var"] + final = pd.merge(res, df_var, how="left", left_index=True, right_index=True) + final = final.fillna(method="ffill") + final = final.drop(columns=["var"] + [f"cov_{x}" for x in uniques]) + for uni in uniques: + final[f"prod_{uni}"] = final[uni] * final[f"beta_{uni}"] + final = final.drop(columns=[f"beta_{x}" for x in uniques] + uniques) + final["total"] = final.sum(axis=1) + final = final[final.index >= datetime.now() - timedelta(days=n + 1)] + return final diff --git a/gamestonk_terminal/portfolio/portfolio_view.py b/gamestonk_terminal/portfolio/portfolio_view.py new file mode 100644 index 00000000000..1e45c4f8612 --- /dev/null +++ b/gamestonk_terminal/portfolio/portfolio_view.py @@ -0,0 +1,235 @@ +"""Portfolio View""" +__docformat__ = "numpy" + +from datetime import datetime +from io import BytesIO +import os + +import pandas as pd +from tabulate import tabulate +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas +from reportlab.lib.utils import ImageReader +from matplotlib import pyplot as plt +import matplotlib.ticker as mtick + +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.portfolio import portfolio_model, yfinance_model +from gamestonk_terminal.portfolio import reportlab_helpers + + +def load_info(): + """Prints instructions to load a CSV + + Returns + ---------- + text : str + Information on how to load a csv + """ + text = """ +In order to load a CSV do the following: + +1. Add headers to the first row, below is data for each column:\n +\t1. Identifier for the asset (such as a stock ticker) +\t2. Type of asset (stock, bond, option, crypto) +\t3. The volume of the asset transacted +\t4. The buy date in yyyy/mm/dd +\t5. The Price paid for the asset +\t6. Any fees paid during the transaction +\t7. A premium paid or received if this was an option +\t8. Whether the asset was bought (covered) or sold (shorted)\n +2. Place this file in gamestonk_terminal/portfolio/portfolios\n + """ + print(text) + + +def show_df(df: pd.DataFrame, show: bool) -> None: + """Shows the given dataframe + + Parameters + ---------- + df : pd.DataFrame + The dataframe to be shown + show : bool + Whether to show the dataframe index + """ + + df = df.dropna(how="all", axis=1).fillna("") + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df, + headers=df.columns, + floatfmt=".2f", + showindex=show, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + +def plot_overall_return( + df: pd.DataFrame, df_m: pd.DataFrame, n: int, m_tick: str, plot: bool = False +) -> ImageReader: + """Generates overall return graph + + Parameters + ---------- + df : pd.DataFrame + The dataframe to be analyzed + df_m : pd.DataFrame + The dataframe for historical market performance + n : int + The number of days to include in chart + m_tick : str + The ticker for the market asset + plot : bool + Whether to plot the graph or return it for PDF + + Returns + ---------- + img : ImageReader + Overal return graph + """ + plt.close("all") + comb = portfolio_model.get_return(df, df_m, n) + comb[("Market", "Return")] = ( + comb[("Market", "Close")] - comb[("Market", "Close")][0] + ) / comb[("Market", "Close")][0] + fig, ax = plt.subplots(figsize=(10, 5)) + ax.plot(comb.index, comb["return"], color="tab:blue", label="Portfolio") + ax.plot(comb.index, comb[("Market", "Return")], color="orange", label=m_tick) + + ax.set_ylabel("", fontweight="bold", fontsize=12, color="black") + ax.set_xlabel("") + ax.yaxis.set_label_coords(-0.1, 0.5) + ax.grid(True) + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["bottom"].set_visible(False) + ax.spines["left"].set_visible(False) + fig.suptitle( + "Cumulative Performance", y=0.99, fontweight="bold", fontsize=14, color="black" + ) + ax.axhline(0, ls="-", lw=1, color="gray", zorder=1) + ax.axhline(0, ls="--", lw=1, color="black", zorder=2) + fig.set_facecolor("white") + ax.set_title( + "%s - %s" + % (df.index[:1][0].strftime("%Y/%m/%d"), df.index[-1:][0].strftime("%Y/%m/%d")), + fontsize=12, + color="gray", + ) + ax.yaxis.set_major_formatter(mtick.PercentFormatter(1.0)) + ax.set_facecolor("white") + ax.legend() + fig.autofmt_xdate() + if plot: + plt.show() + print("") + return None + + imgdata = BytesIO() + fig.savefig(imgdata, format="png") + imgdata.seek(0) + return ImageReader(imgdata) + + +def plot_rolling_beta( + df: pd.DataFrame, hist: pd.DataFrame, mark: pd.DataFrame, n: int +) -> ImageReader: + """Returns a chart with the portfolio's rolling beta + + Parameters + ---------- + df : pd.DataFrame + The dataframe to be analyzed + hist : pd.DataFrame + A dataframe of historical returns + market : pd.DataFrame + A dataframe of SPY returns + n : int + The number of days to include in chart + + Returns + ---------- + img : ImageReader + Rolling beta graph + """ + plt.close("all") + final = portfolio_model.get_rolling_beta(df, hist, mark, n) + + # final[final == 0] = np.nan + fig, ax = plt.subplots(figsize=(10, 5)) + ax.plot( + final.index, + final["total"], + color="tab:blue", + ) + + ax.set_ylabel("", fontweight="bold", fontsize=12, color="black") + ax.set_xlabel("") + ax.yaxis.set_label_coords(-0.1, 0.5) + ax.grid(True) + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["bottom"].set_visible(False) + ax.spines["left"].set_visible(False) + fig.suptitle( + "Rolling Beta of Stocks", y=0.99, fontweight="bold", fontsize=14, color="black" + ) + ax.axhline(0, ls="-", lw=1, color="gray", zorder=1) + ax.axhline(0, ls="--", lw=1, color="black", zorder=2) + fig.set_facecolor("white") + ax.set_title( + "%s - %s" + % (df.index[:1][0].strftime("%Y/%m/%d"), df.index[-1:][0].strftime("%Y/%m/%d")), + fontsize=12, + color="gray", + ) + ax.set_facecolor("white") + fig.autofmt_xdate() + imgdata = BytesIO() + fig.savefig(imgdata, format="png") + imgdata.seek(0) + return ImageReader(imgdata) + + +def annual_report(df: pd.DataFrame, hist: pd.DataFrame, m_tick: str) -> None: + """Generates an annual report + + Parameters + ---------- + df : pd.DataFrame + The dataframe to be analyzed + m_tick : str + The ticker for the market asset + hist : pd.DataFrame + A dataframe of historical returns + """ + df_m = yfinance_model.get_market(df.index[0], m_tick) + dire = os.path.dirname(os.path.abspath(__file__)).replace( + "gamestonk_terminal", "exports" + ) + + path = os.path.abspath( + os.path.join( + dire, + f"ar_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf", + ) + ) + report = canvas.Canvas(path, pagesize=letter) + reportlab_helpers.base_format(report, "Overview") + report.drawImage( + plot_overall_return(df, df_m, 365, m_tick, False), 15, 360, 600, 300 + ) + report.showPage() + reportlab_helpers.base_format(report, "Portfolio Analysis") + if "Holding" in df.columns: + report.drawImage(plot_rolling_beta(df, hist, df_m, 365), 15, 360, 600, 300) + report.save() + + print("File save in:\n", path, "\n") diff --git a/gamestonk_terminal/portfolio/reportlab_helpers.py b/gamestonk_terminal/portfolio/reportlab_helpers.py new file mode 100644 index 00000000000..3dcb802a91e --- /dev/null +++ b/gamestonk_terminal/portfolio/reportlab_helpers.py @@ -0,0 +1,30 @@ +"""Reportlab Helpers""" +__docformat__ = "numpy" + +from datetime import datetime + +from reportlab.pdfgen import canvas + + +def base_format(report: canvas.Canvas, header: str) -> None: + """Applies a base page format to each page + + Parameters + ---------- + report : canvas.Canvas + The report to be formatted + header : str + The header for the page + """ + report.setLineWidth(0.3) + report.setFont("Helvetica", 12) + report.drawString(30, 750, "Gamestonk Terminal") + report.drawString(500, 750, datetime.now().strftime("%Y/%m/%d")) + report.drawString(275, 725, "Annual Report") + report.setFillColorRGB(255, 0, 0) + report.drawString(200, 710, "Warning: currently only analyzes stocks") + report.setFillColorRGB(0, 0, 0) + report.line(50, 700, 580, 700) + report.setFont("Helvetica", 20) + report.drawString(50, 670, header) + report.setFont("Helvetica", 12) diff --git a/gamestonk_terminal/portfolio/yfinance_model.py b/gamestonk_terminal/portfolio/yfinance_model.py new file mode 100644 index 00000000000..0f7026801b1 --- /dev/null +++ b/gamestonk_terminal/portfolio/yfinance_model.py @@ -0,0 +1,85 @@ +"""Yfinance model""" +__docformat__ = "numpy" + +from typing import List +from datetime import datetime, timedelta + +import yfinance as yf +import pandas as pd + + +def get_stocks(tickers: List[str], start: datetime) -> pd.DataFrame: + """Gets historic data for list of tickers + + Parameters + ---------- + tickers : List[str] + Tickers to get data for + start : datetime + First date in stock filtered dataframe + + Returns + ---------- + data : pd.DataFrame + Historic daily prices for stocks + """ + df = yf.download( + tickers=tickers, + start=start - timedelta(days=255), + interval="1d", + progress=False, + ) + df = df["Adj Close"] + if len(tickers) > 1: + df.columns = df.columns.str.lower() + arrays = [["Close" for _ in df.columns], [x.lower() for x in df.columns]] + tuples = list(zip(*arrays)) + headers = pd.MultiIndex.from_tuples(tuples, names=["first", "second"]) + df.columns = headers + if len(tickers) == 1: + df = df.to_frame(name=("Close", tickers[0])) + return df + + +def get_dividends(tickers: List[str]) -> pd.DataFrame: + """Past dividends for list of tickers + + Parameters + ---------- + tickers : List[str] + Tickers to get data for + + Returns + ---------- + data : pd.DataFrame + Historic dividends for stocks + """ + dfs = [] + for ticker in tickers: + tick = yf.Ticker(ticker) + df = tick.dividends.to_frame(name=("Dividend", ticker)) + dfs.append(df) + return pd.concat(dfs) + + +def get_market(start: datetime, ticker: str = "SPY") -> pd.DataFrame: + """Get historical data for market asset + + Parameters + ---------- + ticker : str + Ticker to get data for + start : datetime + First date in stock filtered dataframe + + Returns + ---------- + data : pd.DataFrame + Historic prices for SPY + """ + tick = yf.Ticker(ticker) + df = tick.history( + start=start - timedelta(days=255), + interval="1d", + )["Close"] + return df.to_frame(name=("Market", "Close")) diff --git a/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py b/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py index 2b0f9ac7395..459483f4bec 100644 --- a/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py +++ b/gamestonk_terminal/stocks/prediction_techniques/pred_controller.py @@ -714,6 +714,7 @@ Models: finally: pred_helper.restore_env() + @try_except def call_mc(self, other_args: List[str]): """Process mc command""" parser = argparse.ArgumentParser( @@ -747,25 +748,22 @@ Models: 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 + 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") + 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, + ) def menu(ticker: str, start: datetime, interval: str, stock: pd.DataFrame): diff --git a/gamestonk_terminal/stocks/quantitative_analysis/factors_model.py b/gamestonk_terminal/stocks/quantitative_analysis/factors_model.py index ac664711101..8f8b014f7d0 100644 --- a/gamestonk_terminal/stocks/quantitative_analysis/factors_model.py +++ b/gamestonk_terminal/stocks/quantitative_analysis/factors_model.py @@ -4,14 +4,21 @@ __docformat__ = "numpy" from urllib.request import urlopen from zipfile import ZipFile from io import BytesIO +from typing import Tuple import statsmodels.api as sm import yfinance as yf import pandas as pd -def get_fama_raw(): - """Gets base Fama French data to calculate risk""" +def get_fama_raw() -> pd.DataFrame: + """Gets base Fama French data to calculate risk + + Returns + ---------- + fama : pd.DataFrame + A data with fama french model information + """ with urlopen( "http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip" ) as url: @@ -42,7 +49,18 @@ def get_fama_raw(): def get_historical_5(ticker: str): - """Get 5 year monthly historical performance for a ticker with dividends filtered""" + """Get 5 year monthly historical performance for a ticker with dividends filtered + + Parameters + ---------- + ticker : str + A ticker in string form + + Returns + ---------- + data : pd.DataFrame + A dataframe with historical information + """ tick = yf.Ticker(ticker) df = tick.history(period="5y", interval="1mo") df = df[df.index.to_series().apply(lambda x: x.day == 1)] @@ -51,8 +69,21 @@ def get_historical_5(ticker: str): return df -def capm_information(ticker): - """Provides information that relates to the CAPM model""" +def capm_information(ticker) -> Tuple[float, float]: + """Provides information that relates to the CAPM model + + Parameters + ---------- + ticker : str + A ticker in string form + + Returns + ---------- + beta : float + The beta for a stock + sys : float + The systematic risk for a stock + """ df_f = get_fama_raw() df_h = get_historical_5(ticker) df = df_h.join(df_f) diff --git a/poetry.lock b/poetry.lock index c83cf520311..e7ea617c2e4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2447,6 +2447,20 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "reportlab" +version = "3.6.2" +description = "The Reportlab Toolkit" +category = "main" +optional = false +python-versions = ">=3.6, <4" + +[package.dependencies] +pillow = ">=4.0.0" + +[package.extras] +rlpycairo = ["rlPyCairo (>=0.0.5)"] + [[package]] name = "requests" version = "2.26.0" @@ -3334,7 +3348,7 @@ prediction = ["tensorflow"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "647fe08199b9ba50dca3198d36f21ceba3b8f1cb98374beae93c9d00bfc05915" +content-hash = "6c4d80ae2a7bdf631b6fda05880e4bcbb347a95f44b7f697853b0de8aa0f05d6" [metadata.files] absl-py = [ @@ -4150,8 +4164,6 @@ lxml = [ {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, - {file = "lxml-4.6.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4"}, - {file = "lxml-4.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d"}, {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"}, @@ -4201,28 +4213,12 @@ markdown = [ {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -4231,27 +4227,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -4261,12 +4244,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -4533,10 +4510,8 @@ opt-einsum = [ {file = "opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549"}, ] orjson = [ - {file = "orjson-3.6.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:fc01a15f3101628fd619158daec79b30d7461149735e73542ca8c13be6b835be"}, {file = "orjson-3.6.4-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:48a69fed90f551bf9e9bb7a63e363fed4f67fc7c6e6bfb057054dc78f6721e9e"}, {file = "orjson-3.6.4-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:3722f02f50861d5e2a6be9d50bfe8da27a5155bb60043118a4e1ceb8c7040cf7"}, - {file = "orjson-3.6.4-cp310-none-win_amd64.whl", hash = "sha256:231a99a728322d0271e970b149c57deb67315e6837e6cd4166cf51d30161700c"}, {file = "orjson-3.6.4-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:6cd300421b41f7e84e388b1792a18c3fc4c440ae3039434b9320956be05f0102"}, {file = "orjson-3.6.4-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e55ef66ee1d35b1c43db275aff3a1ba7e0408b31e624912a612bd799df14e73e"}, {file = "orjson-3.6.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef8d332af8e6f7d6d2c1f3b5384c8d239800c1405b136da5f1710e802918d57"}, @@ -4843,8 +4818,6 @@ pymeeus = [ ] pyobjc-core = [ {file = "pyobjc-core-7.3.tar.gz", hash = "sha256:5081aedf8bb40aac1a8ad95adac9e44e148a882686ded614adf46bb67fd67574"}, - {file = "pyobjc_core-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a1f1e6b457127cbf2b5bd2b94520a7c89fb590b739911eadb2b0499a3a5b0e6f"}, - {file = "pyobjc_core-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:ed708cc47bae8b711f81f252af09898a5f986c7a38cec5ad5623d571d328bff8"}, {file = "pyobjc_core-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e93ad769a20b908778fe950f62a843a6d8f0fa71996e5f3cc9fab5ae7d17771"}, {file = "pyobjc_core-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f63fd37bbf3785af4ddb2f86cad5ca81c62cfc7d1c0099637ca18343c3656c1"}, {file = "pyobjc_core-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9b1311f72f2e170742a7ee3a8149f52c35158dc024a21e88d6f1e52ba5d718b"}, @@ -4853,8 +4826,6 @@ pyobjc-core = [ ] pyobjc-framework-cocoa = [ {file = "pyobjc-framework-Cocoa-7.3.tar.gz", hash = "sha256:b18d05e7a795a3455ad191c3e43d6bfa673c2a4fd480bb1ccf57191051b80b7e"}, - {file = "pyobjc_framework_Cocoa-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1e31376806e5de883a1d7c7c87d9ff2a8b09fc05d267e0dfce6e42409fb70c67"}, - {file = "pyobjc_framework_Cocoa-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d999387927284346035cb63ebb51f86331abc41f9376f9a6970e7f18207db392"}, {file = "pyobjc_framework_Cocoa-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9edffdfa6dd1f71f21b531c3e61fdd3e4d5d3bf6c5a528c98e88828cd60bac11"}, {file = "pyobjc_framework_Cocoa-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:35a6340437a4e0109a302150b7d1f6baf57004ccf74834f9e6062fcafe2fd8d7"}, {file = "pyobjc_framework_Cocoa-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c3886f2608ab3ed02482f8b2ebf9f782b324c559e84b52cfd92dba8a1109872"}, @@ -5142,6 +5113,42 @@ regex = [ {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, ] +reportlab = [ + {file = "reportlab-3.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:087f8cfa240269d25212ee4a00653b94eb25e518e0510e43786e6e3ede3e2cb8"}, + {file = "reportlab-3.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:78ab838ecd4a2a63ca16d6b36f24e5324486e896c9908db1748012265c70d8f0"}, + {file = "reportlab-3.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3e7c2f4ddb9268ba60c3565c8267f55df1f6a255011bb6f48705630ad29b346"}, + {file = "reportlab-3.6.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfe876672351b1db89312a7935f755751ef17494ecfbd71484e693bebbd95f8b"}, + {file = "reportlab-3.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:868a65d53b67e7826caf56c5a3bbc7cc7802c3b8949935bf883815400fa138d1"}, + {file = "reportlab-3.6.2-cp310-cp310-win32.whl", hash = "sha256:58076d11ba4a704ee6f1c2a38b6b7330ba1d14c0df27ed976a75898a59bb6927"}, + {file = "reportlab-3.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:40d91b1cf10ea3bf49f1c36108ef0e6480ed669c9350035cf14a9c2d311c13db"}, + {file = "reportlab-3.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:df046aed158f51bcf2b8b882347d90c479905b0d58eee417cdd6ae3efa75ef68"}, + {file = "reportlab-3.6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9b618d1b5f18848b44e500b605b44ac5809f1e5e52e6b41582ed42dbd6aac5c"}, + {file = "reportlab-3.6.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e97d16f62cbfbfa78afad1f6b1c55952de61ca529e6a924f8d05e7e55472817"}, + {file = "reportlab-3.6.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42fa6a1e15208e09a159ed91d3ba26e6bb80f4fc519449cc168b445d9d7c3c3f"}, + {file = "reportlab-3.6.2-cp36-cp36m-win32.whl", hash = "sha256:5528bc5db2b694617ff9f36041e49ca18c3b3202c57e5d87774c27bb4fbc9db9"}, + {file = "reportlab-3.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3d4cf5e7a1940d932d0b76bdb7e826826e1add6e51e3715b85770f6ade3724ef"}, + {file = "reportlab-3.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e0525fc5a9f7ee165c3e5c8b57cad34c479024d97cda2d36e4476ecbb9e46cbd"}, + {file = "reportlab-3.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b17bb378d7abcf004dce16ffe5ddef4c766c5de2360677837afc564550fc281f"}, + {file = "reportlab-3.6.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8214eca2174f288a9d28bd4c0dc1ba24c4e7f25f09c1320eea9587e43dba5e43"}, + {file = "reportlab-3.6.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7b02ab2b48dc6986d0c93257fe17ef84955297db163e7b67b4d9ce894dcca1b"}, + {file = "reportlab-3.6.2-cp37-cp37m-win32.whl", hash = "sha256:a36a325bd8cc8124349e91560cc87dea9ac7849622c9adf4cf11ee0f21c07025"}, + {file = "reportlab-3.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b118a1ae412df57fbeafcc167b0fda2f259b1df93b6c4aec72aa05077d21b375"}, + {file = "reportlab-3.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c7645a5dfa81fc936b3c509a3b842c3d1ae3eaa541684086e244812155d18ac"}, + {file = "reportlab-3.6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:71585db6d44f6e2ed60462dab44c5119d74d8b1b1d6f8738f13f0e6a9fb3d22f"}, + {file = "reportlab-3.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1413cd9ad1120ccade27128d0986823f8ea5f112cc29b0d4f149169ef561ab9"}, + {file = "reportlab-3.6.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1ccb485fa2808900e027fd7eefcbf877079d15e1b19943d34ccb271d809e60"}, + {file = "reportlab-3.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bffeefc8d501de9114d236c7a8709a5f6d2bef96cea3b9088a1275d48369857"}, + {file = "reportlab-3.6.2-cp38-cp38-win32.whl", hash = "sha256:68cc76d0ff2c71ba072a50ea7e6aa5ce720cd4a3e6ca480f1acac0776ba1fd54"}, + {file = "reportlab-3.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ec291de9adccc37283a00c7f9fe44aea6801a22c433076d31cd7319d63ed006"}, + {file = "reportlab-3.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4beb408517f5381404f0c3e16884d237c9f5992a9246f66929a42904672dfcca"}, + {file = "reportlab-3.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7a072766b4dfb1f7a6af96402b2c6c40eb2d41ff79ef3da3eafec1bdd79c00fb"}, + {file = "reportlab-3.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4188915eda1c9316a260ec3599232bcbe45ac6c30040472783a734c3f5e019b7"}, + {file = "reportlab-3.6.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21008872be92c3b8e2073feb582de6b1f821544b65de92e8c192ea2a4a74facd"}, + {file = "reportlab-3.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e88c22b40d18d62d6984f250c9feeedb7112dd3622d1439d2fc311c8dd1e447"}, + {file = "reportlab-3.6.2-cp39-cp39-win32.whl", hash = "sha256:69e440b23bad2181cb4f9c100d2191ee88239a839375c459c90096921eee490f"}, + {file = "reportlab-3.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:c50f95242ebc6d8b7bf6b72e745d4487f8bbfc7acd278e12ea00ca0b682f11f7"}, + {file = "reportlab-3.6.2.tar.gz", hash = "sha256:f0c4b47b012d893b0b9f5703cf6f01b5593714a3fc1e7dc73efbbfe26bb7e16a"}, +] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, diff --git a/pyproject.toml b/pyproject.toml index a4cb517f571..9b67c02ae77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ coverage = "^5.5" tensorflow = {version = "2.6.0", optional = true} jsonschema = "3.2.0" financedatabase = "1.0.0" +reportlab = "^3.6.2" [tool.poetry.dev-dependencies] pytest = "^6.2.2" diff --git a/requirements-full.txt b/requirements-full.txt index 90bdeb6ef9f..4269b88a9b7 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -180,6 +180,7 @@ qtpy==1.11.2; python_version >= "3.6" and python_full_version < "3.0.0" or pytho quandl==3.6.1; python_version >= "3.5" rapidfuzz==1.8.2; python_version >= "2.7" regex==2020.11.13 +reportlab==3.6.2; python_version >= "3.6" and python_version < "4" requests-oauthlib==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" requests-unixsocket==0.2.0; python_version >= "3.6" requests==2.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") diff --git a/requirements.txt b/requirements.txt index ddf38c6f9f8..004d61c14c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -163,6 +163,7 @@ qtpy==1.11.2; python_version >= "3.6" and python_full_version < "3.0.0" or pytho quandl==3.6.1; python_version >= "3.5" rapidfuzz==1.8.2; python_version >= "2.7" regex==2020.11.13 +reportlab==3.6.2; python_version >= "3.6" and python_version < "4" requests-oauthlib==1.3.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" requests-unixsocket==0.2.0; python_version >= "3.6" requests==2.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") diff --git a/website/content/portfolio/add/_index.md b/website/content/portfolio/add/_index.md new file mode 100644 index 00000000000..83a02063f02 --- /dev/null +++ b/website/content/portfolio/add/_index.md @@ -0,0 +1,19 @@ +``` +usage: add [-n NAME] [-t {stock,bond,option,crypto,cash}] [-q QUANTITY] [-d DATE] [-p PRICE] [-f FEES] + [-r PREMIUM] [-a {buy,sell,interest,deposit,withdrawal}] [-h] +``` + +Add an asset to the current portfolio + +``` +optional arguments: + -n, NAME --name NAME the name of the asset + -t, --type the type of asset + -q, --quantity the quantity of the asset + -d, --date the date the transaction occurred + -p, --price the price of the asset (per unit) + -f, --fees the fees paid for the transaction (total) + -r, --premium the premium paid/received for the asset + -a, --action select what you did in the transaction + -h, --help show this help message +``` diff --git a/website/content/portfolio/ar/_index.md b/website/content/portfolio/ar/_index.md new file mode 100644 index 00000000000..1accc74d646 --- /dev/null +++ b/website/content/portfolio/ar/_index.md @@ -0,0 +1,12 @@ +``` +usage: ar [-m MARKET] [-h] +``` + +Generate an a PDF annual report for a portfolio + +``` +optional arguments: + -m MARKET, --market MARKEt + the asset that represents the market + -h, --help show this help message +``` diff --git a/website/content/portfolio/load/_index.md b/website/content/portfolio/load/_index.md new file mode 100644 index 00000000000..8f073f53981 --- /dev/null +++ b/website/content/portfolio/load/_index.md @@ -0,0 +1,11 @@ +``` +usage: load [-n NAME] [-h] +``` + +Load a file from gamestonk_terminal/portfolio/portfolios + +``` +optional arguments: + -n, NAME --name NAME the name of the csv file to load + -h, --help show this help message (default: False) +``` diff --git a/website/content/portfolio/ret/_index.md b/website/content/portfolio/ret/_index.md new file mode 100644 index 00000000000..192241f23b6 --- /dev/null +++ b/website/content/portfolio/ret/_index.md @@ -0,0 +1,12 @@ +``` +usage: ret [-m MARKET] [-h] +``` + +Show portfolio returns versus market returns + +``` +optional arguments: + -m MARKET, --market MARKEt + the asset that represents the market + -h, --help show this help message +``` diff --git a/website/content/portfolio/rmv/_index.md b/website/content/portfolio/rmv/_index.md new file mode 100644 index 00000000000..a96f9602fc9 --- /dev/null +++ b/website/content/portfolio/rmv/_index.md @@ -0,0 +1,10 @@ +``` +usage: rmv [-h] +``` + +Removes an item from the portfolio + +``` +optional arguments: + -h, --help show this help message (default: False) +``` diff --git a/website/content/portfolio/save/_index.md b/website/content/portfolio/save/_index.md new file mode 100644 index 00000000000..cbfdce38b11 --- /dev/null +++ b/website/content/portfolio/save/_index.md @@ -0,0 +1,11 @@ +``` +usage: save [-n NAME] [-h] +``` + +Save a file from gamestonk_terminal/portfolio/portfolios + +``` +optional arguments: + -n, NAME --name NAME the name of the csv file to save + -h, --help show this help message (default: False) +``` diff --git a/website/content/portfolio/show/_index.md b/website/content/portfolio/show/_index.md new file mode 100644 index 00000000000..ade3fad0c61 --- /dev/null +++ b/website/content/portfolio/show/_index.md @@ -0,0 +1,10 @@ +``` +usage: show [-h] +``` + +Shows the active portfolio + +``` +optional arguments: + -h, --help show this help message (default: False) +```