diff --git a/i18n/en.yml b/i18n/en.yml index 0aa75d79179..c021aef54be 100644 --- a/i18n/en.yml +++ b/i18n/en.yml @@ -1014,6 +1014,7 @@ en: portfolio/bro/degiro/companynews: view news about a company with it's isin portfolio/bro/degiro/lastnews: view latest news portfolio/bro/degiro/topnews: view top news preview + portfolio/bro/degiro/paexport: csv export for portfolio analysis portfolio/bro/rh/login: login to robinhood portfolio/bro/rh/holdings: show account holdings in stocks portfolio/bro/rh/history: show equity history of your account diff --git a/openbb_terminal/portfolio/brokers/degiro/degiro_controller.py b/openbb_terminal/portfolio/brokers/degiro/degiro_controller.py index 7e091e99042..cb6acbdb39e 100644 --- a/openbb_terminal/portfolio/brokers/degiro/degiro_controller.py +++ b/openbb_terminal/portfolio/brokers/degiro/degiro_controller.py @@ -1,5 +1,6 @@ # IMPORTATION STANDARD import argparse +import datetime import logging from typing import List @@ -9,6 +10,10 @@ from prompt_toolkit.completion import NestedCompleter # IMPORTATION INTERNAL from openbb_terminal import feature_flags as obbff from openbb_terminal.decorators import log_start_end +from openbb_terminal.helper_funcs import ( + EXPORT_ONLY_RAW_DATA_ALLOWED, + valid_date, +) from openbb_terminal.menu import session from openbb_terminal.parent_classes import BaseController from openbb_terminal.portfolio.brokers.degiro.degiro_view import DegiroView @@ -31,6 +36,7 @@ class DegiroController(BaseController): "pending", "topnews", "update", + "paexport", ] PATH = "/portfolio/bro/degiro/" @@ -307,3 +313,42 @@ class DegiroController(BaseController): ns_parser = self.parse_known_args_and_warn(parser, other_args) self.__degiro_view.update(ns_parser=ns_parser) + + @log_start_end(log=logger) + def call_paexport(self, other_args: List[str]): + """Export transactions for Portfolio Analysis Menu into CSV format.""" + + # PARSING ARGS + parser = argparse.ArgumentParser( + add_help=False, + prog="paexport", + ) + parser.add_argument( + "-s", + "--start", + help="Start date.", + required=True, + type=valid_date, + ) + parser.add_argument( + "-e", + "--end", + help="End date.", + type=valid_date, + default=datetime.datetime.now(), + ) + parser.add_argument( + "-c", + "--currency", + help="Used currency.", + default="USD", + type=str, + ) + ns_parser = self.parse_known_args_and_warn( + parser, + other_args, + export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED, + ) + + if ns_parser: + self.__degiro_view.transactions_export(ns_parser=ns_parser) diff --git a/openbb_terminal/portfolio/brokers/degiro/degiro_model.py b/openbb_terminal/portfolio/brokers/degiro/degiro_model.py index 4839466014a..e2629b050d6 100644 --- a/openbb_terminal/portfolio/brokers/degiro/degiro_model.py +++ b/openbb_terminal/portfolio/brokers/degiro/degiro_model.py @@ -1,7 +1,8 @@ # IMPORTATION STANDARD +import datetime import logging import math -from typing import Union +from typing import List, Union # IMPORTATION THIRDPARTY import pandas as pd @@ -15,6 +16,7 @@ from degiro_connector.trading.models.trading_pb2 import ( ProductSearch, ProductsInfo, TopNewsPreview, + TransactionsHistory, Update, ) @@ -336,3 +338,103 @@ class DegiroModel: return None else: return None + + @log_start_end(log=logger) + def get_transactions( + self, start: datetime.date, end: datetime.date + ) -> pd.DataFrame: + trading_api = self.__trading_api + + from_date = TransactionsHistory.Request.Date( + year=start.year, + month=start.month, + day=start.day, + ) + to_date = TransactionsHistory.Request.Date( + year=end.year, + month=end.month, + day=end.day, + ) + request = TransactionsHistory.Request( + from_date=from_date, + to_date=to_date, + ) + + transactions_dict = trading_api.get_transactions_history( + request=request, + raw=True, + ) + + transactions_df = pd.DataFrame(transactions_dict["data"]) + transactions_df["productId"] = transactions_df["productId"].astype("int") + + return transactions_df + + @log_start_end(log=logger) + def get_products_details(self, product_id_list: List[int]) -> pd.DataFrame: + trading_api = self.__trading_api + + product_id_list = list(set(product_id_list)) + + request = ProductsInfo.Request() + request.products.extend(product_id_list) + + products_info = trading_api.get_products_info( + request=request, + raw=True, + ) + + products_df = pd.DataFrame(products_info["data"].values()) + + return products_df + + @log_start_end(log=logger) + def get_transactions_export( + self, start: datetime.date, end: datetime.date, currency: str + ) -> pd.DataFrame: + transactions_df = self.get_transactions(start=start, end=end) + product_id_list = list(transactions_df.productId) + products_df = self.get_products_details(product_id_list=product_id_list) + + products_df["productId"] = products_df["id"] + transactions_df["productId"] = transactions_df["productId"].astype("int") + products_df["productId"] = products_df["productId"].astype("int") + transactions_full_df = pd.merge( + transactions_df, + products_df[{"productId", "symbol", "productType"}], + on="productId", + ) + + portfolio_df = transactions_full_df.rename( + columns={ + "date": "Date", + "symbol": "Ticker", + "productType": "Type", # STOCK or ETF + "price": "Price", + "quantity": "Quantity", + "buysell": "Side", # BUY or SELL + "totalFeesInBaseCurrency": "Fees", + } + ) + + portfolio_df["Premium"] = 0 + portfolio_df["Currency"] = currency + portfolio_df["Side"] = portfolio_df["Side"].replace({"S": "SELL", "B": "BUY"}) + portfolio_df["Date"] = pd.to_datetime( + portfolio_df["Date"].str.slice(0, 10) + ).dt.date + columns = [ + "Date", + "Ticker", + "Type", + "Price", + "Quantity", + "Fees", + "Premium", + "Side", + "Currency", + ] + portfolio_df = portfolio_df[columns] + portfolio_df = portfolio_df.set_index("Date") + + return portfolio_df diff --git a/openbb_terminal/portfolio/brokers/degiro/degiro_view.py b/openbb_terminal/portfolio/brokers/degiro/degiro_view.py index 5a02e2e1535..38c5a4cdfde 100644 --- a/openbb_terminal/portfolio/brokers/degiro/degiro_view.py +++ b/openbb_terminal/portfolio/brokers/degiro/degiro_view.py @@ -1,6 +1,7 @@ # IMPORTATION THIRDPARTY import logging from argparse import Namespace +from pathlib import Path # IMPORTATION THIRDPARTY import pandas as pd @@ -19,6 +20,10 @@ from openbb_terminal.decorators import log_start_end from openbb_terminal.decorators import check_api_key # IMPORTATION INTERNAL +from openbb_terminal.helper_funcs import ( + export_data, + print_rich_table, +) from openbb_terminal.portfolio.brokers.degiro.degiro_model import DegiroModel from openbb_terminal.rich_config import console, MenuText @@ -68,6 +73,7 @@ class DegiroView: mt.add_cmd("companynews") mt.add_cmd("lastnews") mt.add_cmd("topnews") + mt.add_cmd("paexport") console.print(text=mt.menu_text, menu="Portfolio - Brokers - Degiro") @log_start_end(log=logger) @@ -489,3 +495,29 @@ class DegiroView: @log_start_end(log=logger) def __update_display_success(): console.print("`Order` updated .") + + @log_start_end(log=logger) + def transactions_export(self, ns_parser: Namespace): + degiro_model = self.__degiro_model + + portfolio_df = degiro_model.get_transactions_export( + start=ns_parser.start.date(), + end=ns_parser.end.date(), + currency=ns_parser.currency, + ) + + if portfolio_df is not None: + print_rich_table( + df=portfolio_df, + headers=list(portfolio_df.columns), + show_index=True, + title="Degiro Transactions", + ) + export_data( + export_type=ns_parser.export, + dir_path=str(Path(__file__).parent.parent.parent.absolute()), + func_name="paexport", + df=portfolio_df, + ) + else: + console.print("Error while fetching or processing Transactions.") diff --git a/tests/openbb_terminal/portfolio/brokers/degiro/test_degiro_controller.py b/tests/openbb_terminal/portfolio/brokers/degiro/test_degiro_controller.py index ccb6a6d0c90..e83ff2322d0 100644 --- a/tests/openbb_terminal/portfolio/brokers/degiro/test_degiro_controller.py +++ b/tests/openbb_terminal/portfolio/brokers/degiro/test_degiro_controller.py @@ -289,6 +289,15 @@ def test_call_func_expect_queue(expected_queue, func, queue): [], dict(), ), + ( + "call_paexport", + [ + "--start=2022-01-01", + ], + "DegiroView.transactions_export", + [], + dict(), + ), ], ) def test_call_func( diff --git a/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_controller/test_print_help.txt b/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_controller/test_print_help.txt index 996aab19eb9..1b763869d1c 100644 --- a/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_controller/test_print_help.txt +++ b/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_controller/test_print_help.txt @@ -12,4 +12,5 @@ companynews view news about a company with it's isin lastnews view latest news topnews view top news preview + paexport csv export for portfolio analysis diff --git a/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_view/test_help_display.txt b/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_view/test_help_display.txt index 996aab19eb9..1b763869d1c 100644 --- a/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_view/test_help_display.txt +++ b/tests/openbb_terminal/portfolio/brokers/degiro/txt/test_degiro_view/test_help_display.txt @@ -12,4 +12,5 @@ companynews view news about a company with it's isin lastnews view latest news topnews view top news preview + paexport csv export for portfolio analysis