From 132320c7ff0f1e680bcb01dad4d723a8c60709ea Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Wed, 15 Apr 2026 16:49:45 -0400 Subject: [PATCH 1/9] Update Ethiopia calibration sources and documentation --- .../calibration/exogenous_parameters.md | 16 +- docs/book/content/calibration/macro.md | 22 +-- ogeth/macro_params.py | 157 ++++++++++++++++-- ogeth/ogeth_default_parameters.json | 4 +- tests/test_macro_params.py | 98 ++++++++++- 5 files changed, 255 insertions(+), 42 deletions(-) diff --git a/docs/book/content/calibration/exogenous_parameters.md b/docs/book/content/calibration/exogenous_parameters.md index c5d2c9e..f9b0239 100644 --- a/docs/book/content/calibration/exogenous_parameters.md +++ b/docs/book/content/calibration/exogenous_parameters.md @@ -60,10 +60,10 @@ kernelspec: | $\texttt{use_zeta}$ | Whether to distribute bequests between lifetime income groups | 0.00E+00 | | $\zeta$ | Distribution of bequests | Too large to report here, see default parameters JSON | | $Z_{t}$ | Total factor productivity | Too large to report here, see default parameters JSON | -| $\gamma$ | Capital share of income | [0.401...0.401] | +| $\gamma$ | Capital share of income | [0.618...0.618] | | $\varepsilon$ | Elasticity of substitution between capital and labor | [1.000...1.000] | | $\delta$ | Capital depreciation rate | 0.050 | -| $g_{y}$ | Growth rate of labor augmenting technological progress | 0.00E+00 | +| $g_{y}$ | Growth rate of labor augmenting technological progress | 0.060 | | $\texttt{tax_func_type}$ | Functional form used for income tax functions | linear | | $\texttt{analytical_mtrs}$ | Whether use analytical MTRs or estimate MTRs | 0.00E+00 | | $\texttt{age_specific}$ | Whether use age-specific tax functions | 0.00E+00 | @@ -77,14 +77,14 @@ kernelspec: | $P$ | Coefficient on level term in wealth tax function | [0.000...0.000] | | $\texttt{budget_balance}$ | Whether have a balanced budget in each period | 0.00E+00 | | $\texttt{baseline_spending}$ | Whether level of spending constant between the baseline and reform runs | 0.00E+00 | -| $\alpha^{T}_{t}$ | Transfers as a share of GDP | [0.041...0.041] | +| $\alpha^{T}_{t}$ | Transfers as a share of GDP | [0.004...0.004] | | $\eta_{j,s,t}$ | Distribution of transfers | Too large to report here, see default parameters JSON | -| $\alpha^{G}_{t}$ | Government spending as a share of GDP | [0.267...0.267] | +| $\alpha^{G}_{t}$ | Government spending as a share of GDP | [0.055...0.055] | | $t_{G1}$ | Model period in which budget closure rule starts | 20 | | $t_{G2}$ | Model period in which budget closure rule ends | 256 | | $\rho_{G}$ | Budget closure rule smoothing parameter | 0.100 | | $\bar{\alpha}_{D}$ | Steady-state Debt-to-GDP ratio | 1.200 | -| $\alpha_{D,0}$ | Initial period Debt-to-GDP ratio | 0.740 | +| $\alpha_{D,0}$ | Initial period Debt-to-GDP ratio | 0.327 | | $\tau_{d,t}$ | Scale parameter in government interest rate wedge | [0.245...0.245] | | $\mu_{d,t}$ | Shift parameter in government interest rate wedge | [-0.034...-0.034] | | $\texttt{avg_earn_num_years}$ | Number of years over which compute average earnings for pension benefit | 35 | @@ -97,9 +97,9 @@ kernelspec: | $\texttt{PIA_minpayment}$ | Minimum PIA payment | 0.00E+00 | | $\theta_{adj,t}$ | Adjustment to replacement rate | [1.000...1.000] | | $r^{*}_{t}$ | World interest rate | [0.040...0.040] | -| $D_{f,0}$ | Share of government debt held by foreigners in initial period | 0.237 | -| $\zeta_{D, t}$ | Share of new debt issues purchased by foreigners | [0.237...0.237] | -| $\zeta_{K, t}$ | Share of excess capital demand satisfied by foreigners | [0.900...0.900] | +| $D_{f,0}$ | Share of government debt held by foreigners in initial period | 0.420 | +| $\zeta_{D, t}$ | Share of new debt issues purchased by foreigners | [0.120...0.120] | +| $\zeta_{K, t}$ | Share of excess capital demand satisfied by foreigners | [0.650...0.650] | | $\xi$ | Dampening parameter for TPI | 0.400 | | $\texttt{maxiter}$ | Maximum number of iterations for TPI | 250 | | $\texttt{mindist_SS}$ | SS solution tolerance | 1.00E-09 | diff --git a/docs/book/content/calibration/macro.md b/docs/book/content/calibration/macro.md index 1588c27..cc7825c 100644 --- a/docs/book/content/calibration/macro.md +++ b/docs/book/content/calibration/macro.md @@ -9,21 +9,21 @@ As the default rate of labor augmenting technological change, $g_y$, we use a va ### Foreign holding of government debt in the initial period -The path of foreign holding of domestic debt is endogenous, but the initial period stock of debt held by foreign investors is exogenous. We set this parameter, `initial_foreign_debt_ratio` to 0.95, consistent with [this report from the Ministry of Finance](https://www.mofed.gov.et/media/filer_public/9b/92/9b9264db-1a2b-4cd5-aa7d-f0307d67b4ce/public_sector_debt_statistical_bulletin_no_50.pdf). +The path of foreign holding of domestic debt is endogenous, but the initial period stock of debt held by foreign investors is exogenous. We set this parameter, `initial_foreign_debt_ratio`, to 0.42 using the Ministry of Finance Public Sector Debt Portfolio Analysis for FY2023/24, where external debt is USD 28.89 billion out of USD 68.86 billion of total public debt. ### Foreign purchases of newly issued debt -We set $\zeta_D = 0.95$, the same as initial holdings of government debt by foreigners. +We set $\zeta_D = 0.12$ using the same FY2023/24 Ministry of Finance debt portfolio analysis. In that year, the change in total public debt was about USD 5.53 billion while the change in external debt was about USD 0.64 billion, implying that roughly 11.6% of new debt issuance was external. ### Foreign holdings of excess capital -We set $\zeta_K = 0.95$. Note, this parameter is harder to pin down from the data as foreign purchases on "excess" capital demand is not typically directly measured or reported. A value of 0.95 implies a high degree of openness to international capital flows. +We set $\zeta_K = 0.65$. Note, this parameter is harder to pin down from the data as foreign purchases on "excess" capital demand is not typically directly measured or reported. A value of 0.65 implies a relatively open economy while still allowing for a sizable domestic share of excess capital demand. ## Government Debt, Spending and Transfers ### Government Debt -The path of government debt is endogenous. But the initial value and the steady-state (long-run) value are exogenous. To avoid converting between model units and dollars, we calibrate the initial debt to GDP ratio, rather than the dollar value of the debt. This is the model parameter $\alpha_D$ and the parameter name in [`ogeth_default_parameters.json`](https://github.com/EAPD-DRB/OG-ETH/blob/main/ogeth/ogeth_default_parameters.json) is `initial_debt_ratio`. We compute this from the ratio of publicly held debt outstanding to GDP. Based on the 2019 value reported by the World Bank, the initial debt-to-GDP ratio in Ethiopia is 0.314.[^macro_wb_DY] +The path of government debt is endogenous. But the initial value and the steady-state (long-run) value are exogenous. To avoid converting between model units and dollars, we calibrate the initial debt-to-GDP ratio, rather than the dollar value of debt. This is the model parameter $\alpha_D$ and the parameter name in [`ogeth_default_parameters.json`](https://github.com/EAPD-DRB/OG-ETH/blob/main/ogeth/ogeth_default_parameters.json) is `initial_debt_ratio`. We set `initial_debt_ratio = 0.327` using the IMF WEO gross general government debt series for Ethiopia, where FY2023/24 mapped to calendar year 2024 is 32.66% of GDP. #### Interest rates on government debt @@ -39,21 +39,21 @@ where $\tau_d$ is the scale parameter and $\mu_d$ is the level shift parameter. ### Aggregate transfers -Aggregate (non-Social Security) transfers to households are set as a share of GDP with the parameter $\alpha_T$. We exclude Social Security from transfers since it is modeled specifically. We compute this from the IMF GFS Statement of Operations data as -
$\alpha_T = (\texttt{G27\_T} - \texttt{G271\_T}) / 100$
-For Ethiopia, the available IMF GFS percent-of-GDP series for this calibration are published under the `Budgetary central government` sector (`S1311B`), so the OG-ETH calibration uses that sector rather than mechanically reusing another country repo's sector code. Using the 2024 IMF values for `G27_T` and `G271_T`, the default calibration sets $\alpha_T = 0.0$. +Aggregate (non-Social Security) transfers to households are set as a share of GDP with the parameter $\alpha_T$. We exclude Social Security from transfers since it is modeled specifically. In OG-ETH, the relevant concept is government-financed, non-pension transfers paid to households. For Ethiopia, the IMF GFS `S1311B` social-benefits series (`G27_T` and `G271_T`) do not line up well with that concept in the calibration year because they miss the main FY2024/25 government cash contributions to the rural PSNP and urban UPSNP. + +Instead, the default calibration uses the IMF program target for Government Contributions to Productive Safety Net Programme cash transfers in FY2024/25 and scales it by the IMF nominal GDP series for the same fiscal year. The FY2024/25 transfer target is 51.4 billion birr and nominal GDP is 14,856 billion birr, implying $\alpha_T = 51.4 / 14{,}856 \approx 0.00346$, which we round to 0.0035. This choice is also consistent with the World Bank development policy financing documents that place the government contribution to rural and urban safety nets at about 0.4% of GDP in FY2024/25. Using the 2024 calibration year, the default calibration therefore sets $\alpha_T = 0.0035$. ### Government expenditures Government spending on goods and services are also set as a share of GDP with the parameter $\alpha_G$. We define government spending as:
Government Spending = Total Outlays - Transfers - Net Interest on Debt - Social Security
-In IMF GFS Statement of Operations terms, we compute -
$\alpha_G = (\texttt{G2\_T} - \texttt{G24\_T} - \texttt{G27\_T}) / 100$
-Using Ethiopia's 2024 `S1311B` IMF GFS percent-of-GDP series, the default calibration sets $\alpha_G = 0.0453$. +For Ethiopia, the concept behind $\alpha_G$ is closer to government consumption and public-goods spending than to a budgetary-central-government outlay residual. Because the IMF GFS data available for Ethiopia are published for `Budgetary central government` (`S1311B`), while the model concept is broader and closer to general government spending on goods and services, the default calibration uses the World Bank indicator `NE.CON.GOVT.ZS` instead of mechanically reusing the IMF construction used elsewhere. + +Using the World Bank's 2024 value for general government final consumption expenditure, the default calibration sets $\alpha_G = 0.0552$. (SecLWI_footnotes)= ## Footnotes The following are the footnotes for this section. -[^macro_wb_DY]: See https://data.worldbank.org/country/ethiopia, accessed Nov. 17, 2025. +[^macro_wb_DY]: The macro debt and transfer updates above use FY2023/24 or FY2024/25 sources mapped to the repo's 2024 calibration year when that is the closest official match. diff --git a/ogeth/macro_params.py b/ogeth/macro_params.py index 51fb341..5ee25c7 100644 --- a/ogeth/macro_params.py +++ b/ogeth/macro_params.py @@ -95,6 +95,25 @@ def _fetch_wb_data(indicators, country_iso, start_year, end_year, source): # under budgetary central government (S1311B), not the broader S1311 sector. IMF_GFS_SECTOR_BY_COUNTRY = {"ETH": "S1311B"} +# Ethiopia's IMF GFS social-benefits series do not capture the FY2024/25 +# government cash transfers to rural PSNP and urban UPSNP that best match the +# OG-ETH alpha_T concept of non-pension transfers to households. For the 2024 +# calibration year we therefore use the FY2024/25 program documents as the +# packaged/default alpha_T source instead of the IMF GFS G27/G271 series. +MANUAL_ALPHA_T_BY_COUNTRY_YEAR = {("ETH", 2024): [0.0035]} + +# For Ethiopia, alpha_G is calibrated from general government final +# consumption expenditure because that better matches the OG-ETH concept of +# government spending on goods, services, and public goods than the available +# budgetary central government IMF GFS outlay series. +WB_ALPHA_G_BY_COUNTRY = { + "ETH": "General government final consumption expenditure (% of GDP)" +} + +# Ethiopia's default long-run productivity-growth calibration uses the +# post-2005 growth regime rather than the full World Bank history. +GDP_GROWTH_START_YEAR_BY_COUNTRY = {"ETH": 2006} + def _get_imf_gfs_sector(country_iso): """ @@ -103,6 +122,81 @@ def _get_imf_gfs_sector(country_iso): return IMF_GFS_SECTOR_BY_COUNTRY.get(country_iso.upper(), "S1311") +def _get_manual_alpha_t(country_iso, target_year): + """ + Return a country/year-specific alpha_T override when IMF GFS does not map + cleanly to the model concept of non-pension household transfers. + """ + return MANUAL_ALPHA_T_BY_COUNTRY_YEAR.get( + (country_iso.upper(), int(target_year)) + ) + + +def _get_world_bank_alpha_g(wb_data, country_iso, target_year): + """ + Return a country-specific alpha_G from World Bank data when the spending + concept aligns better with OG-ETH than the available IMF GFS coverage. + """ + series_name = WB_ALPHA_G_BY_COUNTRY.get(country_iso.upper()) + if series_name is None or series_name not in wb_data.columns: + return None + + if isinstance(wb_data.index, pd.MultiIndex): + years = wb_data.index.get_level_values(-1) + else: + years = wb_data.index + + alpha_g_series = pd.Series( + wb_data[series_name].values, + index=pd.to_numeric(years, errors="coerce"), + ).dropna() + alpha_g_series = alpha_g_series[alpha_g_series.index <= int(target_year)] + if alpha_g_series.empty: + raise ValueError( + "No World Bank government consumption data available for " + f"{country_iso} up to {target_year}" + ) + + selected_year = ( + int(target_year) + if int(target_year) in alpha_g_series.index + else int(alpha_g_series.index.max()) + ) + if selected_year != int(target_year): + print( + f"Warning: No World Bank alpha_G data for {target_year}. " + f"Using last available year: {selected_year}" + ) + return [alpha_g_series.loc[selected_year] / 100] + + +def _get_world_bank_g_y_annual(wb_data, country_iso, data_start_date): + """ + Compute average GDP-per-capita growth using the country-specific + calibration window rather than the full available history. + """ + series_name = "GDP per capita (constant 2015 US$)" + if series_name not in wb_data.columns: + return None + + if isinstance(wb_data.index, pd.MultiIndex): + years = wb_data.index.get_level_values(-1) + else: + years = wb_data.index + + growth_start_year = max( + int(data_start_date.year), + GDP_GROWTH_START_YEAR_BY_COUNTRY.get(country_iso.upper(), 0), + ) + gdp_pc_series = pd.Series( + wb_data[series_name].values, + index=pd.to_numeric(years, errors="coerce"), + ).sort_index(ascending=False) + gdp_pc_series = gdp_pc_series[gdp_pc_series.index >= growth_start_year] + g_y_series = gdp_pc_series.pct_change(-1) + return g_y_series.mean() if not g_y_series.isna().all() else None + + def _get_imf_macro_params(country_iso, target_year, data_path=None): """ Fetch IMF GFS data and compute alpha_T and alpha_G. @@ -253,11 +347,13 @@ def get_macro_params( # Annual data wb_a_variable_dict = { "GDP per capita (constant 2015 US$)": "NY.GDP.PCAP.KD", + "General government final consumption expenditure (% of GDP)": "NE.CON.GOVT.ZS", # "Real GDP (constant 2015 US$)": "NY.GDP.MKTP.KD", # "Nominal GDP (current US$)": "NY.GDP.MKTP.CD", # "General government final consumption expenditure (current US$)": "NE.CON.GOVT.CD", } + wb_alpha_g = None if update_from_api: try: # Pull annual series from the World Bank v2 API @@ -271,13 +367,8 @@ def get_macro_params( # Compute annual GDP growth safely if "GDP per capita (constant 2015 US$)" in wb_data_a.columns: - g_y_series = wb_data_a[ - "GDP per capita (constant 2015 US$)" - ].pct_change(-1) - - # If all values are NaN, return None - macro_parameters["g_y_annual"] = ( - g_y_series.mean() if not g_y_series.isna().all() else None + macro_parameters["g_y_annual"] = _get_world_bank_g_y_annual( + wb_data_a, country_iso, data_start_date ) else: print( @@ -287,6 +378,12 @@ def get_macro_params( print( f"g_y_annual updated from World Bank API: {macro_parameters['g_y_annual']}" ) + try: + wb_alpha_g = _get_world_bank_alpha_g( + wb_data_a, country_iso, data_end_date.year + ) + except ValueError: + wb_alpha_g = None except Exception: print("Failed to retrieve data from World Bank") print("Will not update the following parameters:") @@ -356,26 +453,50 @@ def get_macro_params( """ if update_from_api: + imf_year = ( + data_end_date.year if imf_data_year is None else imf_data_year + ) + imf_macro_parameters = None try: - imf_year = ( - data_end_date.year if imf_data_year is None else imf_data_year + imf_macro_parameters = _get_imf_macro_params( + country_iso, + imf_year, + data_path=imf_data_path, ) - macro_parameters.update( - _get_imf_macro_params( - country_iso, - imf_year, - data_path=imf_data_path, - ) + except Exception: + print("Failed to retrieve data from IMF") + + manual_alpha_t = _get_manual_alpha_t(country_iso, imf_year) + if manual_alpha_t is not None: + macro_parameters["alpha_T"] = manual_alpha_t + print( + "alpha_T updated from Ethiopia FY2024/25 IMF/World Bank " + f"safety-net sources: {macro_parameters['alpha_T']}" ) + elif imf_macro_parameters is not None: + macro_parameters["alpha_T"] = imf_macro_parameters["alpha_T"] print( f"alpha_T updated from IMF data: {macro_parameters['alpha_T']}" ) + else: + print("Will not update alpha_T") + + if country_iso.upper() in WB_ALPHA_G_BY_COUNTRY: + if wb_alpha_g is not None: + macro_parameters["alpha_G"] = wb_alpha_g + print( + "alpha_G updated from World Bank government consumption " + f"data: {macro_parameters['alpha_G']}" + ) + else: + print("Will not update alpha_G") + elif imf_macro_parameters is not None: + macro_parameters["alpha_G"] = imf_macro_parameters["alpha_G"] print( f"alpha_G updated from IMF data: {macro_parameters['alpha_G']}" ) - except Exception: - print("Failed to retrieve data from IMF") - print("Will not update alpha_T, alpha_G") + else: + print("Will not update alpha_G") # initial_debt_ratio, gross general government debt as a fraction of GDP # source: from the IMF WEO, Series ETH.GGXWDG_NGDP.A — Gross general government debt (% of GDP). diff --git a/ogeth/ogeth_default_parameters.json b/ogeth/ogeth_default_parameters.json index 5ad048e..3b05417 100644 --- a/ogeth/ogeth_default_parameters.json +++ b/ogeth/ogeth_default_parameters.json @@ -1187,10 +1187,10 @@ "tG1": 20, "tG2": 256, "alpha_T": [ - 0.0 + 0.0035 ], "alpha_G": [ - 0.04525629819754754 + 0.05515691 ], "alpha_I": [ 0.0 diff --git a/tests/test_macro_params.py b/tests/test_macro_params.py index 5a14acd..01b8e3a 100644 --- a/tests/test_macro_params.py +++ b/tests/test_macro_params.py @@ -151,6 +151,9 @@ def _wb_payload(observations): "NY.GDP.PCAP.KD": _wb_payload( [("2024", 100.0), ("2023", 80.0), ("2022", 64.0)] ), + "NE.CON.GOVT.ZS": _wb_payload( + [("2024", 5.515691), ("2023", 6.317772), ("2022", 7.361735)] + ), } @@ -253,8 +256,8 @@ def test_get_macro_params_update_from_api_true(monkeypatch): assert test_dict["zeta_D"] == [0.12] assert test_dict["g_y_annual"] == pytest.approx(0.25) assert test_dict["gamma"] == [pytest.approx(0.61791)] - assert test_dict["alpha_T"] == [pytest.approx(0.0)] - assert test_dict["alpha_G"] == [pytest.approx(0.04525629819754754)] + assert test_dict["alpha_T"] == [pytest.approx(0.0035)] + assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] assert test_dict["r_gov_shift"] == [pytest.approx(-0.03376625043803517)] assert test_dict["r_gov_scale"] == [pytest.approx(0.24484763593657818)] assert any( @@ -274,6 +277,13 @@ def test_get_imf_macro_params_uses_eth_budgetary_sector(monkeypatch): assert any("/ETH.S1311B.G2M.*.POGDP_PT.A" in url for url in requested_urls) +def test_get_manual_alpha_t_returns_eth_2024_override(): + assert macro_params._get_manual_alpha_t("ETH", 2024) == [ + pytest.approx(0.0035) + ] + assert macro_params._get_manual_alpha_t("ETH", 2023) is None + + def test_get_imf_macro_params_overwrites_saved_file(monkeypatch, tmp_path): requested_urls = [] _mock_requests_get(monkeypatch, requested_urls) @@ -354,4 +364,86 @@ def test_get_macro_params_passes_imf_year_override(monkeypatch): data_end_date=datetime.datetime(2024, 12, 31), ) - assert test_dict["alpha_G"] == [pytest.approx(0.05568952015661374)] + assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] + + +def test_get_world_bank_alpha_g_returns_eth_override(): + wb_data = pd.DataFrame( + {"General government final consumption expenditure (% of GDP)": [5.515691]}, + index=["2024"], + ) + + assert macro_params._get_world_bank_alpha_g(wb_data, "ETH", 2024) == [ + pytest.approx(0.05515691) + ] + + +def test_get_world_bank_g_y_annual_uses_ethiopia_2006_window(): + wb_data = pd.DataFrame( + {"GDP per capita (constant 2015 US$)": [100.0, 90.0, 80.0, 40.0]}, + index=["2024", "2023", "2006", "2005"], + ) + + result = macro_params._get_world_bank_g_y_annual( + wb_data, + "ETH", + datetime.datetime(1947, 1, 1), + ) + + # 2005 should be excluded, so the average is over 2024/2023 and 2023/2006 + expected = ((100.0 / 90.0) - 1 + (90.0 / 80.0) - 1) / 2 + assert result == pytest.approx(expected) + + +def test_eth_alpha_g_updates_when_imf_fails(monkeypatch): + requested_urls = [] + _mock_statsmodels(monkeypatch) + + def fake_get(url, params=None, headers=None, timeout=None): + requested_urls.append(url) + if "worldbank.org" in url: + indicator_code = url.rstrip("/").split("/")[-1] + return MockResponse( + json_data=_DEFAULT_WB_PAYLOADS[indicator_code] + ) + if "rplumber.ilo.org" in url: + return MockResponse( + text="time,obs_value\n2024,38.209\n2023,38.0\n" + ) + if "api.imf.org" in url: + raise requests.HTTPError("mock IMF failure") + raise AssertionError(f"Unexpected URL requested in test: {url}") + + monkeypatch.setattr(macro_params.requests, "get", fake_get) + + test_dict = macro_params.get_macro_params(update_from_api=True) + + assert test_dict["alpha_T"] == [pytest.approx(0.0035)] + assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] + + +def test_eth_alpha_t_updates_without_world_bank_alpha_g(monkeypatch): + # Simulate the World Bank alpha_G series returning no observations + # while the GDP-per-capita series is complete. The outer try/except + # in get_macro_params swallows the resulting fetch error, so alpha_T + # still updates from IMF but alpha_G is not set. + requested_urls = [] + _mock_statsmodels(monkeypatch) + _mock_requests_get( + monkeypatch, + requested_urls, + wb_payloads={ + "NY.GDP.PCAP.KD": _wb_payload( + [("2024", 100.0), ("2023", 80.0), ("2022", 64.0)] + ), + "NE.CON.GOVT.ZS": [ + {"page": 1, "pages": 1, "per_page": "10000", "total": 0}, + [], + ], + }, + ) + + test_dict = macro_params.get_macro_params(update_from_api=True) + + assert test_dict["alpha_T"] == [pytest.approx(0.0035)] + assert "alpha_G" not in test_dict From 2d05bd33197c8507a06bdf6135ea6c2fcac06807 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Wed, 15 Apr 2026 17:29:40 -0400 Subject: [PATCH 2/9] Remove r_gov_shift/r_gov_scale from macro calibration --- docs/book/content/calibration/macro.md | 20 +++++++++++ ogeth/macro_params.py | 50 ++++++-------------------- tests/test_macro_params.py | 34 ------------------ 3 files changed, 30 insertions(+), 74 deletions(-) diff --git a/docs/book/content/calibration/macro.md b/docs/book/content/calibration/macro.md index cc7825c..8ebee31 100644 --- a/docs/book/content/calibration/macro.md +++ b/docs/book/content/calibration/macro.md @@ -37,6 +37,26 @@ We assume that there is a wedge between the real rate of return on private capit where $\tau_d$ is the scale parameter and $\mu_d$ is the level shift parameter. We set the values of these two parameters to 0.245 and -0.034, respectively. These are found by using the estimated relationship between corporate and sovereign yields in {cite}`LMW2023` (Table 8, Column 2) and simulating a series of corporate yields given a series of sovereign yields between 2% and 12%. We then estimate the scale and level shift parameters that best fit these simulated data using ordinary least squares. +We use this emerging-markets relationship because a calibration based on readily available US corporate and sovereign yield data would be a poor proxy for Ethiopia. The goal of these parameters is not to capture a country-specific live spread series, but to impose a reasonable wedge between private and government borrowing rates using evidence from a broader sample of emerging markets. + +These values are fixed calibrated defaults in [`ogeth_default_parameters.json`](https://github.com/EAPD-DRB/OG-ETH/blob/main/ogeth/ogeth_default_parameters.json); they are not refreshed from live data during calibration updates. The following Python reproduces the one-time calculation used to obtain them: + +```python +import numpy as np +import statsmodels.api as sm + +sov_y = np.arange(20, 120) / 10 +corp_yhat = 8.199 - (2.975 * sov_y) + (0.478 * sov_y**2) +corp_yhat = sm.add_constant(corp_yhat) +res = sm.OLS(sov_y, corp_yhat).fit() + +r_gov_shift = -res.params[0] / 100 +r_gov_scale = res.params[1] + +print(r_gov_shift) # -0.03376625043803517 +print(r_gov_scale) # 0.24484763593657818 +``` + ### Aggregate transfers Aggregate (non-Social Security) transfers to households are set as a share of GDP with the parameter $\alpha_T$. We exclude Social Security from transfers since it is modeled specifically. In OG-ETH, the relevant concept is government-financed, non-pension transfers paid to households. For Ethiopia, the IMF GFS `S1311B` social-benefits series (`G27_T` and `G271_T`) do not line up well with that concept in the calibration year because they miss the main FY2024/25 government cash contributions to the rural PSNP and urban UPSNP. diff --git a/ogeth/macro_params.py b/ogeth/macro_params.py index 5ee25c7..1989956 100644 --- a/ogeth/macro_params.py +++ b/ogeth/macro_params.py @@ -6,7 +6,6 @@ # imports import pandas as pd -import numpy as np import requests import datetime from io import StringIO @@ -212,6 +211,9 @@ def _get_imf_macro_params(country_iso, target_year, data_path=None): sector = _get_imf_gfs_sector(country_iso) required_indicators = {"G2_T", "G24_T", "G27_T", "G271_T"} data_path = Path(data_path) if data_path is not None else None + + # Request the IMF SDMX 3.0 payload for the country/sector slice that + # contains the four GFS indicators needed for alpha_T and alpha_G. response = requests.get( ( "https://api.imf.org/external/sdmx/3.0/data/dataflow/" @@ -235,6 +237,8 @@ def _get_imf_macro_params(country_iso, target_year, data_path=None): "Empty or malformed IMF response for GFS_SOO" ) from exc + # Flatten the SDMX series/observation structure into one row per + # indicator-year observation so the completeness checks below are simple. records = [] for series_key, series in data_set["series"].items(): dimension_indexes = [int(idx) for idx in series_key.split(":")] @@ -277,6 +281,8 @@ def _get_imf_macro_params(country_iso, target_year, data_path=None): ) print(f"IMF data saved to {data_path}") + # We only use a year when all four indicators are available. If the target + # year is incomplete, fall back to the latest complete year at or before it. available = ( imf_data.pivot_table( index="year", @@ -307,6 +313,8 @@ def _get_imf_macro_params(country_iso, target_year, data_path=None): ) values = available.loc[selected_year] + # Map the selected IMF GFS observations into the OG-ETH transfer and + # government-spending concepts. return { "alpha_T": [(values["G27_T"] - values["G271_T"]) / 100], "alpha_G": [ @@ -518,45 +526,7 @@ def get_macro_params( # We use the latest year. macro_parameters["zeta_D"] = [0.12] - """" - Estimate the discount on sovereign yields relative to private debt - Follow the methodology in Li, Magud, Werner, Witte (2021) - available at: - https://www.imf.org/en/Publications/WP/Issues/2021/06/04/The-Long-Run-Impact-of-Sovereign-Yields-on-Corporate-Yields-in-Emerging-Markets-50224 - discussion is here: https://github.com/EAPD-DRB/OG-ZAF/issues/22 - Steps: - 1) Generate modelled corporate yields (corp_yhat) for a range of - sovereign yields (sov_y) using the estimated equation in col 2 of - table 8 (and figure 3). 2) Estimate the OLS using sovereign yields - as the dependent variable - """ - - try: - import statsmodels.api as sm - - # # estimate r_gov_shift and r_gov_scale - sov_y = np.arange(20, 120) / 10 - corp_yhat = 8.199 - (2.975 * sov_y) + (0.478 * sov_y**2) - corp_yhat = sm.add_constant(corp_yhat) - mod = sm.OLS( - sov_y, - corp_yhat, - ) - res = mod.fit() - # First term is the constant and needs to be divided by 100 to have - # the correct unit. Second term is the coefficient - macro_parameters["r_gov_shift"] = [-res.params[0] / 100] - macro_parameters["r_gov_scale"] = [res.params[1]] - print( - f"r_gov_shift updated from IMF data: {macro_parameters['r_gov_shift']}" - ) - print( - f"r_gov_scale updated from IMF data: {macro_parameters['r_gov_scale']}" - ) - except Exception: - print("Failed to compute r_gov_shift, r_gov_scale") - print("Will not update r_gov_shift, r_gov_scale") else: - print("Not updating alpha_T, alpha_G, r_gov_shift, r_gov_scale") + print("Not updating alpha_T, alpha_G") return macro_parameters diff --git a/tests/test_macro_params.py b/tests/test_macro_params.py index 01b8e3a..4655ca6 100644 --- a/tests/test_macro_params.py +++ b/tests/test_macro_params.py @@ -3,8 +3,6 @@ """ import datetime -import sys -import types import pandas as pd import pytest @@ -199,30 +197,6 @@ def fake_get(url, params=None, headers=None, timeout=None): monkeypatch.setattr(macro_params.requests, "get", fake_get) -def _mock_statsmodels(monkeypatch, params=None): - fake_statsmodels = types.ModuleType("statsmodels") - fake_api = types.ModuleType("statsmodels.api") - - def add_constant(values): - return values - - class OLS: - def __init__(self, endog, exog): - self.endog = endog - self.exog = exog - - def fit(self): - result = types.SimpleNamespace() - result.params = params or [3.376625043803517, 0.24484763593657818] - return result - - fake_api.add_constant = add_constant - fake_api.OLS = OLS - fake_statsmodels.api = fake_api - monkeypatch.setitem(sys.modules, "statsmodels", fake_statsmodels) - monkeypatch.setitem(sys.modules, "statsmodels.api", fake_api) - - def test_get_macro_params_update_from_api_false_returns_empty_dict(): test_dict = macro_params.get_macro_params(update_from_api=False) @@ -232,7 +206,6 @@ def test_get_macro_params_update_from_api_false_returns_empty_dict(): def test_get_macro_params_update_from_api_true(monkeypatch): requested_urls = [] - _mock_statsmodels(monkeypatch) _mock_requests_get(monkeypatch, requested_urls) test_dict = macro_params.get_macro_params(update_from_api=True) @@ -240,8 +213,6 @@ def test_get_macro_params_update_from_api_true(monkeypatch): assert isinstance(test_dict, dict) assert sorted(test_dict.keys()) == sorted( [ - "r_gov_shift", - "r_gov_scale", "alpha_T", "alpha_G", "initial_debt_ratio", @@ -258,8 +229,6 @@ def test_get_macro_params_update_from_api_true(monkeypatch): assert test_dict["gamma"] == [pytest.approx(0.61791)] assert test_dict["alpha_T"] == [pytest.approx(0.0035)] assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] - assert test_dict["r_gov_shift"] == [pytest.approx(-0.03376625043803517)] - assert test_dict["r_gov_scale"] == [pytest.approx(0.24484763593657818)] assert any( ".ETH.S1311B.G2M." in url or "/ETH.S1311B.G2M." in url for url in requested_urls @@ -344,7 +313,6 @@ def test_get_imf_macro_params_falls_back_to_last_available_year(monkeypatch): def test_get_macro_params_passes_imf_year_override(monkeypatch): requested_urls = [] - _mock_statsmodels(monkeypatch) _mock_requests_get( monkeypatch, requested_urls, @@ -397,7 +365,6 @@ def test_get_world_bank_g_y_annual_uses_ethiopia_2006_window(): def test_eth_alpha_g_updates_when_imf_fails(monkeypatch): requested_urls = [] - _mock_statsmodels(monkeypatch) def fake_get(url, params=None, headers=None, timeout=None): requested_urls.append(url) @@ -428,7 +395,6 @@ def test_eth_alpha_t_updates_without_world_bank_alpha_g(monkeypatch): # in get_macro_params swallows the resulting fetch error, so alpha_T # still updates from IMF but alpha_G is not set. requested_urls = [] - _mock_statsmodels(monkeypatch) _mock_requests_get( monkeypatch, requested_urls, From 97cc9a51e903d5ca7a5c020a2de8a39b2f5e4e62 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Wed, 15 Apr 2026 17:43:26 -0400 Subject: [PATCH 3/9] Simplify alpha_T sourcing and IMF fallback notification --- ogeth/macro_params.py | 55 +++++++++++--------------------------- tests/test_macro_params.py | 41 +++------------------------- 2 files changed, 19 insertions(+), 77 deletions(-) diff --git a/ogeth/macro_params.py b/ogeth/macro_params.py index 1989956..38539e8 100644 --- a/ogeth/macro_params.py +++ b/ogeth/macro_params.py @@ -94,13 +94,6 @@ def _fetch_wb_data(indicators, country_iso, start_year, end_year, source): # under budgetary central government (S1311B), not the broader S1311 sector. IMF_GFS_SECTOR_BY_COUNTRY = {"ETH": "S1311B"} -# Ethiopia's IMF GFS social-benefits series do not capture the FY2024/25 -# government cash transfers to rural PSNP and urban UPSNP that best match the -# OG-ETH alpha_T concept of non-pension transfers to households. For the 2024 -# calibration year we therefore use the FY2024/25 program documents as the -# packaged/default alpha_T source instead of the IMF GFS G27/G271 series. -MANUAL_ALPHA_T_BY_COUNTRY_YEAR = {("ETH", 2024): [0.0035]} - # For Ethiopia, alpha_G is calibrated from general government final # consumption expenditure because that better matches the OG-ETH concept of # government spending on goods, services, and public goods than the available @@ -121,16 +114,6 @@ def _get_imf_gfs_sector(country_iso): return IMF_GFS_SECTOR_BY_COUNTRY.get(country_iso.upper(), "S1311") -def _get_manual_alpha_t(country_iso, target_year): - """ - Return a country/year-specific alpha_T override when IMF GFS does not map - cleanly to the model concept of non-pension household transfers. - """ - return MANUAL_ALPHA_T_BY_COUNTRY_YEAR.get( - (country_iso.upper(), int(target_year)) - ) - - def _get_world_bank_alpha_g(wb_data, country_iso, target_year): """ Return a country-specific alpha_G from World Bank data when the spending @@ -161,12 +144,13 @@ def _get_world_bank_alpha_g(wb_data, country_iso, target_year): if int(target_year) in alpha_g_series.index else int(alpha_g_series.index.max()) ) + value = alpha_g_series.loc[selected_year] / 100 if selected_year != int(target_year): print( - f"Warning: No World Bank alpha_G data for {target_year}. " - f"Using last available year: {selected_year}" + f"No World Bank alpha_G data for {country_iso} in {target_year}. " + f"Using last available year {selected_year}: alpha_G={value:.4f}" ) - return [alpha_g_series.loc[selected_year] / 100] + return [value] def _get_world_bank_g_y_annual(wb_data, country_iso, data_start_date): @@ -306,21 +290,21 @@ def _get_imf_macro_params(country_iso, target_year, data_path=None): if int(target_year) in available.index else int(available.index.max()) ) - if selected_year != int(target_year): - print( - f"Warning: No IMF data for {target_year}. " - f"Using last available year: {selected_year}" - ) values = available.loc[selected_year] # Map the selected IMF GFS observations into the OG-ETH transfer and # government-spending concepts. - return { - "alpha_T": [(values["G27_T"] - values["G271_T"]) / 100], - "alpha_G": [ - (values["G2_T"] - values["G24_T"] - values["G27_T"]) / 100 - ], - } + alpha_T = (values["G27_T"] - values["G271_T"]) / 100 + alpha_G = (values["G2_T"] - values["G24_T"] - values["G27_T"]) / 100 + + if selected_year != int(target_year): + print( + f"No complete IMF GFS data for {country_iso} in {target_year}. " + f"Using last available year {selected_year}: " + f"alpha_T={alpha_T:.4f}, alpha_G={alpha_G:.4f}" + ) + + return {"alpha_T": [alpha_T], "alpha_G": [alpha_G]} def get_macro_params( @@ -474,14 +458,7 @@ def get_macro_params( except Exception: print("Failed to retrieve data from IMF") - manual_alpha_t = _get_manual_alpha_t(country_iso, imf_year) - if manual_alpha_t is not None: - macro_parameters["alpha_T"] = manual_alpha_t - print( - "alpha_T updated from Ethiopia FY2024/25 IMF/World Bank " - f"safety-net sources: {macro_parameters['alpha_T']}" - ) - elif imf_macro_parameters is not None: + if imf_macro_parameters is not None: macro_parameters["alpha_T"] = imf_macro_parameters["alpha_T"] print( f"alpha_T updated from IMF data: {macro_parameters['alpha_T']}" diff --git a/tests/test_macro_params.py b/tests/test_macro_params.py index 4655ca6..40e8b54 100644 --- a/tests/test_macro_params.py +++ b/tests/test_macro_params.py @@ -227,7 +227,7 @@ def test_get_macro_params_update_from_api_true(monkeypatch): assert test_dict["zeta_D"] == [0.12] assert test_dict["g_y_annual"] == pytest.approx(0.25) assert test_dict["gamma"] == [pytest.approx(0.61791)] - assert test_dict["alpha_T"] == [pytest.approx(0.0035)] + assert test_dict["alpha_T"] == [pytest.approx(0.0)] assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] assert any( ".ETH.S1311B.G2M." in url or "/ETH.S1311B.G2M." in url @@ -246,13 +246,6 @@ def test_get_imf_macro_params_uses_eth_budgetary_sector(monkeypatch): assert any("/ETH.S1311B.G2M.*.POGDP_PT.A" in url for url in requested_urls) -def test_get_manual_alpha_t_returns_eth_2024_override(): - assert macro_params._get_manual_alpha_t("ETH", 2024) == [ - pytest.approx(0.0035) - ] - assert macro_params._get_manual_alpha_t("ETH", 2023) is None - - def test_get_imf_macro_params_overwrites_saved_file(monkeypatch, tmp_path): requested_urls = [] _mock_requests_get(monkeypatch, requested_urls) @@ -335,34 +328,6 @@ def test_get_macro_params_passes_imf_year_override(monkeypatch): assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] -def test_get_world_bank_alpha_g_returns_eth_override(): - wb_data = pd.DataFrame( - {"General government final consumption expenditure (% of GDP)": [5.515691]}, - index=["2024"], - ) - - assert macro_params._get_world_bank_alpha_g(wb_data, "ETH", 2024) == [ - pytest.approx(0.05515691) - ] - - -def test_get_world_bank_g_y_annual_uses_ethiopia_2006_window(): - wb_data = pd.DataFrame( - {"GDP per capita (constant 2015 US$)": [100.0, 90.0, 80.0, 40.0]}, - index=["2024", "2023", "2006", "2005"], - ) - - result = macro_params._get_world_bank_g_y_annual( - wb_data, - "ETH", - datetime.datetime(1947, 1, 1), - ) - - # 2005 should be excluded, so the average is over 2024/2023 and 2023/2006 - expected = ((100.0 / 90.0) - 1 + (90.0 / 80.0) - 1) / 2 - assert result == pytest.approx(expected) - - def test_eth_alpha_g_updates_when_imf_fails(monkeypatch): requested_urls = [] @@ -385,7 +350,7 @@ def fake_get(url, params=None, headers=None, timeout=None): test_dict = macro_params.get_macro_params(update_from_api=True) - assert test_dict["alpha_T"] == [pytest.approx(0.0035)] + assert "alpha_T" not in test_dict assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] @@ -411,5 +376,5 @@ def test_eth_alpha_t_updates_without_world_bank_alpha_g(monkeypatch): test_dict = macro_params.get_macro_params(update_from_api=True) - assert test_dict["alpha_T"] == [pytest.approx(0.0035)] + assert test_dict["alpha_T"] == [pytest.approx(0.0)] assert "alpha_G" not in test_dict From 63d8ef66e309fcf3b8863d6bc31d7db6eb3db9f8 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Sat, 18 Apr 2026 20:50:54 -0400 Subject: [PATCH 4/9] Apply black to rebased test_macro_params --- tests/test_macro_params.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_macro_params.py b/tests/test_macro_params.py index 40e8b54..6f591b9 100644 --- a/tests/test_macro_params.py +++ b/tests/test_macro_params.py @@ -335,9 +335,7 @@ def fake_get(url, params=None, headers=None, timeout=None): requested_urls.append(url) if "worldbank.org" in url: indicator_code = url.rstrip("/").split("/")[-1] - return MockResponse( - json_data=_DEFAULT_WB_PAYLOADS[indicator_code] - ) + return MockResponse(json_data=_DEFAULT_WB_PAYLOADS[indicator_code]) if "rplumber.ilo.org" in url: return MockResponse( text="time,obs_value\n2024,38.209\n2023,38.0\n" From c75f43c7b34d22f4399fba5a7fba8f102db3e50b Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Sat, 18 Apr 2026 22:02:32 -0400 Subject: [PATCH 5/9] Calibrate Ethiopia alpha_T from PSNP/UPSNP cash transfers; drop IMF data path --- ogeth/macro_params.py | 183 ++------------------------- tests/test_macro_params.py | 250 ++----------------------------------- 2 files changed, 16 insertions(+), 417 deletions(-) diff --git a/ogeth/macro_params.py b/ogeth/macro_params.py index 38539e8..43e648a 100644 --- a/ogeth/macro_params.py +++ b/ogeth/macro_params.py @@ -1,7 +1,6 @@ """ -This module uses data from World Bank WDI, World Bank Quarterly Public -Sector Debt (QPSD) database, the IMF, and UN ILO to find values for -parameters for the OG-ETH model that rely on macro data for calibration. +This module uses World Bank WDI, UN ILO, and documented fiscal-source +values to update OG-ETH macro calibration parameters. """ # imports @@ -9,7 +8,6 @@ import requests import datetime from io import StringIO -from pathlib import Path def _fetch_wb_data(indicators, country_iso, start_year, end_year, source): @@ -89,11 +87,6 @@ def _fetch_wb_data(indicators, country_iso, start_year, end_year, source): return data -# IMF GFS coverage differs by country. For Ethiopia, the percent-of-GDP -# Statement of Operations series used for alpha_T and alpha_G are published -# under budgetary central government (S1311B), not the broader S1311 sector. -IMF_GFS_SECTOR_BY_COUNTRY = {"ETH": "S1311B"} - # For Ethiopia, alpha_G is calibrated from general government final # consumption expenditure because that better matches the OG-ETH concept of # government spending on goods, services, and public goods than the available @@ -107,13 +100,6 @@ def _fetch_wb_data(indicators, country_iso, start_year, end_year, source): GDP_GROWTH_START_YEAR_BY_COUNTRY = {"ETH": 2006} -def _get_imf_gfs_sector(country_iso): - """ - Return the IMF GFS sector code to use for a country's alpha queries. - """ - return IMF_GFS_SECTOR_BY_COUNTRY.get(country_iso.upper(), "S1311") - - def _get_world_bank_alpha_g(wb_data, country_iso, target_year): """ Return a country-specific alpha_G from World Bank data when the spending @@ -180,140 +166,11 @@ def _get_world_bank_g_y_annual(wb_data, country_iso, data_start_date): return g_y_series.mean() if not g_y_series.isna().all() else None -def _get_imf_macro_params(country_iso, target_year, data_path=None): - """ - Fetch IMF GFS data and compute alpha_T and alpha_G. - - Args: - country_iso (str): ISO alpha-3 country code - target_year (int): preferred calibration year - data_path (str | Path | None): optional path to save IMF CSV data - - Returns: - dict: IMF-derived macro parameters - """ - sector = _get_imf_gfs_sector(country_iso) - required_indicators = {"G2_T", "G24_T", "G27_T", "G271_T"} - data_path = Path(data_path) if data_path is not None else None - - # Request the IMF SDMX 3.0 payload for the country/sector slice that - # contains the four GFS indicators needed for alpha_T and alpha_G. - response = requests.get( - ( - "https://api.imf.org/external/sdmx/3.0/data/dataflow/" - f"IMF.STA/GFS_SOO/12.0.0/{country_iso}.{sector}.G2M.*.POGDP_PT.A" - ), - timeout=30, - ) - response.raise_for_status() - try: - payload = response.json() - data = payload["data"] - structure = data["structures"][0] - data_set = data["dataSets"][0] - series_dimensions = structure["dimensions"]["series"] - observation_years = [ - value.get("id", value.get("value")) - for value in structure["dimensions"]["observation"][0]["values"] - ] - except (ValueError, KeyError, IndexError, TypeError) as exc: - raise ValueError( - "Empty or malformed IMF response for GFS_SOO" - ) from exc - - # Flatten the SDMX series/observation structure into one row per - # indicator-year observation so the completeness checks below are simple. - records = [] - for series_key, series in data_set["series"].items(): - dimension_indexes = [int(idx) for idx in series_key.split(":")] - labels = { - dim["id"]: dim["values"][idx].get( - "id", dim["values"][idx].get("value") - ) - for dim, idx in zip(series_dimensions, dimension_indexes) - } - indicator = labels.get("INDICATOR") - if indicator not in required_indicators: - continue - for observation_key, observation in series.get( - "observations", {} - ).items(): - value = observation[0] - records.append( - { - "year": observation_years[int(observation_key)], - "indicator": indicator, - "value": value, - "country_iso": country_iso, - "sector": sector, - "dataset": "IMF.STA:GFS_SOO(12.0.0)", - } - ) - - imf_data = pd.DataFrame(records) - if imf_data.empty: - raise ValueError("Empty or malformed IMF response for GFS_SOO") - - imf_data["year"] = pd.to_numeric(imf_data["year"], errors="coerce") - imf_data["value"] = pd.to_numeric(imf_data["value"], errors="coerce") - imf_data = imf_data.dropna(subset=["year", "value"]) - - if data_path is not None: - data_path.parent.mkdir(parents=True, exist_ok=True) - imf_data.sort_values(["indicator", "year"]).to_csv( - data_path, index=False - ) - print(f"IMF data saved to {data_path}") - - # We only use a year when all four indicators are available. If the target - # year is incomplete, fall back to the latest complete year at or before it. - available = ( - imf_data.pivot_table( - index="year", - columns="indicator", - values="value", - aggfunc="first", - ) - .sort_index() - .dropna(subset=sorted(required_indicators)) - ) - available = available.loc[available.index <= int(target_year)] - - if available.empty: - raise ValueError( - "No complete IMF data available for " - f"{country_iso} sector {sector} up to {target_year}" - ) - - selected_year = ( - int(target_year) - if int(target_year) in available.index - else int(available.index.max()) - ) - - values = available.loc[selected_year] - # Map the selected IMF GFS observations into the OG-ETH transfer and - # government-spending concepts. - alpha_T = (values["G27_T"] - values["G271_T"]) / 100 - alpha_G = (values["G2_T"] - values["G24_T"] - values["G27_T"]) / 100 - - if selected_year != int(target_year): - print( - f"No complete IMF GFS data for {country_iso} in {target_year}. " - f"Using last available year {selected_year}: " - f"alpha_T={alpha_T:.4f}, alpha_G={alpha_G:.4f}" - ) - - return {"alpha_T": [alpha_T], "alpha_G": [alpha_G]} - - def get_macro_params( data_start_date=datetime.datetime(1947, 1, 1), data_end_date=datetime.datetime(2024, 12, 31), country_iso="ETH", update_from_api=False, - imf_data_year=None, - imf_data_path=None, ): """ Compute values of parameters that are derived from macro data @@ -322,9 +179,6 @@ def get_macro_params( data_start_date (datetime): start date for data data_end_date (datetime): end date for data country_iso (str): ISO code for country - imf_data_year (int | None): IMF target year override. Defaults to - data_end_date.year when None. - imf_data_path (str | Path | None): optional path to save IMF CSV data Returns: macro_parameters (dict): dictionary of parameter values @@ -441,30 +295,16 @@ def get_macro_params( print("Not updating from ILOSTAT API") """ - Calibrate parameters from IMF and other sources + Calibrate parameters from documented fiscal sources """ if update_from_api: - imf_year = ( - data_end_date.year if imf_data_year is None else imf_data_year - ) - imf_macro_parameters = None - try: - imf_macro_parameters = _get_imf_macro_params( - country_iso, - imf_year, - data_path=imf_data_path, - ) - except Exception: - print("Failed to retrieve data from IMF") - - if imf_macro_parameters is not None: - macro_parameters["alpha_T"] = imf_macro_parameters["alpha_T"] - print( - f"alpha_T updated from IMF data: {macro_parameters['alpha_T']}" - ) - else: - print("Will not update alpha_T") + # Ethiopia's alpha_T is calibrated from FY2024/25 government + # PSNP/UPSNP cash transfers, not from IMF GFS social-benefits + # series. Source: IMF Country Report 25/188: + # 51.4 / 14,856 ~= 0.0035, with World Bank DPO P181591 as a + # consistent ~0.4% of GDP cross-check. + print("Not updating alpha_T from API for Ethiopia") if country_iso.upper() in WB_ALPHA_G_BY_COUNTRY: if wb_alpha_g is not None: @@ -475,11 +315,6 @@ def get_macro_params( ) else: print("Will not update alpha_G") - elif imf_macro_parameters is not None: - macro_parameters["alpha_G"] = imf_macro_parameters["alpha_G"] - print( - f"alpha_G updated from IMF data: {macro_parameters['alpha_G']}" - ) else: print("Will not update alpha_G") diff --git a/tests/test_macro_params.py b/tests/test_macro_params.py index 6f591b9..32223b7 100644 --- a/tests/test_macro_params.py +++ b/tests/test_macro_params.py @@ -2,9 +2,6 @@ Tests of macro_params.py module """ -import datetime - -import pandas as pd import pytest import requests @@ -33,99 +30,6 @@ def raise_for_status(self): ) -def _imf_payload(indicator_year_values, country="ETH", sector="S1311B"): - years = sorted( - { - int(year) - for observations in indicator_year_values.values() - for year in observations.keys() - } - ) - indicators = list(indicator_year_values.keys()) - return { - "meta": {}, - "data": { - "dataSets": [ - { - "structure": 0, - "action": "Replace", - "series": { - f"0:0:0:{indicator_idx}:0:0": { - "attributes": [0, None, 0], - "observations": { - str(years.index(int(year))): [value] - for year, value in observations.items() - }, - } - for indicator_idx, observations in enumerate( - indicator_year_values.values() - ) - }, - } - ], - "structures": [ - { - "dimensions": { - "series": [ - {"id": "COUNTRY", "values": [{"id": country}]}, - {"id": "SECTOR", "values": [{"id": sector}]}, - {"id": "GFS_GRP", "values": [{"id": "G2M"}]}, - { - "id": "INDICATOR", - "values": [ - {"id": indicator} - for indicator in indicators - ], - }, - { - "id": "TYPE_OF_TRANSFORMATION", - "values": [{"id": "POGDP_PT"}], - }, - {"id": "FREQUENCY", "values": [{"id": "A"}]}, - ], - "observation": [ - { - "id": "TIME_PERIOD", - "values": [ - {"value": str(year)} for year in years - ], - } - ], - }, - "attributes": { - "series": [ - { - "id": "SCALE", - "values": [{"id": "0"}], - }, - {"id": "DECIMALS_DISPLAYED", "values": []}, - {"id": "OVERLAP", "values": [{"id": "OL"}]}, - ], - "observation": [ - {"id": "PRECISION", "values": []}, - { - "id": "DERIVATION_TYPE", - "values": [{"id": "O"}], - }, - {"id": "STATUS", "values": []}, - {"id": "NATURE_OF_DATA", "values": []}, - { - "id": "BASES_OF_RECORDING_CASH_NON_CASH", - "values": [], - }, - { - "id": "BASES_OF_RECORDING_GROSS_NET", - "values": [{"id": "NP"}], - }, - {"id": "VALUATION", "values": [{"id": "_Z"}]}, - ], - }, - } - ], - }, - } - - def _wb_payload(observations): return [ { @@ -160,7 +64,6 @@ def _mock_requests_get( requested_urls, *, ilo_text=None, - imf_json=None, wb_payloads=None, ): payloads = _DEFAULT_WB_PAYLOADS if wb_payloads is None else wb_payloads @@ -174,24 +77,6 @@ def fake_get(url, params=None, headers=None, timeout=None): return MockResponse( text=ilo_text or "time,obs_value\n2024,38.209\n2023,38.0\n" ) - if "api.imf.org" in url: - return MockResponse( - json_data=imf_json - or _imf_payload( - { - "G2_T": { - 2023: 6.121621434173129, - 2024: 5.117707506327274, - }, - "G24_T": { - 2023: 0.5526694185117545, - 2024: 0.5920776865725198, - }, - "G27_T": {2023: 0.0, 2024: 0.0}, - "G271_T": {2023: 0.0, 2024: 0.0}, - } - ) - ) raise AssertionError(f"Unexpected URL requested in test: {url}") monkeypatch.setattr(macro_params.requests, "get", fake_get) @@ -213,7 +98,6 @@ def test_get_macro_params_update_from_api_true(monkeypatch): assert isinstance(test_dict, dict) assert sorted(test_dict.keys()) == sorted( [ - "alpha_T", "alpha_G", "initial_debt_ratio", "g_y_annual", @@ -227,136 +111,15 @@ def test_get_macro_params_update_from_api_true(monkeypatch): assert test_dict["zeta_D"] == [0.12] assert test_dict["g_y_annual"] == pytest.approx(0.25) assert test_dict["gamma"] == [pytest.approx(0.61791)] - assert test_dict["alpha_T"] == [pytest.approx(0.0)] - assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] - assert any( - ".ETH.S1311B.G2M." in url or "/ETH.S1311B.G2M." in url - for url in requested_urls - ) - - -def test_get_imf_macro_params_uses_eth_budgetary_sector(monkeypatch): - requested_urls = [] - _mock_requests_get(monkeypatch, requested_urls) - - result = macro_params._get_imf_macro_params("ETH", 2024) - - assert result["alpha_T"] == [pytest.approx(0.0)] - assert result["alpha_G"] == [pytest.approx(0.04525629819754754)] - assert any("/ETH.S1311B.G2M.*.POGDP_PT.A" in url for url in requested_urls) - - -def test_get_imf_macro_params_overwrites_saved_file(monkeypatch, tmp_path): - requested_urls = [] - _mock_requests_get(monkeypatch, requested_urls) - - data_file = tmp_path / "imf_gfs_soo_eth_s1311b_g2m_pogdp_pt_a.csv" - result = macro_params._get_imf_macro_params( - "ETH", 2024, data_path=data_file - ) - - assert result["alpha_G"] == [pytest.approx(0.04525629819754754)] - assert data_file.exists() - - requested_urls.clear() - _mock_requests_get( - monkeypatch, - requested_urls, - imf_json=_imf_payload( - { - "G2_T": {2024: 5.0}, - "G24_T": {2024: 0.5}, - "G27_T": {2024: 0.0}, - "G271_T": {2024: 0.0}, - } - ), - ) - - refreshed = macro_params._get_imf_macro_params( - "ETH", 2024, data_path=data_file - ) - - assert refreshed != result - saved_data = pd.read_csv(data_file) - saved_2024 = saved_data[saved_data["year"] == 2024].set_index("indicator") - assert saved_2024.loc["G2_T", "value"] == pytest.approx(5.0) - assert saved_2024.loc["G24_T", "value"] == pytest.approx(0.5) - - -def test_get_imf_macro_params_falls_back_to_last_available_year(monkeypatch): - requested_urls = [] - _mock_requests_get( - monkeypatch, - requested_urls, - imf_json=_imf_payload( - { - "G2_T": {2024: 5.117707506327274}, - "G24_T": {2024: 0.5920776865725198}, - "G27_T": {2024: 0.0}, - "G271_T": {2024: 0.0}, - } - ), - ) - - result = macro_params._get_imf_macro_params("ETH", 2025) - - assert result["alpha_T"] == [pytest.approx(0.0)] - assert result["alpha_G"] == [pytest.approx(0.04525629819754754)] - - -def test_get_macro_params_passes_imf_year_override(monkeypatch): - requested_urls = [] - _mock_requests_get( - monkeypatch, - requested_urls, - imf_json=_imf_payload( - { - "G2_T": {2023: 6.121621434173129}, - "G24_T": {2023: 0.5526694185117545}, - "G27_T": {2023: 0.0}, - "G271_T": {2023: 0.0}, - } - ), - ) - - test_dict = macro_params.get_macro_params( - update_from_api=True, - imf_data_year=2023, - data_end_date=datetime.datetime(2024, 12, 31), - ) - - assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] - - -def test_eth_alpha_g_updates_when_imf_fails(monkeypatch): - requested_urls = [] - - def fake_get(url, params=None, headers=None, timeout=None): - requested_urls.append(url) - if "worldbank.org" in url: - indicator_code = url.rstrip("/").split("/")[-1] - return MockResponse(json_data=_DEFAULT_WB_PAYLOADS[indicator_code]) - if "rplumber.ilo.org" in url: - return MockResponse( - text="time,obs_value\n2024,38.209\n2023,38.0\n" - ) - if "api.imf.org" in url: - raise requests.HTTPError("mock IMF failure") - raise AssertionError(f"Unexpected URL requested in test: {url}") - - monkeypatch.setattr(macro_params.requests, "get", fake_get) - - test_dict = macro_params.get_macro_params(update_from_api=True) - assert "alpha_T" not in test_dict assert test_dict["alpha_G"] == [pytest.approx(0.05515691)] + assert not any("api.imf.org" in url for url in requested_urls) -def test_eth_alpha_t_updates_without_world_bank_alpha_g(monkeypatch): - # Simulate the World Bank alpha_G series returning no observations - # while the GDP-per-capita series is complete. The outer try/except - # in get_macro_params swallows the resulting fetch error, so alpha_T - # still updates from IMF but alpha_G is not set. +def test_alpha_g_omitted_when_world_bank_returns_empty(monkeypatch): + # Simulate the World Bank alpha_G series returning no observations. + # Ethiopia does not update alpha_T from any API source, so neither + # alpha_T nor alpha_G should end up in the returned dict. requested_urls = [] _mock_requests_get( monkeypatch, @@ -374,5 +137,6 @@ def test_eth_alpha_t_updates_without_world_bank_alpha_g(monkeypatch): test_dict = macro_params.get_macro_params(update_from_api=True) - assert test_dict["alpha_T"] == [pytest.approx(0.0)] + assert "alpha_T" not in test_dict assert "alpha_G" not in test_dict + assert not any("api.imf.org" in url for url in requested_urls) From a0525bf283651245cc0e23fa03f53d19d449b4e5 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Sat, 18 Apr 2026 22:03:16 -0400 Subject: [PATCH 6/9] Flatten country-keyed dicts to flat constants for single-country package --- ogeth/macro_params.py | 53 ++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/ogeth/macro_params.py b/ogeth/macro_params.py index 43e648a..31e5411 100644 --- a/ogeth/macro_params.py +++ b/ogeth/macro_params.py @@ -87,26 +87,24 @@ def _fetch_wb_data(indicators, country_iso, start_year, end_year, source): return data -# For Ethiopia, alpha_G is calibrated from general government final +# Ethiopia calibrates alpha_G from World Bank general government final # consumption expenditure because that better matches the OG-ETH concept of # government spending on goods, services, and public goods than the available # budgetary central government IMF GFS outlay series. -WB_ALPHA_G_BY_COUNTRY = { - "ETH": "General government final consumption expenditure (% of GDP)" -} +WB_ALPHA_G_SERIES = ( + "General government final consumption expenditure (% of GDP)" +) # Ethiopia's default long-run productivity-growth calibration uses the # post-2005 growth regime rather than the full World Bank history. -GDP_GROWTH_START_YEAR_BY_COUNTRY = {"ETH": 2006} +GDP_GROWTH_START_YEAR = 2006 -def _get_world_bank_alpha_g(wb_data, country_iso, target_year): +def _get_world_bank_alpha_g(wb_data, target_year): """ - Return a country-specific alpha_G from World Bank data when the spending - concept aligns better with OG-ETH than the available IMF GFS coverage. + Return alpha_G from World Bank government consumption data. """ - series_name = WB_ALPHA_G_BY_COUNTRY.get(country_iso.upper()) - if series_name is None or series_name not in wb_data.columns: + if WB_ALPHA_G_SERIES not in wb_data.columns: return None if isinstance(wb_data.index, pd.MultiIndex): @@ -115,14 +113,14 @@ def _get_world_bank_alpha_g(wb_data, country_iso, target_year): years = wb_data.index alpha_g_series = pd.Series( - wb_data[series_name].values, + wb_data[WB_ALPHA_G_SERIES].values, index=pd.to_numeric(years, errors="coerce"), ).dropna() alpha_g_series = alpha_g_series[alpha_g_series.index <= int(target_year)] if alpha_g_series.empty: raise ValueError( - "No World Bank government consumption data available for " - f"{country_iso} up to {target_year}" + "No World Bank government consumption data available up to " + f"{target_year}" ) selected_year = ( @@ -133,15 +131,15 @@ def _get_world_bank_alpha_g(wb_data, country_iso, target_year): value = alpha_g_series.loc[selected_year] / 100 if selected_year != int(target_year): print( - f"No World Bank alpha_G data for {country_iso} in {target_year}. " - f"Using last available year {selected_year}: alpha_G={value:.4f}" + f"No World Bank alpha_G data in {target_year}. Using last " + f"available year {selected_year}: alpha_G={value:.4f}" ) return [value] -def _get_world_bank_g_y_annual(wb_data, country_iso, data_start_date): +def _get_world_bank_g_y_annual(wb_data, data_start_date): """ - Compute average GDP-per-capita growth using the country-specific + Compute average GDP-per-capita growth using the Ethiopia-specific calibration window rather than the full available history. """ series_name = "GDP per capita (constant 2015 US$)" @@ -155,7 +153,7 @@ def _get_world_bank_g_y_annual(wb_data, country_iso, data_start_date): growth_start_year = max( int(data_start_date.year), - GDP_GROWTH_START_YEAR_BY_COUNTRY.get(country_iso.upper(), 0), + GDP_GROWTH_START_YEAR, ) gdp_pc_series = pd.Series( wb_data[series_name].values, @@ -214,7 +212,7 @@ def get_macro_params( # Compute annual GDP growth safely if "GDP per capita (constant 2015 US$)" in wb_data_a.columns: macro_parameters["g_y_annual"] = _get_world_bank_g_y_annual( - wb_data_a, country_iso, data_start_date + wb_data_a, data_start_date ) else: print( @@ -226,7 +224,7 @@ def get_macro_params( ) try: wb_alpha_g = _get_world_bank_alpha_g( - wb_data_a, country_iso, data_end_date.year + wb_data_a, data_end_date.year ) except ValueError: wb_alpha_g = None @@ -306,15 +304,12 @@ def get_macro_params( # consistent ~0.4% of GDP cross-check. print("Not updating alpha_T from API for Ethiopia") - if country_iso.upper() in WB_ALPHA_G_BY_COUNTRY: - if wb_alpha_g is not None: - macro_parameters["alpha_G"] = wb_alpha_g - print( - "alpha_G updated from World Bank government consumption " - f"data: {macro_parameters['alpha_G']}" - ) - else: - print("Will not update alpha_G") + if wb_alpha_g is not None: + macro_parameters["alpha_G"] = wb_alpha_g + print( + "alpha_G updated from World Bank government consumption " + f"data: {macro_parameters['alpha_G']}" + ) else: print("Will not update alpha_G") From 3cd6834895d26d1038c00d8e0b0f34ea8241558b Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Sat, 18 Apr 2026 22:04:10 -0400 Subject: [PATCH 7/9] Isolate alpha_G fetch from GDP-growth fetch in World Bank API calls --- ogeth/macro_params.py | 35 +++++++++++++++++++---------------- tests/test_macro_params.py | 7 ++++--- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/ogeth/macro_params.py b/ogeth/macro_params.py index 31e5411..0620250 100644 --- a/ogeth/macro_params.py +++ b/ogeth/macro_params.py @@ -189,9 +189,8 @@ def get_macro_params( """ # Dictionaries of variables and their corresponding World Bank codes # Annual data - wb_a_variable_dict = { + wb_growth_variable_dict = { "GDP per capita (constant 2015 US$)": "NY.GDP.PCAP.KD", - "General government final consumption expenditure (% of GDP)": "NE.CON.GOVT.ZS", # "Real GDP (constant 2015 US$)": "NY.GDP.MKTP.KD", # "Nominal GDP (current US$)": "NY.GDP.MKTP.CD", # "General government final consumption expenditure (current US$)": "NE.CON.GOVT.CD", @@ -200,9 +199,8 @@ def get_macro_params( wb_alpha_g = None if update_from_api: try: - # Pull annual series from the World Bank v2 API - wb_data_a = _fetch_wb_data( - wb_a_variable_dict, + wb_growth_data = _fetch_wb_data( + wb_growth_variable_dict, country_iso, data_start_date.year, data_end_date.year, @@ -210,9 +208,9 @@ def get_macro_params( ) # Compute annual GDP growth safely - if "GDP per capita (constant 2015 US$)" in wb_data_a.columns: + if "GDP per capita (constant 2015 US$)" in wb_growth_data.columns: macro_parameters["g_y_annual"] = _get_world_bank_g_y_annual( - wb_data_a, data_start_date + wb_growth_data, data_start_date ) else: print( @@ -222,18 +220,23 @@ def get_macro_params( print( f"g_y_annual updated from World Bank API: {macro_parameters['g_y_annual']}" ) - try: - wb_alpha_g = _get_world_bank_alpha_g( - wb_data_a, data_end_date.year - ) - except ValueError: - wb_alpha_g = None except Exception: print("Failed to retrieve data from World Bank") - print("Will not update the following parameters:") - print( - "[initial_debt_ratio, initial_foreign_debt_ratio, zeta_D, g_y]" + print("Will not update g_y_annual") + + try: + wb_alpha_g_data = _fetch_wb_data( + {WB_ALPHA_G_SERIES: "NE.CON.GOVT.ZS"}, + country_iso, + data_start_date.year, + data_end_date.year, + source=2, ) + wb_alpha_g = _get_world_bank_alpha_g( + wb_alpha_g_data, data_end_date.year + ) + except Exception: + wb_alpha_g = None else: print("Not updating from World Bank API") diff --git a/tests/test_macro_params.py b/tests/test_macro_params.py index 32223b7..0649fda 100644 --- a/tests/test_macro_params.py +++ b/tests/test_macro_params.py @@ -117,9 +117,9 @@ def test_get_macro_params_update_from_api_true(monkeypatch): def test_alpha_g_omitted_when_world_bank_returns_empty(monkeypatch): - # Simulate the World Bank alpha_G series returning no observations. - # Ethiopia does not update alpha_T from any API source, so neither - # alpha_T nor alpha_G should end up in the returned dict. + # Simulate the World Bank alpha_G series returning no observations + # while the GDP-per-capita series is complete. The split fetches keep + # g_y_annual intact; alpha_T is not sourced from any API for Ethiopia. requested_urls = [] _mock_requests_get( monkeypatch, @@ -139,4 +139,5 @@ def test_alpha_g_omitted_when_world_bank_returns_empty(monkeypatch): assert "alpha_T" not in test_dict assert "alpha_G" not in test_dict + assert test_dict["g_y_annual"] == pytest.approx(0.25) assert not any("api.imf.org" in url for url in requested_urls) From b4212e2f570a38198143e9a8261fb2e54b36e69a Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Sat, 18 Apr 2026 22:36:19 -0400 Subject: [PATCH 8/9] Format code with black 26.3.0 --- docs/create_doc_figures.py | 1 - ogeth/input_output.py | 1 - tests/test_input_output.py | 1 - 3 files changed, 3 deletions(-) diff --git a/docs/create_doc_figures.py b/docs/create_doc_figures.py index 4195508..42efad0 100644 --- a/docs/create_doc_figures.py +++ b/docs/create_doc_figures.py @@ -11,7 +11,6 @@ from ogcore import parameter_plots as pp from ogcore import demographics as demog - CUR_DIR = os.path.dirname(os.path.realpath(__file__)) UN_COUNTRY_CODE = "231" plot_path = os.path.join(CUR_DIR, "book", "content", "calibration", "images") diff --git a/ogeth/input_output.py b/ogeth/input_output.py index d30b78f..5748c53 100644 --- a/ogeth/input_output.py +++ b/ogeth/input_output.py @@ -3,7 +3,6 @@ import os from ogeth.constants import CONS_DICT, PROD_DICT - CUR_DIR = os.path.dirname(os.path.realpath(__file__)) sam_path = os.path.join(CUR_DIR, "data", "IFPRI_SAM_ETH_2022_SAM.csv") diff --git a/tests/test_input_output.py b/tests/test_input_output.py index eac3782..c180ebd 100644 --- a/tests/test_input_output.py +++ b/tests/test_input_output.py @@ -9,7 +9,6 @@ from ogeth import input_output as io - HH_COLS = [ "hhd-r1", "hhd-r2", From a3804ce89b1fdf2c3536efdac6d9bceade1d7b9b Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Mon, 20 Apr 2026 20:30:35 -0400 Subject: [PATCH 9/9] Round r_gov defaults and remove statsmodels --- docs/book/content/calibration/macro.md | 3 +++ environment.yml | 1 - ogeth/ogeth_default_parameters.json | 4 ++-- setup.py | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/book/content/calibration/macro.md b/docs/book/content/calibration/macro.md index 8ebee31..c3fd9d2 100644 --- a/docs/book/content/calibration/macro.md +++ b/docs/book/content/calibration/macro.md @@ -57,6 +57,9 @@ print(r_gov_shift) # -0.03376625043803517 print(r_gov_scale) # 0.24484763593657818 ``` +We store these defaults rounded to five decimal places in the packaged JSON: +`r_gov_shift = -0.03377` and `r_gov_scale = 0.24485`. + ### Aggregate transfers Aggregate (non-Social Security) transfers to households are set as a share of GDP with the parameter $\alpha_T$. We exclude Social Security from transfers since it is modeled specifically. In OG-ETH, the relevant concept is government-financed, non-pension transfers paid to households. For Ethiopia, the IMF GFS `S1311B` social-benefits series (`G27_T` and `G271_T`) do not line up well with that concept in the calibration year because they miss the main FY2024/25 government cash contributions to the rural PSNP and urban UPSNP. diff --git a/environment.yml b/environment.yml index 12dfdf5..17d890c 100644 --- a/environment.yml +++ b/environment.yml @@ -27,7 +27,6 @@ dependencies: - fsspec - aiohttp - xlwt -- statsmodels - linearmodels - black - jupyter diff --git a/ogeth/ogeth_default_parameters.json b/ogeth/ogeth_default_parameters.json index 3b05417..a2f3797 100644 --- a/ogeth/ogeth_default_parameters.json +++ b/ogeth/ogeth_default_parameters.json @@ -1214,10 +1214,10 @@ "initial_debt_ratio": 0.327, "initial_Kg_ratio": 0.0, "r_gov_scale": [ - 0.24484763593657818 + 0.24485 ], "r_gov_shift": [ - -0.03376625043803517 + -0.03377 ], "cit_rate": [ [ diff --git a/setup.py b/setup.py index 7629f31..8943313 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ "requests", "xlwt", "openpyxl>=3.1.2", - "statsmodels", "linearmodels", "wheel", "black",