summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-02-15 11:55:26 -0800
committerGitHub <noreply@github.com>2024-02-15 19:55:26 +0000
commitccf46aad9250c8858a6c4c782a88ab3a2531dc2f (patch)
tree692fda493341bb56e5f2c3e6a0e368df84681355
parent389333853445bf9672262350a0ef05904d878774 (diff)
[Feature] Add end point: `etf.equity_exposure()` with FMP provider (#6079)
* add etf.equity_exposure from FMP * empty data error * recapture cassette * pylint unused argument * Fix router example typo * fix test...? * static file to fix test? * fix test..? * __json_schema_extra__ * black --------- Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com> Co-authored-by: James Maslek <jmaslek11@gmail.com>
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py43
-rw-r--r--openbb_platform/extensions/etf/integration/test_etf_api.py17
-rw-r--r--openbb_platform/extensions/etf/integration/test_etf_python.py16
-rw-r--r--openbb_platform/extensions/etf/openbb_etf/etf_router.py98
-rw-r--r--openbb_platform/openbb/package/etf.py143
-rw-r--r--openbb_platform/openbb/package/module_map.json1
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/__init__.py2
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/etf_countries.py2
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/etf_equity_exposure.py95
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py29
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py61
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py2
-rw-r--r--openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_equity_exposure_fetcher.yaml585
-rw-r--r--openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml291
-rw-r--r--openbb_platform/providers/fmp/tests/test_fmp_fetchers.py10
15 files changed, 1107 insertions, 288 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py b/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py
new file mode 100644
index 00000000000..1a190b8a87d
--- /dev/null
+++ b/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py
@@ -0,0 +1,43 @@
+"""ETF Equity Exposure Standard Model."""
+
+from typing import Optional, Union
+
+from pydantic import Field, field_validator
+
+from openbb_core.provider.abstract.data import Data
+from openbb_core.provider.abstract.query_params import QueryParams
+from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
+
+
+class EtfEquityExposureQueryParams(QueryParams):
+ """ETF Equity Exposure Query Params."""
+
+ symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "") + " (Stock)")
+
+ @field_validator("symbol")
+ @classmethod
+ def upper_symbol(cls, v: str) -> str:
+ """Convert symbol to uppercase."""
+ return v.upper()
+
+
+class EtfEquityExposureData(Data):
+ """ETF Equity Exposure Data."""
+
+ equity_symbol: str = Field(description="The symbol of the equity requested.")
+ etf_symbol: str = Field(
+ description="The symbol of the ETF with exposure to the requested equity."
+ )
+ shares: Optional[int] = Field(
+ default=None,
+ description="The number of shares held in the ETF.",
+ )
+ weight: Optional[float] = Field(
+ default=None,
+ description="The weight of the equity in the ETF, as a normalized percent.",
+ json_schema_extra={"units_measurement": "percent", "frontend_multiply": 100},
+ )
+ market_value: Optional[Union[int, float]] = Field(
+ default=None,
+ description="The market value of the equity position in the ETF.",
+ )
diff --git a/openbb_platform/extensions/etf/integration/test_etf_api.py b/openbb_platform/extensions/etf/integration/test_etf_api.py
index 15edfeda960..740c677b745 100644
--- a/openbb_platform/extensions/etf/integration/test_etf_api.py
+++ b/openbb_platform/extensions/etf/integration/test_etf_api.py
@@ -264,3 +264,20 @@ def test_etf_holdings_performance(params, headers):
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
+
+
+@parametrize(
+ "params",
+ [
+ ({"symbol": "SPY,VOO,QQQ,IWM,IWN", "provider": "fmp"}),
+ ],
+)
+@pytest.mark.integration
+def test_etf_equity_exposure(params, headers):
+ params = {p: v for p, v in params.items() if v}
+
+ query_str = get_querystring(params, [])
+ url = f"http://0.0.0.0:8000/api/v1/etf/equity_exposure?{query_str}"
+ result = requests.get(url, headers=headers, timeout=10)
+ assert isinstance(result, requests.Response)
+ assert result.status_code == 200
diff --git a/openbb_platform/extensions/etf/integration/test_etf_python.py b/openbb_platform/extensions/etf/integration/test_etf_python.py
index 202fb1c62af..65f89e159e6 100644
--- a/openbb_platform/extensions/etf/integration/test_etf_python.py
+++ b/openbb_platform/extensions/etf/integration/test_etf_python.py
@@ -251,3 +251,19 @@ def test_etf_holdings_performance(params, obb):
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
+
+
+@parametrize(
+ "params",
+ [
+ ({"symbol": "SPY,VOO,QQQ,IWM,IWN", "provider": "fmp"}),
+ ],
+)
+@pytest.mark.integration
+def test_etf_equity_exposure(params, obb):
+ params = {p: v for p, v in params.items() if v}
+
+ result = obb.etf.equity_exposure(**params)
+ assert result
+ assert isinstance(result, OBBject)
+ assert len(result.results) > 0
diff --git a/openbb_platform/extensions/etf/openbb_etf/etf_router.py b/openbb_platform/extensions/etf/openbb_etf/etf_router.py
index 99ee39e43d5..c2906727d7a 100644
--- a/openbb_platform/extensions/etf/openbb_etf/etf_router.py
+++ b/openbb_platform/extensions/etf/openbb_etf/etf_router.py
@@ -18,7 +18,16 @@ router.include_router(discovery_router)
# pylint: disable=unused-argument
-@router.command(model="EtfSearch")
+@router.command(
+ model="EtfSearch",
+ exclude_auto_examples=True,
+ examples=[
+ "### An empty query returns the full list of ETFs from the provider. ###",
+ 'obb.etf.search("", provider="fmp")',
+ "#### The query will return results from text-based fields containing the term. ####"
+ 'obb.etf.search("commercial real estate", provider="fmp")',
+ ],
+)
async def search(
cc: CommandContext,
provider_choices: ProviderChoices,
@@ -43,7 +52,15 @@ async def historical(
return await OBBject.from_query(Query(**locals()))
-@router.command(model="EtfInfo")
+@router.command(
+ model="EtfInfo",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.info("SPY", provider="fmp")',
+ "#### This function accepts multiple tickers. ####",
+ 'obb.etf.info("SPY,IWM,QQQ,DJIA", provider="fmp")',
+ ],
+)
async def info(
cc: CommandContext,
provider_choices: ProviderChoices,
@@ -54,7 +71,13 @@ async def info(
return await OBBject.from_query(Query(**locals()))
-@router.command(model="EtfSectors")
+@router.command(
+ model="EtfSectors",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.sectors("SPY", provider="fmp")',
+ ],
+)
async def sectors(
cc: CommandContext,
provider_choices: ProviderChoices,
@@ -65,7 +88,13 @@ async def sectors(
return await OBBject.from_query(Query(**locals()))
-@router.command(model="EtfCountries")
+@router.command(
+ model="EtfCountries",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.countries("VT", provider="fmp")',
+ ],
+)
async def countries(
cc: CommandContext,
provider_choices: ProviderChoices,
@@ -76,18 +105,34 @@ async def countries(
return await OBBject.from_query(Query(**locals()))
-@router.command(model="PricePerformance")
+@router.command(
+ model="PricePerformance",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.price_performance("SPY,QQQ,IWM,DJIA", provider="fmp")',
+ ],
+)
async def price_performance(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
- """Price performance as a return, over different periods."""
+ """Price performance as a return, over different periods. This is a proxy for `equity.price.performance`."""
return await OBBject.from_query(Query(**locals()))
-@router.command(model="EtfHoldings")
+@router.command(
+ model="EtfHoldings",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.holdings("XLK", provider="fmp").to_df()',
+ "#### Including a date (FMP, SEC) will return the holdings as per NPORT-P filings. ####",
+ 'obb.etf.holdings("XLK", date="2022-03-31",provider="fmp").to_df()',
+ "#### The same data can be returned from the SEC directly. ####",
+ 'obb.etf.holdings("XLK", date="2022-03-31",provider="sec").to_df()',
+ ],
+)
async def holdings(
cc: CommandContext,
provider_choices: ProviderChoices,
@@ -98,23 +143,54 @@ async def holdings(
return await OBBject.from_query(Query(**locals()))
-@router.command(model="EtfHoldingsDate")
+@router.command(
+ model="EtfHoldingsDate",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.holdings_date("XLK", provider="fmp").results',
+ ],
+)
async def holdings_date(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
- """Get the holdings filing date for an individual ETF."""
+ """Use this function to get the holdings dates, if available."""
return await OBBject.from_query(Query(**locals()))
-@router.command(model="EtfHoldingsPerformance")
+@router.command(
+ model="EtfHoldingsPerformance",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.holdings_performance("XLK", provider="fmp")',
+ ],
+)
async def holdings_performance(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
- """Get the ETF holdings performance."""
+ """Get the recent price performance of each ticker held in the ETF."""
+ return await OBBject.from_query(Query(**locals()))
+
+
+@router.command(
+ model="EtfEquityExposure",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.etf.equity_exposure("MSFT", provider="fmp")',
+ "#### This function accepts multiple tickers. ####",
+ 'obb.etf.equity_exposure("MSFT,AAPL", provider="fmp")',
+ ],
+)
+async def equity_exposure(
+ cc: CommandContext,
+ provider_choices: ProviderChoices,
+ standard_params: StandardParams,
+ extra_params: ExtraParams,
+) -> OBBject:
+ """Get the exposure to ETFs for a specific stock."""
return await OBBject.from_query(Query(**locals()))
diff --git a/openbb_platform/openbb/package/etf.py b/openbb_platform/openbb/package/etf.py
index 9563e667242..e076e815c9c 100644
--- a/openbb_platform/openbb/package/etf.py
+++ b/openbb_platform/openbb/package/etf.py
@@ -14,6 +14,7 @@ from typing_extensions import Annotated
class ROUTER_etf(Container):
"""/etf
countries
+ equity_exposure
historical
holdings
holdings_date
@@ -69,7 +70,7 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.countries(symbol="SPY")
+ >>> obb.etf.countries("VT", provider="fmp")
""" # noqa: E501
return self._run(
@@ -90,6 +91,82 @@ class ROUTER_etf(Container):
)
@validate
+ def equity_exposure(
+ self,
+ symbol: Annotated[
+ Union[str, List[str]],
+ OpenBBCustomParameter(
+ description="Symbol to get data for. (Stock) Multiple items allowed: fmp."
+ ),
+ ],
+ provider: Optional[Literal["fmp"]] = None,
+ **kwargs
+ ) -> OBBject:
+ """Get the exposure to ETFs for a specific stock.
+
+ Parameters
+ ----------
+ symbol : Union[str, List[str]]
+ Symbol to get data for. (Stock) Multiple items allowed: fmp.
+ provider : Optional[Literal['fmp']]
+ The provider to use for the query, by default None.
+ If None, the provider specified in defaults is selected or 'fmp' if there is
+ no default.
+
+ Returns
+ -------
+ OBBject
+ results : List[EtfEquityExposure]
+ Serializable results.
+ provider : Optional[Literal['fmp']]
+ Provider name.
+ warnings : Optional[List[Warning_]]
+ List of warnings.
+ chart : Optional[Chart]
+ Chart object.
+ extra: Dict[str, Any]
+ Extra info.
+
+ EtfEquityExposure
+ -----------------
+ equity_symbol : str
+ The symbol of the equity requested.
+ etf_symbol : str
+ The symbol of the ETF with exposure to the requested equity.
+ shares : Optional[int]
+ The number of shares held in the ETF.
+ weight : Optional[float]
+ The weight of the equity in the ETF, as a normalized percent.
+ market_value : Optional[Union[float, int]]
+ The market value of the equity position in the ETF.
+
+ Example
+ -------
+ >>> from openbb import obb
+ >>> obb.etf.equity_exposure("MSFT", provider="fmp")
+ >>> #### This function accepts multiple tickers. ####
+ >>> obb.etf.equity_exposure("MSFT,AAPL", provider="fmp")
+ """ # noqa: E501
+
+ return self._run(
+ "/etf/equity_exposure",
+ **filter_inputs(
+ provider_choices={
+ "provider": self._get_provider(
+ provider,
+ "/etf/equity_exposure",
+ ("fmp",),
+ )
+ },
+ standard_params={
+ "symbol": symbol,
+ },
+ extra_params=kwargs,
+ extra_info={"symbol": {"multiple_items_allowed": ["fmp"]}},
+ )
+ )
+
+ @validate
def historical(
self,
symbol: Annotated[
@@ -389,7 +466,11 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.holdings(symbol="SPY")
+ >>> obb.etf.holdings("XLK", provider="fmp").to_df()
+ >>> #### Including a date (FMP, SEC) will return the holdings as per NPORT-P filings. ####
+ >>> obb.etf.holdings("XLK", date="2022-03-31",provider="fmp").to_df()
+ >>> #### The same data can be returned from the SEC directly. ####
+ >>> obb.etf.holdings("XLK", date="2022-03-31",provider="sec").to_df()
""" # noqa: E501
return self._run(
@@ -418,7 +499,7 @@ class ROUTER_etf(Container):
provider: Optional[Literal["fmp"]] = None,
**kwargs
) -> OBBject:
- """Get the holdings filing date for an individual ETF.
+ """Use this function to get the holdings dates, if available.
Parameters
----------
@@ -453,7 +534,7 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.holdings_date(symbol="SPY")
+ >>> obb.etf.holdings_date("XLK", provider="fmp").results
""" # noqa: E501
return self._run(
@@ -482,7 +563,7 @@ class ROUTER_etf(Container):
provider: Optional[Literal["fmp"]] = None,
**kwargs
) -> OBBject:
- """Get the ETF holdings performance.
+ """Get the recent price performance of each ticker held in the ETF.
Parameters
----------
@@ -543,7 +624,7 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.holdings_performance(symbol="SPY")
+ >>> obb.etf.holdings_performance("XLK", provider="fmp")
""" # noqa: E501
return self._run(
@@ -569,7 +650,7 @@ class ROUTER_etf(Container):
symbol: Annotated[
Union[str, List[str]],
OpenBBCustomParameter(
- description="Symbol to get data for. (ETF) Multiple items allowed: yfinance."
+ description="Symbol to get data for. (ETF) Multiple items allowed: fmp, yfinance."
),
],
provider: Optional[Literal["fmp", "yfinance"]] = None,
@@ -580,7 +661,7 @@ class ROUTER_etf(Container):
Parameters
----------
symbol : Union[str, List[str]]
- Symbol to get data for. (ETF) Multiple items allowed: yfinance.
+ Symbol to get data for. (ETF) Multiple items allowed: fmp, yfinance.
provider : Optional[Literal['fmp', 'yfinance']]
The provider to use for the query, by default None.
If None, the provider specified in defaults is selected or 'fmp' if there is
@@ -610,30 +691,30 @@ class ROUTER_etf(Container):
Description of the fund.
inception_date : Optional[str]
Inception date of the ETF.
- asset_class : Optional[str]
- Asset class of the ETF. (provider: fmp)
- aum : Optional[float]
- Assets under management. (provider: fmp)
- avg_volume : Optional[float]
- Average trading volume of the ETF. (provider: fmp)
+ issuer : Optional[str]
+ Company of the ETF. (provider: fmp)
cusip : Optional[str]
CUSIP of the ETF. (provider: fmp)
- domicile : Optional[str]
- Domicile of the ETF. (provider: fmp)
- etf_company : Optional[str]
- Company of the ETF. (provider: fmp)
- expense_ratio : Optional[float]
- Expense ratio of the ETF. (provider: fmp)
isin : Optional[str]
ISIN of the ETF. (provider: fmp)
+ domicile : Optional[str]
+ Domicile of the ETF. (provider: fmp)
+ asset_class : Optional[str]
+ Asset class of the ETF. (provider: fmp)
+ aum : Optional[float]
+ Assets under management. (provider: fmp)
nav : Optional[float]
Net asset value of the ETF. (provider: fmp)
nav_currency : Optional[str]
Currency of the ETF's net asset value. (provider: fmp)
- website : Optional[str]
- Website link of the ETF. (provider: fmp)
+ expense_ratio : Optional[float]
+ The expense ratio, as a normalized percent. (provider: fmp)
holdings_count : Optional[int]
- Number of holdings in the ETF. (provider: fmp)
+ Number of holdings. (provider: fmp)
+ avg_volume : Optional[float]
+ Average daily trading volume. (provider: fmp)
+ website : Optional[str]
+ Website of the issuer. (provider: fmp)
fund_type : Optional[str]
The legal type of fund. (provider: yfinance)
fund_family : Optional[str]
@@ -700,7 +781,9 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.info(symbol="SPY")
+ >>> obb.etf.info("SPY", provider="fmp")
+ >>> #### This function accepts multiple tickers. ####
+ >>> obb.etf.info("SPY,IWM,QQQ,DJIA", provider="fmp")
""" # noqa: E501
return self._run(
@@ -717,7 +800,7 @@ class ROUTER_etf(Container):
"symbol": symbol,
},
extra_params=kwargs,
- extra_info={"symbol": {"multiple_items_allowed": ["yfinance"]}},
+ extra_info={"symbol": {"multiple_items_allowed": ["fmp", "yfinance"]}},
)
)
@@ -730,7 +813,7 @@ class ROUTER_etf(Container):
provider: Optional[Literal["fmp"]] = None,
**kwargs
) -> OBBject:
- """Price performance as a return, over different periods.
+ """Price performance as a return, over different periods. This is a proxy for `equity.price.performance`.
Parameters
----------
@@ -791,7 +874,7 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.price_performance(symbol="SPY")
+ >>> obb.etf.price_performance("SPY,QQQ,IWM,DJIA", provider="fmp")
""" # noqa: E501
return self._run(
@@ -884,7 +967,9 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.search(query="Vanguard")
+ >>> ### An empty query returns the full list of ETFs from the provider. ###
+ >>> obb.etf.search("", provider="fmp")
+ >>> #### The query will return results from text-based fields containing the term. ####obb.etf.search("commercial real estate", provider="fmp")
""" # noqa: E501
return self._run(
@@ -948,7 +1033,7 @@ class ROUTER_etf(Container):
Example
-------
>>> from openbb import obb
- >>> obb.etf.sectors(symbol="SPY")
+ >>> obb.etf.sectors("SPY", provider="fmp")
""" # noqa: E501
return self._run(
diff --git a/openbb_platform/openbb/package/module_map.json b/openbb_platform/openbb/package/module_map.json
index 39397c721a3..bae65c52ed9 100644
--- a/openbb_platform/openbb/package/module_map.json
+++ b/openbb_platform/openbb/package/module_map.json
@@ -96,6 +96,7 @@
"equity_shorts_fails_to_deliver": "/equity/shorts/fails_to_deliver",
"etf": "/etf",
"etf_countries": "/etf/countries",
+ "etf_equity_exposure": "/etf/equity_exposure",
"etf_historical": "/etf/historical",
"etf_holdings": "/etf/holdings",
"etf_holdings_date": "/etf/holdings_date",
diff --git a/openbb_platform/providers/fmp/openbb_fmp/__init__.py b/openbb_platform/providers/fmp/openbb_fmp/__init__.py
index 1af89b46c0a..c3e2843540f 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/__init__.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/__init__.py
@@ -30,6 +30,7 @@ from openbb_fmp.models.equity_valuation_multiples import (
FMPEquityValuationMultiplesFetcher,
)
from openbb_fmp.models.etf_countries import FMPEtfCountriesFetcher
+from openbb_fmp.models.etf_equity_exposure import FMPEtfEquityExposureFetcher
from openbb_fmp.models.etf_holdings import FMPEtfHoldingsFetcher
from openbb_fmp.models.etf_holdings_date import FMPEtfHoldingsDateFetcher
from openbb_fmp.models.etf_holdings_performance import FMPEtfHoldingsPerformanceFetcher
@@ -96,6 +97,7 @@ fmp_provider = Provider(
"EquityScreener": FMPEquityScreenerFetcher,
"EquityValuationMultiples": FMPEquityValuationMultiplesFetcher,
"EtfCountries": FMPEtfCountriesFetcher,
+ "EtfEquityExposure": FMPEtfEquityExposureFetcher,
"EtfHoldings": FMPEtfHoldingsFetcher,
"EtfHoldingsDate": FMPEtfHoldingsDateFetcher,
"EtfHoldingsPerformance": FMPEtfHoldingsPerformanceFetcher,
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_countries.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_countries.py
index 0109ae1ae04..4ffd1e762d6 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_countries.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_countries.py
@@ -14,6 +14,8 @@ from openbb_fmp.utils.helpers import create_url, get_data_many
class FMPEtfCountriesQueryParams(EtfCountriesQueryParams):
"""FMP ETF Countries Query."""
+ __json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
+
class FMPEtfCountriesData(EtfCountriesData):
"""FMP ETF Countries Data."""
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_equity_exposure.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_equity_exposure.py
new file mode 100644
index 00000000000..06cd0f0ba21
--- /dev/null
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_equity_exposure.py
@@ -0,0 +1,95 @@
+"""FMP ETF Equity Exposure Model."""
+
+# pylint: disable=unused-argument
+
+import asyncio
+import warnings
+from typing import Any, Dict, List, Optional
+
+from openbb_core.provider.abstract.fetcher import Fetcher
+from openbb_core.provider.standard_models.etf_equity_exposure import (
+ EtfEquityExposureData,
+ EtfEquityExposureQueryParams,
+)
+from openbb_core.provider.utils.errors import EmptyDataError
+from openbb_core.provider.utils.helpers import amake_request
+from pydantic import field_validator
+
+_warn = warnings.warn
+
+
+class FMPEtfEquityExposureQueryParams(EtfEquityExposureQueryParams):
+ """
+ FMP ETF Equity Exposure Query Params.
+
+ Source: https://site.financialmodelingprep.com/developer/docs/etf-stock-exposure-api/
+ """
+
+ __json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
+
+
+class FMPEtfEquityExposureData(EtfEquityExposureData):
+ """FMP ETF Equity Exposure Data."""
+
+ __alias_dict__ = {
+ "equity_symbol": "assetExposure",
+ "etf_symbol": "etfSymbol",
+ "shares": "sharesNumber",
+ "weight": "weightPercentage",
+ "market_value": "marketValue",
+ }
+
+ @field_validator("weight", mode="before", check_fields=False)
+ @classmethod
+ def normalize_percent(cls, v):
+ """Normalize percent values."""
+ return float(v) / 100 if v else None
+
+
+class FMPEtfEquityExposureFetcher(
+ Fetcher[FMPEtfEquityExposureQueryParams, List[FMPEtfEquityExposureData]]
+):
+ """FMP ETF Equity Exposure Fetcher."""
+