diff options
11 files changed, 401 insertions, 108 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py b/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py index 8d1c4a59038..0ad30366cd9 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py @@ -2,7 +2,7 @@ from typing import Optional -from pydantic import Field +from pydantic import Field, field_validator from openbb_core.provider.abstract.data import Data from openbb_core.provider.abstract.query_params import QueryParams @@ -17,6 +17,12 @@ class EtfHoldingsQueryParams(QueryParams): symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "") + " (ETF)") + @field_validator("symbol") + @classmethod + def to_upper(cls, v: str) -> str: + """Convert field to uppercase.""" + return v.upper() + class EtfHoldingsData(Data): """ETF Holdings Data.""" diff --git a/openbb_platform/extensions/etf/openbb_etf/etf_router.py b/openbb_platform/extensions/etf/openbb_etf/etf_router.py index 0086c8f0bac..52c367864fe 100644 --- a/openbb_platform/extensions/etf/openbb_etf/etf_router.py +++ b/openbb_platform/extensions/etf/openbb_etf/etf_router.py @@ -1,5 +1,6 @@ """ETF Router.""" +from openbb_core.app.deprecation import OpenBBDeprecationWarning from openbb_core.app.model.command_context import CommandContext from openbb_core.app.model.example import APIEx from openbb_core.app.model.obbject import OBBject @@ -172,6 +173,13 @@ async def holdings_date( @router.command( model="EtfHoldingsPerformance", + deprecated=True, + deprecation=OpenBBDeprecationWarning( + message="This endpoint is deprecated; pass a list of holdings symbols directly to" + + " `/equity/price/performance` instead.", + since=(4, 1), + expected_removal=(4, 2), + ), examples=[APIEx(parameters={"symbol": "XLK", "provider": "fmp"})], ) async def holdings_performance( diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json index 9abf315928f..d1c10c784f0 100644 --- a/openbb_platform/openbb/assets/reference.json +++ b/openbb_platform/openbb/assets/reference.json @@ -5351,6 +5351,20 @@ "optional": true }, { + "name": "smart_score_1m", + "type": "float", + "description": "A weighted average smart score over the last month.", + "default": null, + "optional": true + }, + { + "name": "success_rate_1m", + "type": "float", + "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last month", + "default": null, + "optional": true + }, + { "name": "gain_count_3m", "type": "int", "description": "The number of ratings that have gained value over the last 3 months", @@ -5379,6 +5393,20 @@ "optional": true }, { + "name": "smart_score_3m", + "type": "float", + "description": "A weighted average smart score over the last 3 months.", + "default": null, + "optional": true + }, + { + "name": "success_rate_3m", + "type": "float", + "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 months", + "default": null, + "optional": true + }, + { "name": "gain_count_6m", "type": "int", "description": "The number of ratings that have gained value over the last 6 months", @@ -5435,6 +5463,20 @@ "optional": true }, { + "name": "smart_score_9m", + "type": "float", + "description": "A weighted average smart score over the last 9 months.", + "default": null, + "optional": true + }, + { + "name": "success_rate_9m", + "type": "float", + "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 9 months", + "default": null, + "optional": true + }, + { "name": "gain_count_1y", "type": "int", "description": "The number of ratings that have gained value over the last 1 year", @@ -5463,6 +5505,20 @@ "optional": true }, { + "name": "smart_score_1y", + "type": "float", + "description": "A weighted average smart score over the last 1 year.", + "default": null, + "optional": true + }, + { + "name": "success_rate_1y", + "type": "float", + "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 1 year", + "default": null, + "optional": true + }, + { "name": "gain_count_2y", "type": "int", "description": "The number of ratings that have gained value over the last 2 years", @@ -5491,6 +5547,20 @@ "optional": true }, { + "name": "smart_score_2y", + "type": "float", + "description": "A weighted average smart score over the last 3 years.", + "default": null, + "optional": true + }, + { + "name": "success_rate_2y", + "type": "float", + "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 2 years", + "default": null, + "optional": true + }, + { "name": "gain_count_3y", "type": "int", "description": "The number of ratings that have gained value over the last 3 years", @@ -5517,6 +5587,20 @@ "description": "The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 years", "default": null, "optional": true + }, + { + "name": "smart_score_3y", + "type": "float", + "description": "A weighted average smart score over the last 3 years.", + "default": null, + "optional": true + }, + { + "name": "success_rate_3y", + "type": "float", + "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 years", + "default": null, + "optional": true } ] }, @@ -23216,8 +23300,8 @@ }, "/etf/holdings_performance": { "deprecated": { - "flag": null, - "message": null + "flag": true, + "message": "This endpoint is deprecated; pass a list of holdings symbols directly to `/equity/price/performance` instead. Deprecated in OpenBB Platform V4.1 to be removed in V4.2." }, "description": "Get the recent price performance of each ticker held in the ETF.", "examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.etf.holdings_performance(symbol='XLK', provider='fmp')\n```\n\n", diff --git a/openbb_platform/openbb/package/equity_estimates.py b/openbb_platform/openbb/package/equity_estimates.py index 9c707ff72c7..b7a9573041b 100644 --- a/openbb_platform/openbb/package/equity_estimates.py +++ b/openbb_platform/openbb/package/equity_estimates.py @@ -126,6 +126,10 @@ class ROUTER_equity_estimates(Container): The average percent (normalized) price difference per rating over the last month (provider: benzinga) std_dev_1m : Optional[float] The standard deviation in percent (normalized) price difference in the analyst's ratings over the last month (provider: benzinga) + smart_score_1m : Optional[float] + A weighted average smart score over the last month. (provider: benzinga) + success_rate_1m : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain over the last month (provider: benzinga) gain_count_3m : Optional[int] The number of ratings that have gained value over the last 3 months (provider: benzinga) loss_count_3m : Optional[int] @@ -134,6 +138,10 @@ class ROUTER_equity_estimates(Container): The average percent (normalized) price difference per rating over the last 3 months (provider: benzinga) std_dev_3m : Optional[float] The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 months (provider: benzinga) + smart_score_3m : Optional[float] + A weighted average smart score over the last 3 months. (provider: benzinga) + success_rate_3m : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 months (provider: benzinga) gain_count_6m : Optional[int] The number of ratings that have gained value over the last 6 months (provider: benzinga) loss_count_6m : Optional[int] @@ -150,6 +158,10 @@ class ROUTER_equity_estimates(Container): The average percent (normalized) price difference per rating over the last 9 months (provider: benzinga) std_dev_9m : Optional[float] The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 9 months (provider: benzinga) + smart_score_9m : Optional[float] + A weighted average smart score over the last 9 months. (provider: benzinga) + success_rate_9m : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 9 months (provider: benzinga) gain_count_1y : Optional[int] The number of ratings that have gained value over the last 1 year (provider: benzinga) loss_count_1y : Optional[int] @@ -158,6 +170,10 @@ class ROUTER_equity_estimates(Container): The average percent (normalized) price difference per rating over the last 1 year (provider: benzinga) std_dev_1y : Optional[float] The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 1 year (provider: benzinga) + smart_score_1y : Optional[float] + A weighted average smart score over the last 1 year. (provider: benzinga) + success_rate_1y : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 1 year (provider: benzinga) gain_count_2y : Optional[int] The number of ratings that have gained value over the last 2 years (provider: benzinga) loss_count_2y : Optional[int] @@ -166,6 +182,10 @@ class ROUTER_equity_estimates(Container): The average percent (normalized) price difference per rating over the last 2 years (provider: benzinga) std_dev_2y : Optional[float] The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 2 years (provider: benzinga) + smart_score_2y : Optional[float] + A weighted average smart score over the last 3 years. (provider: benzinga) + success_rate_2y : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 2 years (provider: benzinga) gain_count_3y : Optional[int] The number of ratings that have gained value over the last 3 years (provider: benzinga) loss_count_3y : Optional[int] @@ -174,6 +194,10 @@ class ROUTER_equity_estimates(Container): The average percent (normalized) price difference per rating over the last 3 years (provider: benzinga) std_dev_3y : Optional[float] The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 years (provider: benzinga) + smart_score_3y : Optional[float] + A weighted average smart score over the last 3 years. (provider: benzinga) + success_rate_3y : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 years (provider: benzinga) Examples -------- diff --git a/openbb_platform/openbb/package/etf.py b/openbb_platform/openbb/package/etf.py index 7e00a168999..e8d6f693333 100644 --- a/openbb_platform/openbb/package/etf.py +++ b/openbb_platform/openbb/package/etf.py @@ -2,13 +2,15 @@ import datetime from typing import List, Literal, Optional, Union +from warnings import simplefilter, warn +from openbb_core.app.deprecation import OpenBBDeprecationWarning from openbb_core.app.model.custom_parameter import OpenBBCustomParameter from openbb_core.app.model.obbject import OBBject from openbb_core.app.static.container import Container from openbb_core.app.static.utils.decorators import exception_handler, validate from openbb_core.app.static.utils.filters import filter_inputs -from typing_extensions import Annotated +from typing_extensions import Annotated, deprecated class ROUTER_etf(Container): @@ -698,6 +700,10 @@ class ROUTER_etf(Container): @exception_handler @validate + @deprecated( + "This endpoint is deprecated; pass a list of holdings symbols directly to `/equity/price/performance` instead. Deprecated in OpenBB Platform V4.1 to be removed in V4.2.", + category=OpenBBDeprecationWarning, + ) def holdings_performance( self, symbol: Annotated[ @@ -782,6 +788,13 @@ class ROUTER_etf(Container): >>> obb.etf.holdings_performance(symbol='XLK', provider='fmp') """ # noqa: E501 + simplefilter("always", DeprecationWarning) + warn( + "This endpoint is deprecated; pass a list of holdings symbols directly to `/equity/price/performance` instead. Deprecated in OpenBB Platform V4.1 to be removed in V4.2.", + category=DeprecationWarning, + stacklevel=2, + ) + return self._run( "/etf/holdings_performance", **filter_inputs( diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py index f2dd9a40662..6c6566828de 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py @@ -137,7 +137,7 @@ class FMPEtfHoldingsData(EtfHoldingsData): def replace_empty(cls, v): """Replace empty strings and 0s with None.""" if isinstance(v, str): - return v if v not in ("", "0") else None + return v if v not in ("", "0", "-") else None if isinstance(v, (float, int)): return v if v and v not in (0.0, 0) else None return v if v else None diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py index ef7411940c4..0a6f18b8339 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py @@ -54,7 +54,7 @@ class FMPEtfHoldingsPerformanceFetcher( # Split into chunks of 500 chunks = [holdings_list[i : i + 500] for i in range(0, len(holdings_list), 500)] # Get price performance for the holdings - holdings_performance: list = [] + holdings_performance: List[Dict] = [] for holding_chunk in chunks: holdings_str = ( ",".join(holding_chunk) if len(holding_chunk) > 1 else holding_chunk[0] diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py b/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py index 9a90bd7c7ea..aee89b084fb 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py @@ -9,7 +9,7 @@ from openbb_core.provider.standard_models.recent_performance import ( RecentPerformanceData, RecentPerformanceQueryParams, ) -from openbb_fmp.utils.helpers import create_url, get_data_many +from openbb_fmp.utils.helpers import create_url, get_data_urls from pydantic import Field, model_validator @@ -71,14 +71,23 @@ class FMPPricePerformanceFetcher( ) -> List[Dict]: """Return the raw data from the FMP endpoint.""" api_key = credentials.get("fmp_api_key") if credentials else "" - - url = create_url( - version=3, - endpoint=f"stock-price-change/{query.symbol}", - api_key=api_key, - exclude=["symbol"], - ) - return await get_data_many(url, **kwargs) + symbols = query.symbol.upper().split(",") + symbols = list(dict.fromkeys(symbols)) + chunk_size = 200 + chunks = [ + symbols[i : i + chunk_size] for i in range(0, len(symbols), chunk_size) + ] + urls = [ + create_url( + version=3, + endpoint=f"stock-price-change/{','.join(chunk)}", + api_key=api_key, + exclude=["symbol"], + ) + for chunk in chunks + ] + data = await get_data_urls(urls, **kwargs) + return data @staticmethod def transform_data( @@ -88,7 +97,8 @@ class FMPPricePerformanceFetcher( ) -> List[FMPPricePerformanceData]: """Return the transformed data.""" - symbols = query.symbol.split(",") + symbols = query.symbol.upper().split(",") + symbols = list(dict.fromkeys(symbols)) if len(data) != len(symbols): data_symbols = [d["symbol"] for d in data] missing_symbols = [ diff --git a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml index 5b3be161508..5f2bdcb6659 100644 --- a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml +++ b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml @@ -9,58 +9,95 @@ interactions: Connection: - keep-alive method: GET - uri: https://financialmodelingprep.com/api/v3/etf-holder/SILJ?apikey=MOCK_API_KEY + uri: https://financialmodelingprep.com/api/v3/etf-holder/QQQ?apikey=MOCK_API_KEY response: body: string: !!binary | - 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 - C6m1LzmtZOhNpt/+8X8LG5B3LzUAAA== + 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 + O/knEwYUpL+1caA6LPlAwXNoPY |