diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py index 7e4b21b69a1..155c38b1a5b 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_api.py +++ b/openbb_platform/extensions/equity/integration/test_equity_api.py @@ -964,7 +964,7 @@ def test_equity_news(params, headers): @pytest.mark.parametrize( "params", - [({"symbol": "AAPL", "limit": 100})], + [({"symbol": "AAPL", "limit": 100, "provider": "fmp"})], ) @pytest.mark.integration def test_equity_fundamental_multiples(params, headers): @@ -977,6 +977,52 @@ def test_equity_fundamental_multiples(params, headers): assert result.status_code == 200 +@pytest.mark.parametrize( + "params", + [ + ({"query": "ebit", "limit": 100, "provider": "intrinio"}), + ], +) +@pytest.mark.integration +def test_equity_fundamental_search_financial_attributes(params, obb): + 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/fundamental/search_financial_attributes?{query_str}" + result = requests.get(url, headers=headers, timeout=10) + assert isinstance(result, requests.Response) + assert result.status_code == 200 + + +@pytest.mark.parametrize( + "params", + [ + ( + { + "provider": "intrinio", + "symbol": "AAPL", + "tag": "ebit", + "period": "annual", + "limit": 1000, + "type": None, + "start_date": "2013-01-01", + "end_date": "2023-01-01", + "sort": "desc", + } + ), + ], +) +@pytest.mark.integration +def test_equity_fundamental_financial_attributes(params, obb): + 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/fundamental/financial_attributes?{query_str}" + result = requests.get(url, headers=headers, timeout=10) + assert isinstance(result, requests.Response) + assert result.status_code == 200 + + @pytest.mark.parametrize( "params", [ diff --git a/openbb_platform/extensions/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py index e5422a28ca9..aa254ba8c4c 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_python.py +++ b/openbb_platform/extensions/equity/integration/test_equity_python.py @@ -894,7 +894,7 @@ def test_equity_news(params, obb): @pytest.mark.parametrize( "params", [ - ({"symbol": "AAPL", "limit": 100}), + ({"symbol": "AAPL", "limit": 100, "provider": "fmp"}), ], ) @pytest.mark.integration @@ -905,6 +905,46 @@ def test_equity_fundamental_multiples(params, obb): assert len(result.results) > 0 +@pytest.mark.parametrize( + "params", + [ + ({"query": "ebit", "limit": 100, "provider": "intrinio"}), + ], +) +@pytest.mark.integration +def test_equity_fundamental_search_financial_attributes(params, obb): + result = obb.equity.fundamental.search_financial_attributes(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + + +@pytest.mark.parametrize( + "params", + [ + ( + { + "provider": "intrinio", + "symbol": "AAPL", + "tag": "ebit", + "period": "annual", + "limit": 1000, + "type": None, + "start_date": "2013-01-01", + "end_date": "2023-01-01", + "sort": "desc", + } + ), + ], +) +@pytest.mark.integration +def test_equity_fundamental_financial_attributes(params, obb): + result = obb.equity.fundamental.financial_attributes(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + + @pytest.mark.parametrize( "params", [ diff --git a/openbb_platform/extensions/equity/openbb_equity/fundamental/fundamental_router.py b/openbb_platform/extensions/equity/openbb_equity/fundamental/fundamental_router.py index 41e0c829a7c..bf3ed36464a 100644 --- a/openbb_platform/extensions/equity/openbb_equity/fundamental/fundamental_router.py +++ b/openbb_platform/extensions/equity/openbb_equity/fundamental/fundamental_router.py @@ -103,6 +103,28 @@ def employee_count( return OBBject(results=Query(**locals()).execute()) +@router.command(model="SearchFinancialAttributes") +def search_financial_attributes( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject[BaseModel]: + """Search financial attributes for financial statements.""" + return OBBject(results=Query(**locals()).execute()) + + +@router.command(model="FinancialAttributes") +def financial_attributes( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject[BaseModel]: + """Fetch the value of financial attributes for a selected company and fiscal period.""" + return OBBject(results=Query(**locals()).execute()) + + @router.command(model="IncomeStatement") def income( cc: CommandContext, diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/balance_sheet.py b/openbb_platform/platform/provider/openbb_provider/standard_models/balance_sheet.py index 165a50915ac..e086c26e022 100644 --- a/openbb_platform/platform/provider/openbb_provider/standard_models/balance_sheet.py +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/balance_sheet.py @@ -62,9 +62,7 @@ class BalanceSheetData(Data): accepted_date: Optional[datetime] = Field( default=None, description="Accepted date." ) - period: Optional[str] = Field( - default=None, description="Reporting period of the statement." - ) + period: str = Field(default=None, description="Reporting period of the statement.") cash_and_cash_equivalents: Optional[StrictFloat] = Field( default=None, description="Cash and cash equivalents" diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/cash_flow.py b/openbb_platform/platform/provider/openbb_provider/standard_models/cash_flow.py index a702110e461..a36dacd1214 100644 --- a/openbb_platform/platform/provider/openbb_provider/standard_models/cash_flow.py +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/cash_flow.py @@ -51,9 +51,7 @@ class CashFlowStatementData(Data): default=None, description=DATA_DESCRIPTIONS.get("symbol", "") ) date: dateType = Field(description=DATA_DESCRIPTIONS.get("date" "")) - period: Optional[str] = Field( - default=None, description="Reporting period of the statement." - ) + period: str = Field(default=None, description="Reporting period of the statement.") cik: Optional[str] = Field( default=None, description="Central Index Key (CIK) of the company." ) diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/financial_attributes.py b/openbb_platform/platform/provider/openbb_provider/standard_models/financial_attributes.py new file mode 100644 index 00000000000..d26eece4429 --- /dev/null +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/financial_attributes.py @@ -0,0 +1,42 @@ +"""Financial Attributes standard model.""" + +from datetime import date as dateType +from typing import Literal, Optional + +from pydantic import Field + +from openbb_provider.abstract.data import Data +from openbb_provider.abstract.query_params import QueryParams +from openbb_provider.utils.descriptions import DATA_DESCRIPTIONS, QUERY_DESCRIPTIONS + + +class FinancialAttributesQueryParams(QueryParams): + """Financial Attributes Query.""" + + symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol")) + tag: str = Field(description=QUERY_DESCRIPTIONS.get("tag")) + period: Optional[Literal["annual", "quarter"]] = Field( + default="annual", description=QUERY_DESCRIPTIONS.get("period") + ) + limit: Optional[int] = Field( + default=1000, description=QUERY_DESCRIPTIONS.get("limit") + ) + type: Optional[str] = Field( + default=None, description="Filter by type, when applicable." + ) + start_date: Optional[dateType] = Field( + default=None, description=QUERY_DESCRIPTIONS.get("start_date") + ) + end_date: Optional[dateType] = Field( + default=None, description=QUERY_DESCRIPTIONS.get("end_date") + ) + sort: Optional[Literal["asc", "desc"]] = Field( + default="desc", description="Sort order." + ) + + +class FinancialAttributesData(Data): + """Financial Attributes Data.""" + + date: dateType = Field(description=DATA_DESCRIPTIONS.get("date")) + value: Optional[float] = Field(default=None, description="The value of the data.") diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/income_statement.py b/openbb_platform/platform/provider/openbb_provider/standard_models/income_statement.py index 17ebc08a076..9758ae2d037 100644 --- a/openbb_platform/platform/provider/openbb_provider/standard_models/income_statement.py +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/income_statement.py @@ -54,9 +54,7 @@ class IncomeStatementData(Data): description=DATA_DESCRIPTIONS.get("date", "") + " In this case, the date of the income statement." ) - period: Optional[str] = Field( - default=None, description="Period of the income statement." - ) + period: str = Field(default=None, description="Period of the income statement.") cik: Optional[str] = Field(default=None, description="Central Index Key.") revenue: Optional[StrictFloat] = Field(default=None, description="Revenue.") @@ -95,6 +93,10 @@ class IncomeStatementData(Data): depreciation_and_amortization: Optional[StrictFloat] = Field( default=None, description="Depreciation and amortization." ) + ebit: Optional[StrictFloat] = Field( + default=None, + description="Earnings before interest, and taxes.", + ) ebitda: Optional[StrictFloat] = Field( default=None, description="Earnings before interest, taxes, depreciation and amortization.", diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/search_financial_attributes.py b/openbb_platform/platform/provider/openbb_provider/standard_models/search_financial_attributes.py new file mode 100644 index 00000000000..db3d39a689c --- /dev/null +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/search_financial_attributes.py @@ -0,0 +1,49 @@ +"""Search Financial Attributes standard model.""" + +from typing import Optional + +from pydantic import Field + +from openbb_provider.abstract.data import Data +from openbb_provider.abstract.query_params import QueryParams +from openbb_provider.utils.descriptions import QUERY_DESCRIPTIONS + + +class SearchFinancialAttributesQueryParams(QueryParams): + """Search Financial Attributes Query.""" + + query: str = Field(description="Query to search for.") + limit: Optional[int] = Field( + default=1000, description=QUERY_DESCRIPTIONS.get("limit") + ) + + +class SearchFinancialAttributesData(Data): + """Search Financial Attributes Data.""" + + id: str = Field(description="ID of the financial attribute.") + name: str = Field(description="Name of the financial attribute.") + tag: str = Field(description="Tag of the financial attribute.") + statement_code: str = Field(description="Code of the financial statement.") + statement_type: Optional[str] = Field( + default=None, description="Type of the financial statement." + ) + parent_name: Optional[str] = Field( + default=None, description="Parent's name of the financial attribute." + ) + sequence: Optional[int] = Field( + default=None, description="Sequence of the financial statement." + ) + factor: Optional[str] = Field( + default=None, description="Unit of the financial attribute." + ) + transaction: Optional[str] = Field( + default=None, + description="Transaction type (credit/debit) of the financial attribute.", + ) + type: Optional[str] = Field( + default=None, description="Type of the financial attribute." + ) + unit: Optional[str] = Field( + default=None, description="Unit of the financial attribute." + ) diff --git a/openbb_platform/platform/provider/openbb_provider/utils/descriptions.py b/openbb_platform/platform/provider/openbb_provider/utils/descriptions.py index 4681cf91a02..0199db073f0 100644 --- a/openbb_platform/platform/provider/openbb_provider/utils/descriptions.py +++ b/openbb_platform/platform/provider/openbb_provider/utils/descriptions.py @@ -5,8 +5,6 @@ QUERY_DESCRIPTIONS = { "start_date": "Start date of the data, in YYYY-MM-DD format.", "end_date": "End date of the data, in YYYY-MM-DD format.", "interval": "Time interval of the data to return.", - # "weekly": "Whether to return weekly data.", - # "monthly": "Whether to return monthly data.", "period": "Time period of the data to return.", "date": "A specific date to get data for.", "limit": "The number of data entries to return.", diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py b/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py index 4af7b141ec8..1e6065e7f53 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/__init__.py @@ -7,11 +7,17 @@ from openbb_intrinio.models.company_news import IntrinioCompanyNewsFetcher from openbb_intrinio.models.currency_pairs import IntrinioCurrencyPairsFetcher from openbb_intrinio.models.equity_historical import IntrinioEquityHistoricalFetcher from openbb_intrinio.models.equity_quote import IntrinioEquityQuoteFetcher +from openbb_intrinio.models.financial_attributes import ( + IntrinioFinancialAttributesFetcher, +) from openbb_intrinio.models.fred_indices import IntrinioFredIndicesFetcher from openbb_intrinio.models.global_news import IntrinioGlobalNewsFetcher from openbb_intrinio.models.income_statement import IntrinioIncomeStatementFetcher from openbb_intrinio.models.options_chains import IntrinioOptionsChainsFetcher from openbb_intrinio.models.options_unusual import IntrinioOptionsUnusualFetcher +from openbb_intrinio.models.search_financial_attributes import ( + IntrinioSearchFinancialAttributesFetcher, +) from openbb_provider.abstract.provider import Provider intrinio_provider = Provider( @@ -28,10 +34,12 @@ intrinio_provider = Provider( "CurrencyPairs": IntrinioCurrencyPairsFetcher, "EquityHistorical": IntrinioEquityHistoricalFetcher, "EquityQuote": IntrinioEquityQuoteFetcher, + "FinancialAttributes": IntrinioFinancialAttributesFetcher, "FredIndices": IntrinioFredIndicesFetcher, "GlobalNews": IntrinioGlobalNewsFetcher, "IncomeStatement": IntrinioIncomeStatementFetcher, "OptionsChains": IntrinioOptionsChainsFetcher, "OptionsUnusual": IntrinioOptionsUnusualFetcher, + "SearchFinancialAttributes": IntrinioSearchFinancialAttributesFetcher, }, ) diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/balance_sheet.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/balance_sheet.py index 09a15ae60a8..750144389c6 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/balance_sheet.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/balance_sheet.py @@ -85,24 +85,16 @@ class IntrinioBalanceSheetFetcher( def get_financial_statement_data(period: str, data: List[Dict]) -> None: statement_data: Dict = {} - calculations_data: Dict = {} intrinio_id = f"{query.symbol}-{statement_code}-{period}" statement_url = f"{base_url}/fundamentals/{intrinio_id}/standardized_financials?api_key={api_key}" statement_data = get_data_one(statement_url, **kwargs) - intrinio_id = f"{query.symbol}-calculations-{period}" - calculations_url = f"{base_url}/fundamentals/{intrinio_id}/standardized_financials?api_key={api_key}" - calculations_data = get_data_one(calculations_url, **kwargs) - data.append( { "date": statement_data["fundamental"]["end_date"], "period": statement_data["fundamental"]["fiscal_period"], - "cik": statement_data["fundamental"]["company"]["cik"], - "symbol": statement_data["fundamental"]["company"]["ticker"], - "financials": statement_data["standardized_financials"] - + calculations_data["standardized_financials"], + "financials": statement_data["standardized_financials"], } ) @@ -122,13 +114,11 @@ class IntrinioBalanceSheetFetcher( sub_dict: Dict[str, Any] = {} for sub_item in item["financials"]: - field_name = alias_generators.to_snake(sub_item["data_tag"]["tag"]) + field_name = alias_generators.to_snake(sub_item["data_tag"]["name"]) sub_dict[field_name] = float(sub_item["value"]) sub_dict["date"] = item["date"] sub_dict["period"] = item["period"] - sub_dict["cik"] = item["cik"] - sub_dict["symbol"] = item["symbol"] # Intrinio does not return Q4 data but FY data instead if query.period == "quarter" and item["period"] == "FY": diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/cash_flow.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/cash_flow.py index a3c2ee9208f..c23f010a456 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/cash_flow.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/cash_flow.py @@ -101,24 +101,16 @@ class IntrinioCashFlowStatementFetcher( def get_financial_statement_data(period: str, data: List[Dict]) -> None: statement_data: Dict = {} - calculations_data: Dict = {} intrinio_id = f"{query.symbol}-{statement_code}-{period}" statement_url = f"{base_url}/fundamentals/{intrinio_id}/standardized_financials?api_key={api_key}" statement_data = get_data_one(statement_url, **kwargs) - intrinio_id = f"{query.symbol}-calculations-{period}" - calculations_url = f"{base_url}/fundamentals/{intrinio_id}/standardized_financials?api_key={api_key}" - calculations_data = get_data_one(calculations_url, **kwargs) - data.append( { "date": statement_data["fundamental"]["end_date"], "period": statement_data["fundamental"]["fiscal_period"], - "cik": statement_data["fundamental"]["company"]["cik"], - "symbol": statement_data["fundamental"]["company"]["ticker"], - "financials": statement_data["standardized_financials"] - + calculations_data["standardized_financials"], + "financials": statement_data["standardized_financials"], } ) @@ -143,8 +135,6 @@ class IntrinioCashFlowStatementFetcher( sub_dict["date"] = item["date"] sub_dict["period"] = item["period"] - sub_dict["cik"] = item["cik"] - sub_dict["symbol"] = item["symbol"] # Intrinio does not return Q4 data but FY data instead if query.period == "quarter" and item["period"] == "FY": diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/financial_attributes.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/financial_attributes.py new file mode 100644 index 00000000000..996c60f4f14 --- /dev/null +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/financial_attributes.py @@ -0,0 +1,78 @@ +"""Intrinio Financial Attributes model.""" + +from datetime import datetime +from typing import Any, Dict, List, Optional + +from dateutil.relativedelta import relativedelta +from openbb_intrinio.utils.helpers import get_data_many +from openbb_provider.abstract.fetcher import Fetcher +from openbb_provider.standard_models.financial_attributes import ( + FinancialAttributesData, + FinancialAttributesQueryParams, +) +from openbb_provider.utils.helpers import get_querystring + + +class IntrinioFinancialAttributesQueryParams(FinancialAttributesQueryParams): + """Intrinio Financial Attributes Query.""" + + __alias_dict__ = {"sort": "sort_order", "limit": "page_size"} + + +class IntrinioFinancialAttributesData(FinancialAttributesData): + """Intrinio Financial Attributes Data.""" + + +class IntrinioFinancialAttributesFetcher( + Fetcher[ + IntrinioFinancialAttributesQueryParams, + List[IntrinioFinancialAttributesData], + ] +): + """Transform the query, extract and transform the data from the Intrinio endpoints.""" + + @staticmethod + def transform_query( + params: Dict[str, Any] + ) -> IntrinioFinancialAttributesQueryParams: + """Transform the query params.""" + transformed_params = params + + now = datetime.now().date() + if params.get("start_date") is None: + transformed_params["start_date"] = now - relativedelta(years=5) + + if params.get("end_date") is None: + transformed_params["end_date"] = now + + return IntrinioFinancialAttributesQueryParams(**transformed_params) + + @staticmethod + def extract_data( + query: IntrinioFinancialAttributesQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> List[Dict]: + """Return the raw data from the Intrinio endpoint.""" + api_key = credentials.get("intrinio_api_key") if credentials else "" + frequency = "yearly" if query.period == "annual" else "quarterly" + data: List[Dict] = [] + + base_url = "https://api-v2.intrinio.com" + query_str = get_querystring(query.model_dump(by_alias=True), ["frequency"]) + query_str = f"{query_str}&frequency={frequency}" + + url = f"{base_url}/historical_data/{query.symbol}/{query.tag}?{query_str}&api_key={api_key}" + # data = get_data_one(url).get("historical_data", []) + data = get_data_many(url, "historical_data") + + return data + + @staticmethod + def transform_data( + query: IntrinioFinancialAttributesQueryParams, + data: List[Dict], + **kwargs: Any, + ) -> List[IntrinioFinancialAttributesData]: + """Return the transformed data.""" + return [IntrinioFinancialAttributesData.model_validate(item) for item in data] diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/income_statement.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/income_statement.py index 46c98ab4c41..e374810f023 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/income_statement.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/income_statement.py @@ -28,6 +28,9 @@ class IntrinioIncomeStatementData(IncomeStatementData): __alias_dict__ = { "research_and_development_expenses": "ResearchAndDevelopmentExpense", "selling_general_and_administrative_expenses": "SellingGeneralAndAdministrativeExpense", + "ebit": "earnings before interest and taxes (ebit)", + "ebitda": "earnings before interest, taxes, depreciation and amortization (ebitda)", + "ebitda_margin": "ebitda margin", "operating_income": "OperatingIncomeLoss", "income_before_tax": "IncomeLossFromContinuingOperationsBeforeIncomeTaxesExtraordinaryItemsNoncontrollingInterest", # noqa: E501 "eps_diluted": "EarningsPerShareDiluted", @@ -59,6 +62,7 @@ class IntrinioIncomeStatementFetcher( api_key = credentials.get("intrinio_api_key") if credentials else "" statement_code = "income_statement" period_type = "FY" if query.period == "annual" else "QTR" + data_tags = ["ebit", "ebitda", "ebitdamargin"] fundamentals_data: Dict = {} data: List[Dict] = [] @@ -81,7 +85,7 @@ class IntrinioIncomeStatementFetcher( def get_financial_statement_data(period: str, data: List[Dict]) -> None: statement_data: Dict = {} - calculations_data: Dict = {} + calculations_data: List = [] intrinio_id = f"{query.symbol}-{statement_code}-{period}" statement_url = f"{base_url}/fundamentals/{intrinio_id}/standardized_financials?api_key={api_key}" @@ -89,16 +93,21 @@ class IntrinioIncomeStatementFetcher( intrinio_id = f"{query.symbol}-calculations-{period}" calculations_url = f"{base_url}/fundamentals/{intrinio_id}/standardized_financials?api_key={api_key}" - calculations_data = get_data_one(calculations_url, **kwargs) + calculations_data = get_data_one(calculations_url, **kwargs).get( + "standardized_financials", [] + ) + calculations_data = [ + item + for item in calculations_data + if item["data_tag"]["tag"] in data_tags + ] data.append( { "date": statement_data["fundamental"]["end_date"], "period": statement_data["fundamental"]["fiscal_period"], - "cik": statement_data["fundamental"]["company"]["cik"], - "symbol": statement_data["fundamental"]["company"]["ticker"], "financials": statement_data["standardized_financials"] - + calculations_data["standardized_financials"], + + calculations_data, } ) @@ -123,8 +132,6 @@ class IntrinioIncomeStatementFetcher( sub_dict["date"] = item["date"] sub_dict["period"] = item["period"] - sub_dict["cik"] = item["cik"] - sub_dict["symbol"] = item["symbol"] # Intrinio does not return Q4 data but FY data instead if query.period == "quarter" and item["period"] == "FY": diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/search_financial_attributes.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/search_financial_attributes.py new file mode 100644 index 00000000000..fe1d813ddd4 --- /dev/null +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/search_financial_attributes.py @@ -0,0 +1,76 @@ +"""Intrinio Search Financial Attributes model.""" + +from typing import Any, Dict, List, Optional + +from openbb_intrinio.utils.helpers import get_data_one +from openbb_provider.abstract.fetcher import Fetcher +from openbb_provider.standard_models.search_financial_attributes import ( + SearchFinancialAttributesData, + SearchFinancialAttributesQueryParams, +) +from openbb_provider.utils.helpers import get_querystring + + +class IntrinioSearchFinancialAttributesQueryParams( + SearchFinancialAttributesQueryParams +): + """Intrinio Search Financial Attributes Query.""" + + __alias_dict__ = {"limit": "page_size"} + + +class IntrinioSearchFinancialAttributesData(SearchFinancialAttributesData): + """Intrinio Search Financial Attributes Data.""" + + __alias_dict__ = { + "parent_name": "parent", + "transaction": "balance", + } + + +class IntrinioSearchFinancialAttributesFetcher( + Fetcher[ + IntrinioSearchFinancialAttributesQueryParams, + List[IntrinioSearchFinancialAttributesData], + ] +): + """Transform the query, extract and transform the data from the Intrinio endpoints.""" + + @staticmethod + def transform_query( + params: Dict[str, Any] + ) -> IntrinioSearchFinancialAttributesQueryParams: + """Transform the query params.""" + return IntrinioSearchFinancialAttributesQueryParams(**params) + + @staticmethod + def extract_data( + query: IntrinioSearchFinancialAttributesQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> List[Dict]: + """Return the raw data from the Intrinio endpoint.""" + api_key = credentials.get("intrinio_api_key") if credentials else "" + data: List[Dict] = [] + + base_url = "https://api-v2.intrinio.com" + query_str = get_querystring(query.model_dump(by_alias=True), []) + + url = f"{base_url}/data_tags/search?{query_str}&api_key={api_key}" + data = get_data_one(url).get("tags", []) + # Intrinio doesn't return the correct number of results when using the limit parameter + # Temporary fix until they fix it + data = data[: query.limit] + + return data + + @staticmethod + def transform_data( + query: IntrinioSearchFinancialAttributesQueryParams, + data: List[Dict], + **kwargs: Any, + ) -> List[IntrinioSearchFinancialAttributesData]: + """Return the transformed data.""" + return [ + IntrinioSearchFinancialAttributesData.model_validate(item) for item in data + ] diff --git a/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_financial_attributes.yaml b/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_financial_attributes.yaml new file mode 100644 index 00000000000..c932a6fa61f --- /dev/null +++ b/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_financial_attributes.yaml @@ -0,0 +1,36 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + method: GET + uri: https://api-v2.intrinio.com/historical_data/AAPL/ebit?api_key=MOCK_API_KEY&end_date=2023-01-01&frequency=yearly&page_size=1000&period=annual&provider=intrinio&sort_order=desc&start_date=2013-01-01&symbol=AAPL&tag=ebit + response: + body: + string: !!binary | + H4sIAOjTU2UAA3TQ2wrCMAwG4Hfp9ZQkPaTdq4iMokMHZYpuIoy9u7U3LaPrVWg+/qZZxH14T4/X + cPGhu/rJi/a0iFj0ohUERAdwB1KiER8f5niJ6BAkpHOEtSkxJqwLDI6A6xgSNhkbhhhds+iStYXV + LOu5aJN12TK5nYGR/1ZCkavAuro1200YlEx1q7d/Y9Ko61Yly9lqqezOvHK7Bx3fyrnnRoz9d+qe + /hZ74xzC+gMAAP//AwAceBCU4AEAAA== + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 14 Nov 2023 20:09:12 GMT + Transfer-Encoding: + - chunked + Vary: + - Origin,Accept-Encoding + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_search_financial_attributes.yaml b/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_search_financial_attributes.yaml new file mode 100644 index 00000000000..acc6324e4c0 --- /dev/null +++ b/openbb_platform/providers/intrinio/tests/record/http/test_intrinio_fetchers/test_intrinio_search_financial_attributes.yaml @@ -0,0 +1,48 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + method: GET + uri: https://api-v2.intrinio.com/data_tags/search?api_key=MOCK_API_KEY&page_size=1000&query=ebit + response: + body: + string: !!binary | + H4sIALG6U2UAA9SYa0/bMBSG/0qUT6AxAVVB0G9liSagKxcxSDtNlZOcGrPETm2nlyD++5xek7Qd + mdQm7Uf7xI4f+/Vr+7zrEmGh136968TVa3Gp08Qt3rT0I50iH1SdiTglFAvNhi7joF1TCRyE1BB1 + tSc0BKEdmFfXT4eqiWqvWoBNpCoIiST4QGXHYW7ck4M8J/SQJIyKVFyOgjjeJRRRhyBPBQPEVUSv + 0dDz1KfQC4E6MCt3kSMZn5Vs5KFEcNqbD5ITR3UVUjWcmh4KV/84SoBWoj59jMoAJdQNhRpdUaQv + o1NjkCRVFNrD8YP2nbOBfE3x9FgPz2rLBpsPZAoWAHdUlwhDmu82OjOGVgF8m1VoXrxn66VJ8QLP + AFtqkmkxplGf07mqWrKY0UU7sHiCeX3VZrTg63oMyTSagSt1b7R1tM2uWy4yho1eP6vJFXrcNzFW + ossf51kv2ShXSSbSxPeDRkKKDUbx1yfgvrZGlJ7cxx2ndHlHi8UsYfepxWw0cEalPxDHhKZU6s+q + yl67gLMukcgmHpGjz8VqRKy95C4b5dvsov0nnonrdxcZkzHqq2zGRftmoCau3rS3zlaaibYGlQRc + E+Q6X6Gwl/6ppNm6wUURlmCdJyP7/sJa0uea67SL9vHBwPEJui2KsRybca27qrtsMyvOCBft6Smh + 3kWc4m0jlnvQX1qoBUmlxkmJgBMB2jPyQpjZjnZgPh9nshP9ifPsAHRfDXX8swWwH3qSBF72dYGv + 2EXxuJuVcW5aO+oFS3kLRTjPPJnDAKiAlJYlI9MwDHdgaR3WBx7L9l9nSjXib0b20bht0M0uai5O + 5Ui3NEfu9GiSTzxSN4eAgxpjDDHOM9Z9xiWJJhUHE0c7zHjZDqz6pynHXmTWz3MkV4ufiYKzzE3r + /M9bVvoNEEL7hgJz+Oku8NSnDgpguI/7/mV0+tjOPsPLgC/BC2yrHVQTiZZ1R5m6scwOs5TA+zv0 + Msp9oj1GPkvlXYqELukUV6Z/c5Jg/klBmR242j0Hn4S+0OpCgJxDhtN4MA27624t03F1xCuA7MzD + edF1yaTqIf73eLrmc3B6tpgB/YuemAB9NpZpt+kRrPS634pb7c9OEO+H8Qx+/AUAAP//AwBIw/g6 + VhwAAA== + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 14 Nov 2023 18:21:37 GMT + Transfer-Encoding: + - chunked + Vary: + - Origin,Accept-Encoding + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py b/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py index 2b403bc23c0..6afbf3621c6 100644 --- a/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py +++ b/openbb_platform/providers/intrinio/tests/test_intrinio_fetchers.py @@ -9,11 +9,17 @@ from openbb_intrinio.models.company_news import IntrinioCompanyNewsFetcher from openbb_intrinio.models.currency_pairs import IntrinioCurrencyPairsFetcher from openbb_intrinio.models.equity_historical import IntrinioEquityHistoricalFetcher from openbb_intrinio.models.equity_quote import IntrinioEquityQuoteFetcher +from openbb_intrinio.models.financial_attributes import ( + IntrinioFinancialAttributesFetcher, +) from openbb_intrinio.models.fred_indices import IntrinioFredIndicesFetcher from openbb_intrinio.models.global_news import IntrinioGlobalNewsFetcher from openbb_intrinio.models.income_statement import IntrinioIncomeStatementFetcher from openbb_intrinio.models.options_chains import IntrinioOptionsChainsFetcher from openbb_intrinio.models.options_unusual import IntrinioOptionsUnusualFetcher +from openbb_intrinio.models.search_financial_attributes import ( + IntrinioSearchFinancialAttributesFetcher, +) test_credentials = UserService().default_user_settings.credentials.model_dump( mode="json" @@ -148,3 +154,31 @@ def test_intrinio_calendar_ipo_fetcher(credentials=test_credentials): fetcher = IntrinioCalendarIpoFetcher() result = fetcher.test(params, credentials) assert result is None + + +@pytest.mark.record_http +def test_intrinio_search_financial_attributes(credentials=test_credentials): + params = {"query": "ebit"} + + fetcher = IntrinioSearchFinancialAttributesFetcher() + result = fetcher.test(params, credentials) + assert result is None + + +@pytest.mark.record_http +def test_intrinio_financial_attributes(credentials=test_credentials): + params = { + "provider": "intrinio", + "symbol": "AAPL", + "tag": "ebit", + "period": "annual", + "limit": 1000, + "type": None, + "start_date": date(2013, 1, 1), + "end_date": date(2023, 1, 1), + "sort": "desc", + } + + fetcher = IntrinioFinancialAttributesFetcher() + result = fetcher.test(params, credentials) + assert result is None