mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
equity.estimates.price_target - Integrate with Benzinga (#5946)
* deprecation message * fix lints * adding equity.estimates.ratings and fmp as its provider * unit tests for fmp * removing unused file * typos * add benzinga to AnalystRatings * revert changes to estimates router * moving it to price target instead * reverting changes * fmp support for multiple ticker * benzinga price target * ruffy * unused argument * fix type hints * tests for the new fetcher * new integration tests * fixing tests * missing std arg --------- Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Set, Union
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic import Field, NonNegativeInt, field_validator
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
@@ -18,8 +18,12 @@ class PriceTargetQueryParams(QueryParams):
|
||||
"""Price Target Query."""
|
||||
|
||||
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
|
||||
limit: NonNegativeInt = Field(
|
||||
default=100, description=QUERY_DESCRIPTIONS.get("limit", "")
|
||||
)
|
||||
|
||||
@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):
|
||||
@@ -55,6 +59,7 @@ class PriceTargetData(Data):
|
||||
)
|
||||
|
||||
@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):
|
||||
|
||||
@@ -19,6 +19,7 @@ class PriceTargetConsensusQueryParams(QueryParams):
|
||||
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
|
||||
|
||||
@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):
|
||||
@@ -44,6 +45,7 @@ class PriceTargetConsensusData(Data):
|
||||
)
|
||||
|
||||
@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):
|
||||
|
||||
@@ -606,7 +606,26 @@ def test_equity_ownership_major_holders(params, headers):
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[({"symbol": "AAPL", "provider": "fmp"})],
|
||||
[
|
||||
({"symbol": "AAPL", "limit": 10, "provider": "fmp"}),
|
||||
(
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"limit": 10,
|
||||
"provider": "benzinga",
|
||||
# optional provider params
|
||||
"fields": None,
|
||||
"date": None,
|
||||
"date_from": None,
|
||||
"date_to": None,
|
||||
"importance": None,
|
||||
"updated": None,
|
||||
"action": None,
|
||||
"analyst": None,
|
||||
"firm": None,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_equity_estimates_price_target(params, headers):
|
||||
|
||||
@@ -568,7 +568,24 @@ def test_equity_ownership_major_holders(params, obb):
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"symbol": "AAPL", "provider": "fmp"}),
|
||||
({"symbol": "AAPL", "limit": 10, "provider": "fmp"}),
|
||||
(
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"limit": 10,
|
||||
"provider": "benzinga",
|
||||
# optional provider params
|
||||
"fields": None,
|
||||
"date": None,
|
||||
"date_from": None,
|
||||
"date_to": None,
|
||||
"importance": None,
|
||||
"updated": None,
|
||||
"action": None,
|
||||
"analyst": None,
|
||||
"firm": None,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Benzinga provider module."""
|
||||
from openbb_benzinga.models.company_news import BenzingaCompanyNewsFetcher
|
||||
from openbb_benzinga.models.price_target import BenzingaPriceTargetFetcher
|
||||
from openbb_benzinga.models.world_news import BenzingaWorldNewsFetcher
|
||||
from openbb_core.provider.abstract.provider import Provider
|
||||
|
||||
@@ -12,5 +13,6 @@ benzinga_provider = Provider(
|
||||
fetcher_dict={
|
||||
"CompanyNews": BenzingaCompanyNewsFetcher,
|
||||
"WorldNews": BenzingaWorldNewsFetcher,
|
||||
"PriceTarget": BenzingaPriceTargetFetcher,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
"""Benzinga Price Target Model."""
|
||||
|
||||
import math
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.price_target import (
|
||||
PriceTargetData,
|
||||
PriceTargetQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
|
||||
from pydantic import Field, PrivateAttr, field_validator, model_validator
|
||||
|
||||
|
||||
class BenzingaPriceTargetQueryParams(PriceTargetQueryParams):
|
||||
"""Benzinga Price Target Query.
|
||||
|
||||
Source: https://docs.benzinga.io/benzinga-apis/calendar/get-ratings
|
||||
"""
|
||||
|
||||
__alias_dict__ = {
|
||||
"limit": "pageSize",
|
||||
"symbol": "parameters[tickers]",
|
||||
}
|
||||
|
||||
fields: Optional[str] = Field(
|
||||
default=None,
|
||||
description="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.",
|
||||
)
|
||||
date: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Date for calendar data, shorthand for date_from and date_to.",
|
||||
is_parameters=True,
|
||||
)
|
||||
date_from: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Date to query from point in time.",
|
||||
is_parameters=True,
|
||||
)
|
||||
date_to: Optional[str] = Field(
|
||||
default=None, description="Date to query to point in time.", is_parameters=True
|
||||
)
|
||||
importance: Optional[int] = Field(
|
||||
default=None, description="Importance level to filter by.", is_parameters=True
|
||||
)
|
||||
updated: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Records last updated Unix timestamp (UTC).",
|
||||
is_parameters=True,
|
||||
)
|
||||
action: Optional[
|
||||
Literal[
|
||||
"Downgrades",
|
||||
"Maintains",
|
||||
"Reinstates",
|
||||
"Reiterates",
|
||||
"Upgrades",
|
||||
"Assumes",
|
||||
"Initiates Coverage On",
|
||||
"Terminates Coverage On",
|
||||
"Removes",
|
||||
"Suspends",
|
||||
"Firm Dissolved",
|
||||
]
|
||||
] = Field(
|
||||
default=None,
|
||||
description="Filter by a specific action_company.",
|
||||
is_parameters=True,
|
||||
)
|
||||
analyst: Optional[str] = Field(
|
||||
default=None, description="Comma-separated list of analyst (person) IDs."
|
||||
)
|
||||
firm: Optional[str] = Field(
|
||||
default=None, description="Comma-separated list of analyst firm IDs."
|
||||
)
|
||||
_parameters: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
||||
|
||||
@model_validator(mode="after")
|
||||
@classmethod
|
||||
def assemble_parameters(
|
||||
cls, values: "BenzingaPriceTargetQueryParams"
|
||||
) -> "BenzingaPriceTargetQueryParams":
|
||||
"""Assemble the parameters private attribute."""
|
||||
model_fields = values.model_fields
|
||||
|
||||
for field in deepcopy(values):
|
||||
key, value = field
|
||||
field_info = model_fields[key]
|
||||
if field_info.json_schema_extra and field_info.json_schema_extra.get(
|
||||
"is_parameters", False
|
||||
):
|
||||
if value:
|
||||
# add to parameters
|
||||
# pylint: disable=protected-access
|
||||
values._parameters[f"parameters[{key}]"] = value
|
||||
# remove from values
|
||||
delattr(values, key)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class BenzingaPriceTargetData(PriceTargetData):
|
||||
"""Benzinga Price Target Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "ticker",
|
||||
"published_date": "date",
|
||||
"news_url": "url_news",
|
||||
"adj_price_target": "adjusted_pt_current",
|
||||
"analyst_name": "analyst",
|
||||
"price_target": "pt_current",
|
||||
}
|
||||
|
||||
action_company: Literal[
|
||||
"Downgrades",
|
||||
"Maintains",
|
||||
"Reinstates",
|
||||
"Reiterates",
|
||||
"Upgrades",
|
||||
"Assumes",
|
||||
"Initiates Coverage On",
|
||||
"Terminates Coverage On",
|
||||
"Removes",
|
||||
"Suspends",
|
||||
"Firm Dissolved",
|
||||
"",
|
||||
] = Field(
|
||||
default=None,
|
||||
description="Description of the change in rating from firm's last rating."
|
||||
"Note that all of these terms are precisely defined.",
|
||||
)
|
||||
action_pt: Literal[
|
||||
"Announces", "Maintains", "Lowers", "Raises", "Removes", "Adjusts", ""
|
||||
] = Field(
|
||||
default=None,
|
||||
description="Description of the change in price target from firm's last price target.",
|
||||
)
|
||||
adjusted_pt_prior: str = Field(
|
||||
default=None,
|
||||
description="Analyst's prior price target, adjusted to account for stock splits and stock dividends."
|
||||
" If none are applicable, the pt_prior value is used.",
|
||||
)
|
||||
analyst_id: str = Field(default=None, description="Id of the analyst.")
|
||||
currency: str = Field(
|
||||
default=None, description="Currency the data is denominated in."
|
||||
)
|
||||
exchange: str = Field(default=None, description="Exchange of the price target.")
|
||||
id: str = Field(default=None, description="Unique ID of this entry.")
|
||||
importance: Literal[0, 1, 2, 3, 4, 5] = Field(
|
||||
default=None,
|
||||
description="Subjective Basis of How Important Event is to Market. 5 = High",
|
||||
)
|
||||
notes: str = Field(default=None, description="Notes of the price target.")
|
||||
pt_prior: str = Field(default=None, description="Analyst's prior price target.")
|
||||
rating_current: str = Field(
|
||||
default=None, description="The analyst's rating for the company."
|
||||
)
|
||||
rating_prior: str = Field(
|
||||
default=None, description="Prior analyst rating for the company."
|
||||
)
|
||||
ratings_accuracy: str = Field(
|
||||
default=None, description="Ratings accuracy of the price target."
|
||||
)
|
||||
time: str = Field(default=None, description="Last updated timestamp, UTC.")
|
||||
updated: int = Field(default=None, description="Last updated timestamp, UTC.")
|
||||
url: str = Field(
|
||||
default=None,
|
||||
description="URL for analyst ratings page for this ticker on Benzinga.com.",
|
||||
)
|
||||
url_calendar: str = Field(
|
||||
default=None,
|
||||
description="URL for analyst ratings page for this ticker on Benzinga.com.",
|
||||
)
|
||||
name: str = Field(
|
||||
default=None, description="Name of company that is subject of rating."
|
||||
)
|
||||
|
||||
@field_validator("published_date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def parse_date(cls, v: str) -> datetime:
|
||||
"""Parse the date field."""
|
||||
if v:
|
||||
v = datetime.strptime(v, "%Y-%m-%d")
|
||||
|
||||
return v
|
||||
|
||||
@field_validator("adj_price_target", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def parse_adj_price_target(cls, v: str) -> float:
|
||||
"""Parse the adj_price_target field."""
|
||||
return float(v) if v else 0.0
|
||||
|
||||
@field_validator("price_target", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def parse_price_target(cls, v: str) -> float:
|
||||
"""Parse the price_target field."""
|
||||
return float(v) if v else 0.0
|
||||
|
||||
|
||||
class BenzingaPriceTargetFetcher(
|
||||
Fetcher[
|
||||
BenzingaPriceTargetQueryParams,
|
||||
List[BenzingaPriceTargetData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Benzinga endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> BenzingaPriceTargetQueryParams:
|
||||
"""Transform the query params."""
|
||||
return BenzingaPriceTargetQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: BenzingaPriceTargetQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> Dict:
|
||||
"""Return the raw data from the Benzinga endpoint."""
|
||||
token = credentials.get("benzinga_api_key") if credentials else ""
|
||||
|
||||
base_url = "https://api.benzinga.com/api/v2.1/calendar/ratings"
|
||||
full_query = {
|
||||
**query.model_dump(by_alias=True),
|
||||
**query._parameters, # pylint: disable=protected-access
|
||||
}
|
||||
querystring = get_querystring(full_query, exclude=[])
|
||||
|
||||
pages = math.ceil(query.limit / 100) if query.limit else 1
|
||||
|
||||
urls = [
|
||||
f"{base_url}?{querystring}&page={page}&token={token}"
|
||||
for page in range(pages)
|
||||
]
|
||||
|
||||
data = await amake_requests(urls, **kwargs)
|
||||
|
||||
return data[: query.limit]
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: BenzingaPriceTargetQueryParams,
|
||||
data: Dict,
|
||||
**kwargs: Any,
|
||||
) -> List[BenzingaPriceTargetData]:
|
||||
"""Return the transformed data."""
|
||||
return [BenzingaPriceTargetData.model_validate(d) for d in data[0]["ratings"]]
|
||||
@@ -1,25 +0,0 @@
|
||||
"""Benzinga Helpers."""
|
||||
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from openbb_core.provider import helpers
|
||||
|
||||
|
||||
def get_data(url: str, **kwargs: Any) -> Dict:
|
||||
"""Do an API request to Benzinga and return the data."""
|
||||
result = helpers.make_request(
|
||||
url, timeout=10, headers={"accept": "application/json"}, **kwargs
|
||||
)
|
||||
if result.status_code != 200:
|
||||
data = result.json()
|
||||
if data == ['Access denied for user 0 "anonymous"']:
|
||||
raise RuntimeError("API Key is invalid")
|
||||
if len(data) == 0:
|
||||
raise RuntimeError("No news found!")
|
||||
message = data.get("message")
|
||||
error = data.get("error")
|
||||
value = message or error
|
||||
raise RuntimeError(f"Error in Benzinga request -> {value}")
|
||||
|
||||
return result.json()
|
||||
@@ -0,0 +1,146 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- application/json
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
method: GET
|
||||
uri: https://api.benzinga.com/api/v2.1/calendar/ratings?page=0&pageSize=100¶meters%5Btickers%5D=AAPL&token=MOCK_TOKEN
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
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
|
||||
headers:
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Fri, 12 Jan 2024 10:16:49 GMT
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
Via:
|
||||
- kong/2.4.1, 1.1 56d84f665e9029878cb3adcd83a21026.cloudfront.net (CloudFront)
|
||||
X-Amz-Cf-Id:
|
||||
- u7IyZpkTSODX4-a_LDLxUQJI4E_R06BB8oFSA40IJHC7YxXml3QJuw==
|
||||
X-Amz-Cf-Pop:
|
||||
- LIS50-P1
|
||||
X-Bz-Request-Id:
|
||||
- DSIshnOBTn5TzNsr70peza
|
||||
X-Cache:
|
||||
- Miss from cloudfront
|
||||
X-Kong-Proxy-Latency:
|
||||
- '0'
|
||||
X-Kong-Upstream-Latency:
|
||||
- '16'
|
||||
X-Powered-By:
|
||||
- Athlon 64 X2
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from openbb_benzinga.models.company_news import BenzingaCompanyNewsFetcher
|
||||
from openbb_benzinga.models.price_target import BenzingaPriceTargetFetcher
|
||||
from openbb_benzinga.models.world_news import BenzingaWorldNewsFetcher
|
||||
from openbb_core.app.service.user_service import UserService
|
||||
|
||||
@@ -34,3 +35,12 @@ def test_benzinga_company_news_fetcher(credentials=test_credentials):
|
||||
fetcher = BenzingaCompanyNewsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_benzinga_price_target_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "AAPL"}
|
||||
|
||||
fetcher = BenzingaPriceTargetFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
@@ -8,7 +8,7 @@ from openbb_core.provider.standard_models.price_target import (
|
||||
PriceTargetData,
|
||||
PriceTargetQueryParams,
|
||||
)
|
||||
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, field_validator
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class FMPPriceTargetFetcher(
|
||||
"""Transform the query params."""
|
||||
return FMPPriceTargetQueryParams(**params)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: FMPPriceTargetQueryParams,
|
||||
@@ -63,13 +64,18 @@ class FMPPriceTargetFetcher(
|
||||
api_key = credentials.get("fmp_api_key") if credentials else ""
|
||||
endpoint = "upgrades-downgrades" if query.with_grade else "price-target"
|
||||
|
||||
url = create_url(4, endpoint, api_key, query)
|
||||
urls = []
|
||||
for symbol in query.symbol.split(","):
|
||||
query.symbol = symbol
|
||||
urls.append(create_url(4, endpoint, api_key, query, exclude=["limit"]))
|
||||
|
||||
return await get_data_many(url, **kwargs)
|
||||
return await get_data_urls(urls)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: FMPPriceTargetQueryParams, data: List[Dict], **kwargs: Any
|
||||
) -> List[FMPPriceTargetData]:
|
||||
"""Return the transformed data."""
|
||||
data = data[: query.limit]
|
||||
return [FMPPriceTargetData.model_validate(d) for d in data]
|
||||
|
||||
@@ -10,6 +10,7 @@ from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
ClientResponse,
|
||||
amake_request,
|
||||
amake_requests,
|
||||
get_querystring,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
@@ -78,6 +79,11 @@ async def get_data(url: str, **kwargs: Any) -> Union[list, dict]:
|
||||
return await amake_request(url, response_callback=response_callback, **kwargs)
|
||||
|
||||
|
||||
async def get_data_urls(urls: str, **kwargs: Any) -> Union[list, dict]:
|
||||
"""Get data from FMP for several urls."""
|
||||
return await amake_requests(urls, response_callback=response_callback, **kwargs)
|
||||
|
||||
|
||||
def create_url(
|
||||
version: int,
|
||||
endpoint: str,
|
||||
|
||||
Reference in New Issue
Block a user