[Enhancement] Allow Multiple Symbols For Intrinio Key Metrics (#6186)

* improve intrinio key metrics

* type thing

* some thing

* ruff
This commit is contained in:
Danglewood
2024-03-11 10:20:31 -07:00
committed by GitHub
parent 875969a1c9
commit 0115feb833
4 changed files with 1368 additions and 104 deletions

View File

@@ -1,6 +1,6 @@
"""Key Metrics Standard Model."""
from typing import List, Literal, Optional, Set, Union
from typing import Literal, Optional
from pydantic import Field, field_validator
@@ -48,11 +48,3 @@ class KeyMetricsData(Data):
pe_ratio: Optional[float] = Field(
default=None, description="Price-to-earnings ratio (P/E ratio)"
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)]) if v else None

View File

@@ -2412,7 +2412,7 @@ class ROUTER_equity_fundamental(Container):
symbol: Annotated[
Union[str, List[str]],
OpenBBCustomParameter(
description="Symbol to get data for. Multiple items allowed for provider(s): fmp, yfinance."
description="Symbol to get data for. Multiple items allowed for provider(s): fmp, intrinio, yfinance."
),
],
period: Annotated[
@@ -2436,7 +2436,7 @@ class ROUTER_equity_fundamental(Container):
Parameters
----------
symbol : Union[str, List[str]]
Symbol to get data for. Multiple items allowed for provider(s): fmp, yfinance.
Symbol to get data for. Multiple items allowed for provider(s): fmp, intrinio, yfinance.
period : Optional[Literal['annual', 'quarter']]
Time period of the data to return.
limit : Optional[int]
@@ -2494,8 +2494,8 @@ class ROUTER_equity_fundamental(Container):
Shareholders equity per share (provider: fmp)
interest_debt_per_share : Optional[float]
Interest debt per share (provider: fmp)
enterprise_value : Optional[Union[float, int]]
Enterprise value (provider: fmp, yfinance)
enterprise_value : Optional[int]
Enterprise value (provider: fmp, intrinio, yfinance)
price_to_sales_ratio : Optional[float]
Price-to-sales ratio (provider: fmp)
pocf_ratio : Optional[float]
@@ -2515,7 +2515,8 @@ class ROUTER_equity_fundamental(Container):
ev_to_free_cash_flow : Optional[float]
Enterprise value-to-free cash flow ratio (provider: fmp)
earnings_yield : Optional[float]
Earnings yield (provider: fmp)
Earnings yield (provider: fmp);
Earnings yield, as a normalized percent. (provider: intrinio)
free_cash_flow_yield : Optional[float]
Free cash flow yield (provider: fmp)
debt_to_equity : Optional[float]
@@ -2586,15 +2587,73 @@ class ROUTER_equity_fundamental(Container):
Return on equity (provider: fmp)
capex_per_share : Optional[float]
Capital expenditures per share (provider: fmp)
price_to_book : Optional[float]
Price to book ratio. (provider: intrinio, yfinance)
price_to_tangible_book : Optional[float]
Price to tangible book ratio. (provider: intrinio)
price_to_revenue : Optional[float]
Price to revenue ratio. (provider: intrinio)
quick_ratio : Optional[float]
Quick ratio. (provider: intrinio, yfinance)
gross_margin : Optional[float]
Gross margin, as a normalized percent. (provider: intrinio, yfinance)
ebit_margin : Optional[float]
EBIT margin, as a normalized percent. (provider: intrinio)
profit_margin : Optional[float]
Profit margin, as a normalized percent. (provider: intrinio, yfinance)
eps : Optional[float]
Basic earnings per share. (provider: intrinio)
eps_growth : Optional[float]
EPS growth, as a normalized percent. (provider: intrinio)
revenue_growth : Optional[float]
Revenue growth, as a normalized percent. (provider: intrinio, yfinance)
ebitda_growth : Optional[float]
EBITDA growth, as a normalized percent. (provider: intrinio)
ebit_growth : Optional[float]
EBIT growth, as a normalized percent. (provider: intrinio)
net_income_growth : Optional[float]
Net income growth, as a normalized percent. (provider: intrinio)
free_cash_flow_to_firm_growth : Optional[float]
Free cash flow to firm growth, as a normalized percent. (provider: intrinio)
invested_capital_growth : Optional[float]
Invested capital growth, as a normalized percent. (provider: intrinio)
return_on_assets : Optional[float]
Return on assets, as a normalized percent. (provider: intrinio, yfinance)
return_on_equity : Optional[float]
Return on equity, as a normalized percent. (provider: intrinio, yfinance)
return_on_invested_capital : Optional[float]
Return on invested capital, as a normalized percent. (provider: intrinio)
ebitda : Optional[int]
Earnings before interest, taxes, depreciation, and amortization. (provider: intrinio)
ebit : Optional[int]
Earnings before interest and taxes. (provider: intrinio)
long_term_debt : Optional[int]
Long-term debt. (provider: intrinio)
total_debt : Optional[int]
Total debt. (provider: intrinio)
total_capital : Optional[int]
The sum of long-term debt and total shareholder equity. (provider: intrinio)
free_cash_flow_to_firm : Optional[int]
Free cash flow to firm. (provider: intrinio)
altman_z_score : Optional[float]
Altman Z-score. (provider: intrinio)
beta : Optional[float]
Beta relative to the broad market calculated on a rolling three-year basis. (provider: intrinio);
Beta relative to the broad market (rolling three-year). (provider: intrinio);
Beta relative to the broad market (5-year monthly). (provider: yfinance)
volume : Optional[float]
Volume (provider: intrinio)
fifty_two_week_high : Optional[float]
last_price : Optional[float]
Last price of the stock. (provider: intrinio)
year_high : Optional[float]
52 week high (provider: intrinio)
fifty_two_week_low : Optional[float]
year_low : Optional[float]
52 week low (provider: intrinio)
volume_avg : Optional[int]
Average daily volume. (provider: intrinio)
short_interest : Optional[int]
Number of shares reported as sold short. (provider: intrinio)
shares_outstanding : Optional[int]
Weighted average shares outstanding (TTM). (provider: intrinio)
days_to_cover : Optional[float]
Days to cover short interest, based on average daily volume. (provider: intrinio)
forward_pe : Optional[float]
Forward price-to-earnings ratio. (provider: yfinance)
peg_ratio : Optional[float]
@@ -2611,30 +2670,16 @@ class ROUTER_equity_fundamental(Container):
Earnings growth (Year Over Year), as a normalized percent. (provider: yfinance)
earnings_growth_quarterly : Optional[float]
Quarterly earnings growth (Year Over Year), as a normalized percent. (provider: yfinance)
revenue_growth : Optional[float]
Revenue growth (Year Over Year), as a normalized percent. (provider: yfinance)
enterprise_to_revenue : Optional[float]
Enterprise value to revenue ratio. (provider: yfinance)
quick_ratio : Optional[float]
Quick ratio. (provider: yfinance)
gross_margin : Optional[float]
Gross margin, as a normalized percent. (provider: yfinance)
operating_margin : Optional[float]
Operating margin, as a normalized percent. (provider: yfinance)
ebitda_margin : Optional[float]
EBITDA margin, as a normalized percent. (provider: yfinance)
profit_margin : Optional[float]
Profit margin, as a normalized percent. (provider: yfinance)
return_on_assets : Optional[float]
Return on assets, as a normalized percent. (provider: yfinance)
return_on_equity : Optional[float]
Return on equity, as a normalized percent. (provider: yfinance)
dividend_yield_5y_avg : Optional[float]
5-year average dividend yield, as a normalized percent. (provider: yfinance)
book_value : Optional[float]
Book value per share. (provider: yfinance)
price_to_book : Optional[float]
Price-to-book ratio. (provider: yfinance)
overall_risk : Optional[float]
Overall risk score. (provider: yfinance)
audit_risk : Optional[float]
@@ -2673,7 +2718,11 @@ class ROUTER_equity_fundamental(Container):
"limit": limit,
},
extra_params=kwargs,
extra_info={"symbol": {"multiple_items_allowed": ["fmp", "yfinance"]}},
extra_info={
"symbol": {
"multiple_items_allowed": ["fmp", "intrinio", "yfinance"]
}
},
)
)

View File

@@ -1,12 +1,18 @@
"""Intrinio Key Metrics Model."""
from typing import Any, Dict, List, Optional
# pylint: disable=unused-argument
import asyncio
from typing import Any, Dict, List, Optional
from warnings import warn
from openbb_core.provider.abstract.data import ForceInt
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.key_metrics import (
KeyMetricsData,
KeyMetricsQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
ClientResponse,
amake_requests,
@@ -15,15 +21,13 @@ from pydantic import Field
class IntrinioKeyMetricsQueryParams(KeyMetricsQueryParams):
"""Intrinio Key Metrics Query.
Source: https://data.intrinio.com/data-tag/beta
https://data.intrinio.com/data-tag/volume
https://data.intrinio.com/data-tag/52_week_high
https://data.intrinio.com/data-tag/52_week_low
https://data.intrinio.com/data-tag/dividendyield
https://data.intrinio.com/data-tag/pricetoearnings
"""
Intrinio Key Metrics Query.
Source: https://data.intrinio.com/data-tags/
"""
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
class IntrinioKeyMetricsData(KeyMetricsData):
@@ -32,26 +36,200 @@ class IntrinioKeyMetricsData(KeyMetricsData):
__alias_dict__ = {
"market_cap": "marketcap",
"pe_ratio": "pricetoearnings",
"price_to_book": "pricetobook",
"price_to_tangible_book": "pricetotangiblebook",
"price_to_revenue": "pricetorevenue",
"long_term_debt": "ltdebtandcapleases",
"total_debt": "debt",
"total_capital": "totalcapital",
"enterprise_value": "enterprisevalue",
"eps_growth": "epsgrowth",
"ebit_growth": "ebitgrowth",
"ebitda_growth": "ebitdagrowth",
"revenue_growth": "revenuegrowth",
"net_income_growth": "netincomegrowth",
"free_cash_flow_to_firm_growth": "fcffgrowth",
"invested_capital_growth": "investedcapitalgrowth",
"quick_ratio": "quickratio",
"gross_margin": "grossmargin",
"ebit_margin": "ebitmargin",
"profit_margin": "profitmargin",
"return_on_assets": "roa",
"return_on_equity": "roe",
"return_on_invested_capital": "roic",
"free_cash_flow_to_firm": "freecashflow",
"altman_z_score": "altmanzscore",
"earnings_yield": "earningsyield",
"dividend_yield": "dividendyield",
"year_high": "52_week_high",
"year_low": "52_week_low",
"volume_avg": "average_daily_volume",
"shares_outstanding": "adjweightedavebasicsharesos",
"eps": "basiceps",
}
beta: float = Field(
description="Beta relative to the broad market calculated on a rolling three-year basis."
price_to_book: Optional[float] = Field(
default=None,
description="Price to book ratio.",
)
volume: float = Field(description="Volume")
fifty_two_week_high: float = Field(description="52 week high", alias="52_week_high")
fifty_two_week_low: float = Field(description="52 week low", alias="52_week_low")
dividend_yield: float = Field(
price_to_tangible_book: Optional[float] = Field(
default=None,
description="Price to tangible book ratio.",
)
price_to_revenue: Optional[float] = Field(
default=None,
description="Price to revenue ratio.",
)
quick_ratio: Optional[float] = Field(
default=None,
description="Quick ratio.",
)
gross_margin: Optional[float] = Field(
default=None,
description="Gross margin, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ebit_margin: Optional[float] = Field(
default=None,
description="EBIT margin, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
profit_margin: Optional[float] = Field(
default=None,
description="Profit margin, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
eps: Optional[float] = Field(
default=None,
description="Basic earnings per share.",
)
eps_growth: Optional[float] = Field(
default=None,
description="EPS growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
revenue_growth: Optional[float] = Field(
default=None,
description="Revenue growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ebitda_growth: Optional[float] = Field(
default=None,
description="EBITDA growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ebit_growth: Optional[float] = Field(
default=None,
description="EBIT growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
net_income_growth: Optional[float] = Field(
default=None,
description="Net income growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
free_cash_flow_to_firm_growth: Optional[float] = Field(
default=None,
description="Free cash flow to firm growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
invested_capital_growth: Optional[float] = Field(
default=None,
description="Invested capital growth, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
return_on_assets: Optional[float] = Field(
default=None,
description="Return on assets, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
return_on_equity: Optional[float] = Field(
default=None,
description="Return on equity, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
return_on_invested_capital: Optional[float] = Field(
default=None,
description="Return on invested capital, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ebitda: Optional[ForceInt] = Field(
default=None,
description="Earnings before interest, taxes, depreciation, and amortization.",
)
ebit: Optional[ForceInt] = Field(
default=None,
description="Earnings before interest and taxes.",
)
long_term_debt: Optional[ForceInt] = Field(
default=None,
description="Long-term debt.",
)
total_debt: Optional[ForceInt] = Field(
default=None,
description="Total debt.",
)
total_capital: Optional[ForceInt] = Field(
default=None,
description="The sum of long-term debt and total shareholder equity.",
)
enterprise_value: Optional[ForceInt] = Field(
default=None,
description="Enterprise value.",
)
free_cash_flow_to_firm: Optional[ForceInt] = Field(
default=None,
description="Free cash flow to firm.",
)
altman_z_score: Optional[float] = Field(
default=None,
description="Altman Z-score.",
)
beta: Optional[float] = Field(
default=None,
description="Beta relative to the broad market (rolling three-year).",
)
dividend_yield: Optional[float] = Field(
default=None,
description="Dividend yield, as a normalized percent.",
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
alias="dividendyield",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
earnings_yield: Optional[float] = Field(
default=None,
description="Earnings yield, as a normalized percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
last_price: Optional[float] = Field(
default=None,
description="Last price of the stock.",
)
year_high: Optional[float] = Field(
default=None,
description="52 week high",
)
year_low: float = Field(default=None, description="52 week low")
volume_avg: Optional[ForceInt] = Field(
default=None,
description="Average daily volume.",
)
short_interest: Optional[ForceInt] = Field(
default=None,
description="Number of shares reported as sold short.",
)
shares_outstanding: Optional[ForceInt] = Field(
default=None,
description="Weighted average shares outstanding (TTM).",
)
days_to_cover: Optional[float] = Field(
default=None,
description="Days to cover short interest, based on average daily volume.",
)
class IntrinioKeyMetricsFetcher(
Fetcher[
IntrinioKeyMetricsQueryParams,
IntrinioKeyMetricsData,
List[IntrinioKeyMetricsData],
]
):
"""Transform the query, extract and transform the data from the Intrinio endpoints."""
@@ -59,44 +237,128 @@ class IntrinioKeyMetricsFetcher(
@staticmethod
def transform_query(params: Dict[str, Any]) -> IntrinioKeyMetricsQueryParams:
"""Transform the query params."""
if params.get("period") is not None and params.get("period") != "annual":
warn(
"The period parameter is not available for this Intrinio endpoint, it will be ignored."
)
return IntrinioKeyMetricsQueryParams(**params)
@staticmethod
async def aextract_data(
query: IntrinioKeyMetricsQueryParams, # pylint: disable=unused-argument
query: IntrinioKeyMetricsQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> Dict:
) -> List[Dict]:
"""Return the raw data from the Intrinio endpoint."""
api_key = credentials.get("intrinio_api_key") if credentials else ""
tags = [
"beta",
"volume",
"average_daily_volume",
"basiceps",
"basicdilutedeps",
"marketcap",
"last_price",
"52_week_high",
"52_week_low",
"dividendyield",
"pricetoearnings",
"pricetorevenue",
"pricetobook",
"pricetotangiblebook",
"grossmargin",
"ebitmargin",
"profitmargin",
"roa",
"roe",
"roic",
"earningsyield",
"days_to_cover",
"short_interest",
"revenuegrowth",
"ebitgrowth",
"ebitdagrowth",
"epsgrowth",
"netincomegrowth",
"investedcapitalgrowth",
"debt",
"totalcapital",
"enterprisevalue",
"freecashflow",
"fcffgrowth",
"ltdebtandcapleases",
"altmanzscore",
"quickratio",
"ebit",
"ebitda",
"adjweightedavebasicsharesos",
]
results = []
urls = []
symbols = query.symbol.split(",")
urls = [
f"https://api-v2.intrinio.com/companies/{query.symbol}/data_point/{tag}?api_key={api_key}"
for tag in tags
]
async def get_one(symbol: str):
"""Get data for one symbol."""
async def callback(response: ClientResponse, _: Any) -> Dict:
"""Return the response."""
return {response.url.parts[-1]: await response.json()}
_urls = [
f"https://api-v2.intrinio.com/companies/{symbol}/data_point/{tag}?api_key={api_key}"
for tag in tags
]
urls.extend(_urls)
data: Dict = {"symbol": query.symbol}
for result in await amake_requests(urls, callback, **kwargs):
data.update(result)
async def callback(response: ClientResponse, _: Any) -> Dict:
"""Return the response."""
return {response.url.parts[-1]: await response.json()}
return data
data = {}
for result in await amake_requests(urls, callback, **kwargs):
data.update(result)
if data:
data["symbol"] = symbol
results.append(data)
tasks = [get_one(symbol) for symbol in symbols]
await asyncio.gather(*tasks)
if not results:
raise EmptyDataError()
return results
@staticmethod
def transform_data(
query: IntrinioKeyMetricsQueryParams, data: List[Dict], **kwargs: Any
) -> IntrinioKeyMetricsData:
"""Return the transformed data."""
return IntrinioKeyMetricsData.model_validate(data)
query: IntrinioKeyMetricsQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[IntrinioKeyMetricsData]:
"""Validate and transform the data."""
# Sort the results by the order of the symbols in the query.
symbols = query.symbol.split(",")
data = sorted(
data,
key=(
lambda item: (
symbols.index(item["symbol"])
if item["symbol"] in symbols
else len(symbols)
)
),
)
results: List[IntrinioKeyMetricsData] = []
for item in data:
if item.get("marketcap") is None or isinstance(item.get("marketcap"), dict):
warn(f"Symbol Error: No data found for {item.get('symbol')}")
continue
for key, value in item.copy().items():
# A bad response in a field will return a dict here, so we remove it.
if isinstance(value, dict):
_ = item.pop(key)
results.append(IntrinioKeyMetricsData.model_validate(item))
return results