add mortgage indices (#6501)

This commit is contained in:
Danglewood
2024-06-16 14:30:46 -07:00
committed by GitHub
parent 3db89dae5e
commit 650912bf0c
10 changed files with 869 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
"""Mortgage Indices Standard Model."""
from datetime import (
date as dateType,
)
from typing import Optional
from pydantic import Field
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
class MortgageIndicesQueryParams(QueryParams):
"""Mortgage Indices Query."""
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", ""),
)
class MortgageIndicesData(Data):
"""Mortgage Indices Data."""
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
symbol: Optional[str] = Field(
default=None,
description=DATA_DESCRIPTIONS.get("symbol", ""),
)
name: Optional[str] = Field(
default=None,
description="Name of the index.",
)
rate: float = Field(
description="Mortgage rate.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)

View File

@@ -694,3 +694,29 @@ def test_fixedincome_bond_indices(params, headers):
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@parametrize(
"params",
[
{
"provider": "fred",
"index": "usda_30y,fha_30y",
"start_date": "2023-05-31",
"end_date": "2024-06-01",
"transform": None,
"frequency": None,
"aggregation_method": "avg",
},
],
)
@pytest.mark.integration
def test_fixedincome_mortgage_indices(params, headers):
"""Test the mortgage indices endpoint."""
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/fixedincome/mortgage_indices?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200

View File

@@ -647,3 +647,28 @@ def test_fixedincome_bond_indices(params, obb):
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@parametrize(
"params",
[
{
"provider": "fred",
"index": "usda_30y,fha_30y",
"start_date": "2023-05-31",
"end_date": "2024-06-01",
"transform": None,
"frequency": None,
"aggregation_method": "avg",
},
],
)
@pytest.mark.integration
def test_fixedincome_mortgage_indices(params, obb):
"""Test the mortgage indices endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.fixedincome.mortgage_indices(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0

View File

@@ -81,3 +81,29 @@ async def bond_indices(
) -> OBBject: # type: ignore
"""Bond Indices."""
return await OBBject.from_query(Query(**locals()))
@router.command(
model="MortgageIndices",
examples=[
APIEx(
description="The default state for FRED are the primary mortgage indices from Optimal Blue.",
parameters={"provider": "fred"},
),
APIEx(
description="Multiple indices can be requested.",
parameters={
"index": "jumbo_30y,conforming_30y,conforming_15y",
"provider": "fred",
},
),
],
)
async def mortgage_indices(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject: # type: ignore
"""Mortgage Indices."""
return await OBBject.from_query(Query(**locals()))

View File

@@ -32235,6 +32235,191 @@
},
"model": "BondIndices"
},
"/fixedincome/mortgage_indices": {
"deprecated": {
"flag": null,
"message": null
},
"description": "Mortgage Indices.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\n# The default state for FRED are the primary mortgage indices from Optimal Blue.\nobb.fixedincome.mortgage_indices(provider='fred')\n# Multiple indices can be requested.\nobb.fixedincome.mortgage_indices(index=jumbo_30y,conforming_30y,conforming_15y, provider='fred')\n```\n\n",
"parameters": {
"standard": [
{
"name": "start_date",
"type": "Union[date, str]",
"description": "Start date of the data, in YYYY-MM-DD format.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "end_date",
"type": "Union[date, str]",
"description": "End date of the data, in YYYY-MM-DD format.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "provider",
"type": "Literal['fred']",
"description": "The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: f, r, e, d.",
"default": null,
"optional": true
}
],
"fred": [
{
"name": "index",
"type": "Union[Union[Literal['primary', 'ltv_lte_80', 'ltv_gt_80', 'conforming_30y', 'conforming_30y_na', 'jumbo_30y', 'fha_30y', 'va_30y', 'usda_30y', 'conforming_15y', 'ltv_lte80_fico_ge740', 'ltv_lte80_fico_a720b739', 'ltv_lte80_fico_a700b719', 'ltv_lte80_fico_a680b699', 'ltv_lte80_fico_lt680', 'ltv_gt80_fico_ge740', 'ltv_gt80_fico_a720b739', 'ltv_gt80_fico_a700b719', 'ltv_gt80_fico_a680b699', 'ltv_gt80_fico_lt680'], str], List[Union[Literal['primary', 'ltv_lte_80', 'ltv_gt_80', 'conforming_30y', 'conforming_30y_na', 'jumbo_30y', 'fha_30y', 'va_30y', 'usda_30y', 'conforming_15y', 'ltv_lte80_fico_ge740', 'ltv_lte80_fico_a720b739', 'ltv_lte80_fico_a700b719', 'ltv_lte80_fico_a680b699', 'ltv_lte80_fico_lt680', 'ltv_gt80_fico_ge740', 'ltv_gt80_fico_a720b739', 'ltv_gt80_fico_a700b719', 'ltv_gt80_fico_a680b699', 'ltv_gt80_fico_lt680'], str]]]",
"description": "The specific index, or index group, to query. Default is the 'primary' group. Multiple items allowed for provider(s): fred.",
"default": "primary",
"optional": true,
"choices": [
"primary",
"ltv_lte_80",
"ltv_gt_80",
"conforming_30y",
"conforming_30y_na",
"jumbo_30y",
"fha_30y",
"va_30y",
"usda_30y",
"conforming_15y",
"ltv_lte80_fico_ge740",
"ltv_lte80_fico_a720b739",
"ltv_lte80_fico_a700b719",
"ltv_lte80_fico_a680b699",
"ltv_lte80_fico_lt680",
"ltv_gt80_fico_ge740",
"ltv_gt80_fico_a720b739",
"ltv_gt80_fico_a700b719",
"ltv_gt80_fico_a680b699",
"ltv_gt80_fico_lt680"
]
},
{
"name": "frequency",
"type": "Literal['a', 'q', 'm', 'w', 'd', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']",
"description": "Frequency aggregation to convert daily data to lower frequency. None = No change a = Annual q = Quarterly m = Monthly w = Weekly d = Daily wef = Weekly, Ending Friday weth = Weekly, Ending Thursday wew = Weekly, Ending Wednesday wetu = Weekly, Ending Tuesday wem = Weekly, Ending Monday wesu = Weekly, Ending Sunday wesa = Weekly, Ending Saturday bwew = Biweekly, Ending Wednesday bwem = Biweekly, Ending Monday",
"default": null,
"optional": true,
"choices": [
"a",
"q",
"m",
"w",
"d",
"wef",
"weth",
"wew",
"wetu",
"wem",
"wesu",
"wesa",
"bwew",
"bwem"
]
},
{
"name": "aggregation_method",
"type": "Literal['avg', 'sum', 'eop']",
"description": "A key that indicates the aggregation method used for frequency aggregation. This parameter has no affect if the frequency parameter is not set, default is 'avg'. avg = Average sum = Sum eop = End of Period",
"default": "avg",
"optional": true,
"choices": [
"avg",
"sum",
"eop"
]
},
{
"name": "transform",
"type": "Literal['chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log']",
"description": "Transformation type None = No transformation chg = Change ch1 = Change from Year Ago pch = Percent Change pc1 = Percent Change from Year Ago pca = Compounded Annual Rate of Change cch = Continuously Compounded Rate of Change cca = Continuously Compounded Annual Rate of Change log = Natural Log",
"default": null,
"optional": true,
"choices": [
"chg",
"ch1",
"pch",
"pc1",
"pca",
"cch",
"cca",
"log"
]
}
]
},
"returns": {
"OBBject": [
{
"name": "results",
"type": "List[MortgageIndices]",
"description": "Serializable results."
},
{
"name": "provider",
"type": "Optional[Literal['fred']]",
"description": "Provider name."
},
{
"name": "warnings",
"type": "Optional[List[Warning_]]",
"description": "List of warnings."
},
{
"name": "chart",
"type": "Optional[Chart]",
"description": "Chart object."
},
{
"name": "extra",
"type": "Dict[str, Any]",
"description": "Extra info."
}
]
},
"data": {
"standard": [
{
"name": "date",
"type": "date",
"description": "The date of the data.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "symbol",
"type": "str",
"description": "Symbol representing the entity requested in the data.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "name",
"type": "str",
"description": "Name of the index.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "rate",
"type": "float",
"description": "Mortgage rate.",
"default": "",
"optional": false,
"choices": null
}
],
"fred": []
},
"model": "MortgageIndices"
},
"/index/price/historical": {
"deprecated": {
"flag": null,

View File

@@ -16,6 +16,7 @@ class ROUTER_fixedincome(Container):
bond_indices
/corporate
/government
mortgage_indices
/rate
sofr
/spreads
@@ -184,6 +185,132 @@ class ROUTER_fixedincome(Container):
command_runner=self._command_runner
)
@exception_handler
@validate
def mortgage_indices(
self,
start_date: Annotated[
Union[datetime.date, None, str],
OpenBBField(description="Start date of the data, in YYYY-MM-DD format."),
] = None,
end_date: Annotated[
Union[datetime.date, None, str],
OpenBBField(description="End date of the data, in YYYY-MM-DD format."),
] = None,
provider: Annotated[
Optional[Literal["fred"]],
OpenBBField(
description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: fred."
),
] = None,
**kwargs
) -> OBBject:
"""Mortgage Indices.
Parameters
----------
start_date : Union[datetime.date, None, str]
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.
provider : Optional[Literal['fred']]
The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: fred.
index : Union[Literal['primary', 'ltv_lte_80', 'ltv_gt_80', 'conforming_30y', 'conforming_30y_na', 'jumbo_30y', 'fha_30y', 'va_30y', 'usda_30y', 'conforming_15y', 'ltv_lte80_fico_ge740', 'ltv_lte80_fico_a720b739', 'ltv_lte80_fico_a700b719', 'ltv_lte80_fico_a680b699', 'ltv_lte80_fico_lt680', 'ltv_gt80_fico_ge740', 'ltv_gt80_fico_a720b739', 'ltv_gt80_fico_a700b719', 'ltv_gt80_fico_a680b699', 'ltv_gt80_fico_lt680'], str]
The specific index, or index group, to query. Default is the 'primary' group. Multiple comma separated items allowed. (provider: fred)
frequency : Optional[Literal['a', 'q', 'm', 'w', 'd', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']]
Frequency aggregation to convert daily data to lower frequency.
None = No change
a = Annual
q = Quarterly
m = Monthly
w = Weekly
d = Daily
wef = Weekly, Ending Friday
weth = Weekly, Ending Thursday
wew = Weekly, Ending Wednesday
wetu = Weekly, Ending Tuesday
wem = Weekly, Ending Monday
wesu = Weekly, Ending Sunday
wesa = Weekly, Ending Saturday
bwew = Biweekly, Ending Wednesday
bwem = Biweekly, Ending Monday
(provider: fred)
aggregation_method : Literal['avg', 'sum', 'eop']
A key that indicates the aggregation method used for frequency aggregation.
This parameter has no affect if the frequency parameter is not set, default is 'avg'.
avg = Average
sum = Sum
eop = End of Period
(provider: fred)
transform : Optional[Literal['chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log']]
Transformation type
None = No transformation
chg = Change
ch1 = Change from Year Ago
pch = Percent Change
pc1 = Percent Change from Year Ago
pca = Compounded Annual Rate of Change
cch = Continuously Compounded Rate of Change
cca = Continuously Compounded Annual Rate of Change
log = Natural Log
(provider: fred)
Returns
-------
OBBject
results : List[MortgageIndices]
Serializable results.
provider : Optional[Literal['fred']]
Provider name.
warnings : Optional[List[Warning_]]
List of warnings.
chart : Optional[Chart]
Chart object.
extra : Dict[str, Any]
Extra info.
MortgageIndices
---------------
date : date
The date of the data.
symbol : Optional[str]
Symbol representing the entity requested in the data.
name : Optional[str]
Name of the index.
rate : float
Mortgage rate.
Examples
--------
>>> from openbb import obb
>>> # The default state for FRED are the primary mortgage indices from Optimal Blue.
>>> obb.fixedincome.mortgage_indices(provider='fred')
>>> # Multiple indices can be requested.
>>> obb.fixedincome.mortgage_indices(index='jumbo_30y,conforming_30y,conforming_15y', provider='fred')
""" # noqa: E501
return self._run(
"/fixedincome/mortgage_indices",
**filter_inputs(
provider_choices={
"provider": self._get_provider(
provider,
"fixedincome.mortgage_indices",
("fred",),
)
},
standard_params={
"start_date": start_date,
"end_date": end_date,
},
extra_params=kwargs,
info={"index": {"fred": {"multiple_items_allowed": True}}},
)
)
@property
def rate(self):
# pylint: disable=import-outside-toplevel

View File

@@ -18,6 +18,7 @@ from openbb_fred.models.hqm import FREDHighQualityMarketCorporateBondFetcher
from openbb_fred.models.ice_bofa import FREDICEBofAFetcher
from openbb_fred.models.iorb_rates import FREDIORBFetcher
from openbb_fred.models.moody import FREDMoodyCorporateBondIndexFetcher
from openbb_fred.models.mortgage_indices import FredMortgageIndicesFetcher
from openbb_fred.models.regional import FredRegionalDataFetcher
from openbb_fred.models.retail_prices import FredRetailPricesFetcher
from openbb_fred.models.search import (
@@ -57,6 +58,7 @@ Research division of the Federal Reserve Bank of St. Louis that has more than
"EuropeanCentralBankInterestRates": FREDEuropeanCentralBankInterestRatesFetcher,
"ICEBofA": FREDICEBofAFetcher,
"MoodyCorporateBondIndex": FREDMoodyCorporateBondIndexFetcher,
"MortgageIndices": FredMortgageIndicesFetcher,
"CommercialPaper": FREDCommercialPaperFetcher,
"FredSearch": FredSearchFetcher,
"FredSeries": FredSeriesFetcher,

View File

@@ -0,0 +1,312 @@
"""FRED Mortgage Indices Model."""
# pylint: disable=unused-argument
from typing import Any, Dict, List, Literal, Optional, Union
from warnings import warn
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.mortgage_indices import (
MortgageIndicesData,
MortgageIndicesQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_fred.models.series import FredSeriesFetcher
from pandas import Categorical, DataFrame
from pydantic import Field, field_validator
MORTGAGE_ID_TO_TITLE = {
"OBMMIC30YF": "30-Year Fixed Rate Conforming",
"OBMMIC30YFNA": "30-Year Fixed Rate Conforming Non-Adjusted",
"OBMMIJUMBO30YF": "30-Year Fixed Rate Jumbo",
"OBMMIFHA30YF": "30-Year Fixed Rate FHA",
"OBMMIVA30YF": "30-Year Fixed Rate Veterans Affairs",
"OBMMIUSDA30YF": "30-Year Fixed Rate USDA",
"OBMMIC15YF": "15-Year Fixed Rate Conforming",
"OBMMIC30YFLVLE80FGE740": "30-Year Fixed Rate Conforming LTV <= 80 FICO >= 740",
"OBMMIC30YFLVLE80FB720A739": "30-Year Fixed Rate Conforming LTV <= 80 FICO 720-739",
"OBMMIC30YFLVLE80FB700A719": "30-Year Fixed Rate Conforming LTV <= 80 FICO 700-719",
"OBMMIC30YFLVLE80FB680A699": "30-Year Fixed Rate Conforming LTV <= 80 FICO 680-699",
"OBMMIC30YFLVLE80FLT680": "30-Year Fixed Rate Conforming LTV <= 80 FICO < 680",
"OBMMIC30YFLVGT80FGE740": "30-Year Fixed Rate Conforming LTV > 80 FICO >= 740",
"OBMMIC30YFLVGT80FB720A739": "30-Year Fixed Rate Conforming LTV > 80 FICO 720-739",
"OBMMIC30YFLVGT80FB700A719": "30-Year Fixed Rate Conforming LTV > 80 FICO 700-719",
"OBMMIC30YFLVGT80FB680A699": "30-Year Fixed Rate Conforming LTV > 80 FICO 680-699",
"OBMMIC30YFLVGT80FLT680": "30-Year Fixed Rate Conforming LTV > 80 FICO < 680",
}
MORTGAGE_GROUPS = {
"primary": [
"OBMMIC30YF",
"OBMMIC30YFNA",
"OBMMIJUMBO30YF",
"OBMMIFHA30YF",
"OBMMIVA30YF",
"OBMMIUSDA30YF",
"OBMMIC15YF",
],
"ltv_lte_80": [
"OBMMIC30YFLVLE80FGE740",
"OBMMIC30YFLVLE80FB720A739",
"OBMMIC30YFLVLE80FB700A719",
"OBMMIC30YFLVLE80FB680A699",
"OBMMIC30YFLVLE80FLT680",
],
"ltv_gt_80": [
"OBMMIC30YFLVGT80FGE740",
"OBMMIC30YFLVGT80FB720A739",
"OBMMIC30YFLVGT80FB700A719",
"OBMMIC30YFLVGT80FB680A699",
"OBMMIC30YFLVGT80FLT680",
],
}
MORTGAGE_CHOICES_TO_ID = {
"primary": ",".join(MORTGAGE_GROUPS["primary"]),
"ltv_lte_80": ",".join(MORTGAGE_GROUPS["ltv_lte_80"]),
"ltv_gt_80": ",".join(MORTGAGE_GROUPS["ltv_gt_80"]),
"conforming_30y": "OBMMIC30YF",
"conforming_30y_na": "OBMMIC30YFNA",
"jumbo_30y": "OBMMIJUMBO30YF",
"fha_30y": "OBMMIFHA30YF",
"va_30y": "OBMMIVA30YF",
"usda_30y": "OBMMIUSDA30YF",
"conforming_15y": "OBMMIC15YF",
"ltv_lte80_fico_ge740": "OBMMIC30YFLVLE80FGE740",
"ltv_lte80_fico_a720b739": "OBMMIC30YFLVLE80FB720A739",
"ltv_lte80_fico_a700b719": "OBMMIC30YFLVLE80FB700A719",
"ltv_lte80_fico_a680b699": "OBMMIC30YFLVLE80FB680A699",
"ltv_lte80_fico_lt680": "OBMMIC30YFLVLE80FLT680",
"ltv_gt80_fico_ge740": "OBMMIC30YFLVGT80FGE740",
"ltv_gt80_fico_a720b739": "OBMMIC30YFLVGT80FB720A739",
"ltv_gt80_fico_a700b719": "OBMMIC30YFLVGT80FB700A719",
"ltv_gt80_fico_a680b699": "OBMMIC30YFLVGT80FB680A699",
"ltv_gt80_fico_lt680": "OBMMIC30YFLVGT80FLT680",
}
MortgageChoices = Literal[
"primary",
"ltv_lte_80",
"ltv_gt_80",
"conforming_30y",
"conforming_30y_na",
"jumbo_30y",
"fha_30y",
"va_30y",
"usda_30y",
"conforming_15y",
"ltv_lte80_fico_ge740",
"ltv_lte80_fico_a720b739",
"ltv_lte80_fico_a700b719",
"ltv_lte80_fico_a680b699",
"ltv_lte80_fico_lt680",
"ltv_gt80_fico_ge740",
"ltv_gt80_fico_a720b739",
"ltv_gt80_fico_a700b719",
"ltv_gt80_fico_a680b699",
"ltv_gt80_fico_lt680",
]
class FredMortgageIndicesQueryParams(MortgageIndicesQueryParams):
"""FRED Mortgage Indices Query."""
__json_schema_extra__ = {"index": {"multiple_items_allowed": True}}
index: Union[MortgageChoices, str] = Field(
default="primary",
description="The specific index, or index group, to query. Default is the 'primary' group.",
choices=list(MORTGAGE_CHOICES_TO_ID.keys()),
)
frequency: Union[
None,
Literal[
"a",
"q",
"m",
"w",
"d",
"wef",
"weth",
"wew",
"wetu",
"wem",
"wesu",
"wesa",
"bwew",
"bwem",
],
] = Field(
default=None,
description="""
Frequency aggregation to convert daily data to lower frequency.
None = No change
a = Annual
q = Quarterly
m = Monthly
w = Weekly
d = Daily
wef = Weekly, Ending Friday
weth = Weekly, Ending Thursday
wew = Weekly, Ending Wednesday
wetu = Weekly, Ending Tuesday
wem = Weekly, Ending Monday
wesu = Weekly, Ending Sunday
wesa = Weekly, Ending Saturday
bwew = Biweekly, Ending Wednesday
bwem = Biweekly, Ending Monday
""",
json_schema_extra={
"choices": [
"a",
"q",
"m",
"w",
"d",
"wef",
"weth",
"wew",
"wetu",
"wem",
"wesu",
"wesa",
"bwew",
"bwem",
]
},
)
aggregation_method: Literal["avg", "sum", "eop"] = Field(
default="avg",
description="""
A key that indicates the aggregation method used for frequency aggregation.
This parameter has no affect if the frequency parameter is not set, default is 'avg'.
avg = Average
sum = Sum
eop = End of Period
""",
json_schema_extra={"choices": ["avg", "sum", "eop"]},
)
transform: Union[
None, Literal["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]
] = Field(
default=None,
description="""
Transformation type
None = No transformation
chg = Change
ch1 = Change from Year Ago
pch = Percent Change
pc1 = Percent Change from Year Ago
pca = Compounded Annual Rate of Change
cch = Continuously Compounded Rate of Change
cca = Continuously Compounded Annual Rate of Change
log = Natural Log
""",
json_schema_extra={
"choices": ["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]
},
)
@field_validator("index", mode="before", check_fields=False)
@classmethod
def validate_index(cls, v):
"""Validate index."""
indices = v.split(",")
new_indices: List = []
for index in indices:
if index in MORTGAGE_CHOICES_TO_ID:
new_indices.append(index)
else:
warn(f"Invalid index '{index}' will be ignored.")
if not new_indices:
raise OpenBBError(
f"No valid indices found. Must be any of: {list(MORTGAGE_CHOICES_TO_ID.keys())}"
)
return ",".join(new_indices)
class FredMortgageIndicesData(MortgageIndicesData):
"""FRED Mortgage Indices Data."""
class FredMortgageIndicesFetcher(
Fetcher[
FredMortgageIndicesQueryParams,
List[FredMortgageIndicesData],
]
):
"""FRED Mortgage Indices Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FredMortgageIndicesQueryParams:
"""Transform query."""
return FredMortgageIndicesQueryParams(**params)
@staticmethod
async def aextract_data(
query: FredMortgageIndicesQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> Dict:
"""Extract data."""
indices = query.index.split(",")
ids = [MORTGAGE_CHOICES_TO_ID[index] for index in indices]
try:
response = await FredSeriesFetcher.fetch_data(
dict(
symbol=",".join(ids),
start_date=query.start_date,
end_date=query.end_date,
transform=query.transform,
frequency=query.frequency,
aggregation_method=query.aggregation_method,
),
credentials,
)
except Exception as e:
raise e from e
return {
"metadata": response.metadata,
"data": [d.model_dump() for d in response.result],
}
@staticmethod
def transform_data(
query: FredMortgageIndicesQueryParams,
data: Dict,
**kwargs: Any,
) -> AnnotatedResult[List[FredMortgageIndicesData]]:
"""Transform data."""
if not data.get("data"):
raise EmptyDataError("The request was returned empty.")
df = DataFrame.from_records(data["data"])
metadata = data.get("metadata", {})
# Flatten the data.
df = (
df.melt(id_vars="date", var_name="symbol", value_name="value")
.query("value.notnull()")
.rename(columns={"value": "rate"})
)
df["name"] = df.symbol.map(MORTGAGE_ID_TO_TITLE)
# Normalize the percent values.
df["rate"] = df["rate"] / 100
df = df.fillna("N/A").replace("N/A", None)
df["name"] = Categorical(
df["name"],
categories=[
d
for d in list(MORTGAGE_ID_TO_TITLE.values())
if d in df["name"].unique()
],
ordered=True,
)
df.sort_values(["date", "name"], inplace=True)
records = df.to_dict(orient="records")
return AnnotatedResult(
result=[FredMortgageIndicesData.model_validate(r) for r in records],
metadata=metadata,
)

View File

@@ -0,0 +1,105 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.stlouisfed.org/fred/series/observations?aggregation_method=avg&api_key=MOCK_API_KEY&file_type=json&limit=100000&observation_end=2024-06-04&observation_start=2024-06-01&series_id=OBMMIJUMBO30YF
response:
body:
string: !!binary |
H4sIAAAAAAAEA6pWKkpNzCnJzE2NLy5JLCpRslIyMjAy0TUw0zU0UdJByKbmpaDL5ScVpxaVJZZk
5udhaDYwVNJRQlaAqt8AZHZpXmZJsZKVUk5mHkhxaUlBaUl8SWVBqpKVoY5SWmZOKpSnlFWcD1ZS
lJJaFJ9UqWSFYnRKYkmqko5ScX5RSXw+SImSlVJicbKSjlJyfmleiZKVkY5SflpacWqJkpWBjlJO
Zm5miZKVoQEIoDiyWMkqmpIAATsEEYAGxko6SmWJOaWpSlZK5nqGppZKtTrUNB8UiEjmA2ZmrFQb
WwsAU848LNIBAAA=
headers:
Cache-Control:
- max-age=0, no-cache
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '239'
Content-Type:
- application/json; charset=UTF-8
Date:
- Fri, 14 Jun 2024 05:34:53 GMT
Expires:
- Fri, 14 Jun 2024 05:34:53 GMT
Last-Modified:
- Thu, 13 Jun 2024 12:01:06 GMT
Pragma:
- no-cache
Server:
- Apache
Strict-Transport-Security:
- max-age=86400
Vary:
- Accept-Encoding
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.stlouisfed.org/fred/series?api_key=MOCK_API_KEY&file_type=json&series_id=OBMMIJUMBO30YF
response:
body:
string: !!binary |
H4sIAAAAAAAEA6pWKkpNzCnJzE2NLy5JLCpRslIyMjAy0TUw0zU0VtJByKbmpaDLFacWZaYWFytZ
RVcrZYJk/Z18fT29Qn2d/I0NIt2QdZNsdklmSU6qkpWSsYFuZGpikYJbZkVqikJQYkmqgldpblK+
gm9+UUl6YnqqgmdeSmqFko5SflJxalFZYklmfh6SVwzNdQ0MdQ1AXkFWgOYbIyUdpbSi1MLS1Lzk
SiUrJZfEzJxKZLH44ox8cOC4KOkoleZllhQrWSkFpBYlp+aVwETgalSVdJSKUxOL8/MSc+ITU7JK
i0tyQeqslPzySxSCoTI5lQqOYLnUFOzq4eb5BTsq6SjlJBaXxJcWpCSWpILCGhFLCgbmVgaGVgZm
ugamSjpKBfkFpTmJRZkllUpWZqY6Snn5Jakg1/oXlGTmJuYoOOWUpiJCzzexKDu1BBSImcmpxQoa
GSUlBcVWMfox+uXl5UZ6+RBNSTmlqXrJ+bkx+vlJubmZMfqaMaVGhkZGChr+oCiHcDQVMosVkhNz
kktzQG5USCvKz1VITC4pTcxRyMlPzk5NUShKLEktVijPLMlQSM7PKy7NTS0qVkhMLsovLlbIL0st
UsjPS9UtycgsSlHIT1NIzMlRyIVFc0lRYl5xYjIoeosV8sDRXJ6ZkqqnAHaBQmZeck5pSmqxQm5p
TklmQU4qQmdBUWZyZl66QmZeCtiPKallqTn5BakpColF+aV5KQolGSDVxSUK0KBTKCjKTylNLilW
SMxLUSguSE3OTMtMVkjKLyrKL08tAovm5CfmKeSATFJILCkpykwqLUkt1ovJi8lzTUzOANmVWoEW
HonFYJsSy1KLQOkW6sHEgoKi/IKiTFDKBgUPOKiKwWQqyGlF+aXpGWCNKBEIdaJCak5memZSZk5m
SSXYXTDPpualZ+alKuTnKSQqpGeWpeYppCRW6oHiPVUhJbUkMTOnWCExKb+0RCE3tSQjPyU/Jz8d
YkJKalpmXiYknBOLUhUSyxIzcxKTclIVMlKLUolPIYDpKdXG1gIAFK52ql8EAAA=
headers:
Cache-Control:
- max-age=0, no-cache
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '788'
Content-Type:
- application/json; charset=UTF-8
Date:
- Fri, 14 Jun 2024 05:34:53 GMT
Expires:
- Fri, 14 Jun 2024 05:34:53 GMT
Last-Modified:
- Thu, 13 Jun 2024 12:01:06 GMT
Pragma:
- no-cache
Server:
- Apache
Strict-Transport-Security:
- max-age=86400
Vary:
- Accept-Encoding
status:
code: 200
message: OK
version: 1

View File

@@ -21,6 +21,7 @@ from openbb_fred.models.hqm import FREDHighQualityMarketCorporateBondFetcher
from openbb_fred.models.ice_bofa import FREDICEBofAFetcher
from openbb_fred.models.iorb_rates import FREDIORBFetcher
from openbb_fred.models.moody import FREDMoodyCorporateBondIndexFetcher
from openbb_fred.models.mortgage_indices import FredMortgageIndicesFetcher
from openbb_fred.models.regional import FredRegionalDataFetcher
from openbb_fred.models.retail_prices import FredRetailPricesFetcher
from openbb_fred.models.search import (
@@ -357,3 +358,17 @@ def test_fred_bond_indices_fetcher(credentials=test_credentials):
fetcher = FredBondIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_fred_bond_mortgage_fetcher(credentials=test_credentials):
"""Test FredMortgageIndicesFetcher."""
params = {
"index": "jumbo_30y",
"start_date": datetime.date(2024, 6, 1),
"end_date": datetime.date(2024, 6, 4),
}
fetcher = FredMortgageIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None