summaryrefslogtreecommitdiffstats
path: root/openbb_platform/providers/seeking_alpha/openbb_seeking_alpha/models/forward_eps_estimates.py
diff options
context:
space:
mode:
Diffstat (limited to 'openbb_platform/providers/seeking_alpha/openbb_seeking_alpha/models/forward_eps_estimates.py')
-rw-r--r--openbb_platform/providers/seeking_alpha/openbb_seeking_alpha/models/forward_eps_estimates.py207
1 files changed, 207 insertions, 0 deletions
diff --git a/openbb_platform/providers/seeking_alpha/openbb_seeking_alpha/models/forward_eps_estimates.py b/openbb_platform/providers/seeking_alpha/openbb_seeking_alpha/models/forward_eps_estimates.py
new file mode 100644
index 00000000000..fb0c4458eca
--- /dev/null
+++ b/openbb_platform/providers/seeking_alpha/openbb_seeking_alpha/models/forward_eps_estimates.py
@@ -0,0 +1,207 @@
+"""Seeking Alpha Forward EPS Estimates Model."""
+
+# pylint: disable=unused-argument
+
+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_eps_estimates import (
+ ForwardEpsEstimatesData,
+ ForwardEpsEstimatesQueryParams,
+)
+from openbb_core.provider.utils.helpers import amake_request
+from openbb_seeking_alpha.utils.helpers import HEADERS, get_seekingalpha_id
+from pydantic import Field, field_validator
+
+
+class SAForwardEpsEstimatesQueryParams(ForwardEpsEstimatesQueryParams):
+ """Seeking Alpha Forward EPS Estimates Query.
+
+ Source: https://seekingalpha.com/earnings/earnings-calendar
+ """
+
+ __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
+
+ period: Literal["annual", "quarter"] = Field(
+ default="quarter",
+ description="The reporting period.",
+ json_schema_extra={"choices": ["annual", "quarter"]},
+ )
+
+ @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 Seeking Alpha.")
+ return value
+
+
+class SAForwardEpsEstimatesData(ForwardEpsEstimatesData):
+ """Seeking Alpha Forward EPS Estimates Data."""
+
+ normalized_actual: Optional[float] = Field(
+ default=None,
+ description="Actual normalized EPS.",
+ )
+ period_growth: Optional[float] = Field(
+ default=None,
+ description="Estimated (or actual if reported) EPS growth for the period.",
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ low_estimate_gaap: Optional[float] = Field(
+ default=None,
+ description="Estimated GAAP EPS low for the period.",
+ )
+ high_estimate_gaap: Optional[float] = Field(
+ default=None,
+ description="Estimated GAAP EPS high for the period.",
+ )
+ mean_gaap: Optional[float] = Field(
+ default=None,
+ description="Estimated GAAP EPS mean for the period.",
+ )
+ gaap_actual: Optional[float] = Field(
+ default=None,
+ description="Actual GAAP EPS.",
+ )
+
+
+class SAForwardEpsEstimatesFetcher(
+ Fetcher[
+ SAForwardEpsEstimatesQueryParams,
+ List[SAForwardEpsEstimatesData],
+ ]
+):
+ """Seeking Alpha Forward EPS Estimates Fetcher."""
+
+ @staticmethod
+ def transform_query(params: Dict[str, Any]) -> SAForwardEpsEstimatesQueryParams:
+ """Transform the query."""
+ return SAForwardEpsEstimatesQueryParams(**params)
+
+ @staticmethod
+ async def aextract_data(
+ query: SAForwardEpsEstimatesQueryParams,
+ credentials: Optional[Dict[str, str]],
+ **kwargs: Any,
+ ) -> Dict:
+ """Return the raw data from the Seeking Alpha endpoint."""
+ tickers = query.symbol.split(",")
+ fp = query.period if query.period == "annual" else "quarterly"
+ url = "https://seekingalpha.com/api/v3/symbol_data/estimates"
+ querystring: Dict = {
+ "estimates_data_items": "eps_normalized_actual,eps_normalized_consensus_low,eps_normalized_consensus_mean,"
+ "eps_normalized_consensus_high,eps_normalized_num_of_estimates,"
+ "eps_gaap_actual,eps_gaap_consensus_low,eps_gaap_consensus_mean,eps_gaap_consensus_high,",
+ "period_type": fp,
+ "relative_periods": "-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,11,12",
+ }
+ ids: Dict = {ticker: await get_seekingalpha_id(ticker) for ticker in tickers}
+ querystring["ticker_ids"] = (",").join(list(ids.values()))
+ payload: str = ""
+ response = await amake_request(
+ url, data=payload, headers=HEADERS, params=querystring
+ )
+ estimates: Dict = response.get("estimates", {}) # type: ignore
+ if not estimates:
+ raise RuntimeError(f"No estimates data was returned for: {query.symbol}")
+
+ output: Dict = {"ids": ids, "estimates": estimates}
+
+ return output
+
+ @staticmethod
+ def transform_data(
+ query: SAForwardEpsEstimatesQueryParams,
+ data: Dict,
+ **kwargs: Any,
+ ) -> List[SAForwardEpsEstimatesData]:
+ """Transform the data to the standard format."""
+ tickers = query.symbol.split(",")
+ ids = data.get("ids")
+ estimates = data.get("estimates")
+ results: List[SAForwardEpsEstimatesData] = []
+ for ticker in tickers:
+ sa_id = str(ids.get(ticker, ""))
+ if sa_id == "" or sa_id not in estimates:
+ warn(f"Symbol Error: No data found for, {ticker}")
+ seek_object = estimates.get(sa_id)
+ items = len(seek_object["eps_normalized_num_of_estimates"])
+ for i in range(0, items - 4):
+ eps_estimates: Dict = {}
+ eps_estimates["symbol"] = ticker
+ num_estimates = seek_object["eps_normalized_num_of_estimates"].get(str(i))
+ if not num_estimates:
+ continue
+ period = num_estimates[0].get("period", {})
+ if period:
+ period_type = period.get("periodtypeid")
+ eps_estimates["calendar_year"] = period.get("calendaryear")
+ eps_estimates["calendar_period"] = (
+ "Q" + str(period.get("calendarquarter", ""))
+ if period_type == "quarterly"
+ else "FY"
+ )
+ eps_estimates["date"] = period.get("periodenddate").split("T")[0]
+ eps_estimates["fiscal_year"] = period.get("fiscalyear")
+ eps_estimates["fiscal_period"] = (
+ "Q" + str(period.get("fiscalquarter", ""))
+ if period_type == "quarterly"
+ else "FY"
+ )
+ eps_estimates["number_of_analysts"] = num_estimates[0].get(
+ "dataitemvalue"
+ )
+ actual = seek_object["eps_normalized_actual"].get(str(i))
+ if actual:
+ eps_estimates["normalized_actual"] = actual[0].get("dataitemvalue")
+ gaap_actual = seek_object["eps_gaap_actual"].get(str(i))
+ if gaap_actual:
+ eps_estimates["gaap_actual"] = gaap_actual[0].get("dataitemvalue")
+ low = seek_object["eps_normalized_consensus_low"].get(str(i))
+ if low:
+ eps_estimates["low_estimate"] = low[0].get("dataitemvalue")
+ gaap_low = seek_object["eps_gaap_consensus_low"].get(str(i))
+ if gaap_low:
+ eps_estimates["low_estimate_gaap"] = gaap_low[0].get(
+ "dataitemvalue"
+ )
+ high = seek_object["eps_normalized_consensus_high"].get(str(i))
+ if high:
+ eps_estimates["high_estimate"] = high[0].get("dataitemvalue")
+ gaap_high = seek_object["eps_gaap_consensus_high"].get(str(i))
+ if gaap_high:
+ eps_estimates["high_estimate_gaap"] = gaap_high[0].get(
+ "dataitemvalue"
+ )
+ mean = seek_object["eps_normalized_consensus_mean"].get(str(i))
+ if mean:
+ mean = mean[0].get("dataitemvalue")
+ eps_estimates["mean"] = mean
+ gaap_mean = seek_object["eps_gaap_consensus_mean"].get(str(i))
+ if gaap_mean:
+ eps_estimates["mean_gaap"] = gaap_mean[0].get("dataitemvalue")
+ # Calculate the estimated growth percent.
+ this = float(mean) if mean else None
+ prev = None
+ percent = None
+ try:
+ prev = float(
+ seek_object["eps_normalized_actual"][str(i - 1)][0].get(
+ "dataitemvalue"
+ )
+ )
+ except KeyError:
+ prev = float(
+ seek_object["eps_normalized_consensus_mean"][str(i - 1)][0].get(
+ "dataitemvalue"
+ )
+ )
+ if this and prev:
+ percent = (this - prev) / prev
+ eps_estimates["period_growth"] = percent
+ results.append(SAForwardEpsEstimatesData.model_validate(eps_estimates))
+
+ return results