Skip to content

Commit

Permalink
Feature/fred-releases: Adds some FRED functionality to the economy ro…
Browse files Browse the repository at this point in the history
…uter. (OpenBB-finance#5793)

* fred economic releases search

* add FRED functions for series and searching.

* codespell

* ruff

* data description.

* replace two functions with one fred_search

* rename standard model to fred_series

* remove consolidated fred model

* return series metadata as warning

* recapture test

* fill nan with None
  • Loading branch information
deeleeramone authored Nov 28, 2023
1 parent f42c457 commit 3959f95
Show file tree
Hide file tree
Showing 17 changed files with 2,224 additions and 77 deletions.
1 change: 1 addition & 0 deletions .codespell.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ vai
varian
vie
welp
wew
yeld
zar
zlot
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""FRED Search Model."""

from datetime import (
date as dateType,
datetime,
)
from typing import Optional, Union

from dateutil import parser
from pydantic import Field, field_validator

from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams


class SearchQueryParams(QueryParams):
"""FRED Search Query Params."""

query: Optional[str] = Field(default=None, description="The search word(s).")


class SearchData(Data):
"""FRED Search Data."""

release_id: Optional[Union[str, int]] = Field(
default=None,
description="The release ID for queries.",
)
series_id: Optional[str] = Field(
default=None,
description="The series ID for the item in the release.",
)
name: Optional[str] = Field(
default=None,
description="The name of the release.",
)
title: Optional[str] = Field(
default=None,
description="The title of the series.",
)
observation_start: Optional[dateType] = Field(
default=None, description="The date of the first observation in the series."
)
observation_end: Optional[dateType] = Field(
default=None, description="The date of the last observation in the series."
)
frequency: Optional[str] = Field(
default=None,
description="The frequency of the data.",
)
frequency_short: Optional[str] = Field(
default=None,
description="Short form of the data frequency.",
)
units: Optional[str] = Field(
default=None,
description="The units of the data.",
)
units_short: Optional[str] = Field(
default=None,
description="Short form of the data units.",
)
seasonal_adjustment: Optional[str] = Field(
default=None,
description="The seasonal adjustment of the data.",
)
seasonal_adjustment_short: Optional[str] = Field(
default=None,
description="Short form of the data seasonal adjustment.",
)
last_updated: Optional[datetime] = Field(
default=None,
description="The datetime of the last update to the data.",
)
notes: Optional[str] = Field(
default=None, description="Description of the release."
)
press_release: Optional[bool] = Field(
description="If the release is a press release.",
default=None,
)
url: Optional[str] = Field(default=None, description="URL to the release.")

@field_validator("last_updated", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v):
"""Validate datetime format."""
return parser.isoparse(v) if v else None
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""FRED Indices Standard Model."""
"""FRED Series Standard Model."""


from datetime import date as dateType
Expand All @@ -14,8 +14,8 @@
)


class FredIndicesQueryParams(QueryParams):
"""FRED Indices Query."""
class SeriesQueryParams(QueryParams):
"""FRED Series Query."""

symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", ""),
Expand All @@ -27,7 +27,7 @@ class FredIndicesQueryParams(QueryParams):
description=QUERY_DESCRIPTIONS.get("end_date", ""), default=None
)
limit: Optional[int] = Field(
description=QUERY_DESCRIPTIONS.get("limit", ""), default=100
description=QUERY_DESCRIPTIONS.get("limit", ""), default=100000
)

@field_validator("symbol", mode="before", check_fields=False)
Expand All @@ -38,8 +38,7 @@ def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
return ",".join([symbol.upper() for symbol in list(v)])


class FredIndicesData(Data):
"""FRED Indices Data."""
class SeriesData(Data):
"""FRED Series Data."""

date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
value: Optional[float] = Field(default=None, description="Value of the index.")
97 changes: 97 additions & 0 deletions openbb_platform/extensions/economy/integration/test_economy_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,100 @@ def test_economy_balance_of_payments(params, headers):
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200


@pytest.mark.parametrize(
"params",
[
(
{
"query": None,
"is_release": False,
"release_id": 15,
"offset": 0,
"limit": 1000,
"filter_variable": "frequency",
"filter_value": "Monthly",
"tag_names": "nsa",
"exclude_tag_names": None,
"provider": "fred",
}
),
(
{
"query": "GDP",
"is_release": True,
"release_id": None,
"offset": 0,
"limit": 1000,
"filter_variable": None,
"filter_value": None,
"tag_names": None,
"exclude_tag_names": None,
"provider": "fred",
}
),
(
{
"query": None,
"is_release": False,
"release_id": None,
"offset": None,
"limit": None,
"filter_variable": None,
"filter_value": None,
"tag_names": None,
"exclude_tag_names": None,
"provider": "fred",
}
),
],
)
@pytest.mark.integration
def test_economy_fred_search(params, headers):
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/economy/fred_search?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200


@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "SP500",
"start_date": None,
"end_date": None,
"limit": 10000,
"frequency": "q",
"aggregation_method": "eop",
"transform": "chg",
"provider": "fred",
}
),
(
{
"symbol": "FEDFUNDS",
"start_date": None,
"end_date": None,
"limit": 10000,
"all_pages": True,
"provider": "intrinio",
"sleep": None,
}
),
],
)
@pytest.mark.integration
def test_economy_fred_series(params, headers):
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/economy/fred_series?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
102 changes: 96 additions & 6 deletions openbb_platform/extensions/economy/integration/test_economy_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
@pytest.mark.parametrize(
"params",
[
({"start_date": "2023-01-01", "end_date": "2023-06-06"}),
({"start_date": "2023-01-01", "end_date": "2023-06-06", "provider": "fmp"}),
(
{
"provider": "nasdaq",
Expand All @@ -39,11 +39,6 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
"group": "gdp",
}
),
(
{
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
Expand Down Expand Up @@ -213,3 +208,98 @@ def test_economy_balance_of_payments(params, obb):
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0


@pytest.mark.parametrize(
"params",
[
(
{
"query": None,
"is_release": False,
"release_id": "15",
"offset": 0,
"limit": 1000,
"filter_variable": "frequency",
"filter_value": "Monthly",
"tag_names": "nsa",
"exclude_tag_names": None,
"provider": "fred",
}
),
(
{
"query": "GDP",
"is_release": True,
"release_id": None,
"offset": 0,
"limit": 1000,
"filter_variable": None,
"filter_value": None,
"tag_names": None,
"exclude_tag_names": None,
"provider": "fred",
}
),
(
{
"query": None,
"is_release": False,
"release_id": None,
"offset": None,
"limit": None,
"filter_variable": None,
"filter_value": None,
"tag_names": None,
"exclude_tag_names": None,
"provider": "fred",
}
),
],
)
@pytest.mark.integration
def test_economy_fred_search(params, obb):
params = {p: v for p, v in params.items() if v}

result = obb.economy.fred_search(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0


@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "SP500",
"start_date": None,
"end_date": None,
"limit": 10000,
"frequency": "q",
"aggregation_method": "eop",
"transform": "chg",
"provider": "fred",
}
),
(
{
"symbol": "FEDFUNDS",
"start_date": None,
"end_date": None,
"limit": 10000,
"all_pages": True,
"provider": "intrinio",
"sleep": None,
}
),
],
)
@pytest.mark.integration
def test_economy_fred_series(params, obb):
params = {p: v for p, v in params.items() if v}

result = obb.economy.fred_series(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,29 @@ async def balance_of_payments(
) -> OBBject[BaseModel]:
"""Balance of Payments Reports."""
return await OBBject.from_query(Query(**locals()))


@router.command(model="FredSearch")
async def fred_search(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject[BaseModel]:
"""
Search for FRED series or economic releases by ID or fuzzy query.
This does not return the observation values, only the metadata.
Use this function to find series IDs for `fred_series()`.
"""
return await OBBject.from_query(Query(**locals()))


@router.command(model="FredSeries")
async def fred_series(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject[BaseModel]:
"""Get data by series ID from FRED."""
return await OBBject.from_query(Query(**locals()))
Loading

0 comments on commit 3959f95

Please sign in to comment.