Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding OECD Endpoints (CPI + Share Price Index) #6157

Merged
merged 45 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e84379c
remove ultima + althub
jmaslek Feb 27, 2024
8b68f23
move twitter keys
jmaslek Feb 27, 2024
6a8a1ea
Merge branch 'develop' of https://github.com/OpenBB-finance/OpenBBTer…
jmaslek Feb 29, 2024
86080bb
Enhance caching + more specific urls
jmaslek Feb 29, 2024
37e0cae
Merge branch 'develop' into feature/cleanup-oecd
jmaslek Feb 29, 2024
8cf48d9
Update urls for stir
jmaslek Feb 29, 2024
4dc2be6
LTIR
jmaslek Feb 29, 2024
ea429d5
cli
jmaslek Feb 29, 2024
4999a96
Merge branch 'develop' into feature/cleanup-oecd
jmaslek Feb 29, 2024
fd23ca4
one v3 file to fix
jmaslek Feb 29, 2024
6da6c22
Typing edits + gdp dates + gdp all countries
jmaslek Feb 29, 2024
3252bfe
Tests
jmaslek Feb 29, 2024
41376e8
Merge branch 'develop' into feature/cleanup-oecd
jmaslek Feb 29, 2024
ae28bd1
lint
jmaslek Feb 29, 2024
9781cae
not sure why those didnt record
jmaslek Feb 29, 2024
aa9bb4e
handle the fact that we artifically add some start dates and end dates
jmaslek Feb 29, 2024
40159f2
fix dates with actual solution not weird patch
jmaslek Feb 29, 2024
beceaa6
lint
jmaslek Feb 29, 2024
cef17db
Add CPI from OECD
jmaslek Mar 1, 2024
f356a58
coment
jmaslek Mar 1, 2024
2928da1
Add Share Price endpoint
jmaslek Mar 1, 2024
010562f
Merge branch 'develop' into feature/oecd-cpi
jmaslek Mar 1, 2024
3d23f4a
Merge branch 'develop' into feature/oecd-cpi
jmaslek Mar 4, 2024
21cc561
Merge branch 'develop' of https://github.com/OpenBB-finance/OpenBBTer…
jmaslek Mar 11, 2024
9f9cba3
Merge branch 'develop' of https://github.com/OpenBB-finance/OpenBBTer…
jmaslek Mar 12, 2024
58d48db
finish merge
jmaslek Mar 12, 2024
c1d518a
Clean up the CPI oecd model with the choices and allow expensitures t…
jmaslek Mar 12, 2024
0b10f07
commit this change
jmaslek Mar 12, 2024
895b20a
remove "share prices"
jmaslek Mar 12, 2024
2319164
Correctly handle country "choices" and let oecd do multiple countries
jmaslek Mar 12, 2024
59591e3
Merge branch 'develop' into feature/oecd-cpi
deeleeramone Mar 18, 2024
c7f3d9a
merge conflicts
deeleeramone May 27, 2024
1be0a8d
some fixes
deeleeramone May 28, 2024
0929203
standard model fields
deeleeramone May 28, 2024
1b93b4b
unit test
deeleeramone May 28, 2024
0ef8ee8
frequency in standard model
deeleeramone May 28, 2024
064a864
empty fred
deeleeramone May 28, 2024
4144c35
format oecd dates as period beginning
deeleeramone May 28, 2024
bc6bf77
fred -> when frequency is annual, the transform needs to be same peri…
deeleeramone May 28, 2024
8e81772
test thing
deeleeramone May 28, 2024
50d48b2
add share price index
deeleeramone May 28, 2024
aceba77
test params
deeleeramone May 28, 2024
5d3cddb
merge branch develop
deeleeramone May 29, 2024
775cba2
better parsing with CSV response
deeleeramone May 29, 2024
7b77f0a
update test cassette
deeleeramone May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,19 @@
"united_states",
]

CPI_UNITS = Literal["growth_previous", "growth_same", "index_2015"]

CPI_FREQUENCY = Literal["monthly", "quarter", "annual"]


class ConsumerPriceIndexQueryParams(QueryParams):
"""CPI Query."""

country: str = Field(description=QUERY_DESCRIPTIONS.get("country"))
units: CPI_UNITS = Field(
default="growth_same",
description=QUERY_DESCRIPTIONS.get("units", "")
+ """
Options:
- `growth_previous`: Percent growth from the previous period.
If monthly data, this is month-over-month, etc
- `growth_same`: Percent growth from the same period in the previous year.
If looking at monthly data, this would be year-over-year, etc.
- `index_2015`: Rescaled index value, such that the value in 2015 is 100.""",
country: str = Field(
description=QUERY_DESCRIPTIONS.get("country"), default="united_states"
)
units: Literal["index", "yoy", "mom"] = Field(
description="Units to get CPI for. Either index, month over month or year over year. Defaults to year over year.",
default="yoy",
)
frequency: CPI_FREQUENCY = Field(
default="monthly",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Share Prices 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 SharePriceQueryParams(QueryParams):
"""Share Price 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 SharePriceData(Data):
"""Share Price Data."""

date: Optional[dateType] = Field(
default=None, description=DATA_DESCRIPTIONS.get("date")
)
value: Optional[float] = Field(
default=None,
description="Interest rate (given as a whole number, i.e 10=10%)",
jmaslek marked this conversation as resolved.
Show resolved Hide resolved
)
country: Optional[str] = Field(
default=None,
description="Country for which interest rate is given",
)
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,30 @@ async def composite_leading_indicator(
return await OBBject.from_query(Query(**locals()))


@router.command(
model="SharePrice",
exclude_auto_examples=True,
examples=[
'obb.economy.share_price(country="all").to_df()',
],
)
async def share_price(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Share price indices are calculated from the prices of common shares of companies
traded on national or foreign stock exchanges. They are usually determined by the
stock exchange, using the closing daily values for the monthly data, and normally
expressed as simple arithmetic averages of the daily data. A share price index
measures how the value of the stocks in the index is changing, a share return index
tells the investor what their “return” is, meaning how much money they would make as
a result of investing in that basket of shares.
"""
return await OBBject.from_query(Query(**locals()))


@router.command(
model="STIR",
exclude_auto_examples=True,
Expand Down
2 changes: 1 addition & 1 deletion openbb_platform/providers/fred/openbb_fred/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from openbb_core.provider.abstract.provider import Provider
from openbb_fred.models.ameribor_rates import FREDAMERIBORFetcher
from openbb_fred.models.consumer_price_index import FREDConsumerPriceIndexFetcher
from openbb_fred.models.cp import FREDCommercialPaperFetcher
from openbb_fred.models.cpi import FREDConsumerPriceIndexFetcher
from openbb_fred.models.dwpcr_rates import FREDDiscountWindowPrimaryCreditRateFetcher
from openbb_fred.models.ecb_interest_rates import (
FREDEuropeanCentralBankInterestRatesFetcher,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any, Dict, List, Optional

from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.cpi import (
from openbb_core.provider.standard_models.consumer_price_index import (
ConsumerPriceIndexData,
ConsumerPriceIndexQueryParams,
)
Expand Down Expand Up @@ -41,9 +41,11 @@ def extract_data(
api_key = credentials.get("fred_api_key") if credentials else ""

all_options = all_cpi_options(query.harmonized)

units = {"mom": "growth_previous", "yoy": "growth_same", "index": "index_2015"}[
query.units
]
step_1 = [x for x in all_options if x["country"] in query.country]
step_2 = [x for x in step_1 if x["units"] == query.units]
step_2 = [x for x in step_1 if x["units"] == units]
step_3 = [x for x in step_2 if x["frequency"] == query.frequency]

series_dict = {}
Expand Down
2 changes: 1 addition & 1 deletion openbb_platform/providers/fred/tests/test_fred_fetchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import pytest
from openbb_core.app.service.user_service import UserService
from openbb_fred.models.ameribor_rates import FREDAMERIBORFetcher
from openbb_fred.models.consumer_price_index import FREDConsumerPriceIndexFetcher
from openbb_fred.models.cp import FREDCommercialPaperFetcher
from openbb_fred.models.cpi import FREDConsumerPriceIndexFetcher
from openbb_fred.models.dwpcr_rates import FREDDiscountWindowPrimaryCreditRateFetcher
from openbb_fred.models.ecb_interest_rates import (
FREDEuropeanCentralBankInterestRatesFetcher,
Expand Down
7 changes: 7 additions & 0 deletions openbb_platform/providers/oecd/openbb_oecd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

from openbb_core.provider.abstract.provider import Provider
from openbb_oecd.models.composite_leading_indicator import OECDCLIFetcher
from openbb_oecd.models.consumer_price_index import OECDCPIFetcher
from openbb_oecd.models.gdp_forecast import OECDGdpForecastFetcher
from openbb_oecd.models.gdp_nominal import OECDGdpNominalFetcher
from openbb_oecd.models.gdp_real import OECDGdpRealFetcher
from openbb_oecd.models.long_term_interest_rate import OECDLTIRFetcher
from openbb_oecd.models.short_term_interest_rate import OECDSTIRFetcher
from openbb_oecd.models.unemployment import OECDUnemploymentFetcher

from openbb_platform.providers.oecd.openbb_oecd.models.share_price import (
OECDSharePriceFetcher,
)

oecd_provider = Provider(
name="oecd",
website="https://stats.oecd.org/",
Expand All @@ -21,5 +26,7 @@
"CLI": OECDCLIFetcher,
"STIR": OECDSTIRFetcher,
"LTIR": OECDLTIRFetcher,
"ConsumerPriceIndex": OECDCPIFetcher,
"SharePrice": OECDSharePriceFetcher,
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
"""OECD CPI Data."""

import re
from datetime import date, timedelta
from typing import Any, Dict, List, Literal, Optional, Union

from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.consumer_price_index import (
ConsumerPriceIndexData,
ConsumerPriceIndexQueryParams,
)
from openbb_oecd.utils import helpers
from openbb_oecd.utils.constants import (
CODE_TO_COUNTRY_CPI,
COUNTRY_TO_CODE_CPI,
)
from pydantic import Field, field_validator

countries = tuple(CODE_TO_COUNTRY_CPI.values()) + ("all",)
CountriesLiteral = Literal[countries] # type: ignore

expendature_dict = {
"_T": "total",
"CP01": "food_non_alcoholic_beverages",
"CP02": "alcoholic_beverages_tobacco_narcotics",
"CP03": "clothing_footwear",
"CP04": "housing_water_electricity_gas",
"CP05": "furniture_household_equipment",
"CP06": "health",
"CP07": "transport",
"CP08": "communication",
"CP09": "recreation_culture",
"CP10": "education",
"CP11": "restaurants_hotels",
"CP12": "miscellaneous_goods_services",
"CP045_0722": "energy",
"GD": "goods",
"CP014T043": "housing",
"CP041T043X042": "housing_excluding_rentals" "",
"_TXCP01_NRG": "all_non_food_non_energy",
"SERVXCP041_042_0432": "services_less_housing",
"SERVXCP041_0432": "services_less_house_excl_rentals",
"SERV": "services",
"_TXNRG_01_02": "overall_excl_energy_food_alcohol_tobacco",
"CPRES": "residuals",
"CP0722": "fuels_lubricants_personal",
"CP041": "actual_rentals",
"CP042": "imputed_rentals",
"CP043": "maintenance_repair_dwelling",
"CP044": "water_supply_other_services",
"CP045": "electricity_gas_other_fuels",
}
expendature_dict = {v: k for k, v in expendature_dict.items()}
ExpendatureLiteral = Literal[tuple(expendature_dict.values())] # type: ignore


class OECDCPIQueryParams(ConsumerPriceIndexQueryParams):
"""OECD CPI Query.

Source: https://data-explorer.oecd.org/?lc=en
"""

country: CountriesLiteral = Field(
description="Country to get CPI for.", default="united_states"
)

seasonal_adjustment: bool = Field(
description="Whether to get seasonally adjusted CPI. Defaults to False.",
default=False,
)

units: Literal["index", "yoy", "mom"] = Field(
description="Units to get CPI for. Either index, month over month or year over year. Defaults to year over year.",
default="yoy",
)

expendature: ExpendatureLiteral = Field(
description="Expendature component of CPI.",
default="total",
)


class OECDCPIData(ConsumerPriceIndexData):
"""OECD CPI Data."""

@field_validator("date", mode="before")
@classmethod
def date_validate(cls, in_date: Union[date, str]): # pylint: disable=E0213
"""Validate value."""
if isinstance(in_date, str):
# i.e 2022-Q1
if re.match(r"\d{4}-Q[1-4]$", in_date):
year, quarter = in_date.split("-")
_year = int(year)
if quarter == "Q1":
return date(_year, 3, 31)
if quarter == "Q2":
return date(_year, 6, 30)
if quarter == "Q3":
return date(_year, 9, 30)
if quarter == "Q4":
return date(_year, 12, 31)
# Now match if it is monthly, i.e 2022-01
elif re.match(r"\d{4}-\d{2}$", in_date):
year, month = map(int, in_date.split("-")) # type: ignore
if month == 12:
return date(year, month, 31) # type: ignore
next_month = date(year, month + 1, 1) # type: ignore
return date(next_month.year, next_month.month, 1) - timedelta(days=1)
# Now match if it is yearly, i.e 2022
elif re.match(r"\d{4}$", in_date):
return date(int(in_date), 12, 31)
# If the input date is a year
if isinstance(in_date, int):
return date(in_date, 12, 31)

return in_date


class OECDCPIFetcher(Fetcher[OECDCPIQueryParams, List[OECDCPIData]]):
"""Transform the query, extract and transform the data from the OECD endpoints."""

@staticmethod
def transform_query(params: Dict[str, Any]) -> OECDCPIQueryParams:
"""Transform the query."""
transformed_params = params.copy()
if transformed_params["start_date"] is None:
transformed_params["start_date"] = date(1950, 1, 1)
if transformed_params["end_date"] is None:
transformed_params["end_date"] = date(date.today().year, 12, 31)
if transformed_params["country"] is None:
transformed_params["country"] = "united_states"

return OECDCPIQueryParams(**transformed_params)

# pylint: disable=unused-argument
@staticmethod
def extract_data(
query: OECDCPIQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the OECD endpoint."""
methodology = ["N", "HICP"][query.harmonized]
frequency = query.frequency[0].upper()
units = {
"index": "IX",
"yoy": "PA",
"mom": "PC",
}[query.units]
expendature = expendature_dict[query.expendature]
seasonal_adjustment = "Y" if query.seasonal_adjustment else "N"
country = "" if query.country == "all" else COUNTRY_TO_CODE_CPI[query.country]
# For caching, include this in the key
query_dict = {
k: v
for k, v in query.__dict__.items()
if k not in ["start_date", "end_date"]
}

url = (
f"https://sdmx.oecd.org/public/rest/data/OECD.SDD.TPS,DSD_PRICES@DF_PRICES_ALL,1.0/"
f"{country}.{frequency}.{methodology}.CPI.{units}.{expendature}.{seasonal_adjustment}."
)
data = helpers.get_possibly_cached_data(
url, function="economy_cpi", query_dict=query_dict
)
url_query = (
f"METHODOLOGY=='{methodology}' & UNIT_MEASURE=='{units}' & FREQ=='{frequency}' & "
f"ADJUSTMENT=='{seasonal_adjustment}' & EXPENDITURE=='{expendature}'"
)
url_query = url_query + f" & REF_AREA=='{country}'" if country else url_query

# Filter down
data = (
data.query(url_query)
.reset_index(drop=True)[["REF_AREA", "TIME_PERIOD", "VALUE"]]
.rename(
columns={"REF_AREA": "country", "TIME_PERIOD": "date", "VALUE": "value"}
)
)
data["country"] = data["country"].map(CODE_TO_COUNTRY_CPI)

data["date"] = data["date"].apply(helpers.oecd_date_to_python_date)
data = data[
(data["date"] <= query.end_date) & (data["date"] >= query.start_date)
]

return data.to_dict(orient="records")

# pylint: disable=unused-argument
@staticmethod
def transform_data(
query: OECDCPIQueryParams, data: List[Dict], **kwargs: Any
) -> List[OECDCPIData]:
"""Transform the data from the OECD endpoint."""
return [OECDCPIData.model_validate(d) for d in data]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""OECD Long Term Interest Rate Rate Data."""
"""OECD Long Term Interest Rate Data."""

# pylint: disable=unused-argument

Expand All @@ -23,7 +23,7 @@ class OECDLTIRQueryParams(LTIRQueryParams):
"""OECD Short Term Interest Rate Query."""

country: CountriesLiteral = Field(
description="Country to get GDP for.", default="united_states"
description="Country to get interest rate for.", default="united_states"
)

frequency: Literal["monthly", "quarterly", "annual"] = Field(
Expand Down
Loading
Loading