Skip to content
32 changes: 22 additions & 10 deletions macro_data/configuration/dataconfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@
initial_inventory_to_input_fraction=0.1
)

banks_config = BanksDataConfiguration(
long_term_firm_loan_maturity=60,
mortgage_maturity=120
)
banks_config = BanksDataConfiguration()

# Create country configuration
france_config = CountryDataConfiguration(
Expand Down Expand Up @@ -54,9 +51,13 @@

from pydantic import BaseModel, Field

from macromodel.configurations.bank_configuration import BankParameters

from .countries import Country
from .region import Region

_MODEL_BANK_PARAMETERS = BankParameters()


class FirmsDataConfiguration(BaseModel):
"""
Expand Down Expand Up @@ -107,18 +108,29 @@ class BanksDataConfiguration(BaseModel):
This class controls how synthetic bank data is generated, including loan
maturities and interest rate settings.

Loan maturities are expressed as model-period counts, not calendar months.

Attributes:
constructor (Literal["Compustat", "Default"]): The data constructor to use
long_term_firm_loan_maturity (int): Maturity period (months) for long-term firm loans
consumption_exp_loan_maturity (int): Maturity period (months) for consumption loans
mortgage_maturity (int): Maturity period (months) for mortgages
long_term_firm_loan_maturity (int): Initial maturity in model periods, not months, for long-term firm loans
consumption_exp_loan_maturity (int): Initial maturity in model periods, not months, for consumption loans
mortgage_maturity (int): Initial maturity in model periods, not months, for mortgages
interest_rates (InterestRates): Interest rate markup configuration
"""

constructor: Literal["Compustat", "Default"] = "Compustat"
long_term_firm_loan_maturity: int = 60
consumption_exp_loan_maturity: int = 12
mortgage_maturity: int = 120
long_term_firm_loan_maturity: int = Field(
_MODEL_BANK_PARAMETERS.long_term_firm_loan_maturity,
description="Initial maturity in model periods, not months, for long-term firm loans.",
)
consumption_exp_loan_maturity: int = Field(
_MODEL_BANK_PARAMETERS.household_consumption_loan_maturity,
description="Initial maturity in model periods, not months, for consumption loans.",
)
mortgage_maturity: int = Field(
_MODEL_BANK_PARAMETERS.mortgage_maturity,
description="Initial maturity in model periods, not months, for mortgages.",
)
interest_rates: InterestRates = InterestRates()


Expand Down
62 changes: 55 additions & 7 deletions macro_data/data_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,44 @@
EmissionsData,
EmissionsEnergyFactors,
)
from macro_data.readers.exogenous_data import ExogenousCountryData
from macro_data.readers.exogenous_data import ExogenousCountryData, convert_growth_rates_to_model_period
from macro_data.readers.io_tables.icio_reader import ICIOReader


def _quarter_start_date(year: int, quarter: int) -> pd.Timestamp:
return pd.Timestamp(year, 3 * (quarter - 1) + 1, 1)


def _period_start_for_date(index: pd.Index, date: pd.Timestamp) -> pd.Timestamp:
index = pd.DatetimeIndex(index).sort_values()
if date in index:
return date

prior_dates = index[index <= date]
if len(prior_dates) > 0:
return prior_dates[-1]

later_dates = index[index > date]
if len(later_dates) > 0:
return later_dates[0]

raise ValueError("Cannot select a period from an empty calibration index.")


def _period_values_at_date(data: pd.DataFrame, date: pd.Timestamp) -> pd.Series:
if date in data.index:
return data.loc[date]

return (
data.reindex(data.index.union(pd.DatetimeIndex([date])))
.sort_index()
.interpolate(method="time")
.ffill()
.bfill()
.loc[date]
)


@dataclass
class DataWrapper:
"""
Expand Down Expand Up @@ -181,6 +215,10 @@ def from_config(

year = configuration.year
quarter = configuration.quarter
# Backward compatibility: legacy configs omitted `time_unit` and implicitly used quarterly scaling.
fields_set = getattr(configuration, "model_fields_set", set())
effective_time_unit = configuration.time_unit if "time_unit" in fields_set else 3
yearly_factor = 12 / effective_time_unit

scale_dict = {country: configuration.country_configs[country].scale for country in country_names}

Expand Down Expand Up @@ -211,6 +249,8 @@ def from_config(
use_disagg_can_2014_reader=configuration.can_disaggregation,
use_provincial_can_reader=use_provincial_can_reader,
regions_dict=regions_dict,
yearly_factor=yearly_factor,
simulation_quarter=quarter,
)

if regions_dict:
Expand Down Expand Up @@ -241,6 +281,7 @@ def from_config(
readers=readers,
country_names=country_names,
single_firm_per_industry=single_firm_dict,
yearly_factor=yearly_factor,
)

year_range = 1 if single_hfcs_survey else 10
Expand All @@ -255,6 +296,7 @@ def from_config(
quarter=quarter,
industry_vectors=industry_data[country]["industry_vectors"],
proxy_country=proxy_country_dict.get(country, None),
time_unit=effective_time_unit,
)
for country in country_names
}
Expand All @@ -269,6 +311,7 @@ def from_config(
inflation = readers.imf_reader.get_inflation(proxy_country)
if inflation is None:
inflation = readers.world_bank.get_inflation(proxy_country)
inflation = convert_growth_rates_to_model_period(inflation, effective_time_unit)
proxy_inflation[country] = inflation
else:
proxy_inflation[country] = None
Expand All @@ -291,6 +334,7 @@ def from_config(
country=country,
year=year,
quarter=quarter,
time_unit=effective_time_unit,
country_configuration=configuration.country_configs[country],
industries=industries,
readers=readers,
Expand All @@ -316,14 +360,15 @@ def from_config(
country=country,
proxy_country=configuration.country_configs[country].eu_proxy_country,
year=year,
quarter=quarter,
time_unit=effective_time_unit,
country_configuration=configuration.country_configs[country],
industries=industries,
readers=readers,
exogenous_country_data=exogenous_data[country],
country_industry_data=industry_data[country],
year_range=year_range,
goods_criticality_matrix=readers.goods_criticality.criticality_matrix,
quarter=quarter,
proxy_inflation_data=proxy_inflation[country],
emission_factors=(
EmissionsData.from_readers(
Expand Down Expand Up @@ -372,7 +417,7 @@ def from_config(
EmissionsEnergyFactors.from_readers(readers.icio[year], country_names) if add_emissions else None
),
aggregation_structure=configuration.aggregation_structure,
time_unit=configuration.time_unit,
time_unit=effective_time_unit,
)

@classmethod
Expand Down Expand Up @@ -423,7 +468,8 @@ def calibration_before(self):
year = self.configuration.year
quarter = self.configuration.quarter
calibration_index = self.calibration_data.index
calibration_before_index = calibration_index[calibration_index < f"{year}-Q{quarter}"]
start_date = _period_start_for_date(calibration_index, _quarter_start_date(year, quarter))
calibration_before_index = calibration_index[calibration_index < start_date]
return self.calibration_data.loc[calibration_before_index]

@property
Expand All @@ -438,7 +484,8 @@ def calibration_during(self):
year = self.configuration.year
quarter = self.configuration.quarter
calibration_index = self.calibration_data.index
calibration_during_index = calibration_index[calibration_index == f"{year}-Q{quarter}"]
start_date = _period_start_for_date(calibration_index, _quarter_start_date(year, quarter))
calibration_during_index = calibration_index[calibration_index == start_date]
return self.calibration_data.loc[calibration_during_index]


Expand Down Expand Up @@ -472,8 +519,9 @@ def add_row_to_calibration(
all_exports = calibration_data.xs("Exports (Value)", axis=1, level=1)
all_imports = calibration_data.xs("Imports (Value)", axis=1, level=1)

scaled_exports = all_exports / all_exports.loc[f"{year}-Q{quarter}"].iloc[0]
scaled_imports = all_imports / all_imports.loc[f"{year}-Q{quarter}"].iloc[0]
base_date = _quarter_start_date(year, quarter)
scaled_exports = all_exports / _period_values_at_date(all_exports, base_date)
scaled_imports = all_imports / _period_values_at_date(all_imports, base_date)

total_country_exports = sum(
[country_scaled_exports(country, industry_data, scaled_exports) for country in countries]
Expand Down
10 changes: 2 additions & 8 deletions macro_data/default_country_conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,11 @@ central_bank_configuration:
0.02

banks_configuration:
long_term_firm_loan_maturity:
60
consumption_exp_loan_maturity:
12
mortgage_maturity:
120

# Loan maturities are expressed in model periods, not calendar months.
interest_rates:
consumption_loans_markup:
0.01
mortgage_markup:
0.1
household_overdraft_markup:
0.01
0.01
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def from_readers(
year_range: int = 10,
regression_window: int = 48,
equity_injection: float = 0.0,
yearly_factor: float = 4.0,
) -> SyntheticCentralGovernment:
"""Create a preprocessed central government data container using standard data sources.

Expand Down Expand Up @@ -129,7 +130,11 @@ def from_readers(
if country_exogenous_data is not None:
# if exogenous data is available, use it to fit the benefits models
benefits_inflation_data = readers.get_benefits_inflation_data(
country_name, year_min=year - year_range, year_max=year, exogenous_data=country_exogenous_data
country_name,
year_min=year - year_range,
year_max=year,
exogenous_data=country_exogenous_data,
yearly_factor=yearly_factor,
)
unemployment_benefits_model = build_unemployment_model(
benefits_inflation_data, regression_window=regression_window
Expand Down Expand Up @@ -160,8 +165,15 @@ def from_readers(
# if exogenous data is not available, set the benefits models to None
unemployment_benefits_model = None
other_benefits_model = None
current_unemployment_benefits = readers.get_total_unemployment_benefits_lcu(country_name, year)
current_other_benefits = readers.get_total_benefits_lcu(country_name, year) - current_unemployment_benefits
current_unemployment_benefits = readers.get_total_unemployment_benefits_lcu(
country_name,
year,
yearly_factor=yearly_factor,
)
current_other_benefits = (
readers.get_total_benefits_lcu(country_name, year, yearly_factor=yearly_factor)
- current_unemployment_benefits
)

# TODO: debt in USD or in local currency?

Expand Down
22 changes: 20 additions & 2 deletions macro_data/processing/synthetic_country.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def eu_synthetic_country(
country: Country,
year: int,
quarter: int,
time_unit: int,
country_configuration: CountryDataConfiguration,
industries: list[str],
readers: DataReaders,
Expand Down Expand Up @@ -215,7 +216,14 @@ def eu_synthetic_country(
This method should only be used for EU member countries. For non-EU
countries, use proxied_synthetic_country instead.
"""
central_government = DefaultSyntheticCGovernment.from_readers(readers, country, year, year_range=year_range)
yearly_factor = 12 / time_unit
central_government = DefaultSyntheticCGovernment.from_readers(
readers,
country,
year,
year_range=year_range,
yearly_factor=yearly_factor,
)

total_unemployment_benefits = central_government.central_gov_data["Total Unemployment Benefits"].values[0]

Expand Down Expand Up @@ -245,6 +253,7 @@ def eu_synthetic_country(
total_unemployment_benefits=total_unemployment_benefits,
country_name_short=country.to_two_letter_code(),
exogenous_data=exogenous_country_data,
yearly_factor=yearly_factor,
)

firms = DefaultSyntheticFirms.from_readers(
Expand Down Expand Up @@ -355,6 +364,7 @@ def proxied_synthetic_country(
proxy_country: Country,
year: int,
quarter: int,
time_unit: int,
country_configuration: CountryDataConfiguration,
industries: list[str],
readers: DataReaders,
Expand Down Expand Up @@ -394,7 +404,14 @@ def proxied_synthetic_country(
Returns:
SyntheticCountry: Initialized synthetic country instance
"""
central_government = DefaultSyntheticCGovernment.from_readers(readers, country, year, year_range=year_range)
yearly_factor = 12 / time_unit
central_government = DefaultSyntheticCGovernment.from_readers(
readers,
country,
year,
year_range=year_range,
yearly_factor=yearly_factor,
)

total_unemployment_benefits = central_government.central_gov_data["Total Unemployment Benefits"].values[0]

Expand Down Expand Up @@ -433,6 +450,7 @@ def proxied_synthetic_country(
proxied_country=country,
quarter=quarter,
exogenous_data=exogenous_country_data,
yearly_factor=yearly_factor,
)

firms = DefaultSyntheticFirms.from_readers(
Expand Down
Loading
Loading