diff options
author | nectariosouzo <nectariosouzounidis@gmail.com> | 2023-09-24 12:50:43 -0400 |
---|---|---|
committer | nectariosouzo <nectariosouzounidis@gmail.com> | 2023-09-24 12:50:43 -0400 |
commit | 29a5d273b89abb0da58d5d7c7e01d4887a8ad3f4 (patch) | |
tree | 3a0d58b8172fafe2ed2670b50553b7c392c3e95b | |
parent | bf082d1aeda02ac2d2e77c5c142dde5a0a6b9605 (diff) |
Create holdings' performance for ETFs
-rw-r--r-- | openbb_terminal/etf/etf_controller.py | 54 | ||||
-rw-r--r-- | openbb_terminal/etf/fmp_model.py | 109 | ||||
-rw-r--r-- | openbb_terminal/etf/fmp_view.py | 40 | ||||
-rw-r--r-- | openbb_terminal/miscellaneous/i18n/en.yml | 1 |
4 files changed, 200 insertions, 4 deletions
diff --git a/openbb_terminal/etf/etf_controller.py b/openbb_terminal/etf/etf_controller.py index 26a4311208e..a6801d01fd3 100644 --- a/openbb_terminal/etf/etf_controller.py +++ b/openbb_terminal/etf/etf_controller.py @@ -4,7 +4,7 @@ __docformat__ = "numpy" import argparse import logging import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from typing import List, Optional import yfinance as yf @@ -50,6 +50,7 @@ class ETFController(BaseController): "load", "overview", "holdings", + "holding_perf", "news", "candle", "weights", @@ -106,6 +107,7 @@ class ETFController(BaseController): mt.add_raw("\n") mt.add_cmd("overview", self.etf_name) mt.add_cmd("holdings", self.etf_name) + mt.add_cmd("holding_perf", self.etf_name) mt.add_cmd("weights", self.etf_name) mt.add_cmd("news", self.etf_name) mt.add_cmd("candle", self.etf_name) @@ -638,3 +640,53 @@ class ETFController(BaseController): if ns_parser.sheet_name else None, ) + + @log_start_end(log=logger) + def call_holding_perf(self, other_args: List[str]): + print(self.etf_holdings) + """Process holdings performance command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="holding_perf", + description="Look at ETF company holdings' performance", + ) + parser.add_argument( + "-s", + "--start", + type=valid_date, + default=(datetime.now().date() - timedelta(days=366)), + dest="start", + help="The starting date (format YYYY-MM-DD) to get each holding's price" + ) + parser.add_argument( + "-e", + "--end", + type=valid_date, + default=datetime.now().date(), + dest="end", + help="The ending date (format YYYY-MM-DD) to get each holding's price" + ) + parser.add_argument( + "-l", + "--limit", + type=check_positive, + dest="limit", + help="Number of holdings to get", + default=20, + ) + + if other_args and "-" not in other_args[0][0]: + other_args.insert(0, "-l") + + ns_parser = self.parse_known_args_and_warn(parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES, raw= True) + if ns_parser: + if self.etf_name: + fmp_view.view_etf_holdings_performance( + ticker=self.etf_name, + start_date=ns_parser.start, + end_date=ns_parser.end, + limit=ns_parser.limit, + ) + else: + console.print("Please load a ticker using <load name>. \n") diff --git a/openbb_terminal/etf/fmp_model.py b/openbb_terminal/etf/fmp_model.py index 26b8cfc99db..9f7bf49a606 100644 --- a/openbb_terminal/etf/fmp_model.py +++ b/openbb_terminal/etf/fmp_model.py @@ -3,12 +3,14 @@ __docformat__ = "numpy" import json import logging -from typing import Dict +from typing import Dict, List from urllib.error import HTTPError from urllib.request import urlopen +import pandas as pd from openbb_terminal.core.session.current_user import get_current_user -from openbb_terminal.decorators import log_start_end +from openbb_terminal.decorators import check_api_key, log_start_end +from openbb_terminal.helper_funcs import request from openbb_terminal.rich_config import console logger = logging.getLogger(__name__) @@ -47,3 +49,106 @@ def get_etf_sector_weightings(name: str) -> Dict: raise ValueError(data["Error Message"]) return data + +@log_start_end(log=logger) +@check_api_key(['API_KEY_FINANCIALMODELINGPREP']) +def get_stock_price_change(ticker: str, start_date: str, end_date: str) -> float: + """Get stock's price percent change over specified time period. + + Parameters + ---------- + ticker : str + Ticker to get information from. + start: str + Date from which data is fetched in format YYYY-MM-DD + end: str + Date from which data is fetched in format YYYY-MM-DD + + Returns + ------- + float + Percent change of closing price over time period + """ + current_user = get_current_user() + url = f'''https://financialmodelingprep.com/api/v3/historical-price-full/{ticker}?from={start_date}&to={end_date}&serietype=line&apikey={current_user.credentials.API_KEY_FINANCIALMODELINGPREP}''' + + response = request(url) + if response.status_code != 200 or "Error Message" in response.json(): + message = f"Error, Status Code: {response.status_code}." + message = ( + message + if "Error Message" not in response.json() + else message + "\n" + response.json()["Error Message"] + ".\n" + ) + console.print(message) + return None + data = response.json() + + close_end = data['historical'][0]['close'] + close_start = data['historical'][-1]['close'] + pct_change = 100 * (close_end - close_start) / close_start + + return pct_change + +@log_start_end(log=logger) +@check_api_key(['API_KEY_FINANCIALMODELINGPREP']) +def get_etf_holdings(ticker: str) -> List[Dict[str,any]]: + """This endpoint returns all stocks held by a specific ETF. + + Parameters + ---------- + ticker : str + ETF ticker. + + Returns + ------- + List[Dict[str,any]] + Info for stock holdings in the ETF. + """ + + current_user = get_current_user() + url = f'''https://financialmodelingprep.com/api/v3/etf-holder/{ticker}?apikey={current_user.credentials.API_KEY_FINANCIALMODELINGPREP}''' + response = request(url) + if response.status_code != 200 or "Error Message" in response.json(): + message = f"Error, Status Code: {response.status_code}." + message = ( + message + if "Error Message" not in response.json() + else message + "\n" + response.json()["Error Message"] + ".\n" + ) + console.print(message) + return None + + return response.json() + +@log_start_end(log=logger) +@check_api_key(['API_KEY_FINANCIALMODELINGPREP']) +def get_holdings_pct_change(ticker: str, start_date: str, end_date: str) -> pd.DataFrame: + """Calculate percent change for each holding in ETF. + + Parameters + ---------- + ticker : str + ETF ticker. + + Returns + ------- + pd.DataFrame + Calculated percentage change for each stock in the ETF, in descending order. + """ + holdings = get_etf_holdings(ticker=ticker) + data_list = [] + for stock in holdings: + if stock['asset']: + pct_change = get_stock_price_change(ticker=stock['asset'], + start_date=start_date, end_date=end_date) + data_list.append({"Ticker": stock['asset'], "Name": stock["name"], + "Percent Change": pct_change}) + else: + data_list.append({"Ticker": '', "Name": stock["name"], + "Percent Change": 0}) + + df = pd.DataFrame(data_list) + df.sort_values(by='Percent Change',ascending=False, inplace=True) + + return df diff --git a/openbb_terminal/etf/fmp_view.py b/openbb_terminal/etf/fmp_view.py index 6499aa3fc96..486d21c9d10 100644 --- a/openbb_terminal/etf/fmp_view.py +++ b/openbb_terminal/etf/fmp_view.py @@ -6,9 +6,11 @@ import os from typing import Optional import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt from openbb_terminal import OpenBBFigure, theme -from openbb_terminal.decorators import log_start_end +from openbb_terminal.decorators import check_api_key, log_start_end from openbb_terminal.etf import fmp_model from openbb_terminal.helper_funcs import ( export_data, @@ -120,3 +122,39 @@ def display_etf_weightings( ) return fig.show(external=external_axes) + + +@log_start_end(log=logger) +@check_api_key(['API_KEY_FINANCIALMODELINGPREP']) +def view_etf_holdings_performance(ticker: str, start_date: str, end_date: str, limit: int = 10, raw: bool = False, export: str = "", sheet_name: Optional[str] = None): + + data = fmp_model.get_holdings_pct_change(ticker, start_date, end_date)[0:limit] + + colors = ['g' if x > 0 else 'r' for x in data['Percent Change']] + + sns.barplot( + x= 'Percent Change', + y= 'Name', + data=data, + palette=colors, + ) + if raw: + print_rich_table( + data, + show_index=False, + headers=["Ticker", "Name", "Percent Change"], + title="ETF Holdings' Performance", + limit=limit, + export=bool(export), + ) + + if export: + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "holdings_perf", + data, + sheet_name, + ) + + return plt.show() diff --git a/openbb_terminal/miscellaneous/i18n/en.yml b/openbb_terminal/miscellaneous/i18n/en.yml index b133a06adba..16f148b90e2 100644 --- a/openbb_terminal/miscellaneous/i18n/en.yml +++ b/openbb_terminal/miscellaneous/i18n/en.yml @@ -702,6 +702,7 @@ en: etf/scr: screener ETFs overview/performance, using preset filters etf/overview: get overview etf/holdings: top company holdings + etf/holding_perf: Performance of holdings in ETF. etf/weights: sector weights allocation etf/candle: view a candle chart for ETF etf/news: latest news of the company |