diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json index e16d04d9f47..5020528634f 100644 --- a/openbb_platform/openbb/assets/reference.json +++ b/openbb_platform/openbb/assets/reference.json @@ -28335,8 +28335,8 @@ "standard": [ { "name": "symbol", - "type": "str", - "description": "Symbol to get data for.", + "type": "Union[str, List[str]]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): fmp.", "default": null, "optional": true, "choices": null @@ -28395,8 +28395,8 @@ "name": "symbol", "type": "str", "description": "Symbol representing the entity requested in the data.", - "default": "", - "optional": false, + "default": null, + "optional": true, "choices": null }, { @@ -28426,11 +28426,11 @@ ], "fmp": [ { - "name": "link", - "type": "str", - "description": "Link to the transaction document.", - "default": null, - "optional": true, + "name": "chamber", + "type": "Literal['house', 'senate']", + "description": "Government Chamber - House or Senate.", + "default": "", + "optional": false, "choices": null }, { @@ -28458,7 +28458,7 @@ "choices": null }, { - "name": "type", + "name": "transaction_type", "type": "str", "description": "Type of transaction (e.g., Sale, Purchase).", "default": null, @@ -28480,6 +28480,14 @@ "default": null, "optional": true, "choices": null + }, + { + "name": "url", + "type": "str", + "description": "Link to the transaction document.", + "default": null, + "optional": true, + "choices": null } ] }, diff --git a/openbb_platform/openbb/package/equity_ownership.py b/openbb_platform/openbb/package/equity_ownership.py index 7b9c2f3ff6c..f70abb43b54 100644 --- a/openbb_platform/openbb/package/equity_ownership.py +++ b/openbb_platform/openbb/package/equity_ownership.py @@ -155,7 +155,10 @@ def form_13f( def government_trades( self, symbol: Annotated[ - Optional[str], OpenBBField(description="Symbol to get data for.") + Union[str, None, List[Optional[str]]], + OpenBBField( + description="Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp." + ), ] = None, chamber: Annotated[ Literal["house", "senate", "all"], @@ -179,8 +182,8 @@ def government_trades( Parameters ---------- - symbol : Optional[str] - Symbol to get data for. + symbol : Union[str, None, List[Optional[str]]] + Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp. chamber : Literal['house', 'senate', 'all'] Government Chamber. limit : Optional[Annotated[int, Ge(ge=0)]] @@ -212,20 +215,22 @@ def government_trades( Date of Transaction. representative : Optional[str] Name of Representative. - link : Optional[str] - Link to the transaction document. (provider: fmp) + chamber : Optional[Literal['house', 'senate']] + Government Chamber - House or Senate. (provider: fmp) owner : Optional[str] Ownership status (e.g., Spouse, Joint). (provider: fmp) asset_type : Optional[str] Type of asset involved in the transaction. (provider: fmp) asset_description : Optional[str] Description of the asset. (provider: fmp) - type : Optional[str] + transaction_type : Optional[str] Type of transaction (e.g., Sale, Purchase). (provider: fmp) amount : Optional[str] Transaction amount range. (provider: fmp) comment : Optional[str] Additional comments on the transaction. (provider: fmp) + url : Optional[str] + Link to the transaction document. (provider: fmp) Examples -------- @@ -250,6 +255,9 @@ def government_trades( "limit": limit, }, extra_params=kwargs, + info={ + "symbol": {"fmp": {"multiple_items_allowed": True, "choices": None}} + }, ) ) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/government_trades.py b/openbb_platform/providers/fmp/openbb_fmp/models/government_trades.py index b3ae0e9942c..8358bdc0be6 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/government_trades.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/government_trades.py @@ -1,30 +1,25 @@ -"""Government Trades Model.""" +"""FMP Government Trades Model.""" -import asyncio -import math -from typing import Any, Dict, List, Optional -from warnings import warn +# pylint: disable=unused-argument,too-many-locals + +from typing import Any, Literal, Optional from openbb_core.provider.abstract.fetcher import Fetcher from openbb_core.provider.standard_models.government_trades import ( GovernmentTradesData, GovernmentTradesQueryParams, ) -from openbb_core.provider.utils.errors import EmptyDataError -from openbb_core.provider.utils.helpers import amake_request -from openbb_fmp.utils.helpers import create_url, response_callback -from pydantic import Field +from pydantic import Field, model_validator class FMPGovernmentTradesQueryParams(GovernmentTradesQueryParams): """Government Trades Query Parameters. - Source: https://financialmodelingprep.com/api/v4/senate-trading?symbol=AAPL - Source: https://financialmodelingprep.com/api/v4/senate-trading-rss-feed?page=0 - Source: https://financialmodelingprep.com/api/v4/senate-disclosure?symbol=AAPL - Source: https://financialmodelingprep.com/api/v4/senate-disclosure-rss-feed?page=0 + Source: https://site.financialmodelingprep.com/developer/docs#senate-trading """ + __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}} + class FMPGovernmentTradesData(GovernmentTradesData): """Government Trades Data Model.""" @@ -33,9 +28,11 @@ class FMPGovernmentTradesData(GovernmentTradesData): "symbol": "ticker", "transaction_date": "transactionDate", "representative": "office", + "url": "link", + "transaction_type": "type", } - link: Optional[str] = Field( - default=None, description="Link to the transaction document." + chamber: Literal["house", "senate"] = Field( + description="Government Chamber - House or Senate." ) owner: Optional[str] = Field( default=None, description="Ownership status (e.g., Spouse, Joint)." @@ -46,39 +43,86 @@ class FMPGovernmentTradesData(GovernmentTradesData): asset_description: Optional[str] = Field( default=None, description="Description of the asset." ) - type: Optional[str] = Field( + transaction_type: Optional[str] = Field( default=None, description="Type of transaction (e.g., Sale, Purchase)." ) amount: Optional[str] = Field(default=None, description="Transaction amount range.") comment: Optional[str] = Field( default=None, description="Additional comments on the transaction." ) + url: Optional[str] = Field( + default=None, description="Link to the transaction document." + ) + + @model_validator(mode="before") + @classmethod + def _fill_missing(cls, values): + """Fill missing information that can be identified.""" + description = values.get("assetDescription", "").lower() + if not values.get("owner"): + values["owner"] = "Self" + if (values.get("ticker") or values.get("symbol")) and not values.get( + "assetType" + ): + values["asset_type"] = "ETF" if "etf" in description else "Stock" + elif ( + not values.get("ticker") + and not values.get("symbol") + and not values.get("assetType") + ): + values["asset_type"] = ( + "Treasury" + if "treasury" in description or "bill" in description + else ( + "Bond" + if "%" in description + or "due" in description + or "pct" in description + else ( + "Fund" + if "fund" in description + else ("ETF" if "etf" in description else "Other") + ) + ) + ) + return values class FMPGovernmentTradesFetcher( Fetcher[ FMPGovernmentTradesQueryParams, - List[FMPGovernmentTradesData], + list[FMPGovernmentTradesData], ] ): - """Fetches and transforms data from the Government Trades endpoints.""" + """FMP Government Trades Fetcher.""" @staticmethod - def transform_query(params: Dict[str, Any]) -> FMPGovernmentTradesQueryParams: + def transform_query(params: dict[str, Any]) -> FMPGovernmentTradesQueryParams: """Transform the query params.""" return FMPGovernmentTradesQueryParams(**params) @staticmethod async def aextract_data( query: FMPGovernmentTradesQueryParams, - credentials: Optional[Dict[str, str]] = None, + credentials: Optional[dict[str, str]] = None, **kwargs: Any, - ) -> List[Dict]: + ) -> list[dict]: """Return the raw data from the Government Trades endpoint.""" - symbols = [] + # pylint: disable=import-outside-toplevel + import asyncio # noqa + import math + from openbb_core.app.model.abstract.error import OpenBBError + from openbb_core.provider.utils.errors import EmptyDataError + from openbb_core.provider.utils.helpers import amake_request + from openbb_fmp.utils.helpers import create_url, response_callback + from warnings import warn + + symbols: list = [] + if query.symbol: symbols = query.symbol.split(",") - results: List[Dict] = [] + + results: list[dict] = [] chamber_url_dict = { "house": ["senate-disclosure"], "senate": ["senate-trading"], @@ -86,7 +130,6 @@ async def aextract_data( } api_key = credentials.get("fmp_api_key") if credentials else "" keys_to_remove = { - "comment", "district", "capitalGainsOver200USD", "disclosureYear", @@ -97,8 +140,10 @@ async def aextract_data( async def get_one(url): """Get data for one URL.""" - result = await amake_request(url, response_callback=response_callback, **kwargs) - processed_list = [] + result = await amake_request( + url, response_callback=response_callback, **kwargs + ) + processed_list: list = [] for entry in result: new_entry = { @@ -106,56 +151,69 @@ async def get_one(url): for k, v in entry.items() if k not in keys_to_remove } + new_entry["chamber"] = "senate" if "senate-trading" in url else "house" processed_list.append(new_entry) + if not processed_list or len(processed_list) == 0: - warn(f"Symbol Error: No data found for {url}") + warn(f"No data found for {url.replace(api_key, 'API_KEY')}") + if processed_list: results.extend(processed_list) - if symbols: - urls_list = [] - for symbol in symbols: - query.symbol = symbol - url = [ - create_url( - 4, - f"{i}", - api_key=api_key, - query=query, - exclude=["chamber", "limit"], - ) - for i in chamber_url_dict[query.chamber] - ] - urls_list.extend(url) - await asyncio.gather(*[get_one(url) for url in urls_list]) - else: - urls_list = [] - pages = math.ceil(query.limit / 100) - for page in range(pages): - query.page = page - url = [ - create_url( - 4, - f"{i}-rss-feed", - api_key=api_key, - query=query, - exclude=["chamber", "limit"], + urls_list: list = [] + + try: + if symbols: + for symbol in symbols: + query.symbol = symbol + url = [ + create_url( + 4, + f"{i}", + api_key=api_key, + query=query, + exclude=["chamber", "limit"], + ) + for i in chamber_url_dict[query.chamber] + ] + urls_list.extend(url) + await asyncio.gather(*[get_one(url) for url in urls_list]) + else: + pages = math.ceil(query.limit / 100) + + if pages > 30: + warn( + f"Limit value - {query.limit} exceeds the available data for {query.chamber}," + " returning all available data." ) - for i in chamber_url_dict[query.chamber] - ] - urls_list.extend(url) - await asyncio.gather(*[get_one(url) for url in urls_list]) - if not results: - raise EmptyDataError("No data returned for the given symbol.") - - return results + pages = 31 + for page in range(pages): + query.page = page + url = [ + create_url( + 4, + f"{i}-rss-feed", + api_key=api_key, + query=query, + exclude=["chamber", "limit"], + ) + for i in chamber_url_dict[query.chamber] + ] + urls_list.extend(url) + await asyncio.gather(*[get_one(url) for url in urls_list]) + + if not results: + raise EmptyDataError("No data returned for the given symbol.") + + return results + except OpenBBError as e: + raise e from e @staticmethod def transform_data( - query: FMPGovernmentTradesQueryParams, data: List[Dict], **kwargs: Any - ) -> List[FMPGovernmentTradesData]: + query: FMPGovernmentTradesQueryParams, data: list[dict], **kwargs: Any + ) -> list[FMPGovernmentTradesData]: """Return the transformed data.""" - return sorted( [ FMPGovernmentTradesData( @@ -165,4 +223,4 @@ def transform_data( ], key=lambda x: x.date, reverse=True, - ) + )[: query.limit or len(data)]