diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-02-18 09:24:28 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-18 17:24:28 +0000 |
commit | 9f1891931cca50ea43587bc38dfeb49006222a65 (patch) | |
tree | c464c21a4e9903dd551fe26e57152ad8842ad0a7 | |
parent | 60a93c32669a81c1092a6ed8c118a0d8d89d36d9 (diff) |
[Enhancement] Add `analyst_search` to the Equity/Estimates Router (#6088)
* standardize price_target
* add analyst search
* black
* more black
* ruff
* linter, test parmas
* last missing param
* get rid of convert to upper
* ruff
* pylint
15 files changed, 1528 insertions, 379 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py b/openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py new file mode 100644 index 00000000000..c4b13b4067c --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py @@ -0,0 +1,50 @@ +"""Analyst Search Standard Model.""" + +from datetime import ( + datetime, +) +from typing import Optional + +from pydantic import Field + +from openbb_core.provider.abstract.data import Data +from openbb_core.provider.abstract.query_params import QueryParams + + +class AnalystSearchQueryParams(QueryParams): + """Analyst Search Query.""" + + analyst_name: Optional[str] = Field( + default=None, + description="A comma separated list of analyst names to bring back." + + " Omitting will bring back all available analysts.", + ) + firm_name: Optional[str] = Field( + default=None, + description="A comma separated list of firm names to bring back." + + " Omitting will bring back all available firms.", + ) + + +class AnalystSearchData(Data): + """Analyst Search data.""" + + last_updated: Optional[datetime] = Field( + default=None, + description="Date of the last update.", + ) + firm_name: Optional[str] = Field( + default=None, + description="Firm name of the analyst.", + ) + name_first: Optional[str] = Field( + default=None, + description="Analyst first name.", + ) + name_last: Optional[str] = Field( + default=None, + description="Analyst last name.", + ) + name_full: str = Field( + description="Analyst full name.", + ) diff --git a/openbb_platform/core/openbb_core/provider/standard_models/price_target.py b/openbb_platform/core/openbb_core/provider/standard_models/price_target.py index 6c8babd0d3a..5481ec9ba5b 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/price_target.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/price_target.py @@ -1,7 +1,11 @@ """Price Target Standard Model.""" -from datetime import datetime -from typing import List, Optional, Set, Union +from datetime import ( + date as dateType, + datetime, + time, +) +from typing import Optional, Union from pydantic import Field, NonNegativeInt, field_validator @@ -16,49 +20,67 @@ from openbb_core.provider.utils.descriptions import ( class PriceTargetQueryParams(QueryParams): """Price Target Query.""" - symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "")) + symbol: Optional[str] = Field( + default=None, description=QUERY_DESCRIPTIONS.get("symbol", "") + ) limit: NonNegativeInt = Field( - default=100, description=QUERY_DESCRIPTIONS.get("limit", "") + default=200, description=QUERY_DESCRIPTIONS.get("limit", "") ) @field_validator("symbol", mode="before", check_fields=False) @classmethod - def upper_symbol(cls, v: str) -> str: + def upper_symbol(cls, v: str): """Convert symbol to uppercase.""" - return v.upper() + return v.upper() if v else None class PriceTargetData(Data): """Price Target Data.""" + published_date: Union[dateType, datetime] = Field( + description="Published date of the price target." + ) + published_time: Optional[time] = Field( + default=None, description="Time of the original rating, UTC." + ) symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", "")) - published_date: datetime = Field(description="Published date of the price target.") - news_url: Optional[str] = Field( - default=None, description="News URL of the price target." + exchange: Optional[str] = Field( + default=None, description="Exchange where the company is traded." ) - news_title: Optional[str] = Field( - default=None, description="News title of the price target." + company_name: Optional[str] = Field( + default=None, description="Name of company that is the subject of rating." ) analyst_name: Optional[str] = Field(default=None, description="Analyst name.") - analyst_company: Optional[str] = Field(default=None, description="Analyst company.") - price_target: Optional[float] = Field(default=None, description="Price target.") + analyst_firm: Optional[str] = Field( + default=None, + description="Name of the analyst firm that published the price target.", + ) + currency: Optional[str] = Field( + default=None, description="Currency the data is denominated in." + ) + price_target: Optional[float] = Field( + default=None, description="The current price target." + ) adj_price_target: Optional[float] = Field( - default=None, description="Adjusted price target." + default=None, + description="Adjusted price target for splits and stock dividends.", + ) + price_target_previous: Optional[float] = Field( + default=None, description="Previous price target." + ) + previous_adj_price_target: Optional[float] = Field( + default=None, description="Previous adjusted price target." ) price_when_posted: Optional[float] = Field( default=None, description="Price when posted." ) - news_publisher: Optional[str] = Field( - default=None, description="News publisher of the price target." + rating_current: Optional[str] = Field( + default=None, description="The analyst's rating for the company." ) - news_base_url: Optional[str] = Field( - default=None, description="News base URL of the price target." + rating_previous: Optional[str] = Field( + default=None, description="Previous analyst rating for the company." + ) + action: Optional[str] = Field( + default=None, + description="Description of the change in rating from firm's last rating.", ) - - @field_validator("symbol", mode="before", check_fields=False) - @classmethod - def upper_symbol(cls, v: Union[str, List[str], Set[str]]): - """Convert symbol to uppercase.""" - if isinstance(v, str): - return v.upper() - return ",".join([symbol.upper() for symbol in list(v)]) diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py index 4a69d38954a..30dddf91234 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_api.py +++ b/openbb_platform/extensions/equity/integration/test_equity_api.py @@ -627,7 +627,7 @@ def test_equity_ownership_major_holders(params, headers): @parametrize( "params", [ - ({"symbol": "AAPL", "limit": 10, "provider": "fmp"}), + ({"symbol": "AAPL", "limit": 10, "provider": "fmp", "with_grade": True}), ({"symbol": "AAPL", "provider": "finviz"}), ( { @@ -637,13 +637,14 @@ def test_equity_ownership_major_holders(params, headers): # optional provider params "fields": None, "date": None, - "date_from": None, - "date_to": None, + "start_date": None, + "end_date": None, "importance": None, "updated": None, "action": None, - "analyst": None, - "firm": None, + "analyst_ids": None, + "firm_ids": None, + "page": 0, } ), ], @@ -662,6 +663,34 @@ def test_equity_estimates_price_target(params, headers): @parametrize( "params", [ + ( + { + "limit": 10, + "provider": "benzinga", + # optional provider params + "fields": None, + "analyst_ids": None, + "firm_ids": None, + "firm_name": "Barclays", + "analyst_name": None, + } + ), + ], +) +@pytest.mark.integration +def test_equity_estimates_analyst_search(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/analyst_search?{query_str}" + result = requests.get(url, headers=headers, timeout=10) + assert isinstance(result, requests.Response) + assert result.status_code == 200 + + +@parametrize( + "params", + [ ({"symbol": "AAPL", "provider": "fmp"}), ({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}), ], diff --git a/openbb_platform/extensions/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py index 88bb010785b..d7f4127da35 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_python.py +++ b/openbb_platform/extensions/equity/integration/test_equity_python.py @@ -586,7 +586,7 @@ def test_equity_ownership_major_holders(params, obb): @parametrize( "params", [ - ({"symbol": "AAPL", "limit": 10, "provider": "fmp"}), + ({"symbol": "AAPL", "limit": 10, "provider": "fmp", "with_grade": True}), ( { "symbol": "AAPL", @@ -595,13 +595,14 @@ def test_equity_ownership_major_holders(params, obb): # optional provider params "fields": None, "date": None, - "date_from": None, - "date_to": None, + "start_date": None, + "end_date": None, "importance": None, "updated": None, "action": None, - "analyst": None, - "firm": None, + "analyst_ids": None, + "firm_ids": None, + "page": 0, } ), ({"symbol": "AAPL", "provider": "finviz"}), @@ -618,6 +619,31 @@ def test_equity_estimates_price_target(params, obb): @parametrize( "params", [ + ( + { + "limit": 10, + "provider": "benzinga", + # optional provider params + "fields": None, + "analyst_ids": None, + "firm_ids": None, + "firm_name": "Barclays", + "analyst_name": None, + } + ), + ], +) +@pytest.mark.integration +def test_equity_estimates_analyst_search(params, obb): + result = obb.equity.estimates.analyst_search(**params) + assert result + assert isinstance(result, OBBject) + assert result.results is not None + + +@parametrize( + "params", + [ ({"symbol": "AAPL", "provider": "fmp"}), ({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}), ], 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 b02585e8e05..b1fff34159f 100644 --- a/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py +++ b/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py @@ -15,34 +15,69 @@ router = Router(prefix="/estimates") # pylint: disable=unused-argument -@router.command(model="PriceTarget") +@router.command( + model="PriceTarget", + exclude_auto_examples=True, + examples=[ + 'obb.equity.estimates.price_target(start_date="2020-01-01", end_date="2024-02-16",limit=10, symbol="msft", provider="benzinga",action="downgrades").to_df()' # noqa: E501 pylint: disable=line-too-long + ], +) async def price_target( cc: CommandContext, provider_choices: ProviderChoices, standard_params: StandardParams, extra_params: ExtraParams, ) -> OBBject: - """Price Target. Price target data.""" + """Analyst price targets by company.""" return await OBBject.from_query(Query(**locals())) -@router.command(model="AnalystEstimates") +@router.command( + model="AnalystEstimates", + exclude_auto_examples=True, + examples=[ + 'obb.equity.estimates.historical("AAPL", period="quarter", provider="fmp").to_df()', + ], +) async def historical( cc: CommandContext, provider_choices: ProviderChoices, standard_params: StandardParams, extra_params: ExtraParams, ) -> OBBject: - """Historical Analyst Estimates. Analyst stock recommendations.""" + """Historical analyst estimates for earnings and revenue.""" return await OBBject.from_query(Query(**locals())) -@router.command(model="PriceTargetConsensus") +@router.command( + model="PriceTargetConsensus", + exclude_auto_examples=True, + examples=[ + 'obb.equity.estimates.consensus("AAPL,MSFT", provider="yfinance").to_df()' + ], +) async def consensus( cc: CommandContext, provider_choices: ProviderChoices, standard_params: StandardParams, extra_params: ExtraParams, ) -> OBBject: - """Price Target Consensus. Price target consensus data.""" + """Consensus price target and recommendation.""" + return await OBBject.from_query(Query(**locals())) + + +@router.command( + model="AnalystSearch", + exclude_auto_examples=True, + examples=[ + 'obb.equity.estimates.analyst_search(firm_name="Wedbush", provider="benzinga").to_df()', + ], +) +async def analyst_search( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: + """Search for specific analysts and get their forecast track record.""" 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 4d318e341f9..f27dae4eaa3 100644 --- a/openbb_platform/openbb/package/equity_estimates.py +++ b/openbb_platform/openbb/package/equity_estimates.py @@ -12,6 +12,7 @@ from typing_extensions import Annotated class ROUTER_equity_estimates(Container): """/equity/estimates + analyst_search consensus historical price_target @@ -21,6 +22,176 @@ class ROUTER_equity_estimates(Container): return self.__doc__ or "" @validate + def analyst_search( + self, + analyst_name: Annotated[ + Optional[str], + OpenBBCustomParameter( + description="A comma separated list of analyst names to bring back. Omitting will bring back all available analysts." + ), + ] = None, + firm_name: Annotated[ + Optional[str], + OpenBBCustomParameter( + description="A comma separated list of firm names to bring back. Omitting will bring back all available firms." + ), + ] = None, + provider: Optional[Literal["benzinga"]] = None, + **kwargs + ) -> OBBject: + """Search for specific analysts and get their forecast track record. + + Parameters + ---------- + analyst_name : Optional[str] + A comma separated list of analyst names to bring back. Omitting will bring back all available analysts. + firm_name : Optional[str] + A comma separated list of firm names to bring back. Omitting will bring back all available firms. + provider : Optional[Literal['benzinga']] + The provider to use for the query, by default None. + If None, the provider specified in defaults is selected or 'benzinga' if there is + no default. + analyst_ids : Optional[Union[str, List[str]]] + A comma separated list of analyst IDs to bring back. (provider: benzinga) + firm_ids : Optional[Union[str, List[str]]] + A comma separated list of firm IDs to bring back. (provider: benzinga) + limit : Optional[int] + Number of results returned. Limit 1000. (provider: benzinga) + page : Optional[int] + Page offset. For optimization, performance and technical reasons, page offsets are limited from 0 - 100000. Limit the query results by other parameters such as date. (provider: benzinga) + fields : Optional[Union[str, List[str]]] + Comma-separated list of fields to include in the response. See https://docs.benzinga.io/benzinga-apis/calendar/get-ratings to learn about the available fields. (provider: benzinga) + + Returns + ------- + OBBject + results : List[AnalystSearch] + Serializable results. + provider : Optional[Literal['benzinga']] + Provider name. + warnings : Optional[List[Warning_]] + List of warnings. + chart : Optional[Chart] + Chart object. + extra: Dict[str, Any] + Extra info. + + AnalystSearch + ------------- + last_updated : Optional[datetime] + Date of the last update. + firm_name : Optional[str] + Firm name of the analyst. + name_first : Optional[str] + Analyst first name. + name_last : Optional[str] + Analyst last name. + name_full : str + Analyst full name. + analyst_id : Optional[str] + ID of the analyst. (provider: benzinga) + firm_id : Optional[str] + ID of the analyst firm. (provider: benzinga) + smart_score : Optional[float] + A weighted average of the total_ratings_percentile, overall_avg_return_percentile, and overall_success_rate (provider: benzinga) + overall_success_rate : Optional[float] + The percentage (normalized) of gain/loss ratings that resulted in a gain overall. (provider: benzinga) + overall_avg_return_percentile : Optional[float] + The percentile (normalized) of this analyst's overall average return per rating in comparison to other analysts' overall average returns per rating. (provider: benzinga) + total_ratings_percentile : Optional[float] + The percentile (normalized) of this analyst's total number of ratings in comparison to the total number of ratings published by all other analysts (provider: benzinga) + total_ratings : Optional[int] + Number of recommendations made by this analyst. (provider: benzinga) + overall_gain_count : Optional[int] + The number of ratings that have gained value since the date of recommendation (provider: benzinga) + overall_loss_count : Optional[int] + The number of ratings that have lost value since the date of recommendation (provider: benzinga) + overall_average_return : Optional[float] + The average percent (normalized) price difference per rating since the date of recommendation (provider: benzinga) + overall_std_dev : Optional[float] + The standard deviation in percent (normalized) price difference in the analyst's ratings since the date of recommendation (provider: benzinga) + gain_count_1m : Optional[int] + The number of ratings that have gained value over the last month (provider: benzinga) + loss_count_1m : Optional[int] + The number of ratings that have lost value over the last month (provider: benzinga) + average_return_1m : Optional[float] + 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) + 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] + The number of ratings that have lost value over the last 3 months (provider: benzinga) + average_return_3m : Optional[float] + 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) + 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] + The number of ratings that have lost value over the last 6 months (provider: benzinga) + average_return_6m : Optional[float] + The average percent (normalized) price difference per rating over the last 6 months (provider: benzinga) + std_dev_6m : Optional[float] + The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 6 months (provider: benzinga) + gain_count_9m : Optional[int] + The number of ratings that have gained value over the last 9 months (provider: benzinga) + loss_count_9m : Optional[int] + The number of ratings that have lost value over the last 9 months (provider: benzinga) + average_return_9m : Optional[float] + 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) + 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] + The number of ratings that have lost value over the last 1 year (provider: benzinga) + average_return_1y : Optional[float] + 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) + 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] + The number of ratings that have lost value over the last 2 years (provider: benzinga) + average_return_2y : Optional[float] + 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) + 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] + The number of ratings that have lost value over the last 3 years (provider: benzinga) + average_return_3y : Optional[float] + 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) + + Example + ------- + >>> from openbb import obb + >>> obb.equity.estimates.analyst_search(firm_name="Wedbush", provider="benzinga").to_df() + """ # noqa: E501 + + return self._run( + "/equity/estimates/analyst_search", + **filter_inputs( + provider_choices={ + "provider": self._get_provider( + provider, + "/equity/estimates/analyst_search", + ("benzinga",), + ) + }, + standard_params={ + "analyst_name": analyst_name, + "firm_name": firm_name, + }, + extra_params=kwargs, + ) + ) + + @validate def consensus( self, symbol: Annotated[ @@ -32,7 +203,7 @@ class ROUTER_equity_estimates(Container): provider: Optional[Literal["fmp", "yfinance"]] = None, **kwargs ) -> OBBject: - """Price Target Consensus. Price target consensus data. + """Consensus price target and recommendation. Parameters ---------- @@ -83,7 +254,7 @@ class ROUTER_equity_estimates(Container): Example ------- >>> from openbb import obb - >>> obb.equity.estimates.consensus(symbol="AAPL") + >>> obb.equity.estimates.consensus("AAPL,MSFT", provider="yfinance").to_df() """ # noqa: E501 return self._run( @@ -121,7 +292,7 @@ class ROUTER_equity_estimates(Container): provider: Optional[Literal["fmp"]] = None, **kwargs ) -> OBBject: - """Historical Analyst Estimates. Analyst stock recommendations. + """Historical analyst estimates for earnings and revenue. Parameters ---------- @@ -200,7 +371,7 @@ class ROUTER_equity_estimates(Container): Example ------- >>> from openbb import obb - >>> obb.equity.estimates.historical(symbol="AAPL", period="annual", limit=30) + >>> obb.equity.estimates.historical("AAPL", period="quarter", provider="fmp").to_df() """ # noqa |