diff --git a/openbb_platform/core/openbb_core/provider/standard_models/forward_ebitda_estimates.py b/openbb_platform/core/openbb_core/provider/standard_models/forward_ebitda_estimates.py new file mode 100644 index 000000000000..1fb19e8468b4 --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/forward_ebitda_estimates.py @@ -0,0 +1,75 @@ +"""Forward EBITDA Estimates Standard Model.""" + +from datetime import date as dateType +from typing import Optional, Union + +from pydantic import Field, field_validator + +from openbb_core.provider.abstract.data import Data, ForceInt +from openbb_core.provider.abstract.query_params import QueryParams +from openbb_core.provider.utils.descriptions import ( + DATA_DESCRIPTIONS, + QUERY_DESCRIPTIONS, +) + + +class ForwardEbitdaEstimatesQueryParams(QueryParams): + """Forward EBITDA Estimates Query Parameters.""" + + symbol: Optional[str] = Field( + default=None, + description=QUERY_DESCRIPTIONS["symbol"], + ) + + @field_validator("symbol", mode="before", check_fields=False) + @classmethod + def to_upper(cls, v): + """Convert field to uppercase.""" + return v.upper() if v else None + + +class ForwardEbitdaEstimatesData(Data): + """Forward EBITDA Estimates Data.""" + + symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", "")) + name: Optional[str] = Field(default=None, description="Name of the entity.") + last_updated: Optional[dateType] = Field( + default=None, + description="The date of the last update.", + ) + period_ending: Optional[dateType] = Field( + default=None, + description="The end date of the reporting period.", + ) + fiscal_year: Optional[int] = Field( + default=None, description="Fiscal year for the estimate." + ) + fiscal_period: Optional[str] = Field( + default=None, description="Fiscal quarter for the estimate." + ) + calendar_year: Optional[int] = Field( + default=None, description="Calendar year for the estimate." + ) + calendar_period: Optional[Union[int, str]] = Field( + default=None, description="Calendar quarter for the estimate." + ) + low_estimate: Optional[ForceInt] = Field( + default=None, description="The EBITDA estimate low for the period." + ) + high_estimate: Optional[ForceInt] = Field( + default=None, description="The EBITDA estimate high for the period." + ) + mean: Optional[ForceInt] = Field( + default=None, description="The EBITDA estimate mean for the period." + ) + median: Optional[ForceInt] = Field( + default=None, description="The EBITDA estimate median for the period." + ) + standard_deviation: Optional[ForceInt] = Field( + default=None, + description="The EBITDA estimate standard deviation for the period.", + ) + number_of_analysts: Optional[int] = Field( + default=None, + description="Number of analysts providing estimates for the period.", + ) diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py index 7ab3bb95c403..b559acfff9b0 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_api.py +++ b/openbb_platform/extensions/equity/integration/test_equity_api.py @@ -1982,3 +1982,36 @@ def test_equity_estimates_forward_pe(params, headers): result = requests.get(url, headers=headers, timeout=10) assert isinstance(result, requests.Response) assert result.status_code == 200 + + +@parametrize( + "params", + [ + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "quarter", + "provider": "intrinio", + } + ), + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "annual", + "limit": None, + "include_historical": False, + "provider": "fmp", + } + ), + ], +) +@pytest.mark.integration +def test_equity_estimates_forward_ebitda(params, headers): + """Test the equity estimates forward_ebitda endpoint.""" + 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/equity/estimates/forward_ebitda?{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/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py index 344ea98e5d82..e3da7aa2f390 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_python.py +++ b/openbb_platform/extensions/equity/integration/test_equity_python.py @@ -750,6 +750,36 @@ def test_equity_estimates_forward_eps(params, obb): assert len(result.results) > 0 +@parametrize( + "params", + [ + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "quarter", + "provider": "intrinio", + } + ), + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "annual", + "limit": None, + "include_historical": False, + "provider": "fmp", + } + ), + ], +) +@pytest.mark.integration +def test_equity_estimates_forward_ebitda(params, obb): + """Test the equity estimates forward EBITDA endpoint.""" + result = obb.equity.estimates.forward_ebitda(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + + @parametrize( "params", [ diff --git a/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py b/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py index 36aae0761b81..c779c2d8dc5c 100644 --- a/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py +++ b/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py @@ -116,6 +116,36 @@ async def forward_sales( return await OBBject.from_query(Query(**locals())) +@router.command( + model="ForwardEbitdaEstimates", + examples=[ + APIEx(parameters={"provider": "intrinio"}), + APIEx( + parameters={ + "symbol": "AAPL", + "fiscal_period": "annual", + "provider": "intrinio", + } + ), + APIEx( + parameters={ + "symbol": "AAPL,MSFT", + "fiscal_period": "quarter", + "provider": "fmp", + } + ), + ], +) +async def forward_ebitda( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: + """Get forward EBITDA estimates.""" + return await OBBject.from_query(Query(**locals())) + + @router.command( model="ForwardEpsEstimates", examples=[ diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json index ff6e4f6f446d..adf31f04d3b2 100644 --- a/openbb_platform/openbb/assets/reference.json +++ b/openbb_platform/openbb/assets/reference.json @@ -7651,6 +7651,234 @@ }, "model": "ForwardSalesEstimates" }, + "/equity/estimates/forward_ebitda": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get forward EBITDA estimates.", + "examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.estimates.forward_ebitda(provider='intrinio')\nobb.equity.estimates.forward_ebitda(symbol='AAPL', fiscal_period=annual, provider='intrinio')\nobb.equity.estimates.forward_ebitda(symbol='AAPL,MSFT', fiscal_period=quarter, provider='fmp')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "Union[str, List[str]]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): fmp, intrinio.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "provider", + "type": "Literal['fmp', 'intrinio']", + "description": "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.", + "default": "fmp", + "optional": true + } + ], + "fmp": [ + { + "name": "fiscal_period", + "type": "Literal['annual', 'quarter']", + "description": "The future fiscal period to retrieve estimates for.", + "default": "annual", + "optional": true, + "choices": null + }, + { + "name": "limit", + "type": "int", + "description": "The number of data entries to return.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "include_historical", + "type": "bool", + "description": "If True, the data will include all past data and the limit will be ignored.", + "default": false, + "optional": true, + "choices": null + } + ], + "intrinio": [ + { + "name": "fiscal_period", + "type": "Literal['annual', 'quarter']", + "description": "Filter for only full-year or quarterly estimates.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "estimate_type", + "type": "Literal['ebitda', 'ebit', 'enterprise_value', 'cash_flow_per_share', 'pretax_income']", + "description": "Limit the EBITDA estimates to this type.", + "default": null, + "optional": true, + "choices": null + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "List[ForwardEbitdaEstimates]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Optional[Literal['fmp', 'intrinio']]", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[List[Warning_]]", + "description": "List of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "Dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": "", + "optional": false, + "choices": null + }, + { + "name": "name", + "type": "str", + "description": "Name of the entity.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "last_updated", + "type": "date", + "description": "The date of the last update.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "period_ending", + "type": "date", + "description": "The end date of the reporting period.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "fiscal_year", + "type": "int", + "description": "Fiscal year for the estimate.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "fiscal_period", + "type": "str", + "description": "Fiscal quarter for the estimate.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "calendar_year", + "type": "int", + "description": "Calendar year for the estimate.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "calendar_period", + "type": "Union[int, str]", + "description": "Calendar quarter for the estimate.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "low_estimate", + "type": "int", + "description": "The EBITDA estimate low for the period.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "high_estimate", + "type": "int", + "description": "The EBITDA estimate high for the period.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "mean", + "type": "int", + "description": "The EBITDA estimate mean for the period.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "median", + "type": "int", + "description": "The EBITDA estimate median for the period.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "standard_deviation", + "type": "int", + "description": "The EBITDA estimate standard deviation for the period.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "number_of_analysts", + "type": "int", + "description": "Number of analysts providing estimates for the period.", + "default": null, + "optional": true, + "choices": null + } + ], + "fmp": [], + "intrinio": [ + { + "name": "conensus_type", + "type": "Literal['ebitda', 'ebit', 'enterprise_value', 'cash_flow_per_share', 'pretax_income']", + "description": "The type of estimate.", + "default": null, + "optional": true, + "choices": null + } + ] + }, + "model": "ForwardEbitdaEstimates" + }, "/equity/estimates/forward_eps": { "deprecated": { "flag": null, @@ -8052,7 +8280,7 @@ "choices": null }, { - "name": "last_udpated", + "name": "last_updated", "type": "date", "description": "The date the data was last updated.", "default": null, diff --git a/openbb_platform/openbb/package/equity_estimates.py b/openbb_platform/openbb/package/equity_estimates.py index b93d171b6a17..f802e51594b1 100644 --- a/openbb_platform/openbb/package/equity_estimates.py +++ b/openbb_platform/openbb/package/equity_estimates.py @@ -14,6 +14,7 @@ class ROUTER_equity_estimates(Container): """/equity/estimates analyst_search consensus + forward_ebitda forward_eps forward_pe forward_sales @@ -345,6 +346,122 @@ def consensus( ) ) + @exception_handler + @validate + def forward_ebitda( + self, + symbol: Annotated[ + Union[str, None, List[Optional[str]]], + OpenBBField( + description="Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, intrinio." + ), + ] = None, + provider: Annotated[ + Optional[Literal["fmp", "intrinio"]], + OpenBBField( + description="The provider to use for the query, by default None.\n If None, the provider specified in defaults is selected or 'fmp' if there is\n no default." + ), + ] = None, + **kwargs + ) -> OBBject: + """Get forward EBITDA estimates. + + Parameters + ---------- + symbol : Union[str, None, List[Optional[str]]] + Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, intrinio. + provider : Optional[Literal['fmp', 'intrinio']] + 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. + fiscal_period : Optional[Literal['annual', 'quarter']] + The future fiscal period to retrieve estimates for. (provider: fmp); + Filter for only full-year or quarterly estimates. (provider: intrinio) + limit : Optional[int] + The number of data entries to return. (provider: fmp) + include_historical : bool + If True, the data will include all past data and the limit will be ignored. (provider: fmp) + estimate_type : Optional[Literal['ebitda', 'ebit', 'enterprise_value', 'cash_flow_per_share', 'pretax_income']] + Limit the EBITDA estimates to this type. (provider: intrinio) + + Returns + ------- + OBBject + results : List[ForwardEbitdaEstimates] + Serializable results. + provider : Optional[Literal['fmp', 'intrinio']] + Provider name. + warnings : Optional[List[Warning_]] + List of warnings. + chart : Optional[Chart] + Chart object. + extra : Dict[str, Any] + Extra info. + + ForwardEbitdaEstimates + ---------------------- + symbol : str + Symbol representing the entity requested in the data. + name : Optional[str] + Name of the entity. + last_updated : Optional[date] + The date of the last update. + period_ending : Optional[date] + The end date of the reporting period. + fiscal_year : Optional[int] + Fiscal year for the estimate. + fiscal_period : Optional[str] + Fiscal quarter for the estimate. + calendar_year : Optional[int] + Calendar year for the estimate. + calendar_period : Optional[Union[int, str]] + Calendar quarter for the estimate. + low_estimate : Optional[int] + The EBITDA estimate low for the period. + high_estimate : Optional[int] + The EBITDA estimate high for the period. + mean : Optional[int] + The EBITDA estimate mean for the period. + median : Optional[int] + The EBITDA estimate median for the period. + standard_deviation : Optional[int] + The EBITDA estimate standard deviation for the period. + number_of_analysts : Optional[int] + Number of analysts providing estimates for the period. + conensus_type : Optional[Literal['ebitda', 'ebit', 'enterprise_value', 'cash_flow_per_share', 'pretax_income']] + The type of estimate. (provider: intrinio) + + Examples + -------- + >>> from openbb import obb + >>> obb.equity.estimates.forward_ebitda(provider='intrinio') + >>> obb.equity.estimates.forward_ebitda(symbol='AAPL', fiscal_period='annual', provider='intrinio') + >>> obb.equity.estimates.forward_ebitda(symbol='AAPL,MSFT', fiscal_period='quarter', provider='fmp') + """ # noqa: E501 + + return self._run( + "/equity/estimates/forward_ebitda", + **filter_inputs( + provider_choices={ + "provider": self._get_provider( + provider, + "/equity/estimates/forward_ebitda", + ("fmp", "intrinio"), + ) + }, + standard_params={ + "symbol": symbol, + }, + extra_params=kwargs, + info={ + "symbol": { + "fmp": {"multiple_items_allowed": True}, + "intrinio": {"multiple_items_allowed": True}, + } + }, + ) + ) + @exception_handler @validate def forward_eps( @@ -532,7 +649,7 @@ def forward_pe( Estimated Forward PEG ratio for the next fiscal year. (provider: intrinio) eps_ttm : Optional[float] The latest trailing twelve months earnings per share. (provider: intrinio) - last_udpated : Optional[date] + last_updated : Optional[date] The date the data was last updated. (provider: intrinio) Examples diff --git a/openbb_platform/providers/fmp/openbb_fmp/__init__.py b/openbb_platform/providers/fmp/openbb_fmp/__init__.py index cb98280528ac..035339d49333 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/__init__.py +++ b/openbb_platform/providers/fmp/openbb_fmp/__init__.py @@ -39,6 +39,7 @@ from openbb_fmp.models.etf_sectors import FMPEtfSectorsFetcher from openbb_fmp.models.executive_compensation import FMPExecutiveCompensationFetcher from openbb_fmp.models.financial_ratios import FMPFinancialRatiosFetcher +from openbb_fmp.models.forward_ebitda_estimates import FMPForwardEbitdaEstimatesFetcher from openbb_fmp.models.forward_eps_estimates import FMPForwardEpsEstimatesFetcher from openbb_fmp.models.historical_dividends import FMPHistoricalDividendsFetcher from openbb_fmp.models.historical_employees import FMPHistoricalEmployeesFetcher @@ -107,6 +108,7 @@ "EtfSectors": FMPEtfSectorsFetcher, "ExecutiveCompensation": FMPExecutiveCompensationFetcher, "FinancialRatios": FMPFinancialRatiosFetcher, + "ForwardEbitdaEstimates": FMPForwardEbitdaEstimatesFetcher, "ForwardEpsEstimates": FMPForwardEpsEstimatesFetcher, "HistoricalDividends": FMPHistoricalDividendsFetcher, "HistoricalEmployees": FMPHistoricalEmployeesFetcher, diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/forward_ebitda_estimates.py b/openbb_platform/providers/fmp/openbb_fmp/models/forward_ebitda_estimates.py new file mode 100644 index 000000000000..3da4fe24d119 --- /dev/null +++ b/openbb_platform/providers/fmp/openbb_fmp/models/forward_ebitda_estimates.py @@ -0,0 +1,150 @@ +"""FMP Forward EBITDA Model.""" + +# pylint: disable=unused-argument + +import asyncio +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional +from warnings import warn + +from openbb_core.provider.abstract.fetcher import Fetcher +from openbb_core.provider.standard_models.forward_ebitda_estimates import ( + ForwardEbitdaEstimatesData, + ForwardEbitdaEstimatesQueryParams, +) +from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS +from openbb_core.provider.utils.errors import EmptyDataError +from openbb_core.provider.utils.helpers import amake_request +from openbb_fmp.utils.helpers import create_url, response_callback +from pydantic import Field, field_validator + + +class FMPForwardEbitdaEstimatesQueryParams(ForwardEbitdaEstimatesQueryParams): + """FMP Forward EBITDA Query. + + Source: https://site.financialmodelingprep.com/developer/docs/analyst-estimates-api/ + """ + + __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}} + + __alias_dict__ = {"fiscal_period": "period"} + + fiscal_period: Literal["annual", "quarter"] = Field( + default="annual", + description="The future fiscal period to retrieve estimates for.", + ) + limit: Optional[int] = Field( + default=None, description=QUERY_DESCRIPTIONS.get("limit", "") + ) + include_historical: bool = Field( + default=False, + description="If True, the data will include all past data and the limit will be ignored.", + ) + + @field_validator("symbol", mode="before", check_fields=False) + @classmethod + def check_symbol(cls, value): + """Check the symbol.""" + if not value: + raise RuntimeError("Error: Symbol is a required field for FMP.") + return value + + +class FMPForwardEbitdaEstimatesData(ForwardEbitdaEstimatesData): + """FMP Forward EBITDA Data.""" + + __alias_dict__ = { + "period_ending": "date", + "high_estimate": "estimatedEbitdaHigh", + "low_estimate": "estimatedEbitdaLow", + "mean": "estimatedEbitdaAvg", + } + + +class FMPForwardEbitdaEstimatesFetcher( + Fetcher[ + FMPForwardEbitdaEstimatesQueryParams, + List[FMPForwardEbitdaEstimatesData], + ] +): + """FMP Forward EBITDA Estimates Fetcher.""" + + @staticmethod + def transform_query(params: Dict[str, Any]) -> FMPForwardEbitdaEstimatesQueryParams: + """Transform the query params.""" + return FMPForwardEbitdaEstimatesQueryParams(**params) + + @staticmethod + async def aextract_data( + query: FMPForwardEbitdaEstimatesQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> List[Dict]: + """Return the raw data from the FMP endpoint.""" + api_key = credentials.get("fmp_api_key") if credentials else "" + symbols = query.symbol.split(",") # type: ignore + results: List[Dict] = [] + + async def get_one(symbol): + """Get data for one symbol.""" + url = create_url( + 3, f"analyst-estimates/{symbol}", api_key, query, ["symbol"] + ) + result = await amake_request( + url, response_callback=response_callback, **kwargs + ) + if not result or len(result) == 0: + warn(f"Symbol Error: No data found for {symbol}") + if result: + results.extend(result) + + await asyncio.gather(*[get_one(symbol) for symbol in symbols]) + + if not results: + raise EmptyDataError("No data returned for the given symbols.") + + return results + + @staticmethod + def transform_data( + query: FMPForwardEbitdaEstimatesQueryParams, data: List[Dict], **kwargs: Any + ) -> List[FMPForwardEbitdaEstimatesData]: + """Return the transformed data.""" + + symbols = query.symbol.split(",") if query.symbol else [] + cols = [ + "symbol", + "date", + "estimatedEbitdaAvg", + "estimatedEbitdaHigh", + "estimatedEbitdaLow", + ] + year = datetime.now().year + results: List[FMPForwardEbitdaEstimatesData] = [] + for item in sorted( + data, + key=lambda item: ( # type: ignore + ( + symbols.index(item.get("symbol")) if item.get("symbol") in symbols else len(symbols), # type: ignore + item.get("date"), + ) + if symbols + else item.get("date") + ), + ): + temp: Dict[str, Any] = {} + for col in cols: + temp[col] = item.get(col) + + if ( + query.include_historical is False + and datetime.strptime(temp["date"], "%Y-%m-%d").year < year + ): + continue + results.append(FMPForwardEbitdaEstimatesData.model_validate(temp)) + + return ( + results[: query.limit] + if query.limit and query.include_historical is False + else results + ) diff --git a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_equity_forward_ebitda_fetcher.yaml b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_equity_forward_ebitda_fetcher.yaml new file mode 100644 index 000000000000..431e316e11f4 --- /dev/null +++ b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_equity_forward_ebitda_fetcher.yaml @@ -0,0 +1,269 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + method: GET + uri: https://financialmodelingprep.com/api/v3/analyst-estimates/MSFT?apikey=MOCK_API_KEY&include_historical=False&period=annual + response: + body: + string: !!binary | + H4sIAAAAAAAAA8WcSa8lx3GF9/oVDa6lQkZGjt5pQcMGbC8s7wwvSLFBExCbgrslWzD8333OfTXc + VxGVWdcCzQbJBbtws4b4Ik4Mmf/6qw8f/hv/fvjw1ee//PjtT3/46m8+fPWPv/vbf/nq12//97tv + vnzk/4shtt+E8hsN2998/Pzlhx/xt9/988c/f/z0p4//8NN/4kLt0luWHkPIFxf+3Q/f/zuuTJJC + kBpyyuHiyt/++XteGDR3yYF/zhd+/e0PX7775m1p6aFGiSlnrf5168oxSFJNsXYp/oVvC0svWpri + gap4163LliD4Na09RO+qdVEpXWItIUlxL1uXLKnV1LQk+6z/9PHL33/6/U8/rm9assRei2hP15fu + i8eUtPUco3mS/dr1DnKVqhnfz176u++/+fq//vjx0+f1Foq0hicvLbfrS9dbKBlfMcYcurGL49q3 + WyhacReppGzf1B8/v10TZYFZOH+9fWNZavKveLv3GJaYWtme8dOffvz243/89tM3f/jL5y9fn+yQ + lzfvws9fP/0uX17FRf/z6xegqregKtph/ElUzIt+D5U2WHaXkIr0IVSK91tTxT/FvqNnqCoILQpI + kw6hklaBc4hZ1LXvHaoWY28N9lhd+NZl8WnAldTkI7otmrFobyVLce9uM2kNMNWApc3rO0OlKg9S + uvUhBqoUe1bVWLJ512eoFG4kq8RmP+AZKlh+6rnxDcyhCqUWOJ8q5vHPUD1caAmtJXMHO1TSliwi + A6ykLzRC6183rKQuTbrcxkq9Cw1W+VWsyi2sAmDJYDDZRz5hBah6rfDfJY2xijFoa50OdIhVagF+ + vtQWjO28x6rAZVa88n7F6WbjpbRWY4zF/cF12ZhgrgXu1Q+R26KIP/gDW7Ch+QkrmHTueNxu7eGM + VegFuDDgm/dnsIoldgXQpRsAzlgJ/FLpocFJzbDCC0+5J5i3uQODVY41ApQKGTPFKrQEqOChzc8e + WOWlROn2uxxYlQXqwbrsA6u0lO0lz5jSW6FK9VWm8h2mgIgmBCs4vCtStgjdKsRVhGFYr/yOqdhC + jlop2MZMKUy2RdASJ6GKMNPGWrwQipuBQ431hIhQ3R9cl8X9d8RdasURU1JzTAKBmt2Itlk0jLRr + UCw8YQq2nCpkWhBjpAap0AQqdozf2w102GHOUJVtTpSWWCEV4VSmREE+SBXogz742VXup9gKvGNJ + 1+pPdFHcpReG9uC8pEGUisv2EaY0udgZmtKrNKVbNCWI5oaPUaxJn2iC3dAP4eqx8MOFKZXSITGG + NDUEpwKNVtuFnNthUlgVIgrsewgTjBCpWc+9DgJUVWQ0FY63uDe3sxRgHSXXnqzX3VlCthGQ70BM + XQOyrgrecoHUtCZjUcpZAHsEexOUWu0a4bqCfZYzSvwoeBYIz3kiBTcDP1JKbtNECrKXKOXkCrbt + u0CwVccnHSjFxQrcAyW5j5J74V8fmPQWSgJlJh0uXczLOKEUkVLDwvohYy9QEqj6EKaFiYbsI/UK + Weha64FSDLDZhKjouL5nlAK8I7SeFptNHCgVJm6QhQixI5RCyC1FhE6rtg6UmiKuSocTN27ohFIR + uI3a+WeGUkeumGV86dvykHdwCO57NiRFlcpbGCVau6pn9guNWwfXrp+6to6byM1VcdtnWSADnaz2 + ICksVRy5s8PUlw79dDd50p8peYp3eII2hlZAKBGbmJx4ohgMARZkU893PLHQFhJeszqR/Ymnzvy8 + U5GOQxMArtrhAsOwzNeROQXIRjB/TRPoTSGCzOCWAtcVoWcLEmwp3X2AFSaWNRCBgy0EnmHiVVCX + fVC3WFeuUhDEkLXYfPIEU8GvBYWnsQrzDBMS5FIp6MMNmKJUJIMIpYOfXcMShHVnIWJQiuhLrMOU + qS/Q+tdhqS1wL37AcUiSWyJPyqskyS2S4KV7gpgBJWOSWC0v0BOp2JLve5JKiciWWvJKNU8k5ZB6 + QzSJfozYSaq5gksquIsItpqWyqPCG/zqwpo2BOZ8ChkzKkEUuJbEJoJfTdySb9gbAo5Yck8kaVLa + kzqV6jNJGbE6wJTrIANbM5UIyQ04dZQAbd8YurY3RLxBRWHzl5mVRSrheVhCSIISbZKsV9pIqgt+ + qAyiUlsCdKpXvlhd3wL9GO8GpT3ujlFqr6IUbqGkNeeI79xtontCKUHG51Rh+lfha0UpMYMAcRee + f0MpsVSA/EsndfLCQkZEsmbv8BklSDfeHyLYIFvCWgo33hx38IQSDRq3Vbuj4neUYHHwQxHJ3BQl + eAFoY+22GnNGCWkSAnqHkZp3fEIJYOZWE8TvoJC9oYQvIU2yfWiLUioB6WRUGxUtSpl5LKKjV/5e + P8pScrZ534FSXlpCbL9EKS8phz2hnqG0d6+GKMVXUZJ+CyVkzoghiAxWppxQihA80BPSbEb6HqUI + W9ASYnHc0RNKpDfDXVenIPqMEiyGmgPByU2DNpQApgqUe0kDlCDtKmVqtxn4E0op5hLYmfSLIqst + w1uwflxt9/GEUnyEODaipyU8LQVoInmdRiUNGTE4K3zHDZTwDiHPrWfwUIK4hPmPZOOOUlWFnrFx + cUcpQb7VUVRKS006qIinJcFCXUI8lO6Q9GoNT25NRLBWJcgLkWVPCg9IDaGim/TrK7dMs8PE8Lv5 + aoBhta8KA8sducHFZdu7Ftxg67nmixC3WlZSKAX25UctphYZCLs0N75t1oSPmxBtqt8EXu0otiL4 + J9lK5QmkRNGJJcO8fhcTREIoxfqMc/kucbaiRxvPDUaJBfaQnXaCxQivOsoj1s0wEmRe7KZH61Z3 + jHRpWmyZ6sBIF6S3jpVsGOmCXFVcPP6vGMVXMbo1A9EVkTchxKSrILMlho1FHbzfNq7eIa5BX1UN + dZgiRSbzFV/3qvm0579w+5lV2WEZHGEtRtEWbSXtiaFUqdciIsmIoSjhEXRt2ngwJMwBEws1s2ID + nA/yk4oIN2/RNjh4pFLNSp1zhxYSG8+KlzcoVm/anREaDzVKe7ZHhwNkuEaKP6cosQyK936dIiky + IHVS2yeKRMugbheX54/1y1B0a+ShdZZ+OFAwEXXU9yweI4qPKYIvQ5gu0S8f771ZXAX9kKH9hhRF + RITA/spV12n9qL0FhcRpPiBb4NWQHv1Jl8i9psLefe5N3di3rsd2dug1DaLG26IcnICr0hsQRawL + fGsxC597sln7o+4zj0R4FuRnyPTms0NI+VPHLajtSliGOHtWpNvZvZ2huJTSrOQ8GIpLzd0WB58Y + yhxfucnQrdTo1Xqd3Bpx6AGapLCfMxnG65yO6WyMjluySM5LYd3bHyDYI1GBWT283iQSAY4sHC8a + qznFZ4W/7X5dY4t+wgjZUh4V65SPWKBXfM23GkjBUyrnnq7tfUtLWCFH1marWmeIoOZqiazKzyDi + 0G9pj/bUlKKSa+kiMqoa7DfQ+FnwZ9qNjRxPhidpg24sIOA804giiAyn7XdQdMxVzhj6eeLQrcEG + KFvwQw89U3OZ/W7OeY1TotrfXH27Mvot487sASOTvkBtT7drhWjiQOuQoUDJEkJwJPrBECiDu0Ba + PhplVeifkJhfD1pHsTB14ODddOLu0Spgi2Ra8I45t6SNY6czhpCNBCYkdV7wzqWBIFbV5gxphup7 + zPzMGOJgCF+UEwufGRqU6Kj1BvTsznAGz62O0avj4HJrlAFqmk1NjnaM4Wm5hNhFg40Y7+ERBY/4 + RX/ccxdxXDTD0/dxiZsbK2JTjguM4IkaC6csih3IeBJxmY0xBKnh5oqoCsKSZufbHiIOLgICrjhJ + g0mFKM6kOgVNmwpxoKT3NO27StaWQm7OkJEtKMDlxVhqHBQJ9iwwqnC0bz4OhLSNGgLR8rouh/gx + yIOQ51zDI0t3VZkDT/euO8PTX4Xn1twCtDTzZvx7VR7Y4AmIJXhbYs3lHTylIkzEChkyriNwVEg4 + THoBxeafUkGWxIm6YeRhfRnKsnV/1S3yCDPq3mx9+Tny4G3ANNm8uoYnZqHQQbJ0nfJvkUcrImJO + NlkwkaeEnDkZYmtsJvLgfWS6venQglAkK9Bpg3Rpz4HyOgB1JwfqwsF5J+V8gmfEjtNaeGLnbubj + 760wDVZ5lZ5bswqpcvBGOV4wpqciRefkYdcxPbl3+Haou6sezpaHRMj8VoNfNjvoEe69YP5z0Tra + dBQWRMrvdSCO0NMYZ1uI/gjC9llbKOyfqzPMctCjSNugOqMdfz7Tw0EQpXSbFhAiiESCCek2m0Tl + nqskkNLz3UcSoa7YrrA1Y0MP4gkiCjXrvArHGdQa2Du4pEcWNzHa8XEbsxs+yQ0pHj63sh55dQhV + bs0nPKQCi/q20fIen4KMoYdYw6QTlPFhu7DbcmHve0abOPWlV5cd2awkanY/kd/xCaVCK6Y4mvMR + trqF1wzxYf6OvCKPhhOk8Y2B2Hnw4fAgqzODXGaz3gId1MGZHa0/44OIEmoVGe3IW9fn8HrmJrB5 + 8MGT54ooNUyR1juAHEgp8GYH+DiTJ8fo6eLOLWz47MMovxA+4dZMQuqJgZ2zOGN8uKMvQ88UW1t9 + r93AIxVxuGp8ru+nV7o55L2TxKdD3jfuLBonPpB4uXTYqkvGJqNoHFW7nx3tCkozluMOuGt8IrJm + DmionUo946PI2mFlzWYIJvqEzOYTTH3WSH1s/MkFDmOODwdE4DWy3UviJD4ctMB/bandJj4Fehma + wOnhPUWfwTQCos8o8fmlo0+4NYmQNPQKsxIrok/RJz1qtfgK44EeZLSVxpX8Iw0OfERi7wh949Qn + tkLHXS5mcI66AeJdRC5vxdQTPjyUoEBw+PXAbUUy3UN1zjp4xodbCjO1zgwfeAlF0HZK6k7dAO8j + 4CVPow98RcmtOfmljT4NOSZnOW6It8r9+jVl+4Kc6MPZOLiFwR7XxekQPEUfR3gc+MS7dQM/TBl8 + Xh3kCbcmENil4d7SaPX2e3x4eEbojan3EJ+kqghU6bLIvDnlilAG9X5Vnds9cqmPzaNXPdbNHUfA + 2Nl6HOCDLL4V1eiM4B/4QMGECmK7Hz436+Fb69zIPsGnNQ7x9GJ34xt68ObYm06D31xXxx9EiO7s + xzf0vKWhsY9qDNsNZM5U15RsmmboUc7Xw884BZ2DnkHjVBYnAd3gCUdVYQrPraqbvFp2C7cGD+BA + G8RWcson7+FJPMUA4sgZ/H8PD94qVFnsV/PWm6LAD1Jn+5XhAx6hkSJmjPcKIROB2IqSbd/qGZ6m + 8sgXRpmPvB2+UqptpjzDw41nAuU2GzwQjtVqLmK3chh68sOdJLHF3DM9kTv0OWY4nyUNLXA+q9jY + Z+kp+Bx83/ajWHpygJwOdXAWEALIoO72pM48fPpd6XZrX8Ors9jh1sRB5LZQ7tq26d8JHpaZuLn7 + cmb77Y0pkp7ADGkSeTgsDnr6VS16FzMhQt6B3IsItSkZWggiqK8D39bkNsvOerTL67Ygt2tD0V/U + 7zbjReREjt8sqyd2gGGCkTkHcBh0hBWSwoMrJuh0kM2AZoPxmRy8N8EjVxvLLDjpcXoStx1NwWFX + lXsv7Ezsc9gZguOUd57Audsp/Zl2BIVbgwYMO5wTr9YJvEcHqQz8NTNF88HeoxNjyY8rhwVruCy2 + hVL2d4I+ne+T4TFbaVc91c0NUtlnPMYg7lSm40xpRlOjjErK6XxnnHFHB4EwVu7qmBUM4JIgl4oz + knQmB/kfT55Idj/TCRzETu7Vdw7AO4PDXi7CXr+T7UB7KKS2s/wZnN5iLay8XE8YDMXaUKvdRSZ4 + FxpkwqvI3BoviEgkWBTTy9bNhozCVTd3W/47ZGLljChnpIYdUgk0S57RNC5SA4NQuMXvatPq5gAf + UROOdTAjyjMIlEW2oVJT9i4eAxfXyAhSgkQVOW3xFNw/YlIdHG+1MdMilFIrdkLzzAzHwAOLlDNm + EA2L8tjGeY5TH1PWLY42B70tXwJ3y3K74iUyYXE2Mu7UhMWdLN2w2Yd9ptzcGgt9+USEcGuyQHhq + WeGmlkmKEyHpOSQcJtXpmBi4KnfWjbgBXfCDPIhwjA0MHXeI1H9YXeuJ/s+bZzyoYc+C20X9Aty2 + HqyxcZ+LPxu3UhMU4QjZuZqbOlHDzWAxevd1pqbAaFkwmR4rwjQoqPfiDDU8+I/T5HOJVgUREZ9t + tNF7XZ5HMCGuO6fCPFHjcXFQM4LmJjO3qgIvFwVuTRMIvAscoWRbfj0RU6qGxobIOK+B/GF9m62y + ETHcaAcr4cEfY2QKt4hLz1dbv7cknYObPFFryAzH2ORigGFnJnOMrVycHrkxw8OEeNLMTJ3xGF28 + snbj0MVQeTxvirZkeQ41HelPUGfA4gyNUnG5x7QZaHLjRix1iuFnaFLH1xXcw3Un5/8p1Lh8/dVH + hoRbcwRUstwA6uzWP4HDIf/A40XG4CB2BR7ydNFHPCSaNJaD6qQRKkxCcFXxj3E7JFplwxGIDeYI + KmDgcQnOZ3vOaiLn9XL006gttEGnaK12wvGs0HjoDg8umLZx+uMIWZ6XPcGmZi3CGsO0icNyceKc + 8xybRE0au3Nw0Rmbx+hp5DbZATZOF+8JGzcSbdjcboD+DNGGkuYWNCzaR8nO1Mp7aB7nnz/23Y5L + AfBEPMwANjCEBi6Y59iov8HsYCZwJ0H1dMs7fcYT5yb6LHd2Uq8U4bpeY+qmV3JwtdrU1N/cYtQZ + TxX2fsyoM0588imvg9cqj1J+U3HTmTXoVQ5dOPmqJQafP7na0BCDb/t4dG8qYCNmNCsd/G0IGzG3 + z6b6eYi5NTDQS+Vus+v8fjNdhoTUpoUAHnBY+/S8RJ5e0dxfewcM8h4eKnd52ZqbKnG+umq13pzi + 2wGDA14YqCAsLy5abRYvrLrPd+KFR6b4K5554WFB/rInXriJJrprG15yv3gSwwtHLYp7n4YXcBX8 + 93zw4h6SvfPiHmK18bIHzhkvr7Q5f/Vv/wuKM2cwAWUAAA== + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Headers: + - X-Requested-With, content-type, auth-token, Authorization, stripe-signature, + APPS, publicauthkey, privateauthkey + Access-Control-Allow-Methods: + - GET, POST, OPTIONS + Access-Control-Allow-Origin: + - '*' + Access-Control-Max-Age: + - '3600' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 18 May 2024 06:36:27 GMT + Etag: + - W/"6501-tgbQe675fOPLMNTi82fw5XfTi/g" + Server: + - nginx/1.18.0 (Ubuntu) + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Frame-Options: + - SAMEORIGIN + X-Powered-By: + - Express + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + method: GET + uri: https://financialmodelingprep.com/api/v3/analyst-estimates/AAPL?apikey=MOCK_API_KEY&include_historical=False&period=annual + response: + body: + string: !!binary | + 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 + XjF7ekCWPXLC+bu//x+U/72V31QAAA== + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Headers: + - X-Requested-With, content-type, auth-token, Authorization, stripe-signature, + APPS, publicauthkey, privateauthkey + Access-Control-Allow-Methods: + - GET, POST, OPTIONS + Access-Control-Allow-Origin: + - '*' + Access-Control-Max-Age: + - '3600' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 18 May 2024 06:36:27 GMT + Etag: + - W/"54df-i7UETG7tCzXGSInEE6WsN42Qy9A" + Server: + - nginx/1.18.0 (Ubuntu) + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Frame-Options: + - SAMEORIGIN + X-Powered-By: + - Express + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/fmp/tests/test_fmp_fetchers.py b/openbb_platform/providers/fmp/tests/test_fmp_fetchers.py index 7980ab86344c..80d540a9ade2 100644 --- a/openbb_platform/providers/fmp/tests/test_fmp_fetchers.py +++ b/openbb_platform/providers/fmp/tests/test_fmp_fetchers.py @@ -43,6 +43,7 @@ from openbb_fmp.models.etf_sectors import FMPEtfSectorsFetcher from openbb_fmp.models.executive_compensation import FMPExecutiveCompensationFetcher from openbb_fmp.models.financial_ratios import FMPFinancialRatiosFetcher +from openbb_fmp.models.forward_ebitda_estimates import FMPForwardEbitdaEstimatesFetcher from openbb_fmp.models.forward_eps_estimates import FMPForwardEpsEstimatesFetcher from openbb_fmp.models.historical_dividends import FMPHistoricalDividendsFetcher from openbb_fmp.models.historical_employees import FMPHistoricalEmployeesFetcher @@ -735,3 +736,18 @@ def test_fmp_equity_forward_eps_fetcher(credentials=test_credentials): fetcher = FMPForwardEpsEstimatesFetcher() result = fetcher.test(params, credentials) assert result is None + + +@pytest.mark.record_http +def test_fmp_equity_forward_ebitda_fetcher(credentials=test_credentials): + """Test FMP forward EBITDA estimates fetcher.""" + params = { + "symbol": "MSFT,AAPL", + "fiscal_period": "annual", + "include_historical": False, + "limit": None, + } + + fetcher = FMPForwardEbitdaEstimatesFetcher() + result = fetcher.test(params, credentials) + assert result is None diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py b/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py index 370069557f00..7c0cf34a9ce1 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py @@ -18,6 +18,9 @@ ) from openbb_intrinio.models.etf_search import IntrinioEtfSearchFetcher from openbb_intrinio.models.financial_ratios import IntrinioFinancialRatiosFetcher +from openbb_intrinio.models.forward_ebitda_estimates import ( + IntrinioForwardEbitdaEstimatesFetcher, +) from openbb_intrinio.models.forward_eps_estimates import ( IntrinioForwardEpsEstimatesFetcher, ) @@ -79,6 +82,7 @@ "EtfPricePerformance": IntrinioEtfPricePerformanceFetcher, "EtfSearch": IntrinioEtfSearchFetcher, "FinancialRatios": IntrinioFinancialRatiosFetcher, + "ForwardEbitdaEstimates": IntrinioForwardEbitdaEstimatesFetcher, "ForwardEpsEstimates": IntrinioForwardEpsEstimatesFetcher, "ForwardPeEstimates": IntrinioForwardPeEstimatesFetcher, "ForwardSalesEstimates": IntrinioForwardSalesEstimatesFetcher, diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_ebitda_estimates.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_ebitda_estimates.py new file mode 100644 index 000000000000..d3fa255b00c0 --- /dev/null +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_ebitda_estimates.py @@ -0,0 +1,194 @@ +"""Intrinio Forward EBITDA Estimates Model.""" + +# pylint: disable=unused-argument + +import asyncio +from typing import Any, Dict, List, Literal, Optional +from warnings import warn + +from openbb_core.provider.abstract.fetcher import Fetcher +from openbb_core.provider.standard_models.forward_ebitda_estimates import ( + ForwardEbitdaEstimatesData, + ForwardEbitdaEstimatesQueryParams, +) +from openbb_core.provider.utils.errors import EmptyDataError +from openbb_core.provider.utils.helpers import ( + amake_request, + get_querystring, +) +from openbb_intrinio.utils.helpers import response_callback +from pydantic import Field + + +class IntrinioForwardEbitdaEstimatesQueryParams(ForwardEbitdaEstimatesQueryParams): + """Intrinio Forward EBITDA Estimates Query. + + https://docs.intrinio.com/documentation/web_api/get_zacks_sales_estimates_v2 + """ + + __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}} + __alias_dict__ = {"estimate_type": "type"} + + fiscal_period: Optional[Literal["annual", "quarter"]] = Field( + default=None, description="Filter for only full-year or quarterly estimates." + ) + estimate_type: Optional[ + Literal[ + "ebitda", + "ebit", + "enterprise_value", + "cash_flow_per_share", + "pretax_income", + ] + ] = Field( + default=None, + description="Limit the EBITDA estimates to this type.", + ) + + +class IntrinioForwardEbitdaEstimatesData(ForwardEbitdaEstimatesData): + """Intrinio Forward EBITDA Estimates Data.""" + + __alias_dict__ = { + "last_updated": "updated_date", + "symbol": "ticker", + "calendar_period": "estimate_month", + "name": "company_name", + "fiscal_year": "estimate_year", + "fiscal_period": "period", + "low_estimate": "low", + "high_estimate": "high", + "number_of_analysts": "estimate_count", + "standard_deviation": "std_dev", + } + + conensus_type: Optional[ + Literal[ + "ebitda", + "ebitda", + "ebit", + "enterprise_value", + "cash_flow_per_share", + "pretax_income", + ] + ] = Field( + default=None, + description="The type of estimate.", + ) + + +class IntrinioForwardEbitdaEstimatesFetcher( + Fetcher[ + IntrinioForwardEbitdaEstimatesQueryParams, + List[IntrinioForwardEbitdaEstimatesData], + ] +): + """Intrinio Forward EBITDA Estimates Fetcher.""" + + @staticmethod + def transform_query( + params: Dict[str, Any] + ) -> IntrinioForwardEbitdaEstimatesQueryParams: + """Transform the query params.""" + return IntrinioForwardEbitdaEstimatesQueryParams(**params) + + @staticmethod + async def aextract_data( + query: IntrinioForwardEbitdaEstimatesQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> List[Dict]: + """Return the raw data from the Intrinio endpoint.""" + api_key = credentials.get("intrinio_api_key") if credentials else "" + BASE_URL = ( + "https://api-v2.intrinio.com/zacks/ebitda_consensus?" + + f"page_size=10000&api_key={api_key}" + ) + symbols = query.symbol.split(",") if query.symbol else None + query_str = get_querystring(query.model_dump(by_alias=True), ["symbol"]) + results: List[Dict] = [] + + async def get_one(symbol): + """Get the data for one symbol.""" + url = f"{BASE_URL}&identifier={symbol}" + url = url + f"&{query_str}" if query_str else url + data = await amake_request( + url, response_callback=response_callback, **kwargs + ) + consensus = ( + data.get("ebitda_consensus") + if isinstance(data, dict) and "ebitda_consensus" in data + else [] + ) + if not data or not consensus: + warn(f"Symbol Error: No data found for {symbol}") + if consensus: + results.extend(consensus) + + if symbols: + await asyncio.gather(*[get_one(symbol) for symbol in symbols]) + if not results: + raise EmptyDataError(f"No results were found. -> {query.symbol}") + return results + + async def fetch_callback(response, session): + """Use callback for pagination.""" + data = await response.json() + messages = data.get("messages") + if messages: + raise RuntimeError(str(messages)) + estimates = data.get("ebitda_consensus", []) # type: ignore + if estimates and len(estimates) > 0: + results.extend(estimates) + while data.get("next_page"): # type: ignore + next_page = data["next_page"] # type: ignore + next_url = f"{url}&next_page={next_page}" + data = await amake_request(next_url, session=session, **kwargs) + consensus = ( + data.get("ebitda_consensus") + if isinstance(data, dict) and "ebitda_consensus" in data + else [] + ) + if consensus: + results.extend(consensus) # type: ignore + return results + + url = f"{BASE_URL}&{query_str}" if query_str else BASE_URL + + results = await amake_request(url, response_callback=fetch_callback, **kwargs) # type: ignore + + if not results: + raise EmptyDataError("The request was successful but was returned empty.") + + return results + + @staticmethod + def transform_data( + query: IntrinioForwardEbitdaEstimatesQueryParams, + data: List[Dict], + **kwargs: Any, + ) -> List[IntrinioForwardEbitdaEstimatesData]: + """Transform the raw data into the standard format.""" + if not data: + raise EmptyDataError() + results: List[IntrinioForwardEbitdaEstimatesData] = [] + fiscal_period = None + if query.fiscal_period is not None: + fiscal_period = "fy" if query.fiscal_period == "annual" else "fq" + for item in data: + estimate_count = item.get("estimate_count") + if ( + not estimate_count + or estimate_count == 0 + or not item.get("updated_date") + ): + continue + if fiscal_period and item.get("period") != fiscal_period: + continue + results.append(IntrinioForwardEbitdaEstimatesData.model_validate(item)) + if not results: + raise EmptyDataError() + + return sorted( + results, key=lambda x: (x.fiscal_year, x.last_updated), reverse=True + ) diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_pe_estimates.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_pe_estimates.py index 552db46a76ff..0791c02e9161 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_pe_estimates.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/forward_pe_estimates.py @@ -40,7 +40,7 @@ class IntrinioForwardPeEstimatesData(ForwardPeEstimatesData): "year5": "forward_pe_year5", "peg_ratio_year1": "forward_peg_ratio_year1", "eps_ttm": "latest_ttm_eps", - "last_udpated": "updated_date", + "last_updated": "updated_date", } peg_ratio_year1: Optional[float] = Field( @@ -51,7 +51,7 @@ class IntrinioForwardPeEstimatesData(ForwardPeEstimatesData): default=None, description="The latest trailing twelve months earnings per share.", ) - last_udpated: Optional[dateType] = Field( + last_updated: Optional[dateType] = Field( default=None, description="The date the data was last updated.", ) diff --git a/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_forward_ebitda_fetcher.yaml b/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_forward_ebitda_fetcher.yaml new file mode 100644 index 000000000000..06e34360bc7a --- /dev/null +++ b/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_forward_ebitda_fetcher.yaml @@ -0,0 +1,109 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + method: GET + uri: https://api-v2.intrinio.com/zacks/ebitda_consensus?api_key=MOCK_API_KEY&identifier=AAPL&page_size=10000 + response: + body: + string: !!binary | + H4sIAEpCSGYAA9ybXW/iVhCG/0rlayKdmTmfuVtVvai0qva+qiw38SaowVBwthut9r93DLbxOcDi + 9UcMRFGEHQN55sy878wx+Ralf8/zxyR+WGabNNu8bqL7P79F+fzhn3Qd3UcfPnz6GM2ih+VilWRv + cZYs0uLsp08ff/vl9z9+5V+lm3y+SPI0fksTfgYKpMbJxTLLn6N7wFm0Stfz5SM/+/O/21cs3y/O + 31bFa6ZZnq5X6/kmjb8kL69p86Uflq9ZHt2LWfS6euQTj3HxM7rPXl9eZtHz/Om5evyy/K96uEiT + rHq8yfkp6Zfd4ffZNIC7QC+S9dM8uzm6h2TzHH/mPzDmC+PNc7K+shV0TcS3Y4irdZonX+N5xm91 + yXDqEI5uqfzkrevLEUB343z6xvluqgA7qefN87G93yLTlbYsbeEumKqTj19XH9bNyq+e8bybXz3i + eUO/esRbK8ZOKnpdiJ0k9dzcp0O4qMiXO6HuhI4qSALjSGy/SlY0xqEqz+yQ0RmDBnananJQZNAW + V06kt+cCYNoEQDKCRt0IAClLRuhmAKRwhOCCACAZZ7QcKQDnxXgQfrSCv/0EKC6TXgIYbdBhwE+C + QI+1/ueFehh8WaV/jU/aGGM9fAnOKhngS2W0pSHwj5R//20f2wYfiBwQNvkBBVkJngAA54QjK8IE + sILDB+9RACOGADkIfgoAWCNRBCEQCOjCEBgnlRhEAjolQYvpFVqVgdZCBjrgtl9+FILr3rcjHTcC + Rrudm52JgH/dBfQBJyb8VvJHYGVo/5a133M/ZPFDqUL7J6NI43T2f4K7VdFzDmvwuNn1HUrf9QEU + kg1dn0MB2xKYaEO1BzZaRBcsNyoFQYYbkurA7MlaOZ3Z96KWzh54PJZ9797jEcmZ0OMZ2QxC3U3e + e1Czq2klQ2tXDim0dqN2/a9v7eA0vUtvOzw5Ozqit+Ds6K7q9DxHNweObhVIM0h5d1bzo9t/1ErP + uXAJXVPXhAIAL9WJCwIOuDlgWqmxdK31jegj5LKVomuhpLRNcm5NkdBTdC4Jd+BkHCKlhlnyzore + HZz7b2N8dXM8l6K/5BwcK0JwRU7YsRy8naYf5VbtGraSes+tWbWU8/tV59TBzoVkLVCDcHdW9e7c + ILVlz/bUjcDJuqJLdePBjtnDKlc8xu/Gnel0vQc7YbjoPJNxatuAHZTSB+yF1aF1k617uz3JH0wr + VAeC+7hiP6KMQX20w68PL2A8G5DZCFeIVclcH+2Y68ML2F7vsTGFjVYdMJjMmltVVavO4yiG+5Ig + WPXkWDXeeSD9Qe/WxC570caEQkQhtjblYO7ZuOPBbOLG7fRN6VM5Lu9I7M2clGm2MPVhaeP18SXc + 7zxHjK2INTVXm4IZnOThAG5grDm0ba/Wl7neXi1XWfrNGpmteTU7FjPWKNa2TeuLTM5f5qoZrUYS + ZwJknlnGasn7F3M7ZhUssz6X2iBgrMmz/6dqWjL75Uz+nUTa3VcKurJBbqL0aEd7l7Of21LIgDkU + bT15av9EB+6zQmXP+w0GQJS+TQnjZKVpe2hJsJ3ZJvoo+Lm27JRBwx3KPb+TGsDbYGmeKfmbpy7h + U/AnOrJT2R0QW5SAHjELmfUtyxmJ8kDOQMmt5k36rw0/sZfmgUsuZDLNHaWysJvgEgvF1iG4KibN + 7Y3Rv2ZRln7N41XyVH6+5Pv/AAAA//8DAH/+qlXdMgAA + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sat, 18 May 2024 05:53:14 GMT + Transfer-Encoding: + - chunked + Vary: + - Origin,Accept-Encoding + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + method: GET + uri: https://api-v2.intrinio.com/zacks/ebitda_consensus?api_key=MOCK_API_KEY&identifier=MSFT&page_size=10000 + response: + body: + string: !!binary | + H4sIAEpCSGYAA+Sbb0/bSBDGv8rJr4O0Mzuzf3hbqdK9OPXU9t3pFPnAhajEySWmV1T1u984Ienu + JhDj2BhCkFAwTsLv2ZlnZtbmR1b8M6ku8/HFrFwW5fJ2mZ3/9SOrJhdfi0V2nv3x6f3nbJRdzKbz + vLwbl/m0qI/+/u7jh08f3n/+7d2Hj3/K74tlNZnmVTG+K3J5GSrUwcHprKyus3PAUTYvFpPZpbzF + l39Xb3v/oePqbl6/cVFWxWK+mCyL8bf85rYI3/pidltW2bkaZbfzSzlwOa6/Z+fl7c3NKLueXF1v + nt/M/ts8nRZ5uXm+rOQlxbf1jz9Hw1Fe5Mvr8Rf5K8dy4nh5nS9eI6gJOe/2cc4XRZV/H09K+bwX + T8i7hPrk4pXeRFbuofRvAdK8BcjTS8pW3vo2IKU3OVkwabqm+eJqUp4w4UtHa1X0X2H71q7unwbo + 4dJ/GpyHq/9pcJ5kgrby2FfI2cpwD82RACliVofOmeIzMNkGVYMFxazkcU+MHsAotT6yBkcvD2PX + h7b8TmmC+tiQbtyVCuic8qEK7h4uVMEyep2oIMo44/pU4bBVdySCAtEBQhGsMVqbSATHjlQaChY0 + Qq8iHPbxgyLoJiKgscCbdV+LwF4Tx5FgNFutEhFQGWLsTIQ9pnD85lIzEQCJ2Ecq1J5AiS0AeEmJ + XR0kS0DBs0VDn0Ioi6slDYSQVd6EyFYIZZynvQFB1GdANBj79w+LjYsDW4iSwVvv4yjQipBs6ghG + K+t44OJwJDzJ8mFUE0xaE7RCkcSlldE4bXuFb7BzdxS7ki8XdQVOe1IurodACn3CzoT1mYOWgofY + m1UAyyb2PqkJjhgidov38Z0kPPqBK8BD7NTU+DVTbPweyZvY7xCYHaZ+B2wMaT+s8R/HLz3g2spC + v/fgXeL30gVZk/IzeI0vwO8f20NrlAKM7AIJKHF8Vs6kS2/7zfmGZt8BeRT8O+QSDUYGoZDcwQtw + +g7AfQjOKo54RrApOKxmoYFt/jFwbgS+nmt/rTgmK+40Ji7v2A7v8Y+Bm0bgTsXgnKy452TF0a3q + wMDm3jW4uHYMrl0640u563XFm9p62xgnrd22S11Ba+ncKGllrZfxPzV2bTVq1+to39jbW+MjsY7s + TRO6tJMXfzOcjjGiiPfcq881NfjW9IqkUaeQXitIF5/rAEkXHz1JBXy+hu5p9LbRJGNlFMNo7cEg + JvQSHduW5xd9PQGuAmJwq99D7xr1s1ZJSxpN8KAtMcf8wCASbDIkqPDWEkF3A80Rnt9agXoJIQoA + QPbg4jIv/CKCTRWw4KQOdud+rUKg2QUNfEANOkMfxIMUdSniGyWMNWQDFaw2SqpfuJVBz9fk9U5f + t+/ktvQk8BzQk8x0kgWh/WN3vX279D98Q8tDGzqCHmxqeDRGb01/xe9Xj9gJkvMGuEbbswxGDE03 + kCE676Vcwm29qS0iUHCJgwzo6GIX2XTSRWZk3GmHpJr67lrhdjeyPXGDJ2VX8f4OssaU3SjQtNMM + GVhvcw/M3mb+iTRgTiZeHdEzSHOsoyKAXe5qHgH+hCYgItbGKIov73qnkyZIg9TGnYzHVfnvrgq0 + uzf+UOI/pAKcoQtiH5x3Lol9A3HsM7M3O62w5D12FwIt/w1if+I3Zad47Ec2ogbH7F4TpcM/kLcd + 5n179ieNgBCWPQl1q8jG8S9HoksassDe7HTACMJvuusBj8F/+hYnRL5HLgwAqXjRnQ2Mm4t9wfy7 + 3uL8e5SVxfdqPM+v7u8U+vk/AAAA//8DANho/hUjNAAA + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sat, 18 May 2024 05:53:14 GMT + Transfer-Encoding: + - chunked + Vary: + - Origin,Accept-Encoding + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py b/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py index 8c2217c92c73..e0141e99cd37 100644 --- a/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py +++ b/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py @@ -22,6 +22,9 @@ ) from openbb_intrinio.models.etf_search import IntrinioEtfSearchFetcher from openbb_intrinio.models.financial_ratios import IntrinioFinancialRatiosFetcher +from openbb_intrinio.models.forward_ebitda_estimates import ( + IntrinioForwardEbitdaEstimatesFetcher, +) from openbb_intrinio.models.forward_eps_estimates import ( IntrinioForwardEpsEstimatesFetcher, ) @@ -518,3 +521,13 @@ def test_intrinio_forward_pe_fetcher(credentials=test_credentials): fetcher = IntrinioForwardPeEstimatesFetcher() result = fetcher.test(params, credentials) assert result is None + + +@pytest.mark.record_http +def test_intrinio_forward_ebitda_fetcher(credentials=test_credentials): + """Test forward ebitda fetcher.""" + params = {"symbol": "AAPL,MSFT"} + + fetcher = IntrinioForwardEbitdaEstimatesFetcher() + result = fetcher.test(params, credentials) + assert result is None