diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-04-04 09:09:01 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-04 16:09:01 +0000 |
commit | 3f08e0f3420c07cfe98a2b0e81d629c1d2ca7978 (patch) | |
tree | 5e7b35ad5f7908809aad34961d809fa7c3625630 | |
parent | 45998938eddd6fc29e5a4016e06fc1856cd35252 (diff) |
[Feature] Add Forward Sales and EPS Estimates (#6269)
* add intrinio forward estimates
* decorators
* mypy
* better sentence for descriptions
* black
* mypy
* review things
* update field
---------
Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
22 files changed, 1913 insertions, 21 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/forward_eps_estimates.py b/openbb_platform/core/openbb_core/provider/standard_models/forward_eps_estimates.py new file mode 100644 index 00000000000..e0f95d9c8a1 --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/forward_eps_estimates.py @@ -0,0 +1,67 @@ +"""Forward EPS Estimates Standard Model.""" + +from datetime import date as dateType +from typing import Optional + +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 ( + DATA_DESCRIPTIONS, + QUERY_DESCRIPTIONS, +) + + +class ForwardEpsEstimatesQueryParams(QueryParams): + """Forward EPS 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 ForwardEpsEstimatesData(Data): + """Forward EPS Estimates Data.""" + + symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", "")) + name: Optional[str] = Field(default=None, description="Name of the entity.") + date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", "")) + 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[str] = Field( + default=None, description="Calendar quarter for the estimate." + ) + low_estimate: Optional[float] = Field( + default=None, description="Estimated EPS low for the period." + ) + high_estimate: Optional[float] = Field( + default=None, description="Estimated EPS high for the period." + ) + mean: Optional[float] = Field( + default=None, description="Estimated EPS mean for the period." + ) + median: Optional[float] = Field( + default=None, description="Estimated EPS median for the period." + ) + standard_deviation: Optional[float] = Field( + default=None, description="Estimated EPS 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/core/openbb_core/provider/standard_models/forward_sales_estimates.py b/openbb_platform/core/openbb_core/provider/standard_models/forward_sales_estimates.py new file mode 100644 index 00000000000..8d3e1217d58 --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/forward_sales_estimates.py @@ -0,0 +1,68 @@ +"""Forward Sales Estimates Standard Model.""" + +from datetime import date as dateType +from typing import Optional + +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 ForwardSalesEstimatesQueryParams(QueryParams): + """Forward Sales 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 ForwardSalesEstimatesData(Data): + """Forward Sales Estimates Data.""" + + symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", "")) + name: Optional[str] = Field(default=None, description="Name of the entity.") + date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", "")) + 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[str] = Field( + default=None, description="Calendar quarter for the estimate." + ) + low_estimate: Optional[ForceInt] = Field( + default=None, description="The sales estimate low for the period." + ) + high_estimate: Optional[ForceInt] = Field( + default=None, description="The sales estimate high for the period." + ) + mean: Optional[ForceInt] = Field( + default=None, description="The sales estimate mean for the period." + ) + median: Optional[ForceInt] = Field( + default=None, description="The sales estimate median for the period." + ) + standard_deviation: Optional[ForceInt] = Field( + default=None, + description="The sales 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/core/openbb_core/provider/standard_models/price_target_consensus.py b/openbb_platform/core/openbb_core/provider/standard_models/price_target_consensus.py index f6949cb166a..106879a884f 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/price_target_consensus.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/price_target_consensus.py @@ -15,19 +15,22 @@ from openbb_core.provider.utils.descriptions import ( class PriceTargetConsensusQueryParams(QueryParams): """Price Target Consensus Query.""" - symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "")) + symbol: Optional[str] = Field( + default=None, description=QUERY_DESCRIPTIONS.get("symbol", "") + ) @field_validator("symbol", mode="before", check_fields=False) @classmethod - def to_upper(cls, v: str) -> str: + def to_upper(cls, v): """Convert field to uppercase.""" - return v.upper() + return v.upper() if v else None class PriceTargetConsensusData(Data): """Price Target Consensus Data.""" symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", "")) + name: Optional[str] = Field(default=None, description="The company name") target_high: Optional[float] = Field( default=None, description="High target of the price target consensus." ) diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py index 511b4305417..5b3d5bd56b3 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_api.py +++ b/openbb_platform/extensions/equity/integration/test_equity_api.py @@ -359,7 +359,7 @@ def test_equity_fundamental_employee_count(params, headers): @parametrize( "params", - [({"symbol": "AAPL", "period": "annual", "limit": 30})], + [({"symbol": "AAPL,MSFT", "period": "annual", "limit": 30})], ) @pytest.mark.integration def test_equity_estimates_historical(params, headers): @@ -375,6 +375,67 @@ def test_equity_estimates_historical(params, headers): @parametrize( "params", [ + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "fy", + "fiscal_year": None, + "calendar_year": None, + "calendar_period": None, + "provider": "intrinio", + } + ) + ], +) +@pytest.mark.integration +def test_equity_estimates_forward_sales(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/equity/estimates/forward_sales?{query_str}" + 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": "fy", + "fiscal_year": None, + "calendar_year": None, + "calendar_period": None, + "provider": "intrinio", + } + ), + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "annual", + "limit": None, + "include_historical": False, + "provider": "fmp", + } + ), + ], +) +@pytest.mark.integration +def test_equity_estimates_forward_eps(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/equity/estimates/forward_eps?{query_str}" + result = requests.get(url, headers=headers, timeout=10) + assert isinstance(result, requests.Response) + assert result.status_code == 200 + + +@parametrize( + "params", + [ ({"symbol": "AAPL", "period": "annual", "limit": 12, "provider": "fmp"}), ( { @@ -718,6 +779,13 @@ def test_equity_estimates_analyst_search(params, headers): ({"symbol": "AAPL", "provider": "fmp"}), ({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}), ({"symbol": "TD:US", "provider": "tmx"}), + ( + { + "symbol": "AAPL,MSFT", + "industry_group_number": None, + "provider": "intrinio", + } + ), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py index 671df076840..ce421776080 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_python.py +++ b/openbb_platform/extensions/equity/integration/test_equity_python.py @@ -334,7 +334,7 @@ def test_equity_fundamental_employee_count(params, obb): @parametrize( "params", [ - ({"symbol": "AAPL", "period": "annual", "limit": 30}), + ({"symbol": "AAPL,MSFT", "period": "annual", "limit": 30}), ], ) @pytest.mark.integration @@ -652,6 +652,13 @@ def test_equity_estimates_analyst_search(params, obb): ({"symbol": "AAPL", "provider": "fmp"}), ({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}), ({"symbol": "TD:US", "provider": "tmx"}), + ( + { + "symbol": "AAPL,MSFT", + "industry_group_number": None, + "provider": "intrinio", + } + ), ], ) @pytest.mark.integration @@ -665,6 +672,61 @@ def test_equity_estimates_consensus(params, obb): @parametrize( "params", [ + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "fy", + "fiscal_year": None, + "calendar_year": None, + "calendar_period": None, + "provider": "intrinio", + } + ) + ], +) +@pytest.mark.integration +def test_equity_estimates_forward_sales(params, obb): + result = obb.equity.estimates.forward_sales(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + + +@parametrize( + "params", + [ + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "fy", + "fiscal_year": None, + "calendar_year": None, + "calendar_period": None, + "provider": "intrinio", + } + ), + ( + { + "symbol": "AAPL,MSFT", + "fiscal_period": "annual", + "limit": None, + "include_historical": False, + "provider": "fmp", + } + ), + ], +) +@pytest.mark.integration +def test_equity_estimates_forward_eps(params, obb): + result = obb.equity.estimates.forward_eps(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + + +@parametrize( + "params", + [ ({"symbol": "AAPL", "period": "annual", "limit": 12, "provider": "fmp"}), ( { 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 07c328c334c..d321c3a9d31 100644 --- a/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py +++ b/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py @@ -91,3 +91,49 @@ async def analyst_search( ) -> OBBject: """Search for specific analysts and get their forecast track record.""" return await OBBject.from_query(Query(**locals())) + + +@router.command( + model="ForwardSalesEstimates", + examples=[ + APIEx(parameters={"symbol": "AAPL", "provider": "intrinio"}), + APIEx( + parameters={ + "fiscal_year": 2025, + "fiscal_period": "fy", + "provider": "intrinio", + } + ), + ], +) +async def forward_sales( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: + """Get forward sales estimates.""" + return await OBBject.from_query(Query(**locals())) + + +@router.command( + model="ForwardEpsEstimates", + examples=[ + APIEx(parameters={"symbol": "AAPL", "provider": "intrinio"}), + APIEx( + parameters={ + "fiscal_year": 2025, + "fiscal_period": "fy", + "provider": "intrinio", + } + ), + ], +) +async def forward_eps( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: + """Get forward EPS estimates.""" + return await OBBject.from_query(Query(**locals())) diff --git a/openbb_platform/openbb/package/equity_estimates.py b/openbb_platform/openbb/package/equity_estimates.py index 6a5ef3fa944..9c707ff72c7 100644 --- a/openbb_platform/openbb/package/equity_estimates.py +++ b/openbb_platform/openbb/package/equity_estimates.py @@ -14,6 +14,8 @@ class ROUTER_equity_estimates(Container): """/equity/estimates analyst_search consensus + forward_eps + forward_sales historical price_target """ @@ -210,13 +212,13 @@ class ROUTER_equity_estimates(Container): def consensus( self, symbol: Annotated[ - Union[str, List[str]], + Union[str, None, List[Optional[str]]], OpenBBCustomParameter( - description="Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, yfinance." + description="Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, intrinio, yfinance." ), - ], + ] = None, provider: Annotated[ - Optional[Literal["fmp", "yfinance"]], + Optional[Literal["fmp", "intrinio", "yfinance"]], OpenBBCustomParameter( 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." ), @@ -227,19 +229,21 @@ class ROUTER_equity_estimates(Container): Parameters ---------- - symbol : Union[str, List[str]] - Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, yfinance. - provider : Optional[Literal['fmp', 'yfinance']] + symbol : Union[str, None, List[Optional[str]]] + Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, intrinio, yfinance. + provider : Optional[Literal['fmp', 'intrinio', '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 no default. + industry_group_number : Optional[int] + The Zacks industry group number. (provider: intrinio) Returns ------- OBBject results : List[PriceTargetConsensus] Serializable results. - provider : Optional[Literal['fmp', 'yfinance']] + provider : Optional[Literal['fmp', 'intrinio', 'yfinance']] Provider name. warnings : Optional[List[Warning_]] List of warnings. @@ -252,6 +256,8 @@ class ROUTER_equity_estimates(Container): -------------------- symbol : str Symbol representing the entity requested in the data. + name : Optional[str] + The company name target_high : Optional[float] High target of the price target consensus. target_low : Optional[float] @@ -260,6 +266,18 @@ class ROUTER_equity_estimates(Container): Consensus target of the price target consensus. target_median : Optional[float] Median target of the price target consensus. + standard_deviation : Optional[float] + The standard deviation of target price estimates. (provider: intrinio) + total_anaylsts : Optional[int] + The total number of target price estimates in consensus. (provider: intrinio) + raised : Optional[int] + The number of analysts that have raised their target price estimates. (provider: intrinio) + lowered : Optional[int] + The number of analysts that have lowered their target price estimates. (provider: intrinio) + most_recent_date : Optional[date] + The date of the most recent estimate. (provider: intrinio) + industry_group_number : Optional[int] + The Zacks industry group number. (provider: intrinio) recommendation : Optional[str] Recommendation - buy, sell, etc. (provider: yfinance) recommendation_mean : Optional[float] @@ -285,14 +303,260 @@ class ROUTER_equity_estimates(Container): "provider": self._get_provider( provider, "/equity/estimates/consensus", - ("fmp", "yfinance"), + ("fmp", "intrinio", "yfinance"), + ) + }, + standard_params={ + "symbol": symbol, + }, + extra_params=kwargs, + info={ + "symbol": { + "multiple_items_allowed": ["fmp", "intrinio", "yfinance"] + } + }, + ) + ) + + @exception_handler + @validate + def forward_eps( + self, + symbol: Annotated[ + Union[str, None, List[Optional[str]]], + OpenBBCustomParameter( + description="Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, intrinio." + ), + ] = None, + provider: Annotated[ + Optional[Literal["fmp", "intrinio"]], + OpenBBCustomParameter( + 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 EPS 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[Union[Literal['annual', 'quarter'], Literal['fy', 'q1', 'q2', 'q3', 'q4']]] + The future fiscal period to retrieve estimates for. (provider: fmp, 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) + fiscal_year : Optional[int] + The future fiscal year to retrieve estimates for. When no symbol and year is supplied the current calendar year is used. (provider: intrinio) + calendar_year : Optio |