diff --git a/.gitignore b/.gitignore index 4f92317b787..f88c6941dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ __pycache__/ .env .venv .vscode -.ipynb_checkpoints +*.ipynb +notebooks/* env/ venv/ notebooks/reports/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f27e061338..e0de7075aba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: hooks: - id: codespell entry: codespell - args: [ '--ignore-words-list="mape","ba","hist"', '--quiet-level=2', '--skip=".git,*.yaml,*.json,./tests"' ] + args: [ '--ignore-words-list=mape,ba,hist', '--quiet-level=2', '--skip=".git,*.yaml,*.json,./tests"' ] - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v0.812' hooks: diff --git a/ROADMAP.md b/ROADMAP.md index 876b38836f5..16b1d5d8445 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -45,6 +45,7 @@ * [x] Add dark pool (ATS) data of tickers with growing trades activity (@didier) - [PR #372](https://github.com/DidierRLopes/GamestonkTerminal/pull/372) * [x] Past and future IPOs calendar (@didier) - [PR #432](https://github.com/DidierRLopes/GamestonkTerminal/pull/432) * [x] Short volume and dark pool short information (stockgrid) (@jmaslek) - [PR #496](https://github.com/GamestonkTerminal/GamestonkTerminal/pull/496 +* [x] Combine topdark onto darkshort cmd, improve shortvol cmd (@didier) - [PR #505](https://github.com/DidierRLopes/GamestonkTerminal/pull/505) **NEXT** @@ -95,6 +96,7 @@ * [x] Add failure to deliver command (@hinxx) - [PR #366](https://github.com/DidierRLopes/GamestonkTerminal/pull/366) * [x] Ratings over time (@didier) - [PR #431](https://github.com/DidierRLopes/GamestonkTerminal/pull/431) * [x] Add short volume from stockgrid (@jmaslek) - [PR #496](https://github.com/GamestonkTerminal/GamestonkTerminal/pull/496) +* [x] Allow short vol to output datapoints (@didier) - [PR #505](https://github.com/DidierRLopes/GamestonkTerminal/pull/505) **NEXT** @@ -152,22 +154,25 @@ --- ## Brokers - -*[ ] Port from current portfolio menu - -**NEXT** -* [ ] Merge data from different brokers -* [ ] Add more options around merged portfolios -* [ ] Add Brokers (td, webull, etc) - ---- -## Portfolio Analysis * [x] Add alpaca (@jmaslek) - [PR #259](https://github.com/DidierRLopes/GamestonkTerminal/pull/229) * [x] Add robinhood (@jmaslek) - [PR #229](https://github.com/DidierRLopes/GamestonkTerminal/pull/229) * [x] Add Ally Invest (@jmaslek) - [PR #267](https://github.com/DidierRLopes/GamestonkTerminal/pull/267) * [x] Degiro support (@deel) - [PR #381](https://github.com/DidierRLopes/GamestonkTerminal/pull/381) * [x] Degiro improvement (@Chavithra) - [PR #445](https://github.com/DidierRLopes/GamestonkTerminal/pull/381) +* [x] Brokers to be the old portfolio menu (@didier) - [PR #505](https://github.com/DidierRLopes/GamestonkTerminal/pull/505) +* [x] Refactor degiro menu onto a folder (@didier) - [PR #505](https://github.com/DidierRLopes/GamestonkTerminal/pull/505) + +**NEXT** +* [ ] Merge data from different brokers +* [ ] Add more options around merged portfolios +* [ ] Add Brokers (td, webull, etc) +* [ ] Allow to go directly onto Portfolo Analysis + +--- +## Portfolio Analysis + * [x] Add custom csv integration (jmaslek) - [PR #502](https://github.com/GamestonkTerminal/GamestonkTerminal/pull/502) +* [x] Commands ported to brokers menu (@didier) - [PR #505](https://github.com/DidierRLopes/GamestonkTerminal/pull/505) **NEXT** * [ ] Port to new Broker Menu @@ -181,11 +186,11 @@ ___ ## Portfolio Optimization * [x] Basic Optimization through PyPortFolioOpt(@jmaslek) - [PR #329](https://github.com/DidierRLopes/GamestonkTerminal/pull/329) * [x] Add command to maximise the quadratic utility(@didier) - [PR #349](https://github.com/DidierRLopes/GamestonkTerminal/pull/349) +* [x] Allow to remove ticker from list (@didier) - [PR #505](https://github.com/DidierRLopes/GamestonkTerminal/pull/505) **NEXT** * [ ] Allow for more custom optimization constrains - --- ## Economy diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index 9c409fb208b..0b35afc552b 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -10,6 +10,7 @@ * [Due Diligence](#Due-Diligence-) * [Prediction Techniques](#Prediction-Techniques-) * [Portfolio Analysis](#Portfolio-Analysis-) +* [Brokers](#Brokers-) * [Portfolio Optimization](#Portfolio-Optimization-) * [Cryptocurrencies](#Cryptocurrencies-) * [Comparison Analysis](#Comparison-Analysis-) @@ -101,11 +102,10 @@ Command|Description|Source `spectrum` |spectrum of sectors, industry, country |[Finviz](https://finviz.com) `latest` |latest news |[Seeking Alpha](https://seekingalpha.com/) `trending` |trending news |[Seeking Alpha](https://seekingalpha.com/) -`ratings` |top ratings updates |[MarketBeat](https://marketbeat.com) +`ratings` |top ratings updates |[MarketBeat](https://marketbeat.com) `darkpool` |dark pool tickers with growing activity |[FINRA](https://www.finra.org) -`topdark` |largest dark pool positions | [Stockgrid](https://stockgrid.io) -`darkshort` | dark pool short information|[Stockgrid](https://stockgrid.io) -`shortvol` |short interest |[Stockgrid](https://stockgrid.io) +`darkshort` |dark pool short position|[Stockgrid](https://stockgrid.io) +`shortvol` |short interest and days to cover |[Stockgrid](https://stockgrid.io)   @@ -245,7 +245,7 @@ Command|Description|Source `warnings` |company warnings according to Sean Seah book |[MarketWatch](https://www.marketwatch.com/) `dp` |dark pools (ATS) vs OTC data [FINRA](https://www.finra.org/#/) `ftd` |display fails-to-deliver data [SEC](https://www.sec.gov) -`shortview` |shows price vs short interest over last year [Stockgrid](https://stockgrid.io +`shortview` |shows price vs short interest volume [Stockgrid](https://stockgrid.io)   @@ -266,7 +266,16 @@ Command|Technique|Sources `conv1d` |1D Convolution Neural Net| [Wikipedia](https://en.wikipedia.org/wiki/Convolutional_neural_network)   -## Portfolio Analysis [»](portfolio/README.md) +## Portfolio Analysis [»](portfolio_analysis/README.md) + +Command|Description +------ | ------------ +`load` | load portfolio from csv file +`group` | view holdings by a user input group + +  + +## Brokers [»](brokers/README.md) Command|Description|Brokers ------ | ------------|--- @@ -277,7 +286,22 @@ Command|Description|Brokers `alphist` | view alpaca portfolio history | [Alpaca](https://app.alpaca.markets/login) `allyhold`| view ally holdings | [Ally](https://www.ally.com/invest/) `hold` | view net holdings across all logins +`degiro` | degiro standalone menu | [Degiro](https://trader.degiro.nl/login/#/login) +Degiro command|Description +------ | ------------ +`cancel`| cancel an order using the `id` +`companynews`| view news about a company with it's isin +`create` | create an order +`hold` | view holdings +`lastnews` | view latest news +`login` | connect to degiro's api +`logout` | disconnect from degiro's api +`lookup` | view search for a product by name +`pending` | view pending orders +`topnews` | view top news preview +`update` | view top news preview +   ## Portfolio Optimization [»](portfolio_optimization/README.md) diff --git a/gamestonk_terminal/brokers/README.md b/gamestonk_terminal/brokers/README.md new file mode 100644 index 00000000000..845ba6a5441 --- /dev/null +++ b/gamestonk_terminal/brokers/README.md @@ -0,0 +1,160 @@ +# BROKERS + +This page shows the available brokers for loading your holdings. If yours is not listed, please submit a new issue and we will look into adding it. + +Current brokers: +* rh : Robinhood +* alp : Alpaca +* ally: Ally +* dg : Degiro + + +Once this screen is accessed, the first command to be run is +```` +login [brokers] +```` + +The brokers should just be your brokers from the list shown on the first menu. To login to Robinhood, the command is +```` +login rh +```` +To login to both robinhood and alpaca: +```` +login rh alp +```` +After logging in, the help menu will display which brokers you logged into. + +Your login information should be stored as environment variables in [config file](/gamestonk_terminal/config_terminal.py), but check each individual section for specifics. + +[ROBINHOOD](#ROBINHOOD) +* [rhhold](#rhhold) + * Look at current (stock only) holdings +* [rhhist](#rhhist) + * Get and plot historical portfolio + +[ALPACA](#ALPACA) +* [alphold](#alphold) + * Look at current (stock only) holdings +* [alphist](#rhhist) + * Get and plot historical portfolio + +[ALLY](#ALLY) +* [allyhold](#allyhold) + * Look at Ally Invest Holdings + +[DEGIRO standalone menu](/gamestonk_terminal/brokers/degiro/) + +[Merge](#Merge) +* [hold](#hold) + * View holdings across all brokers + + +## ROBINHOOD +Robinhood has Two Factor Authentication, so you will likely be prompted to enter a code that is texted/emailed to you. +###NOTE THAT LOGGING IN WILL SAVE A TOKEN TO `os.path.expanduser("~")/.tokens` WHICH CAN BE USED TO LOGIN EVEN IF USER CREDENTIALS ARE INCORRECT. + +In order to login, the [config file](/gamestonk_terminal/config_terminal.py) requires two variables: +* "GT_RH_USERNAME" +* "GT_RH_PASSWORD" + +### rhhold + +Displays current holdings to the console: + +```` +usage: rhhold +```` +There are no additional flags. This just prints all current stonks, their last price, previous close, your equity and +the % change from previous close. + +Example: + +![hold](https://user-images.githubusercontent.com/18151143/111685384-3c6ab080-87fe-11eb-80ce-9b256c396bf2.png) + +### rhhist +Display your RH portfolio based on provided interval and span. Based on the API data availablility, plotted as a candlestick chart. +```` +usage: rhhist [-s --span] [-i --interval] +```` +* -s/--span : How long to look back. Defaults to the day. Options: + * [day, week, month, 3month, year, 5year, all] +* -i/--interval: Data resolution. Defaults to 10minute intervals. Options: + * [5minute, 10minute, hour, day, week] + +Example Default Output: + +![rhhist](https://user-images.githubusercontent.com/18151143/111718919-36da8e00-8831-11eb-99e1-957c8eccb583.png) + + +## ALPACA + +Alpaca has a nicely maintained python API that is used here. To login, the best approach is to just save the following environment variables: +* "APCA_API_BASE_URL" +* "APCA_API_KEY_ID" +* "APCA_API_SECRET_KEY" + +If these are defined, then the login command does not explicitly need to be run, but the login command verifies all those keys are defined. + +Note that alpaca does support paper trading, so if your base_url is "https://paper-api.alpaca.markets", then this will be importing +your paper trading account. + +### alphold + +Displays current holdings to the console. Same as rhhold + +```` +usage: alphold +```` +There are no additional flags. This just prints all current stonks, their last price, previous close, your equity and +the % change from previous close. + +Example Output: +![](https://user-images.githubusercontent.com/18151143/112039340-536d1380-8b1a-11eb-99d1-44d1edd4d54a.png) + +### alphist +Plots historical portfolio. The output plot in this case will not be a candle, as the api provides your equity at a gven time, +unlike the robin_stocks, which gives high/low at a given time. + +```` +usage: alphist [-p --period] [-t --timeframe] +```` + +* -p/--period The duration of the data in \ + \, such as 1D, where can be D for day, W for week, M for month and A for year. Defaults to 1M +* -t/--timeframe The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period, 15Min for less than 30 days, or otherwise 1D. + + +## ALLY + +In order to access your Ally Invest account through the API, you must first acquire a key from +[your Ally Invest Account](#https://www.ally.com/api/invest/documentation/getting-started/). + +Once you have this, you want to load in the following environment variables (similar to alpaca, the api will access these +directly from the environment) +* ALLY_CONSUMER_KEY +* ALLY_CONSUMER_SECRET +* ALLY_OAUTH_TOKEN +* ALLY_OAUTH_SECRET +* ALLY_ACCOUNT_NBR + * This is obtained from your actual account - not through the api application. + +As of writing this, the ally API does not provide historical portfolio data, so only a holdings option is provided. +### allyhold +Show all Ally Invest holdings: +```` +usage : allyhold +```` +No additional flags. + + +## Merge + +When logged into 1 or more brokers - this option is available. Currently, this allows for +viewing holding across all platforms. The PnL is shown for all time (Market Value and your Cost Basis) + +### hold + +Show holdings across all brokers that are logged in. +```` +usage: hold +```` +Currently no additional flags. If not all brokers are represented, double check that they are logged in. diff --git a/gamestonk_terminal/portfolio/__init__.py b/gamestonk_terminal/brokers/__init__.py similarity index 100% rename from gamestonk_terminal/portfolio/__init__.py rename to gamestonk_terminal/brokers/__init__.py diff --git a/gamestonk_terminal/portfolio/ally_api.py b/gamestonk_terminal/brokers/ally_api.py similarity index 94% rename from gamestonk_terminal/portfolio/ally_api.py rename to gamestonk_terminal/brokers/ally_api.py index 204b9190f63..f03f5a60458 100644 --- a/gamestonk_terminal/portfolio/ally_api.py +++ b/gamestonk_terminal/brokers/ally_api.py @@ -3,7 +3,7 @@ import numpy as np import ally import yfinance as yf from termcolor import colored -from gamestonk_terminal.portfolio.portfolio_helpers import ally_positions_to_df +from gamestonk_terminal.brokers.brokers_helpers import ally_positions_to_df def login(): diff --git a/gamestonk_terminal/portfolio/alp_api.py b/gamestonk_terminal/brokers/alp_api.py similarity index 97% rename from gamestonk_terminal/portfolio/alp_api.py rename to gamestonk_terminal/brokers/alp_api.py index 84de934573a..b04d82e17f6 100644 --- a/gamestonk_terminal/portfolio/alp_api.py +++ b/gamestonk_terminal/brokers/alp_api.py @@ -11,7 +11,9 @@ from gamestonk_terminal.helper_funcs import ( plot_autoscale, ) from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.portfolio.portfolio_helpers import alpaca_positions_to_df +from gamestonk_terminal.brokers.brokers_helpers import ( + alpaca_positions_to_df, +) register_matplotlib_converters() diff --git a/gamestonk_terminal/portfolio/port_controller.py b/gamestonk_terminal/brokers/bro_controller.py similarity index 66% rename from gamestonk_terminal/portfolio/port_controller.py rename to gamestonk_terminal/brokers/bro_controller.py index 2199930e89d..a6a1261af78 100644 --- a/gamestonk_terminal/portfolio/port_controller.py +++ b/gamestonk_terminal/brokers/bro_controller.py @@ -1,5 +1,7 @@ __docformat__ = "numpy" +# pylint: disable=R1710 + import argparse from typing import List import pandas as pd @@ -8,21 +10,20 @@ from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import get_flair from gamestonk_terminal.menu import session -from gamestonk_terminal.portfolio import ( +from gamestonk_terminal.brokers import ( rh_api, alp_api, ally_api, - degiro_controller, ) -from gamestonk_terminal.portfolio.portfolio_helpers import ( - merge_portfolios, - print_portfolio, +from gamestonk_terminal.brokers.degiro import degiro_controller +from gamestonk_terminal.brokers.brokers_helpers import ( + merge_brokers_holdings, + print_brokers_holdings, ) -from gamestonk_terminal.portfolio import custom_model -class PortfolioController: - """Portfolio Controller""" +class BrokersController: + """Brokers Controller""" CHOICES = [ "alphist", @@ -36,8 +37,6 @@ class PortfolioController: "quit", "rhhold", "rhhist", - "csv", - "group", ] BROKERS = [ @@ -47,11 +46,10 @@ class PortfolioController: ] def __init__(self): - self.port_parser = argparse.ArgumentParser(add_help=False, prog="port") - self.port_parser.add_argument("cmd", choices=self.CHOICES) + self.bro_parser = argparse.ArgumentParser(add_help=False, prog="bro") + self.bro_parser.add_argument("cmd", choices=self.CHOICES) self.broker_list = set() self.merged_holdings = None - self.custom_portfolio = pd.DataFrame() def print_help(self): """Print help""" @@ -66,35 +64,25 @@ class PortfolioController: " q quit this menu, and shows back to main menu, logs out of brokers\n" " quit quit to abandon program, logs out of brokers\n" ) - self.print_portfolio_menu() + self.print_brokers_menu() - def print_portfolio_menu(self): + def print_brokers_menu(self): print( f"Current Broker: {('None', ', '.join(self.broker_list))[bool(self.broker_list)]}\n\n" "Ally:\n" " allyhold view ally holdings\n" "Alpaca:\n" " alphold view alp holdings\n" - " alphist view alp portfolio history\n" + " alphist view alp brokers holdings history\n" "Robinhood:\n" " rhhold view rh holdings\n" - " rhhist plot rh portfolio history\n" + " rhhist plot rh brokers holdings history\n" "\nDegiro:\n" - "> degiro view degiro sub-menu\n" + "> degiro degiro standalone menu\n" "\nMerge:\n" " login login to your brokers\n" " hold view net holdings across all logins\n" - "\nCustom:\n" - " csv load holdings from CSV\n" ) - if not self.custom_portfolio.empty: - self.print_portfolio_view() - - def print_portfolio_view(self): - if not self.custom_portfolio.empty: - print("Custom Portfolio Options:") - print(" group view holdings by a user input group") - print("") def switch(self, an_input: str): """Process and dispatch input @@ -107,28 +95,26 @@ class PortfolioController: None - continue in the menu """ - (known_args, other_args) = self.port_parser.parse_known_args(an_input.split()) + (known_args, other_args) = self.bro_parser.parse_known_args(an_input.split()) return getattr( self, "call_" + known_args.cmd, lambda: "Command not recognized!" )(other_args) def call_help(self, _): - """Process Help command""" - + """Process help command""" self.print_help() def call_q(self, _): - """Process Q command - quit the menu""" - + """Process q command - quit the menu""" return False def call_quit(self, _): - """Process Quit command - quit the program""" - + """Process quit command - quit the program""" return True def call_login(self, other_args): + """Process login command""" logged_in = False if not other_args: print("Please enter brokers you wish to login to") @@ -152,12 +138,14 @@ class PortfolioController: print(f"{broker} not supported") if logged_in: - self.print_portfolio_menu() + self.print_brokers_menu() def call_rhhist(self, other_args: List[str]): + """Process rhhist command""" rh_api.plot_historical(other_args) def call_rhhold(self, _): + """Process rhhold command""" try: rh_api.show_holdings() except Exception as e: @@ -165,6 +153,7 @@ class PortfolioController: print("") def call_alphold(self, _): + """Process alphold command""" try: alp_api.show_holdings() except Exception as e: @@ -172,9 +161,11 @@ class PortfolioController: print("") def call_alphist(self, other_args: List[str]): + """Process alphist command""" alp_api.plot_historical(other_args) def call_allyhold(self, _): + """Process allyhold command""" try: ally_api.show_holdings() except Exception as e: @@ -182,10 +173,13 @@ class PortfolioController: print("") def call_degiro(self, _): - """ "Process degiro command.""" - return degiro_controller.menu() + """Process degiro command.""" + if degiro_controller.menu(): + return True + print("") def call_hold(self, _): + """Process hold command.""" holdings = pd.DataFrame( columns=["Symbol", "MarketValue", "Quantity", "CostBasis"] ) @@ -197,43 +191,40 @@ class PortfolioController: [holdings, eval(broker + "_api.return_holdings()")], axis=0, ) - self.merged_holdings = merge_portfolios(holdings) - print_portfolio(self.merged_holdings) - - def call_csv(self, other_args): - """Process csv command""" - self.custom_portfolio = custom_model.load_csv_portfolio(other_args) - self.print_portfolio_view() - - def call_group(self, other_args): - """Process group command""" - if not self.custom_portfolio.empty: - custom_model.breakdown_by_group(self.custom_portfolio, other_args) - else: - print("Please load a portfolio") + self.merged_holdings = merge_brokers_holdings(holdings) + print_brokers_holdings(self.merged_holdings) def menu(): - """Portfolio Analysis Menu""" - - port_controller = PortfolioController() - port_controller.print_help() + """Brokers Menu""" + bro_controller = BrokersController() + print( + "\nUSE THIS MENU AT YOUR OWN DISCRETION\n" + " - This menu is the only one in the entire repository that has access to your broker accounts. " + "If you have provided your login details on the config_terminal.py file" + " - We review the code thoroughly from each contributor, hence, we can ensure that our codebase " + "does not take advantage of your data.\n" + " - HOWEVER, our project imports almost 200 different open source python modules. Therefore, it " + "is impossible for us to check the coding standards and security of each of these modules. " + "Hence why adding this disclaimer here." + ) + bro_controller.print_help() while True: # Get input command from user if session and gtff.USE_PROMPT_TOOLKIT: completer = NestedCompleter.from_nested_dict( - {c: None for c in port_controller.CHOICES} + {c: None for c in bro_controller.CHOICES} ) an_input = session.prompt( - f"{get_flair()} (pa)> ", + f"{get_flair()} (bro)> ", completer=completer, ) else: - an_input = input(f"{get_flair()} (pa)> ") + an_input = input(f"{get_flair()} (bro)> ") try: - process_input = port_controller.switch(an_input) + process_input = bro_controller.switch(an_input) if process_input is not None: return process_input diff --git a/gamestonk_terminal/portfolio/portfolio_helpers.py b/gamestonk_terminal/brokers/brokers_helpers.py similarity index 96% rename from gamestonk_terminal/portfolio/portfolio_helpers.py rename to gamestonk_terminal/brokers/brokers_helpers.py index 8947eca4856..542b91a7544 100644 --- a/gamestonk_terminal/portfolio/portfolio_helpers.py +++ b/gamestonk_terminal/brokers/brokers_helpers.py @@ -63,7 +63,7 @@ def rh_positions_to_df(holds: dict): return df -def merge_portfolios(df: pd.DataFrame) -> pd.DataFrame: +def merge_brokers_holdings(df: pd.DataFrame) -> pd.DataFrame: if set(df.columns) != {"Symbol", "MarketValue", "Quantity", "CostBasis", "Broker"}: print("Check df generation") return None @@ -79,7 +79,7 @@ def merge_portfolios(df: pd.DataFrame) -> pd.DataFrame: return df -def print_portfolio(portfolio): +def print_brokers_holdings(portfolio): print( "Stonk\t Market Value \t\t Quantity \t\t Cost Basis \t\t All Time % Change \t\t Brokers" ) diff --git a/gamestonk_terminal/brokers/degiro/README.md b/gamestonk_terminal/brokers/degiro/README.md new file mode 100644 index 00000000000..a3435402869 --- /dev/null +++ b/gamestonk_terminal/brokers/degiro/README.md @@ -0,0 +1,150 @@ +# DEGIRO + +This menu aims to contain several commands that are available to a user through his Degiro account. + +In order to be able to use these, just ensure you connect to your degiro account by typing `login` first. + +## Credentials + +In order to login you need to update [config file](/gamestonk_terminal/config_terminal.py) with the following credentials: + +| Parameter | Description | +| :--- | :--- | +| DG_USERNAME | Username used to log into Degiro's website. | +| DG_PASSWORD | Password used to log into Degiro's website. | + +## 2FA + +In order to use 2FA you need to update [config file](/gamestonk_terminal/config_terminal.py) with: + +| Parameter | Description | +| :--- | :--- | +| DG_TOTP_SECRET | This secret key will let Gamestonk generate the `OneTimePassword` for you. | + +If you provide your `DG_TOTP_SECRET`, you won't have to type your `OneTimePassword`, Gamestonk will generate it for you at each connection. + +`DG_TOTP_SECRET` is the text representation of the `QRCODE` that Degiro's provide you when you enable 2FA. + +More information on 2FA credentials, in the documentation of the [degiro-connector library](https://github.com/Chavithra/degiro-connector). + +## Features + +* [login](#login) + * Connect to your Degiro account +* [logout](#login) + * Disconnect from Degiro account + +* [hold](#hold) + * Command to look at current holdings +* [lookup](#lookup) + * Command to search for a product by name + +* [create](#create) + * Create and `Order` +* [update](#update) + * Update and `Order` +* [cancel](#cancel) + * Cancel and `Order` +* [pending](#pending) + * Command to list pending orders + +* [companynews](#companynews) + * Command to get news about a company with it's isin +* [lastnews](#lastnews) + * Command to get latest news +* [topnews](#topnews) + * Command to get top news preview + + +### login +```` +usage: login [-u USERNAME] [-p PASSWORD] [-o OTP] [-s TOPT_SECRET] [-h] + +optional arguments: + -u USERNAME, --username USERNAME + Username in Degiro's account. + -p PASSWORD, --password PASSWORD + Password in Degiro's account. + -o OTP, --otp OTP One time password (2FA). + -s TOPT_SECRET, --topt-secret TOPT_SECRET + TOTP SECRET (2FA). + -h, --help show this help message +```` + +### logout +```` +usage: logout +```` + + +### hold +```` +usage: hold +```` + +Displays current holdings to the console. + +### lookup +```` +usage: lookup TESLA +```` + +It will display a result like this : +``` + name isin symbol productType currency closePrice closePriceDate +0 Tesla US88160R1014 TSLA STOCK USD 571.690 2021-05-13 +1 Tesla Inc US88160R1014 TL0 STOCK EUR 478.000 2021-05-13 +2 Tesla Inc US88160R1014 TL0 STOCK EUR 467.650 2021-05-13 +3 TurboC O.End Tesla 535,04 DE000VQ1S3R3 None WARRANT EUR 3.740 2021-05-13 +4 TurboC O.End Tesla 591,4 DE000VQ5R6T9 None WARRANT EUR 0.001 2021-05-13 +5 TurboP O.End Tesla 685,29 DE000VQ7HWU6 None WARRANT EUR 9.870 2021-05-13 +6 TurboC O.End Tesla 448,63 DE000VQ1KYG8 None WARRANT EUR 10.890 2021-05-13 +7 Call 17.12.21 Tesla 392 DE000VP53547 DE000VP53547 WARRANT EUR 8.960 2021-05-13 +8 Call 20.01.23 Tesla 720 DE000VQ68691 None WARRANT EUR 1.160 2021-05-13 +9 Leverage Shares 1x Tesla ETP IE00BKT6ZH01 STSL ETF GBX 135.550 2021-05-13 +``` + + +### create +```` +usage: create [-a {buy,sell}] (-prod PRODUCT | -sym SYMBOL) -p PRICE (-s SIZE | -up UP_TO) [-d {gtd,gtc}] [-t {limit,market,stop-limit,stop-loss}] [-h] +```` + +### update +```` +usage: update ORDER_ID -p PRICE +```` + +### cancel +```` +usage: cancel ORDER_ID +```` + +### pending +```` +usage: pending +```` + + +### companynews +```` +usage: companynews NL0000235190 +```` + +You need to provide the `ISIN` number of the company. +You can get this `ISIN` using the `dglookup` command for instance. + +### lastnews +```` +usage: lastnews [-l LIMIT] [-h] + +optional arguments: + -l LIMIT, --limit LIMIT + Number of news to display. + -h, --help show this help message +```` + +### topnews +```` +usage: topnews +```` diff --git a/gamestonk_terminal/portfolio/degiro_controller.py b/gamestonk_terminal/brokers/degiro/degiro_controller.py similarity index 97% rename from gamestonk_terminal/portfolio/degiro_controller.py rename to gamestonk_terminal/brokers/degiro/degiro_controller.py index d2b565feb69..4d737ee0d0e 100644 --- a/gamestonk_terminal/portfolio/degiro_controller.py +++ b/gamestonk_terminal/brokers/degiro/degiro_controller.py @@ -13,7 +13,7 @@ from gamestonk_terminal.helper_funcs import ( parse_known_args_and_warn, ) from gamestonk_terminal.menu import session -from gamestonk_terminal.portfolio.degiro_view import DegiroView +from gamestonk_terminal.brokers.degiro.degiro_view import DegiroView class DegiroController: @@ -287,16 +287,10 @@ class DegiroController: def q(self, _): """Process Q command - quit the menu.""" - - DegiroView.q_display() - return False def quit(self, _): """Process Quit command - quit the program.""" - - DegiroView.quit_display() - return True def topnews(self, l_args): @@ -377,11 +371,11 @@ def menu(): {c: None for c in degiro_controller.CHOICES} ) an_input = session.prompt( - f"{get_flair()} (pa)>(degiro)> ", + f"{get_flair()} (bro)>(degiro)> ", completer=completer, ) else: - an_input = input(f"{get_flair()} (pa)>(degiro)> ") + an_input = input(f"{get_flair()} (bro)>(degiro)> ") try: process_input = degiro_controller.switch(an_input) diff --git a/gamestonk_terminal/portfolio/degiro_model.py b/gamestonk_terminal/brokers/degiro/degiro_model.py similarity index 100% rename from gamestonk_terminal/portfolio/degiro_model.py rename to gamestonk_terminal/brokers/degiro/degiro_model.py diff --git a/gamestonk_terminal/portfolio/degiro_view.py b/gamestonk_terminal/brokers/degiro/degiro_view.py similarity index 93% rename from gamestonk_terminal/portfolio/degiro_view.py rename to gamestonk_terminal/brokers/degiro/degiro_view.py index e9065d527ee..f1b46573cea 100644 --- a/gamestonk_terminal/portfolio/degiro_view.py +++ b/gamestonk_terminal/brokers/degiro/degiro_view.py @@ -14,7 +14,7 @@ from degiro_connector.trading.pb.trading_pb2 import ( ) # IMPORTATION INTERNAL -from gamestonk_terminal.portfolio.degiro_model import DegiroModel +from gamestonk_terminal.brokers.degiro.degiro_model import DegiroModel # pylint: disable=no-member @@ -44,19 +44,22 @@ class DegiroView: def help_display(): print( "Degiro:\n" - " cancel cancel an order using the `id`\n" - " companynews view news about a company with it's isin\n" - " create create an order.\n" - " hold view holdings\n" - " lastnews view latest news\n" - " login connect to degiro's api\n" - " logout disconnect from degiro's api\n" - " lookup view search for a product by name\n" - " pending view pending orders\n" - " q quit degiro integration\n" - " quit quit the app\n" - " topnews view top news preview\n" - " update view top news preview\n" + " q quit degiro standalone menu\n" + " quit quit the app\n" + " login connect to degiro's api\n" + " logout disconnect from degiro's api\n" + "\n" + " hold view holdings\n" + " lookup view search for a product by name\n" + "\n" + " create create an order\n" + " update update an order\n" + " cancel cancel an order using the id\n" + " pending view pending orders\n" + "\n" + " companynews view news about a company with it's isin\n" + " lastnews view latest news\n" + " topnews view top news preview\n" ) def cancel(self, ns_parser: Namespace): @@ -399,14 +402,6 @@ class DegiroView: def __pending_display_no_result(): print("No pending orders.") - @staticmethod - def q_display(): - print("Quit Degiro integration.") - - @staticmethod - def quit_display(): - print("Quit the app.") - def topnews(self, ns_parser: Namespace): _ = ns_parser diff --git a/gamestonk_terminal/portfolio/rh_api.py b/gamestonk_terminal/brokers/rh_api.py similarity index 98% rename from gamestonk_terminal/portfolio/rh_api.py rename to gamestonk_terminal/brokers/rh_api.py index 4111802d1ad..eaacbff9bab 100644 --- a/gamestonk_terminal/portfolio/rh_api.py +++ b/gamestonk_terminal/brokers/rh_api.py @@ -15,7 +15,7 @@ from gamestonk_terminal.helper_funcs import ( ) from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.config_terminal import RH_USERNAME as user, RH_PASSWORD as pw -from gamestonk_terminal.portfolio.portfolio_helpers import rh_positions_to_df +from gamestonk_terminal.brokers.brokers_helpers import rh_positions_to_df register_matplotlib_converters() diff --git a/gamestonk_terminal/discovery/README.md b/gamestonk_terminal/discovery/README.md index 89e8e6e5f3e..634822ba5fb 100644 --- a/gamestonk_terminal/discovery/README.md +++ b/gamestonk_terminal/discovery/README.md @@ -42,15 +42,10 @@ This menu aims to discover new stocks, and the usage of the following commands a * top ratings updates [MarketBeat] * [darkpool](#darkpool) * dark pool tickers with growing activity [FINRA] - -* [topdark](#topdark) - * largest dark pool positions [Stockgrid.io] - * [darkshort](#darkshort) - * dark pool short information [Stockgrid.io] - + * dark pool short position [Stockgrid] * [shortvol](#shortvol) - * short interest [Stockgrid.io] + * short interest and days to cover [Stockgrid] ## ipo @@ -305,72 +300,27 @@ Display dark pool (ATS) data of tickers with growing trades activity. [Source: F ![darkpool](https://user-images.githubusercontent.com/25267873/115323195-8d642080-a17f-11eb-9ef8-d456ce769ab7.png) -# Stockgrid.io - -## topdark - -``` -usage: topdark [-n] -``` -Display the largest dark pool positions -* -n : Number of tickers to display - -```python -(✨) (disc)> topdark -n 4 - -╒══════════╤═══════════════════╤═══════════════╤════════════╕ -│ Ticker │ Position ($1B) │ Shares (1M) │ Date │ -╞══════════╪═══════════════════╪═══════════════╪════════════╡ -│ QQQ │ 16.83 │ 51.11 │ 2021-06-10 │ -├──────────┼───────────────────┼───────────────┼────────────┤ -│ IWM │ 5.18 │ 22.99 │ 2021-06-10 │ -├──────────┼───────────────────┼───────────────┼────────────┤ -│ BND │ 3.24 │ 37.98 │ 2021-06-10 │ -├──────────┼───────────────────┼───────────────┼────────────┤ -│ EEM │ 3.13 │ 56.95 │ 2021-06-10 │ -╘══════════╧═══════════════════╧═══════════════╧════════════╛ -``` - ## darkshort ``` -usage: darkshort [-n] +usage: darkshort [-n NUM] [-s {sv,sv_pct,nsv,nsv_dollar,dpp,dpp_dollar}] ``` -Display the largest dark pool short positions -* -n : Number of tickers to display +Get dark pool short positions. [Source: Stockgrid] +* -n: Number of top tickers to show +* -s: Field for which to sort by, where 'sv': Short Vol. (1M), 'sv_pct': Short Vol. %, 'nsv': Net Short Vol. (1M), 'nsv_dollar': Net Short Vol. ($100M), 'dpp': DP Position (1M), 'dpp_dollar': DP Position ($1B). Default: dpp_dollar. + +darkshort -``` -(✨) (disc)> darkshort -n 4 -╒══════════╤════════════╤══════════════════╤═════════════════════╤═════════════════════╤══════════════════╕ -│ Ticker │ Date │ Net Short (1M) │ Net Short ($100M) │ Short Volume (1M) │ Short Volume % │ -╞══════════╪════════════╪══════════════════╪═════════════════════╪═════════════════════╪══════════════════╡ -│ QQQ │ 2021-06-10 │ 1.66 │ 5.65 │ 5.90 │ 0.58 │ -├──────────┼────────────┼──────────────────┼─────────────────────┼─────────────────────┼──────────────────┤ -│ IWM │ 2021-06-10 │ 1.38 │ 3.17 │ 5.43 │ 0.57 │ -├──────────┼────────────┼──────────────────┼─────────────────────┼─────────────────────┼──────────────────┤ -│ RH │ 2021-06-10 │ 0.43 │ 3.03 │ 1.26 │ 0.60 │ -├──────────┼────────────┼──────────────────┼─────────────────────┼─────────────────────┼──────────────────┤ -│ TLT │ 2021-06-10 │ 1.70 │ 2.42 │ 2.25 │ 0.80 │ -╘══════════╧════════════╧══════════════════╧═════════════════════╧═════════════════════╧══════════════════╛ -``` ## shortvol -Display the largest short positions -* -n : Number of tickers to display - ``` -(✨) (disc)> shortvol -n 4 +usage: shortvol [-n NUM] [-s {float,dtc,si}] [-h] +``` + +Print short interest and days to cover. [Source: Stockgrid] +* -n: Number of top tickers to show +* -s: Field for which to sort by, where 'float': Float Short %, 'dtc': Days to Cover, 'si': Short Interest. Default: float. + +shortvol -╒══════════╤════════════╤════════════════╤═════════════════╤═══════════════════════╕ -│ Ticker │ Date │ %Float Short │ Days To Cover │ Short Interest (1M) │ -╞══════════╪════════════╪════════════════╪═════════════════╪═══════════════════════╡ -│ AGC │ 2021-05-28 │ 42.51 │ 24.75 │ 15.94 │ -├──────────┼────────────┼────────────────┼─────────────────┼───────────────────────┤ -│ PETS │ 2021-05-28 │ 32.69 │ 20.05 │ 6.40 │ -├──────────┼────────────┼────────────────┼─────────────────┼───────────────────────┤ -│ BCEL │ 2021-05-28 │ 27.86 │ 24.51 │ 6.23 │ -├──────────┼────────────┼────────────────┼─────────────────┼───────────────────────┤ -│ KDMN │ 2021-05-28 │ 25.54 │ 35.96 │ 39.73 │ -╘══════════╧════════════╧════════════════╧═════════════════╧═══════════════════════╛ -``` \ No newline at end of file diff --git a/gamestonk_terminal/discovery/disc_controller.py b/gamestonk_terminal/discovery/disc_controller.py index cf67f5658e7..1f40c0f4b56 100644 --- a/gamestonk_terminal/discovery/disc_controller.py +++ b/gamestonk_terminal/discovery/disc_controller.py @@ -55,7 +55,6 @@ class DiscoveryController: "latest", "trending", "darkpool", - "topdark", "darkshort", "shortvol", ] @@ -103,10 +102,8 @@ class DiscoveryController: print(" latest latest news [Seeking Alpha]") print(" trending trending news [Seeking Alpha]") print(" ratings top ratings updates [MarketBeat]") - print(" darkpool dark pool tickers with growing activity [FINRA]") - print(" topdark largest dark pool positions [Stockgrid.io]") - print(" darkshort dark pool short information [Stockgrid.io]") - print(" shortvol short interest [Stockgrid.io]") + print(" darkshort dark pool short position [Stockgrid.io]") + print(" shortvol short interest and days to cover [Stockgrid.io]") print("") def switch(self, an_input: str): @@ -227,12 +224,8 @@ class DiscoveryController: """Process darkpool command""" finra_ats_view.dark_pool(other_args) - def call_topdark(self, other_args: List[str]): - """Process topdark command""" - stockgrid_view.top_dark_pools(other_args) - def call_darkshort(self, other_args: List[str]): - """Process shortvol command""" + """Process darkshort command""" stockgrid_view.darkshort(other_args) def call_shortvol(self, other_args: List[str]): diff --git a/gamestonk_terminal/discovery/stockgrid_view.py b/gamestonk_terminal/discovery/stockgrid_view.py index b5f1accc8e0..7ac27ac4f93 100644 --- a/gamestonk_terminal/discovery/stockgrid_view.py +++ b/gamestonk_terminal/discovery/stockgrid_view.py @@ -6,61 +6,12 @@ from typing import List import requests import pandas as pd from tabulate import tabulate -from gamestonk_terminal.helper_funcs import ( - parse_known_args_and_warn, -) - - -def top_dark_pools(other_args: List[str]): - """ - Get and show the top dark pool positions - Parameters - ---------- - other_args: List[str[ - Argparse arguments - - """ - - parser = argparse.ArgumentParser( - prog="topdark", add_help=False, description="Get top dark pool positions" - ) - parser.add_argument("-n", help="Number to show", type=int, default=10, dest="num") - print("") - - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - link1 = "https://stockgridapp.herokuapp.com/get_dark_pool_data?top=Dark+Pools+Position+$&minmax=desc" - response = requests.get(link1) - df = pd.DataFrame(response.json()["data"]).sort_values( - "Dark Pools Position $", ascending=False - ) - df = df[["Ticker", "Dark Pools Position $", "Dark Pools Position", "Date"]] - df["Dark Pools Position $"] = df["Dark Pools Position $"] / ( - 1_000_000_000 - ) # %% - df["Dark Pools Position"] = df["Dark Pools Position"] / 1_000_000 - - df.columns = ["Ticker", "Position ($1B) ", "Shares (1M)", "Date"] - print( - tabulate( - df.iloc[: ns_parser.num], - tablefmt="fancy_grid", - floatfmt=".2f", - headers=list(df.columns), - showindex=False, - ) - ) - print("") - except Exception as e: - print(e, "\n") +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, check_positive def darkshort(other_args: List[str]): - """ - Get short volume data from Dark Pools. + """Get dark pool short positions. + Parameters ---------- other_args: List[str] @@ -68,16 +19,44 @@ def darkshort(other_args: List[str]): """ parser = argparse.ArgumentParser( - prog="darkshort", add_help=False, description="Get dark pool short positions" + prog="darkshort", + add_help=False, + description="Get dark pool short positions. [Source: Stockgrid]", + ) + parser.add_argument( + "-n", + "--number", + help="Number of top tickers to show", + type=check_positive, + default=10, + dest="num", + ) + parser.add_argument( + "-s", + "--sort", + help="Field for which to sort by, where 'sv': Short Vol. (1M), " + "'sv_pct': Short Vol. %%, 'nsv': Net Short Vol. (1M), " + "'nsv_dollar': Net Short Vol. ($100M), 'dpp': DP Position (1M), " + "'dpp_dollar': DP Position ($1B)", + choices=["sv", "sv_pct", "nsv", "nsv_dollar", "dpp", "dpp_dollar"], + default="dpp_dollar", + dest="sort_field", ) - parser.add_argument("-n", help="Number to show", type=int, default=10, dest="num") - print("") try: ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return + d_fields = { + "sv": "Short Volume", + "sv_pct": "Short Volume %", + "nsv": "Net Short Volume", + "nsv_dollar": "Net Short Volume $", + "dpp": "Dark Pools Position", + "dpp_dollar": "Dark Pools Position $", + } + link = "https://stockgridapp.herokuapp.com/get_dark_pool_data?top=Dark+Pools+Position+$&minmax=desc" response = requests.get(link) df = pd.DataFrame(response.json()["data"]) @@ -86,24 +65,33 @@ def darkshort(other_args: List[str]): [ "Ticker", "Date", - "Net Short Volume", - "Net Short Volume $", "Short Volume", "Short Volume %", + "Net Short Volume", + "Net Short Volume $", + "Dark Pools Position", + "Dark Pools Position $", ] - ].sort_values(by="Net Short Volume $", ascending=False) + ].sort_values(by=d_fields[ns_parser.sort_field], ascending=False) + dp_date = df["Date"].values[0] + df = df.drop(columns=["Date"]) df["Net Short Volume $"] = df["Net Short Volume $"] / 100_000_000 df["Short Volume"] = df["Short Volume"] / 1_000_000 df["Net Short Volume"] = df["Net Short Volume"] / 1_000_000 df["Short Volume %"] = df["Short Volume %"] * 100 + df["Dark Pools Position $"] = df["Dark Pools Position $"] / (1_000_000_000) + df["Dark Pools Position"] = df["Dark Pools Position"] / 1_000_000 df.columns = [ "Ticker", - "Date", - "Net Short (1M)", - "Net Short ($100M)", - "Short Volume (1M)", - "Short Volume %", + "Short Vol. (1M)", + "Short Vol. %", + "Net Short Vol. (1M)", + "Net Short Vol. ($100M)", + "DP Position (1M)", + "DP Position ($1B)", ] + # Assuming that the datetime is the same, which from my experiments seems to be the case + print(f"The following data corresponds to the date: {dp_date}") print( tabulate( df.iloc[: ns_parser.num], @@ -120,8 +108,8 @@ def darkshort(other_args: List[str]): def shortvol(other_args: List[str]): - """ - Get and show short volume from stockgrid.io + """Print short interest and days to cover + Parameters ---------- other_args: List[str] @@ -130,10 +118,25 @@ def shortvol(other_args: List[str]): parser = argparse.ArgumentParser( prog="shortvol", add_help=False, - description="Scrape stockgrid.io for short volume data", + description="Print short interest and days to cover. [Source: Stockgrid]", + ) + parser.add_argument( + "-n", + "--number", + help="Number of top tickers to show", + type=check_positive, + default=10, + dest="num", + ) + parser.add_argument( + "-s", + "--sort", + help="Field for which to sort by, where 'float': Float Short %%, " + "'dtc': Days to Cover, 'si': Short Interest", + choices=["float", "dtc", "si"], + default="float", + dest="sort_field", ) - parser.add_argument("-n", help="Number to show", type=int, default=10, dest="num") - print("") try: ns_parser = parse_known_args_and_warn(parser, other_args) @@ -145,20 +148,31 @@ def shortvol(other_args: List[str]): df = pd.DataFrame(r.json()["data"]) df.head() - # %% + d_fields = { + "float": "%Float Short", + "dtc": "Days To Cover", + "si": "Short Interest", + } + df = df[ ["Ticker", "Date", "%Float Short", "Days To Cover", "Short Interest"] - ].sort_values(by="%Float Short", ascending=False) + ].sort_values( + by=d_fields[ns_parser.sort_field], + ascending=bool(ns_parser.sort_field == "dtc"), + ) + dp_date = df["Date"].values[0] + df = df.drop(columns=["Date"]) df["Short Interest"] = df["Short Interest"] / 1_000_000 df.head() df.columns = [ "Ticker", - "Date", - "%Float Short", - "Days To Cover", + "Float Short %", + "Days to Cover", "Short Interest (1M)", ] + # Assuming that the datetime is the same, which from my experiments seems to be the case + print(f"The following data corresponds to the date: {dp_date}") print( tabulate( df.iloc[: ns_parser.num], @@ -168,6 +182,7 @@ def shortvol(other_args: List[str]): showindex=False, ) ) + print("") except Exception as e: print(e, "\n") diff --git a/gamestonk_terminal/due_diligence/README.md b/gamestonk_terminal/due_diligence/README.md index aa6be243c0e..6986742992d 100644 --- a/gamestonk_terminal/due_diligence/README.md +++ b/gamestonk_terminal/due_diligence/README.md @@ -31,7 +31,7 @@ This menu aims to help in due-diligence of a pre-loaded stock, and the usage of * [ftd](#ftd) * fails-to-deliver data [SEC] * [shortview](#shortview) - * shows price vs short interest over last year [Stockgrid.io] + * shows price vs short interest volume [Stockgrid] ## news @@ -201,7 +201,17 @@ The fails-to-deliver data collected by SEC. Fails to deliver on a given day are ![ftd](https://user-images.githubusercontent.com/25267873/115201750-93a9bc80-a0ed-11eb-8f13-68508749259e.png) ## shortview + ```text -usage: shortview +usage: shortvol [-n NUM] [-r] ``` -Plots price and short volume over the past year. + +Shows price vs short interest volume. [Source: Stockgrid] +* -r: Flag to print raw data instead. +* -n: Number of last open market days to show. Default: 120, but if -r is set it's 10. + +![shortvol_gme](https://user-images.githubusercontent.com/25267873/122323986-ee119e80-cf1f-11eb-8a01-2fd90f786d70.png) + +shortvolraw_gme + + diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py index 4008686664f..348bd4284d7 100644 --- a/gamestonk_terminal/due_diligence/dd_controller.py +++ b/gamestonk_terminal/due_diligence/dd_controller.py @@ -110,9 +110,7 @@ class DueDiligenceController: ) print(" dp dark pools (ATS) vs OTC data [FINRA]") print(" ftd fails-to-deliver data [SEC]") - print( - " shortview shows price vs short interest over last year [Stockgrid.io]" - ) + print(" shortview price vs short interest volume [Stockgrid.io]") print("") def switch(self, an_input: str): @@ -204,6 +202,7 @@ class DueDiligenceController: sec_view.fails_to_deliver(other_args, self.ticker) def call_shortview(self, other_args: List[str]): + """Process shortview command""" sg_view.shortview(self.ticker, other_args) diff --git a/gamestonk_terminal/due_diligence/stockgrid_dd_view.py b/gamestonk_terminal/due_diligence/stockgrid_dd_view.py index 14bc64d1ca3..3b8f7c172f1 100644 --- a/gamestonk_terminal/due_diligence/stockgrid_dd_view.py +++ b/gamestonk_terminal/due_diligence/stockgrid_dd_view.py @@ -7,14 +7,19 @@ from datetime import timedelta import requests import pandas as pd import matplotlib.pyplot as plt +from tabulate import tabulate from gamestonk_terminal.config_plot import PLOT_DPI from gamestonk_terminal.feature_flags import USE_ION -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + plot_autoscale, + check_positive, +) def shortview(ticker: str, other_args: List[str]): - """ - Plot price vs short volume over last year + """Plot price vs short interest volume + Parameters ---------- ticker: str @@ -23,9 +28,25 @@ def shortview(ticker: str, other_args: List[str]): Argparse arguments """ - parser = argparse.ArgumentParser( - prog="shortview", add_help=False, description="Shows price vs short interest" + prog="shortvol", + add_help=False, + description="Shows price vs short interest volume. [Source: Stockgrid]", + ) + parser.add_argument( + "-n", + "--number", + help="Number of last open market days to show", + type=check_positive, + default=10 if "-r" in other_args else 120, + dest="num", + ) + parser.add_argument( + "-r", + action="store_true", + default=False, + help="Flag to print raw data instead", + dest="raw", ) try: @@ -34,56 +55,101 @@ def shortview(ticker: str, other_args: List[str]): return link = f"https://stockgridapp.herokuapp.com/get_dark_pool_individual_data?ticker={ticker}" response = requests.get(link) - response.json().keys() - df1 = pd.DataFrame(response.json()["individual_dark_pool_position_data"]) - df2 = pd.DataFrame(response.json()["individual_short_volume"]) - prices = response.json()["prices"]["prices"] - dates = pd.to_datetime(df1.dates) + df = pd.DataFrame(response.json()["individual_short_volume_table"]["data"]) + df["date"] = pd.to_datetime(df["date"]) - fig = plt.figure(figsize=(plot_autoscale()), dpi=PLOT_DPI) + if ns_parser.raw: + df = df.sort_values(by="date", ascending=False) - ax = fig.add_subplot(111) + df["Short Vol. (1M)"] = df["short_volume"] / 1_000_000 + df["Short Vol. %"] = df["short_volume%"] * 100 + df["Short Exempt Vol. (1K)"] = df["short_exempt_volume"] / 1_000 + df["Total Vol. (1M)"] = df["total_volume"] / 1_000_000 - ax.bar( - dates, - df2["total_volume"], - width=timedelta(days=1), - color="b", - alpha=0.4, - label="Total Volume", - ) - ax.bar( - dates, - df2["short_volume"], - width=timedelta(days=1), - color="r", - alpha=0.4, - label="Short Volume", - ) + df = df[ + [ + "date", + "Short Vol. (1M)", + "Short Vol. %", + "Short Exempt Vol. (1K)", + "Total Vol. (1M)", + ] + ] - ax.set_ylabel("Volume") - ax2 = ax.twinx() + df.date = df.date.dt.date - ax2.plot(dates, prices, c="k", label="Price") - ax2.set_ylabel("Price ($)") + print( + tabulate( + df.iloc[: ns_parser.num], + tablefmt="fancy_grid", + floatfmt=".2f", + headers=list(df.columns), + showindex=False, + ) + ) + else: + prices = response.json()["prices"]["prices"] - lines, labels = ax.get_legend_handles_labels() - lines2, labels2 = ax2.get_legend_handles_labels() - ax2.legend(lines + lines2, labels + labels2, loc="upper left") + fig = plt.figure(figsize=(plot_autoscale()), dpi=PLOT_DPI) - ax.set_xlim(dates[0], dates[251]) + ax = fig.add_subplot(111) + ax.bar( + df["date"], + df["total_volume"], + width=timedelta(days=1), + color="b", + alpha=0.4, + label="Total Volume", + ) + ax.bar( + df["date"], + df["short_volume"], + width=timedelta(days=1), + color="r", + alpha=0.4, + label="Short Volume", + ) - ax.grid(b=True, which="major", color="#666666", linestyle="-") - ax.minorticks_on() - ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax.set_ylabel("Volume") + ax2 = ax.twinx() + ax2.plot( + df["date"].values, prices[len(prices) - len(df) :], c="k", label="Price" + ) + ax2.set_ylabel("Price ($)") - plt.suptitle(f"Price vs Short Volume for {ticker}") - plt.gcf().autofmt_xdate() - if USE_ION: - plt.ion() + ax3 = ax.twinx() + ax3.plot( + df["date"].values, + 100 * df["short_volume%"], + c="green", + label="Short Vol. %", + ) + ax3.set_ylabel("Short Volume %") + ax3.spines["right"].set_position(("outward", 50)) + ax3.yaxis.label.set_color("green") - plt.show() + lines, labels = ax.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + lines3, labels3 = ax3.get_legend_handles_labels() + ax3.legend( + lines + lines2 + lines3, labels + labels2 + labels3, loc="upper left" + ) + + ax.set_xlim( + df["date"].values[max(0, len(df) - ns_parser.num)], + df["date"].values[len(df) - 1], + ) + + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + plt.suptitle(f"Price vs Short Volume Interest for {ticker}") + plt.gcf().autofmt_xdate() + if USE_ION: + plt.ion() + + plt.show() print("") except Exception as e: diff --git a/gamestonk_terminal/helper_funcs.py b/gamestonk_terminal/helper_funcs.py index e44f27e4d11..1e111d2b2fc 100644 --- a/gamestonk_terminal/helper_funcs.py +++ b/gamestonk_terminal/helper_funcs.py @@ -26,7 +26,13 @@ if cfgPlot.BACKEND is not None: def check_valid_path(path: str) -> str: - if not os.path.exists(path): + if not os.path.exists( + os.path.abspath( + os.path.join( + "gamestonk_terminal", "portfolio_analysis", "portfolios", f"{path}.csv" + ) + ) + ): raise argparse.ArgumentTypeError("Path does not exist") return path diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 345e2a2eacf..7a7aaf7560a 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -65,20 +65,23 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): " > scr screener stocks, \t\t e.g. overview/performance, using preset filters" ) print(" > mill papermill menu, \t\t menu to generate notebook reports") - print(" > econ economic data, \t\t e.g.: FRED, events") - print( - " > pa portfolio analysis, \t\t supports: robinhood, alpaca, ally " - ) + print(" > econ economic data, \t\t e.g.: events, FRED data, GDP, VIXCLS") + print(" > pa portfolio analysis, \t\t analyses your custom portfolio") + print(" > bro brokers holdings, \t\t supports: robinhood, alpaca, ally") print( " > crypto cryptocurrencies, \t\t from: coingecko, coinmarketcap, binance" ) print( " > po portfolio optimization, \t optimal portfolio weights from pyportfolioopt" ) - print(" > gov government menu, \t\t congress, senate, house trading") + print( + " > gov government menu, \t\t house trading, contracts, corporate lobbying" + ) print(" > etf etf menu, \t\t\t from: StockAnalysis.com") print(" > fx forex menu, \t\t\t forex support through Oanda") - print(" > rc resource collection, \t\t e.g. hf letters") + print( + " > rc resource collection, \t\t e.g. hf letters, arXiv, EDGAR, FINRA" + ) if s_ticker: print( @@ -112,7 +115,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): " > ra residuals analysis, \t e.g.: model fit, qqplot, hypothesis test" ) print( - " > op options info, \t e.g.: volume and open interest" + " > op options info, \t e.g.: volume, open interest, chains, volatility" ) print("") diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py index 6148459f087..f6a4da93fb5 100644 --- a/gamestonk_terminal/options/op_controller.py +++ b/gamestonk_terminal/options/op_controller.py @@ -32,7 +32,6 @@ class OptionsController: choices=self.CHOICES, ) - @staticmethod def expiry_dates(self, other_args: List[str]): """Print all available expiry dates.""" parser = argparse.ArgumentParser( @@ -81,7 +80,7 @@ class OptionsController: @staticmethod def print_help(expiry_date): """Print help.""" - print("\nOptions insight Mode:") + print("\nOptions:") print(" help show this menu again") print(" q quit this menu, and shows back to main menu") print(" quit quit to abandon program") @@ -128,7 +127,7 @@ class OptionsController: def call_exp(self, other_args: List[str]): """Process exp command.""" - self.expiry_dates(self, other_args) + self.expiry_dates(other_args) def call_voi(self, other_args: List[str]): """Process voi command.""" diff --git a/gamestonk_terminal/portfolio/README.md b/gamestonk_terminal/portfolio/README.md deleted file mode 100644 index 5deedd95072..00000000000 --- a/gamestonk_terminal/portfolio/README.md +++ /dev/null @@ -1,311 +0,0 @@ -# PORTFOLIO - -This page shows the available brokers for loading in data. If yours is not listed, please submit a new issue and we will look at adding it. - -Current brokers: -* rh : Robinhood -* alp : Alpaca -* ally: Ally -* dg : Degiro - - -Once this screen is accessed, the first command to be run is -```` -login [brokers] -```` - -The brokers should just be your brokers from the list shown on the first menu. To login to Robinhood, the command is -```` -login rh -```` -To login to both robinhood and alpaca: -```` -login rh alp -```` -After logging in, the help menu will display which brokers you logged into. - -Your login information should be stored as environment variables in [config file](/gamestonk_terminal/config_terminal.py), but check each individual section for specifics. - -[ROBINHOOD](#ROBINHOOD) -* [rhhold](#rhhold) - * Look at current (stock only) holdings -* [rhhist](#rhhist) - * Get and plot historical portfolio - -[ALPACA](#ALPACA) -* [alphold](#alphold) - * Look at current (stock only) holdings -* [alphist](#rhhist) - * Get and plot historical portfolio - -[ALLY](#ALLY) -* [allyhold](#allyhold) - * Look at Ally Invest Holdings - -[DEGIRO](#DEGIRO) -* [cancel](#cancel) - * Cancel and `Order` -* [companynews](#companynews) - * Command to get news about a company with it's isin -* [create](#create) - * Create and `Order` -* [hold](#hold) - * Command to look at current holdings -* [lastnews](#lastnews) - * Command to get latest news -* [login](#login) - * Connect to your Degiro account -* [logout](#login) - * Disconnect from Degiro account -* [lookup](#lookup) - * Command to search for a product by name -* [pending](#pending) - * Command to list pending orders -* [topnews](#topnews) - * Command to get top news preview -* [update](#update) - * Update and `Order` - -[Merge](#Merge) -* [hold](#hold) - * View holdings across all brokers - - -## ROBINHOOD -Robinhood has Two Factor Authentication, so you will likely be prompted to enter a code that is texted/emailed to you. -###NOTE THAT LOGGING IN WILL SAVE A TOKEN TO `os.path.expanduser("~")/.tokens` WHICH CAN BE USED TO LOGIN EVEN IF USER CREDENTIALS ARE INCORRECT. - -In order to login, the [config file](/gamestonk_terminal/config_terminal.py) requires two variables: -* "GT_RH_USERNAME" -* "GT_RH_PASSWORD" - -### rhhold - -Displays current holdings to the console: - -```` -usage: rhhold -```` -There are no additional flags. This just prints all current stonks, their last price, previous close, your equity and -the % change from previous close. - -Example: - -![hold](https://user-images.githubusercontent.com/18151143/111685384-3c6ab080-87fe-11eb-80ce-9b256c396bf2.png) - -### rhhist -Display your RH portfolio based on provided interval and span. Based on the API data availablility, plotted as a candlestick chart. -```` -usage: rhhist [-s --span] [-i --interval] -```` -* -s/--span : How long to look back. Defaults to the day. Options: - * [day, week, month, 3month, year, 5year, all] -* -i/--interval: Data resolution. Defaults to 10minute intervals. Options: - * [5minute, 10minute, hour, day, week] - -Example Default Output: - -![rhhist](https://user-images.githubusercontent.com/18151143/111718919-36da8e00-8831-11eb-99e1-957c8eccb583.png) - -## ALPACA - -Alpaca has a nicely maintained python API that is used here. To login, the best approach is to just save the following environment variables: -* "APCA_API_BASE_URL" -* "APCA_API_KEY_ID" -* "APCA_API_SECRET_KEY" - -If these are defined, then the login command does not explicitly need to be run, but the login command verifies all those keys are defined. - -Note that alpaca does support paper trading, so if your base_url is "https://paper-api.alpaca.markets", then this will be importing -your paper trading account. - -### alphold - -Displays current holdings to the console. Same as rhhold - -```` -usage: alphold -```` -There are no additional flags. This just prints all current stonks, their last price, previous close, your equity and -the % change from previous close. - -Example Output: -![](https://user-images.githubusercontent.com/18151143/112039340-536d1380-8b1a-11eb-99d1-44d1edd4d54a.png) - -### alphist -Plots historical portfolio. The output plot in this case will not be a candle, as the api provides your equity at a gven time, -unlike the robin_stocks, which gives high/low at a given time. - -```` -usage: alphist [-p --period] [-t --timeframe] -```` - -* -p/--period The duration of the data in \ + \, such as 1D, where can be D for day, W for week, M for month and A for year. Defaults to 1M -* -t/--timeframe The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period, 15Min for less than 30 days, or otherwise 1D. - -## ALLY - -In order to access your Ally Invest account through the API, you must first acquire a key from -[your Ally Invest Account](#https://www.ally.com/api/invest/documentation/getting-started/). - -Once you have this, you want to load in the following environment variables (similar to alpaca, the api will access these -directly from the environment) -* ALLY_CONSUMER_KEY -* ALLY_CONSUMER_SECRET -* ALLY_OAUTH_TOKEN -* ALLY_OAUTH_SECRET -* ALLY_ACCOUNT_NBR - * This is obtained from your actual account - not through the api application. - -As of writing this, the ally API does not provide historical portfolio data, so only a holdings option is provided. -### allyhold -Show all Ally Invest holdings: -```` -usage : allyhold -```` -No additional flags. - -## DEGIRO - -**Sub-menu** -```` -usage: - pa - degiro - login -```` - -To use the `degiro` sub-menu you need to : -* Enter `portfolio analysis`, with the command : `pa` -* Enter `degiro` sub-menu using the command: `degiro` -* Connect to `degiro` by typing : `login` - -**Credentials** - -In order to login you need to provide the following credentials : - -| Parameter | Description | -| :--- | :--- | -| DG_USERNAME | Username used to log into Degiro's website. | -| DG_PASSWORD | Password used to log into Degiro's website. | - -**2FA** - -In order to use 2FA you need to provide one of these parameters : - -| Parameter | Description | -| :--- | :--- | -| DG_TOTP_SECRET | This secret key will let Gamestonk generate the `OneTimePassword` for you. | - -If you provide your `DG_TOTP_SECRET` : you won't have to type your `OneTimePassword`, Gamestonk will generate it for you at each connection. - -`DG_TOTP_SECRET` is the text representation of the `QRCODE` that Degiro's provide you when you enable 2FA. - -More information on 2FA credentials, in the documentation of this library : - -https://github.com/Chavithra/degiro-connector - - -### cancel -```` -usage: cancel ORDER_ID -```` - -### companynews -```` -usage: companynews NL0000235190 -```` - -You need to provide the `ISIN` number of the company. -You can get this `ISIN` using the `dglookup` command for instance. - -### create -```` -usage: create [-a {buy,sell}] (-prod PRODUCT | -sym SYMBOL) -p PRICE (-s SIZE | -up UP_TO) [-d {gtd,gtc}] [-t {limit,market,stop-limit,stop-loss}] [-h] -```` - -### hold -```` -usage: hold -```` - -Displays current holdings to the console. - - -### lastnews -```` -usage: lastnews [-l LIMIT] [-h] - -optional arguments: - -l LIMIT, --limit LIMIT - Number of news to display. - -h, --help show this help message -```` - -### login -```` -usage: login [-u USERNAME] [-p PASSWORD] [-o OTP] [-s TOPT_SECRET] [-h] - -optional arguments: - -u USERNAME, --username USERNAME - Username in Degiro's account. - -p PASSWORD, --password PASSWORD - Password in Degiro's account. - -o OTP, --otp OTP One time password (2FA). - -s TOPT_SECRET, --topt-secret TOPT_SECRET - TOTP SECRET (2FA). - -h, --help show this help message -```` - -### logout -```` -usage: logout -```` - -### lookup -```` -usage: lookup TESLA -```` - -It will display a result like this : -``` - name isin symbol productType currency closePrice closePriceDate -0 Tesla US88160R1014 TSLA STOCK USD 571.690 2021-05-13 -1 Tesla Inc US88160R1014 TL0 STOCK EUR 478.000 2021-05-13 -2 Tesla Inc US88160R1014 TL0 STOCK EUR 467.650 2021-05-13 -3 TurboC O.End Tesla 535,04 DE000VQ1S3R3 None WARRANT EUR 3.740 2021-05-13 -4 TurboC O.End Tesla 591,4 DE000VQ5R6T9 None WARRANT EUR 0.001 2021-05-13 -5 TurboP O.End Tesla 685,29 DE000VQ7HWU6 None WARRANT EUR 9.870 2021-05-13 -6 TurboC O.End Tesla 448,63 DE000VQ1KYG8 None WARRANT EUR 10.890 2021-05-13 -7 Call 17.12.21 Tesla 392 DE000VP53547 DE000VP53547 WARRANT EUR 8.960 2021-05-13 -8 Call 20.01.23 Tesla 720 DE000VQ68691 None WARRANT EUR 1.160 2021-05-13 -9 Leverage Shares 1x Tesla ETP IE00BKT6ZH01 STSL ETF GBX 135.550 2021-05-13 -``` - -### pending -```` -usage: pending -```` - -### topnews -```` -usage: topnews -```` - -### update -```` -usage: update ORDER_ID -p PRICE -```` - -## Merge - -When logged into 1 or more brokers - this option is available. Currently, this allows for -viewing holding across all platforms. The PnL is shown for all time (Market Value and your Cost Basis) - -### hold - -Show holdings across all brokers that are logged in. -```` -usage: hold -```` -Currently no additional flags. If not all brokers are represented, double check that they are logged in. \ No newline at end of file diff --git a/gamestonk_terminal/portfolio/custom_portfolio/portfolio.csv b/gamestonk_terminal/portfolio/custom_portfolio/portfolio.csv deleted file mode 100644 index 59fb9f950aa..00000000000 --- a/gamestonk_terminal/portfolio/custom_portfolio/portfolio.csv +++ /dev/null @@ -1,6 +0,0 @@ -Ticker,Shares,Other -AAPL,5, -GME,5,lol -MSFT,3, -O,2,s -BAC,3, diff --git a/gamestonk_terminal/portfolio/CUSTOM_PORT.md b/gamestonk_terminal/portfolio_analysis/README.md similarity index 84% rename from gamestonk_terminal/portfolio/CUSTOM_PORT.md rename to gamestonk_terminal/portfolio_analysis/README.md index 3f5b0a033ba..2498cdf95e4 100644 --- a/gamestonk_terminal/portfolio/CUSTOM_PORT.md +++ b/gamestonk_terminal/portfolio_analysis/README.md @@ -1,23 +1,24 @@ -# PORTFOLIO +# PORTFOLIO ANALYSIS -This document will walk through the custom portfolio loading. +This menu aims to analyze a portfolio loaded through csv, and the usage of the following commands along with an example will be exploited below. -## csv +* [load](#load) + * load portfolio from csv file +* [group](#group) + * view holdings by a user input group + +## load ``` -usage: csv [-p PATH] [--no_sector] [--no_last_price] [--nan] +usage: load [-p PATH] [--no_sector] [--no_last_price] [--nan] ``` +This function allows you to load from a predefined csv file. The csv file can contain any information you want, but the stocks should be under a column `Ticker`. The number owned should be defined in `Shares`. By default, this will loop through all Tickers and pull sector and latest price. An example column that could be added would be if the ticker is considered a Value or Growth stock. * -p/--path : Path to csv. A template is supplied and the default path points to it. * --no_sector : Flag to avoid getting the sector of each stock. * --no_last_price : Flag to avoid getting the latest price for each supplied stock. * --nan : Flag to display NaN columns. Useful if importing partial information. -This function allows you to load from a predefined csv file. The csv file can contain any information you want, but the stocks -should be under a column `Ticker`. The number owned should be defined in `Shares`. By default, this will loop through -all Tickers and pull sector and latest price. An example column that could be added would be if the ticker is considered -a Value or Growth stock. - ```python -(✨) (pa)> csv +(✨) (pa)> load ╒════╤══════════╤══════════╤════════════════════╤══════════════╤═════════╕ │ │ Ticker │ Shares │ sector │ last_price │ value │ ╞════╪══════════╪══════════╪════════════════════╪══════════════╪═════════╡ @@ -32,19 +33,14 @@ a Value or Growth stock. │ 4 │ BAC │ 3 │ Financial Services │ 41.39 │ 124.17 │ ╘════╧══════════╧══════════╧════════════════════╧══════════════╧═════════╛ ``` + ## group ``` -usage: groupby [-g GROUP] +usage: group [-g GROUP] ``` +Displays data in groups based on columns. Currently this only looks at the `value` column. * -g/--group : Column to group data by. Ex: "sector" -Displays data in groups based on columns. Currently this only looks at the `value` column. - -Example commands to load csv file and then group by sector. -``` -csv -group -g sector -``` ```python (✨) (pa)> group -g sector ╒════════════════════╤═════════╕ @@ -58,3 +54,4 @@ group -g sector ├────────────────────┼─────────┤ │ Technology │ 1423.28 │ ╘════════════════════╧═════════╛ +``` diff --git a/gamestonk_terminal/portfolio_analysis/__init__.py b/gamestonk_terminal/portfolio_analysis/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/portfolio_analysis/pa_controller.py b/gamestonk_terminal/portfolio_analysis/pa_controller.py new file mode 100644 index 00000000000..04945855c2d --- /dev/null +++ b/gamestonk_terminal/portfolio_analysis/pa_controller.py @@ -0,0 +1,118 @@ +__docformat__ = "numpy" + +import argparse +import pandas as pd +from prompt_toolkit.completion import NestedCompleter + +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.menu import session + +from gamestonk_terminal.portfolio_analysis import portfolio_parser + + +class PortfolioController: + """Portfolio Controller""" + + CHOICES = [ + "help", + "q", + "quit", + "load", + "group", + ] + + def __init__(self): + self.pa_parser = argparse.ArgumentParser(add_help=False, prog="pa") + self.pa_parser.add_argument("cmd", choices=self.CHOICES) + self.portfolio_name = "" + self.portfolio = pd.DataFrame() + + def print_help(self): + """Print help""" + print("\nPortfolio Analysis:") + print(" help show this menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" load load portfolio from csv file") + print("") + if self.portfolio_name: + print(f"Portfolio: {self.portfolio_name}") + print("") + print(" group view holdings by a user input group") + print("") + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + + (known_args, other_args) = self.pa_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help() + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_load(self, other_args): + """Process csv command""" + self.portfolio_name, self.portfolio = portfolio_parser.load_csv_portfolio( + other_args + ) + + if self.portfolio_name: + print(f"Successfully loaded: {self.portfolio_name}\n") + + def call_group(self, other_args): + """Process group command""" + if self.portfolio_name: + portfolio_parser.breakdown_by_group(self.portfolio, other_args) + else: + print("Please load a portfolio") + + +def menu(): + """Portfolio Analysis Menu""" + pa_controller = PortfolioController() + pa_controller.print_help() + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in pa_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (pa)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (pa)> ") + + try: + process_input = pa_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/portfolio/custom_model.py b/gamestonk_terminal/portfolio_analysis/portfolio_parser.py similarity index 83% rename from gamestonk_terminal/portfolio/custom_model.py rename to gamestonk_terminal/portfolio_analysis/portfolio_parser.py index 1c1b1880916..0edd38d819c 100644 --- a/gamestonk_terminal/portfolio/custom_model.py +++ b/gamestonk_terminal/portfolio_analysis/portfolio_parser.py @@ -1,5 +1,7 @@ -"""Custom portfolio model""" +"""Portfolio parser module""" __docformat__ = "numpy" + +import os import argparse from typing import List from tabulate import tabulate @@ -9,11 +11,32 @@ from gamestonk_terminal.helper_funcs import check_valid_path, parse_known_args_a def load_csv_portfolio(other_args: List[str]): + """Load portfolio from csv + Parameters + ---------- + other_args: List[str] + Argparse arguments + + Returns + ---------- + portfolio_name : str + Portfolio name + portfolio : pd.DataFrame + Portfolio dataframe + """ parser = argparse.ArgumentParser( - prog="csv", + prog="load", add_help=False, - description="Function to get portfolio holdings from predefined csv file", + description="Function to get portfolio from predefined csv file inside portfolios folder", + ) + parser.add_argument( + "-p", + "--path", + default="my_portfolio", + type=check_valid_path, + help="Path to csv file", + dest="path", ) parser.add_argument( "--no_sector", @@ -22,7 +45,6 @@ def load_csv_portfolio(other_args: List[str]): help="Add sector to dataframe", dest="sector", ) - parser.add_argument( "--no_last_price", action="store_true", @@ -30,7 +52,6 @@ def load_csv_portfolio(other_args: List[str]): help="Add last price from yfinance", dest="last_price", ) - parser.add_argument( "--nan", action="store_true", @@ -39,20 +60,20 @@ def load_csv_portfolio(other_args: List[str]): dest="show_nan", ) - parser.add_argument( - "-p", - "--path", - default="./gamestonk_terminal/portfolio/custom_portfolio/portfolio.csv", - type=check_valid_path, - help="Path to csv file", - dest="path", - ) - try: ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: - return pd.DataFrame() - df = pd.read_csv(ns_parser.path) + return "", pd.DataFrame() + + full_path = os.path.abspath( + os.path.join( + "gamestonk_terminal", + "portfolio_analysis", + "portfolios", + f"{ns_parser.path}.csv", + ) + ) + df = pd.read_csv(full_path) if not ns_parser.sector: df["sector"] = df.apply( @@ -76,11 +97,11 @@ def load_csv_portfolio(other_args: List[str]): print(tabulate(df, tablefmt="fancy_grid", headers=df.columns)) print("") - return df + return ns_parser.path, df except Exception as e: print(e, "\n") - return pd.DataFrame() + return "", pd.DataFrame() def breakdown_by_group(portfolio: pd.DataFrame, other_args: List[str]): diff --git a/gamestonk_terminal/portfolio_analysis/portfolios/my_portfolio.csv b/gamestonk_terminal/portfolio_analysis/portfolios/my_portfolio.csv new file mode 100644 index 00000000000..9123480e064 --- /dev/null +++ b/gamestonk_terminal/portfolio_analysis/portfolios/my_portfolio.csv @@ -0,0 +1,6 @@ +Ticker,Shares,Other +GME,69,https://www.gmefloor.com +AMC,420, +MSFT,3, +BAC,2, +O,1, diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md index 9aa9f6551a5..ae0c9d464dd 100644 --- a/gamestonk_terminal/portfolio_optimization/README.md +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -2,10 +2,13 @@ This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of the following commands along with an example will be exploited below. -* [add](#add) - * add ticker to optimize + * [select](#select) - * overwrite current tickers with new tickers + * select list of tickers to be optimized +* [add](#add) + * add tickers to the list of the tickers to be optimized +* [rmv](#rmv) + * remove tickers from the list of the tickers to be optimized [Optimization](#Optimization) @@ -38,27 +41,34 @@ This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of th * **comparison analysis for selected tickers** + + +### select + +```text +select [-t SELECT_TICKERS] +``` + +Add/Select tickers for portfolio to be optimized. +* -t: Tickers to be used in the portfolio to optimize. + ### add ```text add [-t ADD_TICKERS] ``` -Add/Select tickers for portfolio to be optimized. +Add tickers to the list of the tickers to be optimized. +* -t: Tickers to be used in the portfolio to optimize -* -t : Tickers to be used in the portfolio to optimize -* - -### select +### rmv ```text -select [-t ADD_TICKERS] +rmv [-t RMV_TICKERS] ``` -Add/Select tickers for portfolio to be optimized. - -* -t : Tickers to be used in the portfolio to optimize - +Remove tickers from the list of the tickers to be optimized. +* -t: Tickers to be removed from the tickers to optimize. ## Optimization @@ -127,11 +137,11 @@ These approaches are based off of the efficient frontier approach, which is mean

-* Where S is the covariance matrix between stocks and R is the expected returns. -* The condition that all weights add up to 1 just implies that you want to have a net long portfolio (with no margin). +* Where S is the covariance matrix between stocks and R is the expected returns. +* The condition that all weights add up to 1 just implies that you want to have a net long portfolio (with no margin). * A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy. -Currently, we do not allow for changing risk models or adding constraints. +Currently, we do not allow for changing risk models or adding constraints. If there is something specific, please submit a feature request, or if you can write it, feel free to add a PR! All of these commands use [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package. @@ -222,11 +232,11 @@ Maximise return for a target risk. The resulting portfolio will have a volatilit ### ef ```` -usage: ef [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-n N_PORT] +usage: ef [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-n N_PORT] ```` This function plots random portfolios based on their risk and returns and shows the efficient frontier. - + * -n : Number of portfolios to simulate. Default 300. * -p : Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py index 87e6513d98a..1cac7b28344 100644 --- a/gamestonk_terminal/portfolio_optimization/po_controller.py +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -19,6 +19,7 @@ class PortfolioOptimization: "quit", "select", "add", + "rmv", "equal", "mktcap", "dividend", @@ -55,8 +56,11 @@ class PortfolioOptimization: print(" quit quit to abandon program") print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}") print("") - print(" add add ticker to optimize") - print(" select overwrite current tickers with new tickers") + print(" select select list of tickers to be optimized") + print(" add add tickers to the list of the tickers to be optimized") + print( + " rmv remove tickers from the list of the tickers to be optimized" + ) print("") print("Optimization:") print(" equal equally weighted") @@ -111,15 +115,19 @@ class PortfolioOptimization: """Process Quit command - quit the program""" return True - def call_add(self, other_args: List[str]): - """Process add command""" - self.add_stocks(other_args) - def call_select(self, other_args: List[str]): """Process select command""" self.tickers = [] self.add_stocks(other_args) + def call_add(self, other_args: List[str]): + """Process add command""" + self.add_stocks(other_args) + + def call_rmv(self, other_args: List[str]): + """Process rmv command""" + self.rmv_stocks(other_args) + def call_equal(self, other_args: List[str]): """Process equal command""" optimizer_view.equal_weight(self.tickers, other_args) @@ -201,11 +209,49 @@ class PortfolioOptimization: ) self.tickers = list(tickers) + print("") except Exception as e: - print(e) + print(e, "\n") - print("") + def rmv_stocks(self, other_args: List[str]): + """Remove one of the tickers to be optimized""" + parser = argparse.ArgumentParser( + add_help=False, + prog="rmv", + description="""Remove one of the tickers to be optimized.""", + ) + parser.add_argument( + "-t", + "--tickers", + dest="rmv_tickers", + type=lambda s: [str(item).upper() for item in s.split(",")], + default=[], + help="tickers to be removed from the tickers to optimize.", + ) + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-t") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + tickers = set(self.tickers) + for ticker in ns_parser.rmv_tickers: + tickers.remove(ticker) + + if self.tickers: + print( + f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}" + ) + + self.tickers = list(tickers) + print("") + + except Exception as e: + print(e, "\n") def menu(tickers: List[str]): diff --git a/terminal.py b/terminal.py index c9dc54b1cc7..204635c2193 100644 --- a/terminal.py +++ b/terminal.py @@ -40,7 +40,8 @@ from gamestonk_terminal.options import op_controller from gamestonk_terminal.econ import econ_controller from gamestonk_terminal.residuals_analysis import ra_controller -from gamestonk_terminal.portfolio import port_controller +from gamestonk_terminal.portfolio_analysis import pa_controller +from gamestonk_terminal.brokers import bro_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.portfolio_optimization import po_controller @@ -113,6 +114,7 @@ def main(): "rc", "gov", "etf", + "bro", ] menu_parser.add_argument("opt", choices=choices) @@ -354,7 +356,10 @@ def main(): b_quit = econ_controller.menu() elif ns_known_args.opt == "pa": - b_quit = port_controller.menu() + b_quit = pa_controller.menu() + + elif ns_known_args.opt == "bro": + b_quit = bro_controller.menu() elif ns_known_args.opt == "crypto": b_quit = crypto_controller.menu()