[Feature] Add IMF Provider Extension and Extend obb.economic.indicators (#6666)

* add imf provider extension and extend economic.indicators

* ruff

* mypy

* more mypy

* provider.json
This commit is contained in:
Danglewood
2024-09-20 11:43:32 -07:00
committed by GitHub
parent 57dd66ca6d
commit 39e30e823a
28 changed files with 7218 additions and 48 deletions

View File

@@ -145,6 +145,14 @@
"credentials": [],
"website": "https://data.gov"
},
{
"packageName": "openbb-imf",
"optional": false,
"reprName": "International Monetary Fund (IMF) Public Data API",
"description": "This provider allows you to access International Monetary Fund data through the IMF Public Data API.",
"credentials": [],
"website": "https://datahelp.imf.org/knowledgebase/articles/667681-using-json-restful-web-service"
},
{
"packageName": "openbb-intrinio",
"optional": false,

View File

@@ -16,10 +16,6 @@ from openbb_core.provider.utils.descriptions import (
class EconomicIndicatorsQueryParams(QueryParams):
"""Economic Indicators Query."""
symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", "")
+ " The base symbol for the indicator (e.g. GDP, CPI, etc.).",
)
country: Optional[str] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("country", "")

View File

@@ -28,6 +28,7 @@ openbb-econdb = { path = "./providers/econdb", develop = true }
openbb-federal-reserve = { path = "./providers/federal_reserve", develop = true }
openbb-fmp = { path = "./providers/fmp", develop = true }
openbb-fred = { path = "./providers/fred", develop = true }
openbb-imf = { path = "./providers/imf", develop = true }
openbb-intrinio = { path = "./providers/intrinio", develop = true }
openbb-oecd = { path = "./providers/oecd", develop = true }
openbb-polygon = { path = "./providers/polygon", develop = true }

View File

@@ -609,6 +609,26 @@ def test_economy_fred_regional(params, headers):
"frequency": "quarter",
}
),
(
{
"provider": "imf",
"country": "us,uk,jp",
"symbol": "gold_reserves",
"start_date": "2022-01-01",
"end_date": "2023-12-31",
"frequency": "annual",
}
),
(
{
"provider": "imf",
"country": "all",
"symbol": "derivative_assets",
"start_date": "2022-01-01",
"end_date": "2023-12-31",
"frequency": "annual",
}
),
],
)
@pytest.mark.integration

View File

@@ -629,6 +629,26 @@ def test_economy_available_indicators(params, obb):
"frequency": "quarter",
}
),
(
{
"provider": "imf",
"country": "us,uk,jp",
"symbol": "gold_reserves",
"start_date": "2022-01-01",
"end_date": "2023-12-31",
"frequency": "annual",
}
),
(
{
"provider": "imf",
"country": "all",
"symbol": "derivative_assets",
"start_date": "2022-01-01",
"end_date": "2023-12-31",
"frequency": "annual",
}
),
],
)
@pytest.mark.integration

View File

@@ -411,6 +411,29 @@ async def available_indicators(
description="Use the `main` symbol to get the group of main indicators for a country.",
parameters={"provider": "econdb", "symbol": "main", "country": "eu"},
),
APIEx(
description="When the provider is 'imf', the absence of a symbol will default to 'irfcl_top_lines'."
+ " Use 'IRFCL' to get all the data from the set of indicators.",
parameters={"provider": "imf"},
),
APIEx(
description="When the provider is 'imf', complete tables are returned by using a 'preset'."
+ " Refer to the function's docstring for descriptions of each preset."
+ " When no country is supplied, the data is returned for all countries.",
parameters={"provider": "imf", "symbol": "gold_reserves"},
),
APIEx(
description="When the provider is 'imf', multiple countries and symbols can be supplied."
+ " Enter countries as a two-letter ISO country code, or the country name in lower_snake_case.",
parameters={
"provider": "imf",
"symbol": "RAFA_USD,RAPFA_USD,RAFA_RAPFA_RO",
"country": "us,china,jp,4f,gb",
"start_date": "2010-01-01",
"end_date": "2020-12-31",
"frequency": "annual",
},
),
],
)
async def indicators(

View File

@@ -26,6 +26,7 @@
"federal_reserve@1.3.2",
"fmp@1.3.2",
"fred@1.3.2",
"imf@1.0.0b0",
"intrinio@1.3.2",
"oecd@1.3.2",
"polygon@1.3.2",
@@ -7050,7 +7051,8 @@
"optional": true,
"choices": null
}
]
],
"imf": []
},
"returns": {
"OBBject": [
@@ -7061,7 +7063,7 @@
},
{
"name": "provider",
"type": "Optional[Literal['econdb']]",
"type": "Optional[Literal['econdb', 'imf']]",
"description": "Provider name."
},
{
@@ -7197,6 +7199,32 @@
"optional": true,
"choices": null
}
],
"imf": [
{
"name": "table",
"type": "str",
"description": "The name of the table associated with the symbol.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "level",
"type": "int",
"description": "The indentation level of the data, relative to the table and symbol_root",
"default": null,
"optional": true,
"choices": null
},
{
"name": "order",
"type": "int",
"description": "Order of the data, relative to the table.",
"default": null,
"optional": true,
"choices": null
}
]
},
"model": "AvailableIndicators"
@@ -7207,24 +7235,237 @@
"message": null
},
"description": "Get economic indicators by country and indicator.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.indicators(provider='econdb', symbol='PCOCO')\n# Enter the country as the full name, or iso code. Use `available_indicators()` to get a list of supported indicators from EconDB.\nobb.economy.indicators(symbol='CPI', country='united_states,jp', provider='econdb')\n# Use the `main` symbol to get the group of main indicators for a country.\nobb.economy.indicators(provider='econdb', symbol='main', country='eu')\n```\n\n",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.indicators(provider='econdb', symbol=PCOCO)\n# Enter the country as the full name, or iso code. Use `available_indicators()` to get a list of supported indicators from EconDB.\nobb.economy.indicators(symbol=CPI, country='united_states,jp', provider='econdb')\n# Use the `main` symbol to get the group of main indicators for a country.\nobb.economy.indicators(provider='econdb', symbol=main, country='eu')\n# When the provider is 'imf', the absence of a symbol will default to 'irfcl_top_lines'. Use 'IRFCL' to get all the data from the set of indicators.\nobb.economy.indicators(provider='imf')\n# When the provider is 'imf', complete tables are returned by using a 'preset'. Refer to the function's docstring for descriptions of each preset. When no country is supplied, the data is returned for all countries.\nobb.economy.indicators(provider='imf', symbol=gold_reserves)\n# When the provider is 'imf', multiple countries and symbols can be supplied. Enter countries as a two-letter ISO country code, or the country name in lower_snake_case.\nobb.economy.indicators(provider='imf', symbol=RAFA_USD,RAPFA_USD,RAFA_RAPFA_RO, country='us,china,jp,4f,gb', start_date='2010-01-01', end_date='2020-12-31', frequency=annual)\n```\n\n",
"parameters": {
"standard": [
{
"name": "symbol",
"type": "Union[str, List[str]]",
"description": "Symbol to get data for. The base symbol for the indicator (e.g. GDP, CPI, etc.). Multiple items allowed for provider(s): econdb.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "country",
"type": "Union[str, List[str]]",
"description": "The country to get data. The country represented by the indicator, if available. Multiple items allowed for provider(s): econdb.",
"description": "The country to get data. The country represented by the indicator, if available. Multiple items allowed for provider(s): econdb, imf.",
"default": null,
"optional": true,
"choices": null
"choices": [
"all",
"afghanistan",
"albania",
"algeria",
"american_samoa",
"angola",
"anguilla",
"antigua_and_barbuda",
"argentina",
"armenia",
"aruba",
"australia",
"austria",
"azerbaijan",
"bahamas",
"bahrain",
"bangladesh",
"barbados",
"belarus",
"belgium",
"belize",
"benin",
"bermuda",
"bhutan",
"bolivia",
"bosnia_and_herzegovina",
"botswana",
"brazil",
"brunei_darussalam",
"bulgaria",
"burkina_faso",
"burundi",
"cabo_verde",
"cambodia",
"cameroon",
"canada",
"cayman_islands",
"central_african_republic",
"chad",
"chile",
"hong_kong",
"macao",
"china",
"colombia",
"comoros",
"congo",
"cook_islands",
"costa_rica",
"cote_divoire",
"croatia",
"cuba",
"curacao",
"cyprus",
"czech_republic",
"denmark",
"djibouti",
"dominica",
"dominican_republic",
"ecuador",
"egypt",
"el_salvador",
"equatorial_guinea",
"eritrea",
"estonia",
"eswatini",
"ethiopia",
"ecb",
"falkland_islands",
"faroe_islands",
"fiji",
"finland",
"france",
"french_polynesia",
"gabon",
"gambia",
"georgia",
"germany",
"ghana",
"gibraltar",
"greece",
"greenland",
"grenada",
"guadeloupe",
"guam",
"guatemala",
"french_guiana",
"guinea",
"guyana",
"haiti",
"honduras",
"hungary",
"iceland",
"india",
"indonesia",
"iran",
"iraq",
"ireland",
"israel",
"italy",
"jamaica",
"japan",
"jordan",
"kazakhstan",
"kenya",
"kiribati",
"korea",
"kosovo",
"kuwait",
"kyrgyzstan",
"lao_peoples_democratic_republic",
"latvia",
"lebanon",
"lesotho",
"liberia",
"libya",
"lithuania",
"luxembourg",
"madagascar",
"malawi",
"malaysia",
"maldives",
"mali",
"malta",
"marshall_islands",
"martinique",
"mauritania",
"mauritius",
"mexico",
"micronesia",
"moldova",
"mongolia",
"montenegro",
"montserrat",
"morocco",
"mozambique",
"myanmar",
"namibia",
"nauru",
"nepal",
"netherlands_antilles",
"netherlands",
"new_caledonia",
"new_zealand",
"nicaragua",
"niger",
"nigeria",
"north_macedonia",
"norway",
"oman",
"pakistan",
"palau",
"panama",
"papua_new_guinea",
"paraguay",
"peru",
"philippines",
"poland",
"portugal",
"qatar",
"reunion",
"romania",
"russian_federation",
"rwanda",
"saint_helena",
"saint_pierre_and_miquelon",
"samoa",
"san_marino",
"sao_tome_and_principe",
"saudi_arabia",
"senegal",
"serbia_and_montenegro",
"serbia",
"seychelles",
"sierra_leone",
"singapore",
"sint_maarten",
"slovakia",
"slovenia",
"solomon_islands",
"somalia",
"south_africa",
"south_sudan",
"spain",
"sri_lanka",
"saint_kitts_and_nevis",
"saint_lucia",
"saint_vincent_and_the_grenadines",
"sudan",
"suriname",
"sweden",
"switzerland",
"syrian_arab_republic",
"taiwan",
"tajikistan",
"tanzania",
"thailand",
"timor",
"timor-leste",
"togo",
"tonga",
"trinidad_and_tobago",
"tunisia",
"turkey",
"turkmenistan",
"tuvalu",
"uganda",
"ukraine",
"united_arab_emirates",
"united_kingdom",
"united_states",
"uruguay",
"uzbekistan",
"vanuatu",
"venezuela",
"viet_nam",
"palestine",
"yemen",
"zambia",
"zimbabwe",
"euro_area"
]
},
{
"name": "start_date",
@@ -7244,6 +7485,14 @@
}
],
"econdb": [
{
"name": "symbol",
"type": "Union[str, List[str]]",
"description": "Symbol to get data for. The base symbol for the indicator (e.g. GDP, CPI, etc.). Use `available_indicators()` to get a list of available symbols. Multiple items allowed for provider(s): econdb.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "transform",
"type": "Literal['toya', 'tpop', 'tusd', 'tpgp']",
@@ -7268,6 +7517,28 @@
"optional": true,
"choices": null
}
],
"imf": [
{
"name": "symbol",
"type": "Union[str, List[str]]",
"description": "Symbol to get data for. Use `available_indicators()` to get the list of available symbols. Use 'IRFCL' to get all the data from the set of indicators. Complete tables are available only by single country, and are keyed as described below. The default is 'irfcl_top_lines'. Available presets not listed in `available_indicators()` are:\n\n 'IRFCL': All the data from the set of indicators. Not compatible with multiple countries.\n 'irfcl_top_lines': The default, top line items from the IRFCL data. Compatible with multiple countries.\n 'reserve_assets_and_other_fx_assets': Table I of the IRFCL data. Not compatible with multiple countries.\n 'predetermined_drains_on_fx_assets': Table II of the IRFCL data. Not compatible with multiple countries.\n 'contingent_drains_fx_assets': Table III of the IRFCL data. Not compatible with multiple countries.\n 'memorandum_items': The memorandum items table of the IRFCL data. Not compatible with multiple countries.\n 'gold_reserves': Gold reserves as value in USD and Fine Troy Ounces. Compatible with multiple countries.\n 'derivative_assets': Net derivative assets as value in USD. Compatible with multipile countries. Multiple items allowed for provider(s): imf.",
"default": "irfcl_top_lines",
"optional": true,
"choices": null
},
{
"name": "frequency",
"type": "Literal['annual', 'quarter', 'month']",
"description": "Frequency of the data.",
"default": "quarter",
"optional": true,
"choices": [
"annual",
"quarter",
"month"
]
}
]
},
"returns": {
@@ -7279,7 +7550,7 @@
},
{
"name": "provider",
"type": "Optional[Literal['econdb']]",
"type": "Optional[Literal['econdb', 'imf']]",
"description": "Provider name."
},
{
@@ -7342,7 +7613,57 @@
"choices": null
}
],
"econdb": []
"econdb": [],
"imf": [
{
"name": "scale",
"type": "str",
"description": "The scale of the value.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "table",
"type": "str",
"description": "The name of the table associated with the symbol.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "level",
"type": "int",
"description": "The indentation level of the data, relative to the table and symbol_root",
"default": null,
"optional": true,
"choices": null
},
{
"name": "order",
"type": "int",
"description": "Order of the data, relative to the table.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "reference_sector",
"type": "str",
"description": "The reference sector for the data.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "title",
"type": "str",
"description": "The title of the series associated with the symbol.",
"default": null,
"optional": true,
"choices": null
}
]
},
"model": "EconomicIndicators"
},

View File

@@ -39,6 +39,7 @@ Extensions:
- federal_reserve@1.3.2
- fmp@1.3.2
- fred@1.3.2
- imf@1.0.0b0
- intrinio@1.3.2
- oecd@1.3.2
- polygon@1.3.2

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ from openbb_core.provider.standard_models.economic_indicators import (
EconomicIndicatorsData,
EconomicIndicatorsQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from pydantic import Field, field_validator
@@ -25,6 +26,12 @@ class EconDbEconomicIndicatorsQueryParams(EconomicIndicatorsQueryParams):
"country": {"multiple_items_allowed": True},
}
symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", "")
+ " The base symbol for the indicator (e.g. GDP, CPI, etc.)."
+ " Use `available_indicators()` to get a list of available symbols.",
)
transform: Union[None, Literal["toya", "tpop", "tusd", "tpgp"]] = Field(
default=None,
description="The transformation to apply to the data, default is None."

31
openbb_platform/providers/imf/README.md vendored Normal file
View File

@@ -0,0 +1,31 @@
# OpenBB IMF Provider Extension
This package adds the `openbb-imf` provider extension to the OpenBB Platform.
## Installation
Install from PyPI with:
```sh
pip install openbb-imf
```
## Implementation
The extension utilizes the JSON RESTful Web Service ((https://datahelp.imf.org/knowledgebase/articles/630877-data-services)[https://datahelp.imf.org/knowledgebase/articles/630877-data-services])
No authorization is required to use, but IP addresses are bound by the limitations described in the link above.
## Coverage
- Databases:
- International Reserves and Foreign Currency Liquidity
Coverage:
- All IRFCL tables.
- Individual, or multiple, time series from single or multiple countries.
### Endpoints
- `obb.economy.available_indicators`
- `obb.economy.indicators`

View File

@@ -0,0 +1,17 @@
"""OpenBB IMF Provider Module."""
from openbb_core.provider.abstract.provider import Provider
from openbb_imf.models.available_indicators import ImfAvailableIndicatorsFetcher
from openbb_imf.models.economic_indicators import ImfEconomicIndicatorsFetcher
imf_provider = Provider(
name="imf",
website="https://datahelp.imf.org/knowledgebase/articles/667681-using-json-restful-web-service",
description="This provider allows you to access International Monetary Fund data through the IMF Public Data API.",
fetcher_dict={
"AvailableIndicators": ImfAvailableIndicatorsFetcher,
"EconomicIndicators": ImfEconomicIndicatorsFetcher,
},
repr_name="International Monetary Fund (IMF) Public Data API",
)

View File

@@ -0,0 +1 @@
"""IMF Static Assets."""

View File

@@ -0,0 +1,252 @@
{
"AF": "Afghanistan",
"AL": "Albania",
"DZ": "Algeria",
"AS": "American Samoa",
"AO": "Angola",
"AI": "Anguilla",
"AG": "Antigua and Barbuda",
"5M": "AMF (Arab Monetary Fund)",
"AR": "Argentina",
"AM": "Armenia",
"AW": "Aruba",
"AU": "Australia",
"AT": "Austria",
"AZ": "Azerbaijan",
"BS": "Bahamas",
"BH": "Bahrain",
"BD": "Bangladesh",
"5W": "BEAC (Banque des Etats de l`Afrique Centrale)",
"5B": "BIS (Bank for International Settlements)",
"BB": "Barbados",
"BY": "Belarus",
"BE": "Belgium",
"R1": "Belgo-Luxembourg Economic Union",
"BZ": "Belize",
"BJ": "Benin",
"BM": "Bermuda",
"BT": "Bhutan",
"BO": "Bolivia",
"BA": "Bosnia and Herzegovina",
"BW": "Botswana",
"BR": "Brazil",
"BN": "Brunei Darussalam",
"BG": "Bulgaria",
"BF": "Burkina Faso",
"BI": "Burundi",
"CV": "Cabo Verde",
"KH": "Cambodia",
"CM": "Cameroon",
"CA": "Canada",
"KY": "Cayman Islands",
"CF": "Central African Republic",
"1C_752": "Central Bank West Africa",
"TD": "Chad",
"CL": "Chile",
"HK": "Hong Kong, China",
"MO": "Macao",
"CN": "China",
"CO": "Colombia",
"KM": "Comoros",
"CD": "Congo, the Democratic Republic of the",
"CG": "Congo",
"CK": "Cook Islands",
"CR": "Costa Rica",
"CI": "Cote d`Ivoire",
"HR": "Croatia",
"CU": "Cuba",
"1C_355": "Curacao & St. Maarten",
"CW": "Curacao",
"CY": "Cyprus",
"CZ": "Czech Republic",
"CSH": "Former Czechoslovakia",
"DK": "Denmark",
"DJ": "Djibouti",
"DM": "Dominica",
"DO": "Dominican Republic",
"5I": "ECCB (Eastern Caribbean Central Bank)",
"EC": "Ecuador",
"EG": "Egypt",
"SV": "El Salvador",
"GQ": "Equatorial Guinea",
"ER": "Eritrea",
"EE": "Estonia",
"SZ": "Eswatini",
"ET": "Ethiopia",
"4F": "ECB (European Central Bank)",
"1C_977": "European Monetary Institution",
"FK": "Falkland Islands (Malvinas)",
"FO": "Faroe Islands",
"FJ": "Fiji",
"FI": "Finland",
"FR": "France",
"PF": "French Polynesia",
"GA": "Gabon",
"GM": "Gambia",
"GE": "Georgia",
"DE": "Germany",
"GH": "Ghana",
"GI": "Gibraltar",
"GR": "Greece",
"GL": "Greenland",
"GD": "Grenada",
"GP": "Guadeloupe",
"GU": "Guam",
"GT": "Guatemala",
"GF": "French Guiana",
"GN": "Guinea",
"GW": "Guinea-Bissau",
"GY": "Guyana",
"HT": "Haiti",
"HN": "Honduras",
"HU": "Hungary",
"IS": "Iceland",
"IN": "India",
"ID": "Indonesia",
"1E": "IBRD (International Bank for Reconstruction and Development)",
"X0": "IMF Member Countries (IMF)",
"IR": "Iran, Islamic Republic of",
"IQ": "Iraq",
"IE": "Ireland",
"7B": "IDB (Islamic Development Bank)",
"IL": "Israel",
"IT": "Italy",
"JM": "Jamaica",
"JP": "Japan",
"JO": "Jordan",
"KZ": "Kazakhstan",
"KE": "Kenya",
"KI": "Kiribati",
"KR": "Korea, Republic of",
"XK": "Kosovo, Republic of",
"KW": "Kuwait",
"KG": "Kyrgyzstan",
"LA": "Lao People`s Democratic Republic",
"LV": "Latvia",
"LB": "Lebanon",
"LS": "Lesotho",
"LR": "Liberia",
"LY": "Libya",
"LT": "Lithuania",
"LU": "Luxembourg",
"MG": "Madagascar",
"MW": "Malawi",
"MY": "Malaysia",
"MV": "Maldives",
"ML": "Mali",
"MT": "Malta",
"MH": "Marshall islands",
"MQ": "Martinique",
"MR": "Mauritania",
"MU": "Mauritius",
"MX": "Mexico",
"FM": "Micronesia, Federated States of",
"MD": "Moldova, Republic of",
"MN": "Mongolia",
"ME": "Montenegro",
"MS": "Montserrat",
"MA": "Morocco",
"MZ": "Mozambique",
"MM": "Myanmar",
"NA": "Namibia",
"NR": "Nauru",
"NP": "Nepal",
"AN": "Netherlands Antilles",
"NL": "Netherlands",
"NC": "New Caledonia",
"NZ": "New Zealand",
"NI": "Nicaragua",
"NE": "Niger",
"NG": "Nigeria",
"5H": "NIB (Nordic Investment Bank)",
"MK": "North Macedonia, Republic of",
"NO": "Norway",
"OM": "Oman",
"PK": "Pakistan",
"PW": "Palau",
"PA": "Panama",
"1C_356": "Panama Canal Zone",
"PG": "Papua New Guinea",
"PY": "Paraguay",
"PE": "Peru",
"PH": "Philippines",
"PL": "Poland",
"PT": "Portugal",
"QA": "Qatar",
"RE": "Reunion",
"RO": "Romania",
"RU": "Russian Federation",
"RW": "Rwanda",
"1C_568": "Ryukyus",
"SH": "Saint Helena",
"PM": "Saint Pierre and Miquelon",
"WS": "Samoa",
"SM": "San Marino",
"ST": "Sao Tome and Principe",
"SA": "Saudi Arabia",
"SN": "Senegal",
"CS": "Serbia and Montenegro",
"RS": "Serbia",
"SC": "Seychelles",
"SL": "Sierra Leone",
"SG": "Singapore",
"SX": "Sint Maarten (Dutch part)",
"SK": "Slovakia",
"SI": "Slovenia",
"SB": "Solomon Islands",
"SO": "Somalia",
"ZA": "South Africa",
"SS": "South Sudan",
"ES": "Spain",
"LK": "Sri Lanka",
"KN": "Saint Kitts and Nevis",
"LC": "Saint Lucia",
"VC": "Saint Vincent and the Grenadines",
"SD": "Sudan",
"SR": "Suriname",
"SE": "Sweden",
"CH": "Switzerland",
"SY": "Syrian Arab Republic",
"TW": "Taiwan, Province of China",
"TJ": "Tajikistan",
"TZ": "Tanzania, United Republic of",
"TH": "Thailand",
"TP": "Timor",
"TL": "Timor-Leste",
"TG": "Togo",
"TO": "Tonga",
"TT": "Trinidad and Tobago",
"TN": "Tunisia",
"TR": "Turkey",
"TM": "Turkmenistan",
"TV": "Tuvalu",
"UG": "Uganda",
"UA": "Ukraine",
"AE": "United Arab Emirates",
"GB": "United Kingdom",
"US": "United States",
"UY": "Uruguay",
"UZ": "Uzbekistan",
"VU": "Vanuatu",
"VE": "Venezuela, Bolivarian Republic",
"VN": "Viet Nam",
"PS": "Palestine, State of",
"1C_473": "Yemen Arab Rep.",
"1C_459": "Yemen, P.D. Rep.",
"YE": "Yemen",
"YUC": "Former Yugoslavia",
"ZM": "Zambia",
"ZW": "Zimbabwe",
"1C_ALLC": "All Countries",
"1C_ALL": "All Countries and Country Groups",
"1C_ALLG": "All Country Groups",
"1C_All_Reporting_Countries_Data": "All Reporting Countries, Data",
"1C_All_Reporting_Countries_Metadata": "All Reporting Countries, Metadata",
"1C_Daily_Reporters": "Daily Reporters",
"1C_Dual_Reporters": "Dual Reporters",
"U2": "Euro area (Member States and Institutions of the Euro Area) changing composition",
"B00": "EU (Member States of the European Union) changing composition, excluding EU Institutions",
"1C_Monthly_Reporters": "Monthly Reporters",
"1C_Single_Reporters": "Single Reporters",
"1C_Single_Reporters_USA_and_Peru": "Single Reporters - USA and Peru"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
"""OpenBB IMF Provider Models."""

View File

@@ -0,0 +1,81 @@
"""IMF Available Indicators."""
# pylint: disable=unused-argument
from typing import Any, Dict, List, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.available_indicators import (
AvailableIndicatorsData,
AvailableIndicesQueryParams,
)
from pydantic import Field
class ImfAvailableIndicatorsQueryParams(AvailableIndicesQueryParams):
"""IMF Available Indicators Query Parameters."""
class ImfAvailableIndicatorsData(AvailableIndicatorsData):
"""IMF Available Indicators Data."""
__alias_dict__ = {
"symbol_root": "parent",
"description": "title",
}
table: Optional[str] = Field(
default=None,
description="The name of the table associated with the symbol.",
)
level: Optional[int] = Field(
default=None,
description="The indentation level of the data, relative to the table and symbol_root",
)
order: Optional[int] = Field(
default=None,
description="Order of the data, relative to the table.",
)
class ImfAvailableIndicatorsFetcher(
Fetcher[ImfAvailableIndicatorsQueryParams, List[ImfAvailableIndicatorsData]]
):
"""IMF Available Indicators Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> ImfAvailableIndicatorsQueryParams:
"""Transform the query."""
return ImfAvailableIndicatorsQueryParams(**params)
@staticmethod
def extract_data(
query: ImfAvailableIndicatorsQueryParams,
credentials: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> List[Dict]:
"""Fetch the data."""
# pylint: disable=import-outside-toplevel
from openbb_imf.utils.irfcl_helpers import load_irfcl_symbols
try:
irfcl_symbols = load_irfcl_symbols()
except OpenBBError as e:
raise OpenBBError(f"Failed to load IMF IRFCL symbols: {e}") from e
records: List = []
for key, value in irfcl_symbols.items():
value["symbol"] = key
records.append(value)
return records
@staticmethod
def transform_data(
query: ImfAvailableIndicatorsQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[ImfAvailableIndicatorsData]:
"""Transform the data."""
return [ImfAvailableIndicatorsData.model_validate(d) for d in data]

View File

@@ -0,0 +1,197 @@
"""IMF Economic Indicators Model."""
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.economic_indicators import (
EconomicIndicatorsData,
EconomicIndicatorsQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from pydantic import Field
from openbb_imf.utils.constants import IRFCL_PRESET, IRFCL_TABLES
from openbb_imf.utils.irfcl_helpers import (
load_country_to_code_map,
load_irfcl_symbols,
validate_countries,
)
class ImfEconomicIndicatorsQueryParams(EconomicIndicatorsQueryParams):
"""IMF Economic Indicators Query."""
__json_schema_extra__ = {
"symbol": {
"multiple_items_allowed": True,
},
"country": {
"multiple_items_allowed": True,
"choices": ["all"] + list(list(load_country_to_code_map())),
},
"frequency": {
"choices": ["annual", "quarter", "month"],
},
}
symbol: str = Field(
default="irfcl_top_lines",
description=QUERY_DESCRIPTIONS.get("symbol", "")
+ " Use `available_indicators()` to get the list of available symbols."
+ " Use 'IRFCL' to get all the data from the set of indicators."
+ " Complete tables are available only by single country, and are keyed as described below."
+ " The default is 'irfcl_top_lines'. Available presets not listed in `available_indicators()` are:"
+ """\n
'IRFCL': All the data from the set of indicators. Not compatible with multiple countries.
'irfcl_top_lines': The default, top line items from the IRFCL data. Compatible with multiple countries.
'reserve_assets_and_other_fx_assets': Table I of the IRFCL data. Not compatible with multiple countries.
'predetermined_drains_on_fx_assets': Table II of the IRFCL data. Not compatible with multiple countries.
'contingent_drains_fx_assets': Table III of the IRFCL data. Not compatible with multiple countries.
'memorandum_items': The memorandum items table of the IRFCL data. Not compatible with multiple countries.
'gold_reserves': Gold reserves as value in USD and Fine Troy Ounces. Compatible with multiple countries.
'derivative_assets': Net derivative assets as value in USD. Compatible with multipile countries.
""",
)
frequency: Literal["annual", "quarter", "month"] = Field(
default="quarter",
description="Frequency of the data.",
)
class ImfEconomicIndicatorsData(EconomicIndicatorsData):
"""IMF Economic Indicators Data."""
__alias_dict__ = {
"symbol_root": "parent",
}
scale: Optional[str] = Field(
default=None,
description="The scale of the value.",
)
table: Optional[str] = Field(
default=None,
description="The name of the table associated with the symbol.",
)
level: Optional[int] = Field(
default=None,
description="The indentation level of the data, relative to the table and symbol_root",
)
order: Optional[int] = Field(
default=None,
description="Order of the data, relative to the table.",
)
reference_sector: Optional[str] = Field(
default=None,
description="The reference sector for the data.",
)
title: Optional[str] = Field(
default=None,
description="The title of the series associated with the symbol.",
)
class ImfEconomicIndicatorsFetcher(
Fetcher[ImfEconomicIndicatorsQueryParams, List[ImfEconomicIndicatorsData]]
):
"""IMF Economic Indicators Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> ImfEconomicIndicatorsQueryParams:
"""Transform the query."""
symbols = params.get("symbol", "")
countries = params.get("country")
now = datetime.now().date()
symbols = "IRFCL" if ("all" in symbols or "IRFCL" in symbols) else symbols
if symbols == "IRFCL" and not (countries or countries == "all"):
raise OpenBBError(
f"The selected symbol(s), {params.get('symbol')}, requires a single country for the 'country' parameter."
)
if countries:
params["country"] = validate_countries(countries)
if symbols and symbols in IRFCL_PRESET:
params["symbol"] = IRFCL_PRESET[symbols]
if symbols in IRFCL_TABLES and countries and countries.split(",") > 1:
raise OpenBBError(
f"Symbol '{symbols}' is a table and can only be used with one country."
)
elif symbols:
params["symbol"] = symbols
if not params.get("start_date") and (not countries or countries == "all"):
params["start_date"] = now.replace(
year=now.year - 1, month=1, day=1
).strftime("%Y-%m-%d")
if not params.get("end_date"):
params["end_date"] = now.replace(month=12, day=31).strftime("%Y-%m-%d")
if (not symbols or symbols == "all") and not params.get("start_date"):
params["start_date"] = now.replace(year=now.year - 1).strftime("%Y-%m-%d")
return ImfEconomicIndicatorsQueryParams(**params)
@staticmethod
async def aextract_data(
query: ImfEconomicIndicatorsQueryParams,
credentials: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> List[Dict]:
"""Extract the data."""
# pylint: disable = import-outside-toplevel
from openbb_imf.utils.irfcl_helpers import _get_irfcl_data
try:
res = await _get_irfcl_data(**query.model_dump(exclude_none=True))
except OpenBBError as e:
raise e from e
if not res:
raise EmptyDataError("No results returned for the query.")
return res
@staticmethod
def transform_data(
query: ImfEconomicIndicatorsQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[ImfEconomicIndicatorsData]:
"""Transform the data."""
# pylint: disable = import-outside-toplevel
from pandas import Categorical, DataFrame
if not data:
raise EmptyDataError("The data is empty.")
all_symbols = load_irfcl_symbols()
df = DataFrame(data)
if df.empty:
raise EmptyDataError("The data is empty.")
df = df[df["symbol"].isin(all_symbols)]
if len(df) == 0:
raise OpenBBError("The data has a length of 0.")
df["symbol"] = Categorical(
df["symbol"],
categories=all_symbols,
ordered=True,
)
df["parent"] = Categorical(
df["parent"],
categories=all_symbols,
ordered=True,
)
df = df.sort_values(
by=["date", "symbol", "parent", "value"],
ascending=[True, True, True, False],
).reset_index(drop=True)
return [ImfEconomicIndicatorsData(**d) for d in df.to_dict(orient="records")]

View File

@@ -0,0 +1 @@
"""IMF Utilities."""

View File

@@ -0,0 +1,93 @@
"""IMF Constants."""
# pylint: disable=line-too-long
# flake8: noqa: E501
IRFCL_HEADLINE = "RAF_USD,RAFA_USD,RAFAFX_USD,RAOFA_USD,RAPFA_USD,RAFAIMF_USD,RAFASDR_USD,RAFAGOLD_USD,RACFA_USD,RAMDCD_USD,RAMFIFC_USD,RAMSR_USD"
RESERVE_ASSETS_AND_OTHER_FX_ASSETS = "RAF_USD,RAFA_USD,RAFAFX_USD,RAFAFXS_USD,RAFAFXSI_USD,RAFAFXCD_USD,RAFAFXCDN_USD,RAFAFXCDBI_USD,RAFAFXCDBIA_USD,RAFAFXCDBO_USD,RAFAFXCDBOA_USD,RAFAIMF_USD,RAFASDR_USD,RAFAGOLD_USD,RAFAGOLDV_OZT,RAFAO_USD,RAFAOF_USD,RAFAOL_USD,RAFAOO_USD,RAOFA_USD,RAOFAS_USD,RAOFAD_USD,RAOFAL_USD,RAOFAF_USD,RAOFAG_USD"
PREDETERMINED_DRAINS_ON_FX_ASSETS = "RAPFA_USD,RAPFALSD_USD,RAPFALSD_1M_USD,RAPFALSD_1M_3M_USD,RAPFALSD_3M_1Y_USD,RAPFALSDOP_USD,RAPFALSDOP_1M_USD,RAPFALSDOP_1M_3M_USD,RAPFALSDOP_3M_1Y_USD,RAPFALSDOI_USD,RAPFALSDOI_1M_USD,RAPFALSDOI_1M_3M_USD,RAPFALSDOI_3M_1Y_USD,RAPFALSDIP_USD,RAPFALSDIP_1M_USD,RAPFALSDIP_1M_3M_USD,RAPFALSDIP_3M_1Y_USD,RAPFALSDII_USD,RAPFALSDII_1M_USD,RAPFALSDII_1M_3M_USD,RAPFALSDII_3M_1Y_USD,RAPFAFFS_USD,RAPFAFFS_1M_USD,RAPFAFFS_1M_3M_USD,RAPFAFFS_3M_1Y_USD,RAPFAFFL_USD,RAPFAFFL_1M_USD,RAPFAFFL_1M_3M_USD,RAPFAFFL_3M_1Y_USD,RAPFAO_USD,RAPFAO_1M_USD,RAPFAO_1M_3M_USD,RAPFAO_3M_1Y_USD,RAPFAOOR_USD,RAPFAOOR_1M_USD,RAPFAOOR_1M_3M_USD,RAPFAOOR_3M_1Y_USD,RAPFAOIRR_USD,RAPFAOIRR_1M_USD,RAPFAOIRR_1M_3M_USD,RAPFAOIRR_3M_1Y_USD,RAPFAOOC_USD,RAPFAOOC_1M_USD,RAPFAOOC_1M_3M_USD,RAPFAOOC_3M_1Y_USD,RAPFAOIC_USD,RAPFAOIC_1M_USD,RAPFAOIC_1M_3M_USD,RAPFAOIC_3M_1Y_USD,RAPFAOOP_USD,RAPFAOOP_1M_USD,RAPFAOOP_1M_3M_USD,RAPFAOOP_3M_1Y_USD,RAPFAOIR_USD,RAPFAOIR_1M_USD,RAPFAOIR_1M_3M_USD,RAPFAOIR_3M_1Y_USD,RAFA_RAPFA_RO"
CONTINGENT_DRAINS_FX_ASSETS = "RACFA_USD,RACFAL_USD,RACFAL_1M_USD,RACFAL_1M_3M_USD,RACFAL_3M_1Y_USD,RACFALG_USD,RACFALG_1M_USD,RACFALG_1M_3M_USD,RACFALO_USD,RACFALO_1M_USD,RACFALO_1M_3M_USD,RACFALO_3M_1Y_USD,RACFAS_USD,RACFACB_USD,RACFACB_1M_USD,RACFACB_1M_3M_USD,RACFACB_3M_1Y_USD,RACFACBA_USD,RACFACBA_1M_USD,RACFACBA_1M_3M_USD,RACFACBA_3M_1Y_USD,RACFACBAOI_USD,RACFACBAOI_1M_USD,RACFACBAOI_1M_3M_USD,RACFACBAON_USD,RACFACBAON_1M_USD,RACFACBAON_1M_3M_USD,RACFACBAON_3M_1Y_USD,RACFACBABIS_USD,RACFACBAIMF_1M_USD,RACFACBAIMF_1M_3M_USD,RACFACBAIMF_3M_1Y_USD,RACFACBAIMF_USD,RACFACBFIR_USD,RACFACBFIR_1M_USD,RACFACBFIR_1M_3M_USD,RACFACBFIR_3M_1Y_USD,RACFACBFIO_USD,RACFACBFIO_1M_USD,RACFACBFIO_1M_3M_USD,RACFACBFIO_3M_1Y_USD,RACFACT_USD,RACFACT_1M_USD,RACFACT_1M_3M_USD,RACFACT_3M_1Y_USD,RACFACTA_USD,RACFACTA_1M_USD,RACFACTA_1M_3M_USD,RACFACTA_3M_1Y_USD,RACFACTAOI_USD,RACFACTAOI_1M_USD,RACFACTAOI_1M_3M_USD,RACFACTAOI_3M_1Y_USD,RACFACTAON_USD,RACFACTAON_1M_USD,RACFACTAON_1M_3M_USD,RACFACTAON_3M_1Y_USD,RACFACTABIS_USD,RACFACTABIS_1M_USD,RACFACTABIS_1M_3M_USD,RACFACTABIS_3M_1Y_USD,RACFACTAIMF_USD,RACFACTAIMF_1M_USD,RACFACTFIR_USD,RACFACTFIR_1M_USD,RACFACTFIR_1M_3M_USD,RACFACTFIR_3M_1Y_USD,RACFACTFIO_USD,RACFACTFIO_1M_USD,RACFACTFIO_1M_3M_USD,RACFACTFIO_3M_1Y_USD,RACFAPPS_USD,RACFAPPS_1M_USD,RACFAPPS_1M_3M_USD,RACFAPPS_3M_1Y_USD,RACFAPPSBP_USD,RACFAPPSBP_1M_USD,RACFAPPSBP_1M_3M_USD,RACFAPPSBP_3M_1Y_USD,RACFAPPSWC_USD,RACFAPPSWC_1M_USD,RACFAPPSWC_1M_3M_USD,RACFAPPSWC_3M_1Y_USD,RACFAPPL_USD,RACFAPPL_1M_USD,RACFAPPL_1M_3M_USD,RACFAPPL_3M_1Y_USD,RACFAPPLBC_USD,RACFAPPLBC_1M_USD,RACFAPPLBC_1M_3M_USD,RACFAPPLBC_3M_1Y_USD,RACFAPPLWP_USD,RACFAPPLWP_1M_USD,RACFAPPLWP_1M_3M_USD,RACFAPPLWP_3M_1Y_USD,RACFAMPAS_USD,RACFAMPAS_1M_USD,RACFAMPAS_1M_3M_USD,RACFAMPAS_3M_1Y_USD,RACFAMPAL_USD,RACFAMPAL_1M_USD,RACFAMPAL_1M_3M_USD,RACFAMPAL_3M_1Y_USD,RACFAMPBS_USD,RACFAMPBS_1M_USD,RACFAMPBS_1M_3M_USD,RACFAMPBS_3M_1Y_USD,RACFAMPBL_USD,RACFAMPBL_1M_USD,RACFAMPBL_1M_3M_USD,RACFAMPBL_3M_1Y_USD,RACFAMPCS_USD,RACFAMPCS_1M_USD,RACFAMPCS_1M_3M_USD,RACFAMPCS_3M_1Y_USD,RACFAMPCL_USD,RACFAMPCL_1M_USD,RACFAMPCL_1M_3M_USD,RACFAMPCL_3M_1Y_USD,RACFAMPDS_USD,RACFAMPDS_1M_USD,RACFAMPDS_1M_3M_USD,RACFAMPDS_3M_1Y_USD,RACFAMPDL_USD,RACFAMPDL_1M_USD,RACFAMPDL_1M_3M_USD,RACFAMPDL_3M_1Y_USD,RACFAMPES_USD,RACFAMPES_1M_USD,RACFAMPES_1M_3M_USD,RACFAMPES_3M_1Y_USD,RACFAMPEL_USD,RACFAMPEL_1M_USD,RACFAMPEL_1M_3M_USD,RACFAMPEL_3M_1Y_USD,RACFAMPFS_USD,RACFAMPFS_1M_USD,RACFAMPFS_1M_3M_USD,RACFAMPFS_3M_1Y_USD,RACFAMPFL_USD,RACFAMPFL_1M_USD,RACFAMPFL_1M_3M_USD,RACFAMPFL_3M_1Y_USD"
IRFCL_MEMORANDUM_ITEMS = "RAMDCD_USD,RAMFIFC_USD,RAMPA_USD,RAMFFS_USD,RAMPAOA_USD,RAMSR_USD,RAMSRLRI_USD,RAMSRLRN_USD,RAMSRBRI_USD,RAMSRBAN_USD,RAMFDA_USD,RAMFDAF_USD,RAMFDAU_USD,RAMFDAW_USD,RAMFDAP_USD,RAMFDAO_USD,RAMFFL_USD,RAMPPS_USD,RAMPPSBP_USD,RAMPPSWC_USD,RAMPPL_USD,RAMPPLBP_USD,RAMPPLWC_USD,RAMCR_USD,RAMCRISDR_USD,RAMCRIC_USD_USD,RAMCRIC_EUR_USD,RAMCRIC_CNY_USD,RAMCRIC_JPY_USD,RAMCRIC_GBP_USD,RAMCROSDR_USD"
IRFCL_TABLES = {
"reserve_assets_and_other_fx_assets": RESERVE_ASSETS_AND_OTHER_FX_ASSETS,
"predetermined_drains_on_fx_assets": PREDETERMINED_DRAINS_ON_FX_ASSETS,
"contingent_drains_fx_assets": CONTINGENT_DRAINS_FX_ASSETS,
"memorandum_items": IRFCL_MEMORANDUM_ITEMS,
}
IRFCL_PRESET = {
"irfcl_top_lines": IRFCL_HEADLINE,
**IRFCL_TABLES,
"gold_reserves": "RAFAGOLD_USD,RAFAGOLDV_OZT",
"derivative_assets": "RAMFDA_USD",
}
FREQUENCY_DICT = {
"month": "M",
"quarter": "Q",
"annual": "A",
}
REF_SECTORS_DICT = {
"government_ex_social_security": "S1311",
"central_bank": "S121",
"monetary_authorities": "S1X",
"all_sectors": "",
}
SECTOR_MAP = {v: k for k, v in REF_SECTORS_DICT.items()}
UNIT_MULTIPLIERS_MAP = {
"0": "Units",
"2": "Hundreds",
"3": "Thousands",
"6": "Millions",
"9": "Billions",
"12": "Trillions",
"N15": "Quadrillionths",
"N14": "Hundred Trillionths",
"N13": "Ten Trillionths",
"N12": "Trillionths",
"N11": "Hundred Billionths",
"N10": "Ten Billionths",
"N9": "Billionths",
"N8": "Hundred Millionths",
"N7": "Ten Millionths",
"N6": "Millionths",
"N5": "Hundred Thousandths",
"N4": "Ten Thousandths",
"N3": "Thousandths",
"N2": "Hundredths",
"N1": "Tenths",
"1": "Tens",
"4": "Ten Thousands",
"5": "Hundred Thousands",
"7": "Ten Millions",
"8": "Hundred Millions",
"10": "Ten Billions",
"11": "Hundred Billions",
"13": "Ten Trillions",
"14": "Hundred Trillions",
"15": "Quadrillions",
}
TIME_FORMAT_MAP = {
"P1Y": "Annual",
"P6M": "Bi-annual",
"P3M": "Quarterly",
"P1M": "Monthly",
"P7D": "Weekly",
"P1D": "Daily",
}
REF_SECTOR_MAP = {
"S1311": "Central government excluding social security",
"S121": "Central Bank",
"S1X": "Monetary Authorities",
"1C_AS": "All Sectors",
"AllSectorsIncludingAllSectors": "All Sectors Including All Sectors",
}

View File

@@ -0,0 +1,306 @@
"""IMF IRFCL Data Set Helpers."""
from typing import Dict, List, Optional
def load_irfcl_symbols() -> Dict:
"""Load IMF IRFCL symbols."""
# pylint: disable=import-outside-toplevel
import json # noqa
from json.decoder import JSONDecodeError
from pathlib import Path
from openbb_core.app.model.abstract.error import OpenBBError
try:
symbols_file = Path(__file__).parents[1].joinpath("assets", "imf_symbols.json")
with symbols_file.open(encoding="utf-8") as file:
symbols = json.load(file)
except (FileNotFoundError, JSONDecodeError) as e:
raise OpenBBError(f"Failed to load IMF IRFCL symbols: {e}") from e
return {k: v for k, v in symbols.items() if v["dataset"] == "IRFCL"}
def load_country_map() -> Dict:
"""Load IMF IRFCL country map."""
# pylint: disable=import-outside-toplevel
import json # noqa
from json.decoder import JSONDecodeError
from pathlib import Path
from openbb_core.app.model.abstract.error import OpenBBError
try:
country_map_file = (
Path(__file__).parents[1].joinpath("assets", "imf_country_map.json")
)
with country_map_file.open(encoding="utf-8") as file:
country_map_dict = json.load(file)
except (FileNotFoundError, JSONDecodeError) as e:
raise OpenBBError(f"Failed to load IMF IRFCL country map: {e}") from e
return {
k: v.replace("`", "").split(",")[0].split("_(")[0]
for k, v in country_map_dict.items()
if len(k) == 2 and k[0] not in ("5", "1", "7") and k not in ("X0", "R1", "GW")
}
def load_country_to_code_map() -> Dict:
"""Load a map of lowercase country name to 2-letter ISO symbol."""
return {
(
"euro_area"
if k == "U2"
else v.lower()
.replace(" ", "_")
.replace("`", "")
.split(",")[0]
.split("_(")[0]
): k
for k, v in load_country_map().items()
}
def validate_countries(countries) -> str:
"""Validate the country and convert to a 2-letter ISO country code."""
# pylint: disable=import-outside-toplevel
from warnings import warn # noqa
from openbb_core.app.model.abstract.error import OpenBBError
country_map_dict = load_country_to_code_map()
if isinstance(countries, str):
countries = countries.split(",")
elif not isinstance(countries, list):
raise OpenBBError("Invalid countries list.")
new_countries: List = []
if "all" in countries or "ALL" in countries:
return "all"
for country in countries:
if country.lower() not in country_map_dict and country.upper() not in list(
country_map_dict.values()
):
warn(f"Invalid IMF IRFCL country: {country}")
continue
if country.upper() in list(country_map_dict.values()):
new_countries.append(country.upper())
else:
new_countries.append(country_map_dict.get(country, country).upper())
new_countries = [c for c in new_countries if c]
if not new_countries:
raise OpenBBError("No valid countries found in the supplied list.")
return ",".join(new_countries)
def validate_symbols(symbols) -> str:
"""Validate the IMF IRFCL symbols."""
# pylint: disable=import-outside-toplevel
from warnings import warn # noqa
from openbb_core.app.model.abstract.error import OpenBBError
irfcl_symbols = load_irfcl_symbols()
if isinstance(symbols, str):
symbols = symbols.split(",")
elif not isinstance(symbols, list):
raise OpenBBError("Invalid symbols list.")
if "IRFCL" in symbols or "all" in symbols:
return "all"
new_symbols: List = []
for symbol in symbols:
if symbol.upper() not in irfcl_symbols:
warn(f"Invalid IMF IRFCL symbol: {symbol}")
new_symbols.append(symbol.upper())
return "+".join(new_symbols) if len(new_symbols) > 1 else new_symbols[0]
# We use this as a helper to allow future expansion of the supported IMF indicators.
# Each database has its own nuances with URL construction and schemas.
# In the future, we will check the supported symbols map and direct the call
# to the appropriate dtatabase's function.
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
async def _get_irfcl_data(**kwargs) -> List[Dict]:
"""Get IMF IRFCL data.
This function is not intended to be called directly,
but through the `ImfEconomicIndicatorsFetcher` class.
"""
# pylint: disable=import-outside-toplevel
from json.decoder import JSONDecodeError # noqa
from openbb_core.provider.utils.helpers import amake_request
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_imf.utils import constants
from pandas import to_datetime
from pandas.tseries import offsets
countries = kwargs.get("country", "")
countries = (
"" if countries == "all" else countries.replace(",", "+") if countries else ""
)
frequency = constants.FREQUENCY_DICT.get(kwargs.get("frequency", "quarter"), "Q")
sector = kwargs.get("sector", "monetary_authorities")
sector = constants.REF_SECTORS_DICT.get(sector, "")
start_date = kwargs.get("start_date", "")
end_date = kwargs.get("end_date", "")
# Adjust the dates to the date relative to frequency.
# The API does not accept arbitrary dates, so we need to adjust them.
if start_date:
start_date = to_datetime(start_date)
if frequency == "Q":
# offset = offsets.QuarterBegin(startingMonth=1)
# start_date = start_date + offset
start_date = offsets.QuarterBegin(startingMonth=1).rollback(start_date)
elif frequency == "A":
start_date = offsets.YearBegin().rollback(start_date)
else:
start_date = offsets.MonthBegin().rollback(start_date)
start_date = start_date.strftime("%Y-%m-%d")
if end_date:
end_date = to_datetime(end_date)
if frequency == "Q":
end_date = offsets.QuarterEnd().rollforward(end_date)
elif frequency == "A":
end_date = offsets.YearEnd().rollforward(end_date)
else:
end_date = offsets.MonthEnd().rollforward(end_date)
end_date = end_date.strftime("%Y-%m-%d")
indicator = kwargs.get("symbol")
indicator = validate_symbols(indicator) if indicator else ""
indicator = "" if indicator == "all" else indicator
if not indicator and not countries:
raise OpenBBError("Country is required when returning the complete dataset.")
date_range = (
f"?startPeriod={start_date}&endPeriod={end_date}"
if start_date and end_date
else ""
)
base_url = "http://dataservices.imf.org/REST/SDMX_JSON.svc/"
key = f"CompactData/IRFCL/{frequency}.{countries}.{indicator}.{sector}"
url = f"{base_url}{key}{date_range}"
try:
response = await amake_request(url, timeout=20)
except JSONDecodeError as e:
raise OpenBBError(
"Error fetching data; This might be rate-limiting. Try again later."
) from e
if "ErrorDetails" in response:
raise OpenBBError(
f"{response['ErrorDetails'].get('Code')} -> {response['ErrorDetails'].get('Message')}" # type: ignore
)
series = response.get("CompactData", {}).get("DataSet", {}).pop("Series", {}) # type: ignore
if not series:
raise OpenBBError(f"No time series data found -> {response}")
# If there is only one series, they ruturn a dict instead of a list.
if series and isinstance(series, dict):
series = [series]
data: List = []
all_symbols = load_irfcl_symbols()
country_map_dict = {
v: k.replace("_", " ").title().replace("Ecb", "ECB")
for k, v in load_country_to_code_map().items()
}
# Iterate over the series to extract observations and map the metadata.
for s in series:
if "Obs" not in s:
continue
meta = {
k.replace("@", "").lower(): (
constants.UNIT_MULTIPLIERS_MAP.get(str(v), v)
if k == "@UNIT_MULT"
else v
)
for k, v in s.items()
if k != "Obs"
}
_symbol = meta.get("indicator")
_parent: Optional[str] = None
_order: Optional[str] = None
_level: Optional[str] = None
_table: Optional[str] = None
_title: Optional[str] = None
if _symbol in all_symbols:
_table = all_symbols.get(_symbol, {}).get("table")
_parent = all_symbols.get(_symbol, {}).get("parent", "")
_order = all_symbols.get(_symbol, {}).get("order", "")
_level = all_symbols.get(_symbol, {}).get("level", "")
_title = all_symbols.get(_symbol, {}).get("title", "")
_data = s.pop("Obs", [])
if isinstance(_data, dict):
_data = [_data]
for d in _data:
_date = d.pop("@TIME_PERIOD", None)
val = d.pop("@OBS_VALUE", None)
if not val:
continue
if _date:
offset = (
offsets.QuarterEnd
if "Q" in _date
else offsets.YearEnd if len(str(_date)) == 4 else offsets.MonthEnd
)
_date = to_datetime(_date)
_date = _date + offset(0)
_date = _date.strftime("%Y-%m-%d")
vals = {
k: v
for k, v in {
"date": _date,
"table": _table,
"symbol": _symbol,
"parent": _parent,
"order": _order,
"level": _level,
"country": country_map_dict.get(
meta.get("ref_area"), meta.get("ref_area")
),
"reference_sector": constants.SECTOR_MAP.get(
meta.get("ref_sector"), meta.get("ref_sector")
),
"title": _title,
"value": float(val) if val else None,
"scale": meta.get("unit_mult"),
}.items()
if v
}
if vals.get("value") and vals.get("date"):
d.update(vals)
if _data:
data.extend([d for d in _data if d])
if not data:
raise OpenBBError("No data found.")
return data

1997
openbb_platform/providers/imf/poetry.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
[tool.poetry]
name = "openbb-imf"
version = "1.0.0b"
description = "https://datahelp.imf.org/knowledgebase/articles/630877-api"
authors = ["OpenBB Team <hello@openbb.co>"]
license = "AGPL-3.0-only"
readme = "README.md"
packages = [{ include = "openbb_imf" }]
[tool.poetry.dependencies]
python = "^3.9"
openbb-core = "^1.3.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_provider_extension"]
imf = "openbb_imf:imf_provider"

View File

@@ -0,0 +1 @@
"""IMF Provider Fetcher Tests."""

View File

@@ -0,0 +1,54 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: http://dataservices.imf.org/REST/SDMX_JSON.svc/CompactData/IRFCL/M.JP.RAMFDA_USD.S1X?startPeriod=2023-01-01&endPeriod=2023-12-31
response:
body:
string: !!binary |
H4sIAAAAAAAEA6SSX4+aQBTFv4qhL226yICiC09S/qQ0oBawMfaBTJmrkggYZlZtzH733hlNttuk
zSZ9nHPP7557Z+aq+V1zpJUIqKCae9Vml+bQcvfCa83V9kIcXcM4n8/D82jY9TvDIsQ01mmSV3to
qF63XNC2Au3hBWRvAJWf1y5XbZKuoqLu2tdgHqRrldkD7576CrghpTQxbhQ3TlZJjAY4pzsYyFk5
DtvDruai/znkrLko/pVdtbghwwtnCkOK4foc+lONMcO62Sqwul2NEWeRn7zdKfuqBeVN/s9O2OQz
UAa9fJg4wFbjEXu02cTW2eN2pI8dRnUHto5Ox4wSx7bNCiykCuAC3Vt64PJplj0caQ8MJYtYY504
ukkKc+qatmvZ6M+hvafMaukyfYLqnDYgk+WXcA+03SEPLRbeCbgIPMRppD0/aH7XCvxB0rrKYtT3
L9/mfpUIFXCA475rAQ0fB+bgPY7yYTCxRvrEsoj2jI0yqKA+gVp3VjM0bjYbmRBQQXMQcYCSeozf
RBkrJ2w5Fu/JDP38n6+JA+XQ14DQVZtFWfgV6RTVWRZGpZeFHp6/LKUQz4PY94pFhkrmpVHglas8
kBVpzUP/VsrNtdRW87go01VSoHsihSJOwzJaZKknpaUpQxY/MPc7BqviMsziRYB+i+BtEFNSi095
+c1LViHK+tQZ48J/sVt/2m0y/dXnHNwADIMwFN2l90oBAnGG6SjZvTmDjM9P1ufaslYfXM+ssY1r
z1pMGx6Fo2tZhesCj0HhNjfnu3BfQbmMwrt3kcwxJj/XrF8NPOc7dz97tJr2sAUAAA==
headers:
Cache-Control:
- private
Connection:
- Keep-Alive
Content-Encoding:
- gzip
Content-Length:
- '619'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 10 Sep 2024 21:15:25 GMT
Set-Cookie: null
Vary:
- Accept-Encoding
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,54 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: http://dataservices.imf.org/REST/SDMX_JSON.svc/CompactData/IRFCL/M.JP.RAMFDA_USD.S1X?startPeriod=2023-01-01&endPeriod=2023-12-31
response:
body:
string: !!binary |
H4sIAAAAAAAEA6SSXY+aQBSG/4qhN226yDB+ULmS8pHSgFrAxtgLMg5HJREwDKs2Zv97z4w27W6y
zSa95J3nPe/54Kq5TXVkvPNYxzT7qk0v1aEW9kWUmq3tu+5oG8b5fO6fB/2m3RmUENNYxVHK91Ax
vaxFx2oO2sMfY/EGo+JFaQtVJmo468qmfm5MvXilMlsQzWPLQRhSiiPj5hLGiebEqEAItoOe7FVg
sy3sStG1P/uiqC7K/wxXJW6W/kUUyoauAscX0J5KjOmX1VYZ+W01RpgEbvR2UtZVA8pN/s9MWOQL
sAJaeZjQw1IFZeaWTLjOh9ZAH47ISN8UfKOTicVhY32a8BFFVwaiQ3rLDkKeZtHCkbVQoEQJHSKt
EyszB/aQ2qMx8inU95RpKSnTJajOWAUyWf4S9oHVO/RDjQ/vOrh0+BHGgfb0oLlN3TGOwlVbJiHq
8hS4U/nb3FeJpgwOcNw3NSDwsWf23mMrH3pjOtDHlBLtCQslwKE8gRp3WhYIrtdrmeDhdVLo1ArU
Mf4Sf3dYC+TvyQXy4p/XxIZSaEtA01WbBon/Dd0xqtPED3In8R38/rqQQjjzQtfJ5gkqiRMHnpMv
U0++SDT13dtTaq6ktpyFWR4vowzpsRSyMPbzYJ7EjpQWpgyZbzD3Bwarx4WfhHMPeUpwG8SUrvnn
NP/uREsfZd2aDHHgV3D6Eh8R63V68JL+1eecmwAMxEAU7cW5QfdRjEvZ3r2xhDTxY/ikMGupOpJn
rVUj08Kt8dhavHHymGOicZaceTaubiNHaHx7R6w8QOZzqvoli+d85+4HilPlVbAFAAA=
headers:
Cache-Control:
- private
Connection:
- Keep-Alive
Content-Encoding:
- gzip
Content-Length:
- '620'
Content-Type:
- application/json; charset=utf-8
Date:
- Sat, 07 Sep 2024 17:42:56 GMT
Set-Cookie: null
Vary:
- Accept-Encoding
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,60 @@
"""IMF Fetcher Tests."""
from datetime import date
import pytest
from openbb_imf.models.available_indicators import ImfAvailableIndicatorsFetcher
from openbb_imf.models.economic_indicators import ImfEconomicIndicatorsFetcher
from openbb_core.app.service.user_service import UserService
test_credentials = UserService().default_user_settings.credentials.model_dump(
mode="json"
)
def scrub_string(key):
"""Scrub a string from the response."""
def before_record_response(response):
response["headers"][key] = response["headers"].update({key: None})
return response
return before_record_response
@pytest.fixture(scope="module")
def vcr_config():
"""VCR configuration."""
return {
"filter_headers": [("User-Agent", None)],
"before_record_response": [
scrub_string("Set-Cookie"),
],
}
@pytest.mark.record_http
def test_imf_economic_indicators_fetcher(credentials=test_credentials):
"""Test the IMF EconomicIndicators fetcher."""
params = {
"country": "JP",
"frequency": "month",
"symbol": "RAMFDA_USD",
"start_date": date(2023, 1, 1),
"end_date": date(2023, 12, 31),
}
fetcher = ImfEconomicIndicatorsFetcher()
result = fetcher.test(params, credentials)
assert result is None
# The data for this request are local files, so we can't record them.
def test_imf_available_indicators_fetcher(credentials=test_credentials):
"""Test the IMF Available Indicators fetcher."""
params = {}
fetcher = ImfAvailableIndicatorsFetcher()
result = fetcher.test(params, credentials)
assert result is None

View File

@@ -18,6 +18,7 @@ openbb-econdb = "^1.2.2"
openbb-federal-reserve = "^1.3.2"
openbb-fmp = "^1.3.2"
openbb-fred = "^1.3.2"
openbb-imf = "^1.0.0b"
openbb-intrinio = "^1.3.2"
openbb-oecd = "^1.3.2"
openbb-polygon = "^1.3.2"