diff --git a/openbb_platform/core/openbb_core/api/rest_api.py b/openbb_platform/core/openbb_core/api/rest_api.py index 8705b8fa35b..7c9e0f1488e 100644 --- a/openbb_platform/core/openbb_core/api/rest_api.py +++ b/openbb_platform/core/openbb_core/api/rest_api.py @@ -88,6 +88,7 @@ AppLoader.from_routers( @app.exception_handler(Exception) async def api_exception_handler(_: Request, exc: Exception): """Exception handler for all other exceptions.""" + logger.error(exc) return JSONResponse( status_code=404, content={ @@ -100,6 +101,7 @@ async def api_exception_handler(_: Request, exc: Exception): @app.exception_handler(OpenBBError) async def openbb_exception_handler(_: Request, exc: OpenBBError): """Exception handler for OpenBB errors.""" + logger.error(exc.original) openbb_error = exc.original status_code = 400 if "No results" in str(openbb_error) else 500 return JSONResponse( diff --git a/openbb_platform/core/openbb_core/app/model/custom_parameter.py b/openbb_platform/core/openbb_core/app/model/custom_parameter.py index 0df8298e59e..06b947e0c55 100644 --- a/openbb_platform/core/openbb_core/app/model/custom_parameter.py +++ b/openbb_platform/core/openbb_core/app/model/custom_parameter.py @@ -2,6 +2,8 @@ import sys from dataclasses import dataclass from typing import Dict, Optional +from typing_extensions import LiteralString + # `slots` is available on Python >= 3.10 if sys.version_info >= (3, 10): slots_true = {"slots": True} @@ -24,3 +26,10 @@ class OpenBBCustomParameter(BaseMetadata): """Custom parameter for OpenBB.""" description: Optional[str] = None + + +@dataclass(frozen=True, **slots_true) +class OpenBBCustomChoices(BaseMetadata): + """Custom choices for OpenBB.""" + + choices: Optional[LiteralString] = None diff --git a/openbb_platform/core/openbb_core/app/static/package_builder.py b/openbb_platform/core/openbb_core/app/static/package_builder.py index abfad97eda6..a64c0fd1fd2 100644 --- a/openbb_platform/core/openbb_core/app/static/package_builder.py +++ b/openbb_platform/core/openbb_core/app/static/package_builder.py @@ -36,7 +36,10 @@ from starlette.routing import BaseRoute from typing_extensions import Annotated, _AnnotatedAlias from openbb_core.app.extension_loader import ExtensionLoader, OpenBBGroups -from openbb_core.app.model.custom_parameter import OpenBBCustomParameter +from openbb_core.app.model.custom_parameter import ( + OpenBBCustomChoices, + OpenBBCustomParameter, +) from openbb_core.app.provider_interface import ProviderInterface from openbb_core.app.router import CommandMap, RouterLoader from openbb_core.app.static.utils.console import Console @@ -330,9 +333,7 @@ class ImportDefinition: hint_type_list = cls.get_path_hint_type_list(path=path) code = "from openbb_core.app.static.container import Container" code += "\nfrom openbb_core.app.model.obbject import OBBject" - code += ( - "\nfrom openbb_core.app.model.custom_parameter import OpenBBCustomParameter" - ) + code += "\nfrom openbb_core.app.model.custom_parameter import OpenBBCustomParameter, OpenBBCustomChoices" # These imports were not detected before build, so we add them manually and # ruff --fix the resulting code to remove unused imports. @@ -500,7 +501,10 @@ class MethodDefinition: """Get json schema extra.""" field_default = getattr(field, "default", None) if field_default: - return getattr(field_default, "json_schema_extra", {}) + # Getting json_schema_extra without changing the original dict + json_schema_extra = getattr(field_default, "json_schema_extra", {}).copy() + json_schema_extra.pop("choices", None) + return json_schema_extra return {} @staticmethod @@ -571,7 +575,9 @@ class MethodDefinition: type_ = MethodDefinition.get_type(field) default = MethodDefinition.get_default(field) extra = MethodDefinition.get_extra(field) - new_type = MethodDefinition.get_expanded_type(field_name, extra) + new_type = MethodDefinition.get_expanded_type( + field_name, extra, type_ + ) updated_type = type_ if new_type is ... else Union[type_, new_type] formatted[field_name] = Parameter( @@ -602,10 +608,10 @@ class MethodDefinition: return MethodDefinition.reorder_params(params=formatted) @staticmethod - def add_field_descriptions( + def add_field_custom_annotations( od: OrderedDict[str, Parameter], model_name: Optional[str] = None ): - """Add the field description to the param signature.""" + """Add the field custom description and choices to the param signature as annotations.""" if model_name: available_fields: Dict[str, Field] = ( ProviderInterface().params[model_name]["standard"].__dataclass_fields__ @@ -615,16 +621,28 @@ class MethodDefinition: if param not in available_fields: continue - field = available_fields[param] + field_default = available_fields[param].default - new_value = value.replace( - annotation=Annotated[ - value.annotation, - OpenBBCustomParameter( - description=getattr(field.default, "description", "") - ), - ], + choices = getattr(field_default, "json_schema_extra", {}).get( + "choices", [] ) + description = getattr(field_default, "description", "") + + if choices: + new_value = value.replace( + annotation=Annotated[ + value.annotation, + OpenBBCustomParameter(description=description), + OpenBBCustomChoices(choices=choices), + ], + ) + else: + new_value = value.replace( + annotation=Annotated[ + value.annotation, + OpenBBCustomParameter(description=description), + ], + ) od[param] = new_value @@ -665,7 +683,7 @@ class MethodDefinition: model_name: Optional[str] = None, ) -> str: """Build the command method signature.""" - MethodDefinition.add_field_descriptions( + MethodDefinition.add_field_custom_annotations( od=formatted_params, model_name=model_name ) # this modified `od` in place func_params = MethodDefinition.build_func_params(formatted_params) @@ -782,10 +800,19 @@ class MethodDefinition: return code @classmethod - def get_expanded_type(cls, field_name: str, extra: Optional[dict] = None) -> object: + def get_expanded_type( + cls, + field_name: str, + extra: Optional[dict] = None, + original_type: Optional[type] = None, + ) -> object: """Expand the original field type.""" if extra and "multiple_items_allowed" in extra: - return List[str] + if original_type is None: + raise ValueError( + "multiple_items_allowed requires the original type to be specified." + ) + return List[original_type] return cls.TYPE_EXPANSION.get(field_name, ...) @classmethod diff --git a/openbb_platform/core/openbb_core/app/static/utils/decorators.py b/openbb_platform/core/openbb_core/app/static/utils/decorators.py index febb23cd3b5..18eadeffa69 100644 --- a/openbb_platform/core/openbb_core/app/static/utils/decorators.py +++ b/openbb_platform/core/openbb_core/app/static/utils/decorators.py @@ -80,8 +80,9 @@ def exception_handler(func: Callable[P, R]) -> Callable[P, R]: ).with_traceback(tb) from None # If the error is not a ValidationError, then it is a generic exception + error_type = getattr(e, "original", e).__class__.__name__ raise OpenBBError( - f"\nType -> {e.original.__class__.__name__}\n\nDetail -> {str(e)}" + f"\nType -> {error_type}\n\nDetail -> {str(e)}" ).with_traceback(tb) from None return wrapper diff --git a/openbb_platform/core/openbb_core/app/static/utils/filters.py b/openbb_platform/core/openbb_core/app/static/utils/filters.py index aaefb9a040f..36e9d3164db 100644 --- a/openbb_platform/core/openbb_core/app/static/utils/filters.py +++ b/openbb_platform/core/openbb_core/app/static/utils/filters.py @@ -29,7 +29,9 @@ def filter_inputs( if field in kwargs.get(p, {}): current = kwargs[p][field] new = ( - ",".join(current) if isinstance(current, list) else current + ",".join(map(str, current)) + if isinstance(current, list) + else current ) if provider and provider not in props[PROPERTY]: diff --git a/openbb_platform/core/openbb_core/provider/standard_models/cpi.py b/openbb_platform/core/openbb_core/provider/standard_models/cpi.py index cb4d7afbdde..a1e37f81ba6 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/cpi.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/cpi.py @@ -74,7 +74,10 @@ CPI_FREQUENCY = Literal["monthly", "quarter", "annual"] class ConsumerPriceIndexQueryParams(QueryParams): """CPI Query.""" - country: str = Field(description=QUERY_DESCRIPTIONS.get("country")) + country: str = Field( + description=QUERY_DESCRIPTIONS.get("country"), + choices=CPI_COUNTRIES, # type: ignore + ) units: CPI_UNITS = Field( default="growth_same", description=QUERY_DESCRIPTIONS.get("units", "") diff --git a/openbb_platform/core/openbb_core/provider/standard_models/spot.py b/openbb_platform/core/openbb_core/provider/standard_models/spot.py index f976fe9cdcc..4717a677bd7 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/spot.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/spot.py @@ -3,9 +3,9 @@ from datetime import ( date as dateType, ) -from typing import List, Literal, Optional +from typing import Optional, Union -from pydantic import Field, field_validator +from pydantic import Field from openbb_core.provider.abstract.data import Data from openbb_core.provider.abstract.query_params import QueryParams @@ -26,25 +26,15 @@ class SpotRateQueryParams(QueryParams): default=None, description=QUERY_DESCRIPTIONS.get("end_date", ""), ) - maturity: List[float] = Field( - default=[10.0], description="The maturities in years." + maturity: Union[float, str] = Field( + default=10.0, description="Maturities in years." ) - category: List[Literal["par_yield", "spot_rate"]] = Field( - default=["spot_rate"], - description="The category.", + category: str = Field( + default="spot_rate", + description="Rate category. Options: spot_rate, par_yield.", + choices=["par_yield", "spot_rate"], ) - @field_validator("maturity") - @classmethod - def maturity_validate(cls, v): - """Validate maturity.""" - for i in v: - if not isinstance(i, float): - raise ValueError("`maturity` must be a float") - if not 1 <= i <= 100: - raise ValueError("`maturity` must be between 1 and 100") - return v - class SpotRateData(Data): """Spot Rate Data.""" diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py index 401ae64cd8a..465513c42b0 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_api.py +++ b/openbb_platform/extensions/equity/integration/test_equity_api.py @@ -1675,7 +1675,7 @@ def test_equity_darkpool_otc(params, headers): "params", [ ({"provider": "fmp", "market": "euronext"}), - ({"provider": "polygon"}), # premium endpoint + # ({"provider": "polygon"}), # premium endpoint ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py index 6c1150d24f5..5dcef760320 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_python.py +++ b/openbb_platform/extensions/equity/integration/test_equity_python.py @@ -1,6 +1,6 @@ """Python interface integration tests for the equity extension.""" -from datetime import time +from datetime import date, time, timedelta import pytest from extensions.tests.conftest import parametrize @@ -767,8 +767,6 @@ def test_equity_fundamental_revenue_per_segment(params, obb): "symbol": "IBM:US", "start_date": "2023-09-30", "end_date": "2023-12-31", - "limit": None, - "form_type": None, } ), ], @@ -878,8 +876,8 @@ def test_equity_compare_groups(params, obb): { "provider": "cboe", "symbol": "AAPL", - "start_date": "2024-02-19", - "end_date": "2024-02-20", + "start_date": (date.today() - timedelta(days=1)).strftime("%Y-%m-%d"), + "end_date": date.today().strftime("%Y-%m-%d"), "interval": "1m", "use_cache": False, } @@ -1572,7 +1570,7 @@ def test_equity_darkpool_otc(params, obb): "params", [ ({"provider": "fmp", "market": "euronext"}), - ({"provider": "polygon"}), # premium endpoint + # ({"provider": "polygon"}), # premium endpoint ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py index 87539ad0eef..95b759beaac 100644 --- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py +++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py @@ -341,10 +341,26 @@ def test_fixedincome_corporate_commercial_paper(params, headers): "start_date": "2023-01-01", "end_date": "2023-06-06", "maturity": [10.0], - "category": ["spot_rate"], + "category": "spot_rate", "provider": "fred", } - ) + ), + ( + { + "start_date": None, + "end_date": None, + "maturity": 5.5, + "category": ["spot_rate"], + } + ), + ( + { + "start_date": None, + "end_date": None, + "maturity": "1,5.5,10", + "category": "spot_rate,par_yield", + } + ), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py index df53341fb85..40c73bf077d 100644 --- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py +++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py @@ -312,10 +312,26 @@ def test_fixedincome_corporate_commercial_paper(params, obb): "start_date": "2023-01-01", "end_date": "2023-06-06", "maturity": [10.0], - "category": ["spot_rate"], + "category": "spot_rate", "provider": "fred", } - ) + ), + ( + { + "start_date": None, + "end_date": None, + "maturity": 5.5, + "category": ["spot_rate"], + } + ), + ( + { + "start_date": None, + "end_date": None, + "maturity": "1,5.5,10", + "category": "spot_rate,par_yield", + } + ), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/quantitative/integration/test_quantitative_api.py b/openbb_platform/extensions/quantitative/integration/test_quantitative_api.py index 10e9432d1f8..ecb4960c4d4 100644 --- a/openbb_platform/extensions/quantitative/integration/test_quantitative_api.py +++ b/openbb_platform/extensions/quantitative/integration/test_quantitative_api.py @@ -26,9 +26,11 @@ def get_headers(): return data["headers"] -def request_data(menu: str, symbol: str, provider: str): +def request_data( + menu: str, symbol: str, provider: str, start_date: str = "", end_date: str = "" +): """Randomly pick a symbol and a provider and get data from the selected menu.""" - url = f"http://0.0.0.0:8000/api/v1/{menu}/price/historical?symbol={symbol}&provider={provider}" + url = f"http://0.0.0.0:8000/api/v1/{menu}/price/historical?symbol={symbol}&provider={provider}&start_date={start_date}&end_date={end_date}" result = requests.get(url, headers=get_headers(), timeout=10) return result.json()["results"] @@ -40,7 +42,13 @@ def get_stocks_data(): symbol = random.choice(["AAPL", "NVDA", "MSFT", "TSLA", "AMZN", "V"]) # noqa: S311 provider = random.choice(["fmp", "polygon", "yfinance"]) # noqa: S311 - data["stocks_data"] = request_data("equity", symbol=symbol, provider=provider) + data["stocks_data"] = request_data( + menu="equity", + symbol=symbol, + provider=provider, + start_date="2023-01-01", + end_date="2023-12-31", + ) return data["stocks_data"] @@ -56,6 +64,8 @@ def get_crypto_data(): menu="crypto", symbol=symbol, provider=provider, + start_date="2023-01-01", + end_date="2023-12-31", ) return data["crypto_data"] @@ -244,7 +254,7 @@ def test_quantitative_performance_sharpe_ratio(params, data_type): "data": "", "target": "close", "target_return": "0.5", - "window": "275", + "window": "150", "adjusted": "true", "index": "date", }, diff --git a/openbb_platform/extensions/quantitative/openbb_quantitative/stats/stats_router.py b/openbb_platform/extensions/quantitative/openbb_quantitative/stats/stats_router.py index deff80471b1..56ab1017ecf 100644 --- a/openbb_platform/extensions/quantitative/openbb_quantitative/stats/stats_router.py +++ b/openbb_platform/extensions/quantitative/openbb_quantitative/stats/stats_router.py @@ -144,7 +144,7 @@ def stdev(data: List[Data], target: str) -> OBBject[List[Data]]: 'obb.quantitative.stats.kurtosis(data=returns, target="close")', ], ) -def kurtosis(data: List[Data], target) -> OBBject[List[Data]]: +def kurtosis(data: List[Data], target: str) -> OBBject[List[Data]]: """ Calculate the rolling kurtosis of a target column. @@ -154,10 +154,11 @@ def kurtosis(data: List[Data], target) -> OBBject[List[Data]]: This function helps in assessing the risk of outliers in financial returns or other time series data. Parameters - data: List[Data] - The time series data as a list of data points. - target: str - The name of the column for which to calculate kurtosis. + ---------- + data: List[Data] + The time series data as a list of data points. + target: str + The name of the column for which to calculate kurtosis. Returns ------ diff --git a/openbb_platform/extensions/technical/openbb_technical/technical_router.py b/openbb_platform/extensions/technical/openbb_technical/technical_router.py index 60b27220b95..245ac33c799 100644 --- a/openbb_platform/extensions/technical/openbb_technical/technical_router.py +++ b/openbb_platform/extensions/technical/openbb_technical/technical_router.py @@ -623,6 +623,8 @@ def vwap( The calculated data. """ df = basemodel_to_df(data, index=index) + if index == "date": + df.index = pd.to_datetime(df.index) df_target = get_target_columns(df, ["high", "low", "close", "volume"]) df_vwap = pd.DataFrame(df_target.ta.vwap(anchor=anchor, offset=offset).dropna()) diff --git a/openbb_platform/extensions/tests/utils/helpers.py b/openbb_platform/extensions/tests/utils/helpers.py index 264bdb4958d..ad15ae75fb5 100644 --- a/openbb_platform/extensions/tests/utils/helpers.py +++ b/openbb_platform/extensions/tests/utils/helpers.py @@ -89,13 +89,13 @@ def list_openbb_extensions() -> Tuple[Set[str], Set[str], Set[str]]: obbject_extensions = set() entry_points_dict = entry_points() - for entry_point in entry_points_dict["openbb_core_extension"]: + for entry_point in entry_points_dict.get("openbb_core_extension", []): core_extensions.add(f"{entry_point.name}") - for entry_point in entry_points_dict["openbb_provider_extension"]: + for entry_point in entry_points_dict.get("openbb_provider_extension", []): provider_extensions.add(f"{entry_point.name}") - for entry_point in entry_points_dict["openbb_obbject_extension"]: + for entry_point in entry_points_dict.get("openbb_obbject_extension", []): obbject_extensions.add(f"{entry_point.name}") return core_extensions, provider_extensions, obbject_extensions diff --git a/openbb_platform/openbb/package/economy.py b/openbb_platform/openbb/package/economy.py index 9f4556f72fb..4b0149ad315 100644 --- a/openbb_platform/openbb/package/economy.py +++ b/openbb_platform/openbb/package/economy.py @@ -3,7 +3,10 @@ import datetime from typing import List, Literal, Optional, Union -from openbb_core.app.model.custom_parameter import OpenBBCustomParameter +from openbb_core.app.model.custom_parameter import ( + OpenBBCustomChoices, + OpenBBCustomParameter, +) from openbb_core.app.model.obbject import OBBject from openbb_core.app.static.container import Container from openbb_core.app.static.utils.decorators import exception_handler, validate @@ -240,6 +243,59 @@ class ROUTER_economy(Container): OpenBBCustomParameter( description="The country to get data. Multiple items allowed for provider(s): fred." ), + OpenBBCustomChoices( + choices=[ + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "croatia", + "cyprus", + "czech_republic", + "denmark", + "estonia", + "euro_area", + "finland", + "france", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "malta", + "mexico", + "netherlands", + "new_zealand", + "norway", + "poland", + "portugal", + "romania", + "russian_federation", + "slovak_republic", + "slovakia", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkey", + "united_kingdom", + "united_states", + ] + ), ], units: Annotated[ Literal["growth_previous", "growth_same", "index_2015"], diff --git a/openbb_platform/openbb/package/equity_estimates.py b/openbb_platform/openbb/package/equity_estimates.py index 1880f70d440..a1d21d83b4d 100644 --- a/openbb_platform/openbb/package/equity_estimates.py +++ b/openbb_platform/openbb/package/equity_estimates.py @@ -401,7 +401,7 @@ class ROUTER_equity_estimates(Container): def price_target( self, symbol: Annotated[ - Union[str, None, List[str]], + Union[str, None, List[Optional[str]]], OpenBBCustomParameter( description="Symbol to get data for. Multiple items allowed for provider(s): benzinga." ), @@ -417,7 +417,7 @@ class ROUTER_equity_estimates(Container): Parameters ---------- - symbol : Union[str, None, List[str]] + symbol : Union[str, None, List[Optional[str]]] Symbol to get data for. Multiple items allowed for provider(s): benzinga. limit : int The number of data entries to return. diff --git a/openbb_platform/openbb/package/equity_fundamental.py b/openbb_platform/openbb/package/equity_fundamental.py index b62691b6a2d..9ad938292d9 100644 --- a/openbb_platform/openbb/package/equity_fundamental.py +++ b/openbb_platform/openbb/package/equity_fundamental.py @@ -708,6 +708,10 @@ class ROUTER_equity_fundamental(Container): Link to the filing. (provider: fmp) final_link : Optional[str] Link to the filing document. (provider: fmp) + net_income_continuing_operations : Optional[float] + Net Income (Continuing Operations) (provider: intrinio) + net_income_discontinued_operations : Optional[float] + Net Income (Discontinued Operations) (provider: intrinio) provision_for_loan_losses : Optional[float] Provision for Loan Losses (provider: intrinio) provision_for_credit_losses : Optional[float] @@ -726,10 +730,6 @@ class ROUTER_equity_fundamental(Container): Net Cash from Continuing Operating Activities (provider: intrinio) net_cash_from_discontinued_operating_activities : Optional[float] Net Cash from Discontinued Operating Activities (provider: intrinio) - net_income_continuing_operations : Optional[float] - Net Income (Continuing Operations) (provider: intrinio) - net_income_discontinued_operations : Optional[float] - Net Income (Discontinued Operations) (provider: intrinio) divestitures : Optional[float] Divestitures (provider: intrinio) sale_of_property_plant_and_equipment : Optional[float] diff --git a/openbb_platform/openbb/package/fixedincome_corporate.py b/openbb_platform/openbb/package/fixedincome_corporate.py index 6d8d8778a8d..b651e91c163 100644 --- a/openbb_platform/openbb/package/fixedincome_corporate.py +++ b/openbb_platform/openbb/package/fixedincome_corporate.py @@ -419,12 +419,17 @@ class ROUTER_fixedincome_corporate(Container): ), ] = None, maturity: Annotated[ - List[float], OpenBBCustomParameter(description="The maturities in years.") - ] = [10.0], + Union[float, str, List[Union[float, str]]], + OpenBBCustomParameter( + description="Maturities in years. Multiple items allowed for provider(s): fred." + ), + ] = 10.0, category: Annotated[ - List[Literal["par_yield", "spot_rate"]], - OpenBBCustomParameter(description="The category."), - ] = ["spot_rate"], + Union[str, List[str]], + OpenBBCustomParameter( + description="Rate category. Options: spot_rate, par_yield. Multiple items allowed for provider(s): fred." + ), + ] = "spot_rate", provider: Optional[Literal["fred"]] = None, **kwargs ) -> OBBject: @@ -442,10 +447,10 @@ class ROUTER_fixedincome_corporate(Container): Start date of the data, in YYYY-MM-DD format. end_date : Union[datetime.date, None, str] End date of the data, in YYYY-MM-DD format. - maturity : List[float] - The maturities in years. - category : List[Literal['par_yield', 'spot_rate']] - The category. + maturity : Union[float, str, List[Union[float, str]]] + Maturities in years. Multiple items allowed for provider(s): fred. + category : Union[str, List[str]] + Rate category. Options: spot_rate, par_yield. Multiple items allowed for provider(s): fred. provider : Optional[Literal['fred']] The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'fred' if there is @@ -495,5 +500,12 @@ class ROUTER_fixedincome_corporate(Container): "category": category, }, extra_params=kwargs, + extra_info={ + "maturity": {"multiple_items_allowed": ["fred"]}, + "category": { + "choices": ["par_yield", "spot_rate"], + "multiple_items_allowed": ["fred"], + }, + }, ) ) diff --git a/openbb_platform/openbb/package/news.py b/openbb_platform/openbb/package/news.py index fcb394cb2ca..0f1f140b580 100644 --- a/openbb_platform/openbb/package/news.py +++ b/openbb_platform/openbb/package/news.py @@ -26,7 +26,7 @@ class ROUTER_news(Container): def company( self, symbol: Annotated[ - Union[str, None, List[str]], + Union[str, None, List[Optional[str]]], OpenBBCustomParameter( description="Symbol to get data for. This endpoint will accept multiple symbols separated by commas. Multiple items allowed for provider(s): benzinga, fmp, intrinio, polygon, tiingo, yfinance." ), @@ -56,7 +56,7 @@ class ROUTER_news(Container): Parameters ---------- - symbol : Union[str, None, List[str]] + symbol : Union[str, None, List[Optional[str]]] Symbol to get data for. This endpoint will accept multiple symbols separated by commas. Multiple items allowed for provider(s): benzinga, fmp, intrinio, polygon, tiingo, yfinance. start_date : Union[datetime.date, None, str] Start date of the data, in YYYY-MM-DD format. diff --git a/openbb_platform/providers/fred/openbb_fred/models/spot.py b/openbb_platform/providers/fred/openbb_fred/models/spot.py index 2dae3a230ec..4db9cebe4e0 100644 --- a/openbb_platform/providers/fred/openbb_fred/models/spot.py +++ b/openbb_platform/providers/fred/openbb_fred/models/spot.py @@ -8,13 +8,18 @@ from openbb_core.provider.standard_models.spot import ( SpotRateQueryParams, ) from openbb_fred.utils.fred_base import Fred -from openbb_fred.utils.fred_helpers import get_spot_series_id +from openbb_fred.utils.fred_helpers import comma_to_float_list, get_spot_series_id from pydantic import field_validator class FREDSpotRateQueryParams(SpotRateQueryParams): """FRED Spot Rate Query.""" + __json_schema_extra__ = { + "maturity": ["multiple_items_allowed"], + "category": ["multiple_items_allowed"], + } + class FREDSpotRateData(SpotRateData): """FRED Spot Rate Data.""" @@ -56,9 +61,17 @@ class FREDSpotRateFetcher( key = credentials.get("fred_api_key") if credentials else "" fred = Fred(key) + maturity = ( + comma_to_float_list(query.maturity) + if isinstance(query.maturity, str) + else [query.maturity] + ) + if any(1 > m > 100 for m in maturity): + raise ValueError("Maturity must be between 1 and 100") + series = get_spot_series_id( - maturity=query.maturity, - category=query.category, + maturity=maturity, + category=query.category.split(","), ) data = [] diff --git a/openbb_platform/providers/fred/openbb_fred/utils/fred_helpers.py b/openbb_platform/providers/fred/openbb_fred/utils/fred_helpers.py index 568017249ef..b61cc26625e 100644 --- a/openbb_platform/providers/fred/openbb_fred/utils/fred_helpers.py +++ b/openbb_platform/providers/fred/openbb_fred/utils/fred_helpers.py @@ -51,6 +51,16 @@ YIELD_CURVE_SERIES_CORPORATE_PAR = { } +def comma_to_float_list(v: str) -> List[float]: + """Convert comma-separated string to list of floats.""" + try: + return [float(m) for m in v.split(",")] + except ValueError as e: + raise ValueError( + "'maturity' must be a float or a comma-separated string of floats" + ) from e + + def all_cpi_options(harmonized: bool = False) -> List[dict]: """Get all CPI options.""" data = [] @@ -136,6 +146,7 @@ def get_ice_bofa_series_id( units = "index" if type_ == "total_return" else "percent" for s in series: + # pylint: disable=too-many-boolean-expressions if ( s["Type"] == type_ and s["Units"] == units 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 1dd61e89588..0476ab44ead 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/balance_sheet.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/balance_sheet.py @@ -1,6 +1,7 @@ """Intrinio Balance Sheet Model.""" # pylint: disable=unused-argument + import warnings from typing import Any, Dict, List, Literal, Optional @@ -482,7 +483,7 @@ class IntrinioBalanceSheetFetcher( for sub_item in item["financials"]: field_name = sub_item["data_tag"]["tag"] unit = sub_item["data_tag"].get("unit", "") - if unit and "share" not in unit: + if unit and len(unit) == 3: units.append(unit) sub_dict[field_name] = ( float(sub_item["value"]) 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 a0189cca0c5..c8fffcc5237 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/cash_flow.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/cash_flow.py @@ -1,6 +1,7 @@ """Intrinio Cash Flow Statement Model.""" # pylint: disable=unused-argument + import warnings from typing import Any, Dict, List, Literal, Optional @@ -89,6 +90,12 @@ class IntrinioCashFlowStatementData(CashFlowStatementData): description="The currency in which the balance sheet is reported.", default=None, ) + net_income_continuing_operations: Optional[float] = Field( + default=None, description="Net Income (Continuing Operations)" + ) + net_income_discontinued_operations: Optional[float] = Field( + default=None, description="Net Income (Discontinued Operations)" + ) net_income: Optional[float] = Field( default=None, description="Consolidated Net Income." ) @@ -119,12 +126,6 @@ class IntrinioCashFlowStatementData(CashFlowStatementData): net_cash_from_discontinued_operating_activities: Optional[float] = Field( default=None, description="Net Cash from Discontinued Operating Activities" ) - net_income_continuing_operations: Optional[float] = Field( - default=None, description="Net Income (Continuing Operations)" - ) - net_income_discontinued_operations: Optional[float] = Field( - default=None, description="Net Income (Discontinued Operations)" - ) net_cash_from_operating_activities: Optional[float] = Field( default=None, description="Net Cash from Operating Activities" ) @@ -220,7 +221,7 @@ class IntrinioCashFlowStatementData(CashFlowStatementData): @model_validator(mode="before") @classmethod - def replace_zero(cls, values): # pylint: disable=no-self-argument + def replace_zero(cls, values): """Check for zero values and replace with None.""" return ( {k: None if v == 0 else v for k, v in values.items()} @@ -282,10 +283,10 @@ class IntrinioCashFlowStatementFetcher( """Return the response.""" statement_data = await response.json() return { - "period_ending": statement_data["fundamental"]["end_date"], - "fiscal_period": statement_data["fundamental"]["fiscal_period"], - "fiscal_year": statement_data["fundamental"]["fiscal_year"], - "financials": statement_data["standardized_financials"], + "period_ending": statement_data["fundamental"]["end_date"], # type: ignore + "fiscal_period": statement_data["fundamental"]["fiscal_period"], # type: ignore + "fiscal_year": statement_data["fundamental"]["fiscal_year"], # type: ignore + "financials": statement_data["standardized_financials"], # type: ignore } intrinio_id = f"{query.symbol}-{statement_code}" @@ -308,7 +309,7 @@ class IntrinioCashFlowStatementFetcher( for sub_item in item["financials"]: unit = sub_item["data_tag"].get("unit", "") - if unit and "share" not in unit: + if unit and len(unit) == 3: units.append(unit) field_name = sub_item["data_tag"]["tag"] sub_dict[field_name] = ( diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py index 6b98eb5a53a..d6be9d79f6e 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py @@ -1,5 +1,7 @@ """Intrinio Equity Historical Price Model.""" +# pylint: disable = unused-argument + from datetime import datetime, time from typing import Any, Dict, List, Literal, Optional @@ -221,7 +223,6 @@ class IntrinioEquityHistoricalFetcher( return await amake_requests([url], callback, **kwargs) - # pylint: disable=unused-argument @staticmethod def transform_data( query: IntrinioEquityHistoricalQueryParams, @@ -229,7 +230,10 @@ class IntrinioEquityHistoricalFetcher( **kwargs: Any, ) -> List[IntrinioEquityHistoricalData]: """Return the transformed data.""" - return [ - IntrinioEquityHistoricalData.model_validate(d) - for d in sorted(data, key=lambda x: x["date"], reverse=False) - ] + try: + sorted_data = sorted( + data, key=lambda x: x.get("date") or x.get("time"), reverse=False + ) + except Exception: + sorted_data = data + return [IntrinioEquityHistoricalData.model_validate(d) for d in sorted_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 f23467392b4..64b95e5976a 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/income_statement.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/income_statement.py @@ -1,6 +1,7 @@ """Intrinio Income Statement Model.""" # pylint: disable=unused-argument + import warnings from typing import Any, Dict, List, Literal, Optional @@ -472,7 +473,7 @@ class IntrinioIncomeStatementFetcher( for sub_item in item["financials"]: unit = sub_item["data_tag"].get("unit", "") - if unit and "share" not in unit: + if unit and len(unit) == 3: units.append(unit) field_name = sub_item["data_tag"]["tag"] sub_dict[field_name] = ( diff --git a/openbb_platform/providers/nasdaq/openbb_nasdaq/models/economic_calendar.py b/openbb_platform/providers/nasdaq/openbb_nasdaq/models/economic_calendar.py index 7baae29800e..3970a0b091b 100644 --- a/openbb_platform/providers/nasdaq/openbb_nasdaq/models/economic_calendar.py +++ b/openbb_platform/providers/nasdaq/openbb_nasdaq/models/economic_calendar.py @@ -4,7 +4,7 @@ import html from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta from itertools import repeat -from typing import Any, Dict, List, Optional, Set, Union +from typing import Any, Dict, List, Optional import requests from openbb_core.provider.abstract.fetcher import Fetcher @@ -24,18 +24,16 @@ class NasdaqEconomicCalendarQueryParams(EconomicCalendarQueryParams): __json_schema_extra__ = {"country": ["multiple_items_allowed"]} - country: Optional[Union[str, List[str]]] = Field( + country: Optional[str] = Field( default=None, description="Country of the event", ) @field_validator("country", mode="before", check_fields=False) @classmethod - def validate_country(cls, v: Union[str, List[str], Set[str]]): - """Validate the country input.""" - if isinstance(v, str): - return v.lower().replace(" ", "_") - return ",".join([country.lower().replace(" ", "_") for country in list(v)]) + def validate_country(cls, c: str): # pylint: disable=E0213 + """Validate country.""" + return ",".join([v.lower() for v in c.replace(" ", "_").split(",")]) class NasdaqEconomicCalendarData(EconomicCalendarData):