summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornectariosouzo <nectariosouzounidis@gmail.com>2023-09-24 12:50:43 -0400
committernectariosouzo <nectariosouzounidis@gmail.com>2023-09-24 12:50:43 -0400
commit29a5d273b89abb0da58d5d7c7e01d4887a8ad3f4 (patch)
tree3a0d58b8172fafe2ed2670b50553b7c392c3e95b
parentbf082d1aeda02ac2d2e77c5c142dde5a0a6b9605 (diff)
Create holdings' performance for ETFs
-rw-r--r--openbb_terminal/etf/etf_controller.py54
-rw-r--r--openbb_terminal/etf/fmp_model.py109
-rw-r--r--openbb_terminal/etf/fmp_view.py40
-rw-r--r--openbb_terminal/miscellaneous/i18n/en.yml1
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