summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Maslek <jmaslek11@gmail.com>2023-10-03 10:30:43 -0400
committerGitHub <noreply@github.com>2023-10-03 10:30:43 -0400
commitc23f2c8b73d71cb87186b66d38975ab9772dd0e8 (patch)
treeba7294a832e1cbf0e811321f1fca5033fb5ae12b
parent702f86067122ae7c595bbc0ebeb14987fc074f7b (diff)
parentdb5e198a34e08e47340af666840079409d8ab09d (diff)
Merge pull request #5488 from nectariosouzou/feature/add-etf-holding-performance
Feature/add etf holding performance
-rw-r--r--.github/workflows/linting.yml2
-rw-r--r--openbb_terminal/etf/etf_controller.py59
-rw-r--r--openbb_terminal/etf/fmp_model.py164
-rw-r--r--openbb_terminal/etf/fmp_view.py84
-rw-r--r--openbb_terminal/miscellaneous/i18n/en.yml1
-rw-r--r--openbb_terminal/miscellaneous/integration_tests_scripts/etf/test_etf.openbb1
-rw-r--r--openbb_terminal/miscellaneous/sources/openbb_default.json3
-rw-r--r--tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[ARKK].yaml48
-rw-r--r--tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[VTI].yaml48
-rw-r--r--tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].yaml48
-rw-r--r--tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].yaml48
-rw-r--r--tests/openbb_terminal/etf/cassettes/test_fmp_view/test_view_etf_holdings_performance[2023-09-01-2022-09-01-TSLA].yaml48
-rw-r--r--tests/openbb_terminal/etf/csv/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].csv11
-rw-r--r--tests/openbb_terminal/etf/csv/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].csv1
-rw-r--r--tests/openbb_terminal/etf/json/test_fmp_model/test_get_etf_holdings[ARKK].json1
-rw-r--r--tests/openbb_terminal/etf/test_fmp_model.py64
-rw-r--r--tests/openbb_terminal/etf/test_fmp_view.py25
-rw-r--r--tests/openbb_terminal/etf/txt/test_etf_controller/test_print_help.txt1
18 files changed, 647 insertions, 10 deletions
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index c131fdf5294..37178c9c315 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -44,7 +44,7 @@ jobs:
restore-keys: ${{ runner.os }}-linting-${{ hashFiles('**/poetry.lock') }}
- run: |
- pip install bandit black codespell mypy==1.5.1 pylint==2.17.0 ruff==0.0.285
+ pip install bandit black codespell==2.2.5 mypy==1.5.1 pylint==2.17.0 ruff==0.0.285
pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil types-setuptools types-six
- run: bandit -x ./tests -r . || true
- run: black --diff --check .
diff --git a/openbb_terminal/etf/etf_controller.py b/openbb_terminal/etf/etf_controller.py
index 26a4311208e..589829f2802 100644
--- a/openbb_terminal/etf/etf_controller.py
+++ b/openbb_terminal/etf/etf_controller.py
@@ -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,60 @@ class ETFController(BaseController):
if ns_parser.sheet_name
else None,
)
+
+ @log_start_end(log=logger)
+ def call_holding_perf(self, other_args: List[str]):
+ """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-date",
+ 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-date",
+ 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_allowed=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,
+ export=ns_parser.export,
+ sheet_name=ns_parser.sheet_name,
+ raw=ns_parser.raw,
+ )
+ 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..39720586efc 100644
--- a/openbb_terminal/etf/fmp_model.py
+++ b/openbb_terminal/etf/fmp_model.py
@@ -3,13 +3,16 @@ __docformat__ = "numpy"
import json
import logging
-from typing import Dict
+from typing import Any, 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.rich_config import console
+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, optional_rich_track
logger = logging.getLogger(__name__)
@@ -47,3 +50,158 @@ 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(
+ tickers: List[str], start_date: str, end_date: str
+) -> Dict[str, float]:
+ """Get stock's price percent change over specified time period.
+
+ Parameters
+ ----------
+ tickers : List[str]
+ Ticker(s) to get information for.
+ 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
+ -------
+ Dict[str, float]
+ Percent change of closing price over time period, or dictionary of ticker, change pairs.
+ """
+ tickers_tracker = optional_rich_track(
+ tickers, False, "Gathering stock prices", len(tickers)
+ )
+ current_user = get_current_user()
+ data_aggregate = dict()
+
+ for tick in tickers_tracker:
+ tickers_req = str(tick) + ","
+ for _ in range(4):
+ try:
+ _tick = next(tickers_tracker)
+ tickers_req += _tick + ","
+ except StopIteration:
+ break
+
+ url = f"""https://financialmodelingprep.com/api/v3/historical-price-full/{tickers_req}?\
+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 dict()
+
+ data = response.json()
+ stock_list = data
+ if "historicalStockList" in data:
+ stock_list = data["historicalStockList"]
+
+ for stock in stock_list:
+ close_end = stock["historical"][0]["close"]
+ close_start = stock["historical"][-1]["close"]
+ pct_change = 100 * (close_end - close_start) / close_start
+ data_aggregate[stock["symbol"]] = pct_change
+
+ return data_aggregate
+
+
+@log_start_end(log=logger)
+@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
+def get_etf_holdings(ticker: str, limit: int = 10) -> List[Dict[str, Any]]:
+ """This endpoint returns all stocks held by a specific ETF.
+
+ Parameters
+ ----------
+ ticker : str
+ ETF ticker.
+ limit: int
+ Limit amount of stocks to return. FMP returns data
+ by descending weighting.
+
+ 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 []
+
+ return response.json()[0:limit]
+
+
+@log_start_end(log=logger)
+@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
+def get_holdings_pct_change(
+ ticker: str,
+ start_date: str,
+ end_date: str,
+ limit: int = 10,
+) -> pd.DataFrame:
+ """Calculate percent change for each holding in ETF.
+
+ Parameters
+ ----------
+ ticker : str
+ ETF ticker.
+ limit: int
+ Limit amount of stocks to return. FMP returns data
+ by descending weighting.
+
+ Returns
+ -------
+ pd.DataFrame
+ Calculated percentage change for each stock in the ETF, in descending order.
+ """
+
+ df = pd.DataFrame(columns=["Ticker", "Name", "Percent Change"], data=[])
+ holdings = get_etf_holdings(ticker, limit)
+ tickers = []
+ for stock in holdings:
+ tickers.append(stock.get("asset", " "))
+
+ pct_changes = get_stock_price_change(tickers, start_date, end_date)
+
+ for stock in holdings:
+ pct_change = pct_changes.get(stock["asset"], 0)
+ if pct_change == 0:
+ console.print(
+ f"""Percent change not found for: {stock["asset"]}: {stock["name"]}"""
+ )
+ new_df = pd.DataFrame(
+ {
+ "Ticker": stock["asset"],
+ "Name": stock["name"],
+ "Percent Change": pct_changes.get(stock["asset"], 0),
+ },
+ index=[0],
+ )
+
+ df = pd.concat([df, new_df], ignore_index=True)
+
+ sorted_df = df.sort_values(by="Percent Change", ascending=False, inplace=False)
+
+ return sorted_df
diff --git a/openbb_terminal/etf/fmp_view.py b/openbb_terminal/etf/fmp_view.py
index 6499aa3fc96..42f02cc93e2 100644
--- a/openbb_terminal/etf/fmp_view.py
+++ b/openbb_terminal/etf/fmp_view.py
@@ -8,12 +8,9 @@ from typing import Optional
import pandas as pd
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,
- print_rich_table,
-)
+from openbb_terminal.helper_funcs import export_data, print_rich_table
from openbb_terminal.rich_config import console
logger = logging.getLogger(__name__)
@@ -120,3 +117,80 @@ 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,
+):
+ """Display ETF's holdings' performance over specified time. [Source: FinancialModelingPrep]
+ Parameters
+ ----------
+ ticker: str
+ ETF ticker.
+ start_date: 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.
+ limit: int
+ Limit number of holdings to view. Sorted by holding percentage (desc).
+ raw: bool
+ Display holding performance
+ sheet_name: str
+ Optionally specify the name of the sheet the data is exported to.
+ export: str
+ Type of format to export data.
+ """
+ data = fmp_model.get_holdings_pct_change(ticker, start_date, end_date, limit)[::-1]
+
+ if raw:
+ print_rich_table(
+ data,
+ show_index=False,
+ headers=["Ticker", "Name", "Percent Change"],
+ title="ETF Holdings' Performance",
+ limit=limit,
+ export=bool(export),
+ )
+
+ fig = OpenBBFigure()
+
+ if not raw or fig.is_image_export(export):
+ fig.add_bar(
+ hovertext=[f"{x:.2f}" + "%" for x in data["Percent Change"]],
+ x=data["Percent Change"],
+ y=data["Name"],
+ name="Stock",
+ orientation="h",
+ marker_color=[
+ "darkgreen" if x > 0 else "darkred" for x in data["Percent Change"]
+ ],
+ )
+
+ fig.update_layout(
+ title=f"Percent Change in Price for Each Holding from {start_date} to {end_date} for {ticker}",
+ xaxis=dict(title="Percent Change"),
+ yaxis=dict(title="Asset Name"),
+ )
+
+ if export:
+ export_data(
+ export_type=export,
+ dir_path=os.path.dirname(os.path.abspath(__file__)),
+ func_name=f"{ticker}_holdings_perf",
+ df=data,
+ sheet_name=sheet_name,
+ figure=fig,
+ )
+ return
+
+ fig.show(external=raw or bool(export))
+
+ return
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
diff --git a/openbb_terminal/miscellaneous/integration_tests_scripts/etf/test_etf.openbb b/openbb_terminal/miscellaneous/integration_tests_scripts/etf/test_etf.openbb
index eee0afe7816..f1376c391dc 100644
--- a/openbb_terminal/miscellaneous/integration_tests_scripts/etf/test_etf.openbb
+++ b/openbb_terminal/miscellaneous/integration_tests_scripts/etf/test_etf.openbb
@@ -2,6 +2,7 @@ etf
load spy
overview
holdings
+holdings_perf --limit 3
weights
weights --raw
candle
diff --git a/openbb_terminal/miscellaneous/sources/openbb_default.json b/openbb_terminal/miscellaneous/sources/openbb_default.json
index 0a440cfd03c..4a97941bd7b 100644
--- a/openbb_terminal/miscellaneous/sources/openbb_default.json
+++ b/openbb_terminal/miscellaneous/sources/openbb_default.json
@@ -504,7 +504,8 @@
"holdings": ["StockAnalysis"],
"weights": ["FinancialModelingPrep"],
"news": ["NewsApi"],
- "compare": ["StockAnalysis"]
+ "compare": ["StockAnalysis"],
+ "holding_perf": ["FinancialModelingPrep"]
},
"economy": {
"overview": ["WallStreetJournal"],
diff --git a/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[ARKK].yaml b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[ARKK].yaml
new file mode 100644
index 00000000000..8ef804515aa
--- /dev/null
+++ b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[ARKK].yaml
@@ -0,0 +1,48 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ method: GET
+ uri: https://financialmodelingprep.com/api/v3/etf-holder/ARKK?apikey=MOCK_API_KEY
+ response:
+ body:
+ string: "{\n \"Error Message\": \"Invalid API KEY. Please retry or visit our
+ documentation to create one FREE https://site.financialmodelingprep.com/developer/docs\"\n}"
+ headers:
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Access-Control-Allow-Headers:
+ - X-Requested-With, content-type, auth-token, Authorization, stripe-signature,
+ APPS
+ Access-Control-Allow-Methods:
+ - GET, POST, OPTIONS
+ Access-Control-Allow-Origin:
+ - '*'
+ Access-Control-Max-Age:
+ - '3600'
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '154'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Wed, 27 Sep 2023 14:51:51 GMT
+ ETag:
+ - W/"9a-ufKRTAmgqkT6Vv3bqU5trChBRE8"
+ Server:
+ - nginx/1.18.0 (Ubuntu)
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Powered-By:
+ - Express
+ status:
+ code: 401
+ message: Unauthorized
+version: 1
diff --git a/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[VTI].yaml b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[VTI].yaml
new file mode 100644
index 00000000000..a96eaf7e257
--- /dev/null
+++ b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_etf_holdings[VTI].yaml
@@ -0,0 +1,48 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ method: GET
+ uri: https://financialmodelingprep.com/api/v3/etf-holder/VTI?apikey=MOCK_API_KEY
+ response:
+ body:
+ string: "{\n \"Error Message\": \"Invalid API KEY. Please retry or visit our
+ documentation to create one FREE https://site.financialmodelingprep.com/developer/docs\"\n}"
+ headers:
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Access-Control-Allow-Headers:
+ - X-Requested-With, content-type, auth-token, Authorization, stripe-signature,
+ APPS
+ Access-Control-Allow-Methods:
+ - GET, POST, OPTIONS
+ Access-Control-Allow-Origin:
+ - '*'
+ Access-Control-Max-Age:
+ - '3600'
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '154'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Wed, 27 Sep 2023 14:40:52 GMT
+ ETag:
+ - W/"9a-ufKRTAmgqkT6Vv3bqU5trChBRE8"
+ Server:
+ - nginx/1.18.0 (Ubuntu)
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Powered-By:
+ - Express
+ status:
+ code: 401
+ message: Unauthorized
+version: 1
diff --git a/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].yaml b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].yaml
new file mode 100644
index 00000000000..8858b828d78
--- /dev/null
+++ b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].yaml
@@ -0,0 +1,48 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ method: GET
+ uri: https://financialmodelingprep.com/api/v3/etf-holder/ARKK?apikey=MOCK_API_KEY
+ response:
+ body:
+ string: "{\n \"Error Message\": \"Invalid API KEY. Please retry or visit our
+ documentation to create one FREE https://site.financialmodelingprep.com/developer/docs\"\n}"
+ headers:
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Access-Control-Allow-Headers:
+ - X-Requested-With, content-type, auth-token, Authorization, stripe-signature,
+ APPS
+ Access-Control-Allow-Methods:
+ - GET, POST, OPTIONS
+ Access-Control-Allow-Origin:
+ - '*'
+ Access-Control-Max-Age:
+ - '3600'
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '154'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Wed, 27 Sep 2023 17:42:35 GMT
+ ETag:
+ - W/"9a-ufKRTAmgqkT6Vv3bqU5trChBRE8"
+ Server:
+ - nginx/1.18.0 (Ubuntu)
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Powered-By:
+ - Express
+ status:
+ code: 401
+ message: Unauthorized
+version: 1
diff --git a/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].yaml b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].yaml
new file mode 100644
index 00000000000..eb917028176
--- /dev/null
+++ b/tests/openbb_terminal/etf/cassettes/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].yaml
@@ -0,0 +1,48 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ method: GET
+ uri: https://financialmodelingprep.com/api/v3/etf-holder/TSLA?apikey=MOCK_API_KEY
+ response:
+ body:
+ string: "{\n \"Error Message\": \"Invalid API KEY. Please retry or visit our
+ documentation to create one FREE https://site.financialmodelingprep.com/developer/docs\"\n}"
+ headers:
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Access-Control-Allow-Headers:
+ - X-Requested-With, content-type, auth-token, Authorization, stripe-signature,
+ APPS
+ Access-Control-Allow-Methods:
+ - GET, POST, OPTIONS
+ Access-Control-Allow-Origin:
+ - '*'
+ Access-Control-Max-Age:
+ - '3600'
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '154'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Wed, 27 Sep 2023 17:42:36 GMT
+ ETag:
+ - W/"9a-ufKRTAmgqkT6Vv3bqU5trChBRE8"
+ Server:
+ - nginx/1.18.0 (Ubuntu)
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Powered-By:
+ - Express
+ status:
+ code: 401
+ message: Unauthorized
+version: 1
diff --git a/tests/openbb_terminal/etf/cassettes/test_fmp_view/test_view_etf_holdings_performance[2023-09-01-2022-09-01-TSLA].yaml b/tests/openbb_terminal/etf/cassettes/test_fmp_view/test_view_etf_holdings_performance[2023-09-01-2022-09-01-TSLA].yaml
new file mode 100644
index 00000000000..12a096f5e88
--- /dev/null
+++ b/tests/openbb_terminal/etf/cassettes/test_fmp_view/test_view_etf_holdings_performance[2023-09-01-2022-09-01-TSLA].yaml
@@ -0,0 +1,48 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ method: GET
+ uri: https://financialmodelingprep.com/api/v3/etf-holder/TSLA?apikey=MOCK_API_KEY
+ response:
+ body:
+ string: "{\n \"Error Message\": \"Invalid API KEY. Please retry or visit our
+ documentation to create one FREE https://site.financialmodelingprep.com/developer/docs\"\n}"
+ headers:
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Access-Control-Allow-Headers:
+ - X-Requested-With, content-type, auth-token, Authorization, stripe-signature,
+ APPS
+ Access-Control-Allow-Methods:
+ - GET, POST, OPTIONS
+ Access-Control-Allow-Origin:
+ - '*'
+ Access-Control-Max-Age:
+ - '3600'
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '154'
+ Content-Type:
+ - application/json; charset=utf-8
+ Date:
+ - Wed, 27 Sep 2023 17:52:02 GMT
+ ETag:
+ - W/"9a-ufKRTAmgqkT6Vv3bqU5trChBRE8"
+ Server:
+ - nginx/1.18.0 (Ubuntu)
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Powered-By:
+ - Express
+ status:
+ code: 401
+ message: Unauthorized
+version: 1
diff --git a/tests/openbb_terminal/etf/csv/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].csv b/tests/openbb_terminal/etf/csv/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].csv
new file mode 100644
index 00000000000..1bd705edd57
--- /dev/null
+++ b/tests/openbb_terminal/etf/csv/test_fmp_model/test_get_holdings_pct_change[2023-09-01-2022-09-01-ARKK].csv
@@ -0,0 +1,11 @@
+,Ticker,Name,Percent Change
+4,EXAS,EXACT SCIENCES CORP,134.98896247240614
+7,SHOP,SHOPIFY INC - CLASS A,113.33546530220659
+9,DKNG,DRAFTKINGS INC-CL A,85.71428571428571
+2,ROKU,ROKU INC,19.648609077598834
+3,COIN,COINBASE GLOBAL INC -CLASS A,19.01419197314206
+6,PATH,UIPATH INC - CLASS A,-1.670792079207918
+0,TSLA,TESLA INC,-11.599797950642241
+1,ZM,ZOOM VIDEO COMMUNICATIONS-A,-11.609563717032293
+5,SQ,BLOCK INC,-15.389090909090905
+8,TDOC,TELADOC HEALTH INC,-25.746753246753247
diff --git a/tests/openbb_terminal/etf/csv/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].csv b/tests/openbb_terminal/etf/csv/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].csv
new file mode 100644
index 00000000000..09bd5b648a0
--- /dev/null
+++ b/tests/openbb_terminal/etf/csv/test_fmp_model/test_get_stock_price_change[2023-09-01-2022-09-01-TSLA].csv
@@ -0,0 +1 @@
+,Ticker,Name,Percent Change
diff --git a/tests/openbb_terminal/etf/json/test_fmp_model/test_get_etf_holdings[ARKK].json b/tests/openbb_terminal/etf/json/test_fmp_model/test_get_etf_holdings[ARKK].json
new file mode 100644
index 00000000000..0637a088a01
--- /dev/null
+++ b/tests/openbb_terminal/etf/json/test_fmp_model/test_get_etf_holdings[ARKK].json
@@ -0,0 +1 @@
+[] \ No newline at end of file
diff --git a/tests/openbb_ter