module IMS using Agents using Pipe using Random using StatsBase using Distributions using Combinatorics include("model/params.jl") include("model/structs.jl") include("model/init.jl") include("model/utils.jl") include("model/SFC/gov.jl") include("model/SFC/cb.jl") include("model/SFC/hh.jl") include("model/SFC/firms.jl") include("model/SFC/banks.jl") """ model_step!(model) → model Stepping function of the model: defines what happens during each simulation step. """ function model_step!(model) model.step += 1 #begin: apply shocks IMS.shocks!(model) #end: apply shocks IMS.update_vars!(model) for id in ids_by_type(Bank, model) IMS.prev_vars!(model[id]) IMS.credit_rates!(model[id], model.χ1) IMS.reset_vars!(model[id], model.scenario) end for id in ids_by_type(Firm, model) IMS.prev_vars!(model[id]) IMS.interests_payments!(model[id], model) IMS.deposits!(model[id], model) IMS.prices!(model[id], model.ρ) IMS.investments!(model[id], model.gk) IMS.capital!(model[id], model.δ) IMS.output!(model[id], model.β, model.ϕ, model.σ) IMS.wages!(model[id], model) IMS.unit_costs!(model[id]) end IMS.consumption_matching!(model) for id in ids_by_type(Household, model) IMS.prev_vars!(model[id]) IMS.interests_payments!(model[id], model) IMS.expected_income!(model[id], model) IMS.consumption!(model[id], model) IMS.taxes!(model[id], model.τ) end IMS.hhs_matching!(model) for id in ids_by_type(Household, model) IMS.loans!(model[id], model) IMS.non_performing_loans!(model[id], model) end for id in ids_by_type(Government, model) IMS.spending!(model[id], model) IMS.taxes!(model[id], model) end IMS.firms_matching!(model) spending = sum(a.spending for a in allagents(model) if a isa Government) / model.n_f for id in ids_by_type(Firm, model) IMS.consumption!(model[id], model) IMS.sales!(model[id], model) IMS.rationing!(model[id], model) IMS.inventories!(model[id]) IMS.profits!(model[id], spending) IMS.loans!(model[id], model) IMS.non_performing_loans!(model[id], model) IMS.current_balance!(model[id], spending) IMS.SFC!(model[id], model) end for id in ids_by_type(Bank, model) IMS.interests_payments!(model[id], model) IMS.profits!(model[id]) IMS.current_balance!(model[id]) IMS.portfolio!(model[id]) end profits = sum(a.profits for a in allagents(model) if a isa Bank || a isa Firm) / model.n_hh for id in ids_by_type(Household, model) IMS.income!(model[id], profits) IMS.SFC!(model[id], model) end for id in ids_by_type(CentralBank, model) IMS.prev_vars!(model[id]) IMS.profits!(model[id], model) IMS.current_balance!(model[id], model) end # begin: Interbank Market IMS.update_willingenss_ON!(model) for id in ids_by_type(Bank, model) IMS.update_status!(model[id]) if model.scenario == "Maturity" IMS.NSFR!(model[id], model) IMS.borrowing_targets!(model[id], model.rng, model.arbitrary_threshold) IMS.lending_targets!(model[id], model.rng, model.arbitrary_threshold) end IMS.reset_after_status!(model[id]) IMS.tot_demand!(model[id]) IMS.tot_supply!(model[id]) end IMS.ib_matching!(model) for id in ids_by_type(Bank, model) IMS.on_demand!(model[id], model) IMS.term_demand!(model[id], model) if model.scenario == "Maturity" IMS.on_supply!(model[id], model.LbW) IMS.term_supply!(model[id]) end IMS.ib_on!(model[id], model) IMS.ib_term!(model[id], model) end # do other stuff for id in ids_by_type(Bank, model) model.scenario == "Baseline" && IMS.restore_total_supply!(model[id], model) IMS.lending_facility!(model[id]) IMS.deposit_facility!(model[id]) IMS.funding_costs!(model[id], model.icbt, model.ion, model.iterm, model.icbl) IMS.bonds!(model[id]) end for id in ids_by_type(Bank, model) IMS.check_ib_stocks!(model[id], model) IMS.restore_supply!(model[id]) end IMS.ib_rates!(model) model.scenario == "Maturity" && IMS.check_clearing!(model) # end: Interbank Market for id in ids_by_type(Government, model) IMS.SFC!(model[id], model) end for id in ids_by_type(Bank, model) IMS.SFC!(model[id], model) end for id in ids_by_type(CentralBank, model) IMS.SFC!(model[id], model) end # SFC checks IMS.SFC_checks!(model) return model end """ update_willingenss_ON!(model) → model.θ, model.LbW Updates money market conditions based on interest rates. """ function update_willingenss_ON!(model) model.θ = max(0.0, min(model.a0 + (model.icbl - model.ion_prev) + (model.iterm_prev - model.ion_prev) - model.PDU, 1.0)) model.LbW = max(0.0, min(model.a0 + (model.ion_prev - model.icbd) - (model.iterm_prev - model.ion_prev) + model.PDU, 1.0)) return model.θ, model.LbW end """ consumption_matching!(model) → model Updates firms' and households' matching in the goods market. """ function consumption_matching!(model) for id in ids_by_type(Household, model) #Select potential partners potential_partners = filter(i -> model[i] isa Firm && i != model[id].belongToFirm, collect(allids(model)))[1:model.χ] #Select new partner with the best price new_partner = rand(model.rng, filter(i -> i in potential_partners && model[i].prices == minimum(model[a].prices for a in potential_partners), potential_partners)) #Select price of the new potential partner inew = model[new_partner].prices #Pick up old partner old_partner = model[id].belongToFirm #PICK UP THE PRICE OF THE OLD PARTNER iold = model[old_partner].prices #COMPARE OLD AND NEW PRICES if rand(model.rng) < (1 - exp(model.λ * (inew - iold)/inew)) #THEN SWITCH TO A NEW PARTNER deleteat!(model[old_partner].customers, findall(x -> x == id, model[old_partner].customers)) model[id].belongToFirm = new_partner push!(model[new_partner].customers, id) end end return model end """ firms_matching!(model) → model Updates firms' matching in the credit market. """ function firms_matching!(model) for id in ids_by_type(Firm, model) # Select potential partners potential_partners = filter(i -> model[i] isa Bank && i != model[id].belongToBank && model[i].type == :business, collect(allids(model)))[1:model.χ] # Select new partner with the best interest rate among potential partners new_partner = rand(model.rng, filter(i -> i in potential_partners && model[i].il_rate == minimum(model[a].il_rate for a in potential_partners), potential_partners)) # Select interest rate of the new potential partner inew = model[new_partner].il_rate # Pick up old partner old_partner = model[id].belongToBank # PICK UP THE INTEREST OF THE OLD PARTNER iold = model[old_partner].il_rate # COMPARE OLD AND NEW INTERESTS if rand(model.rng) < (1 - exp(model.λ * (inew - iold)/inew)) # THEN SWITCH TO A NEW PARTNER deleteat!(model[old_partner].firms_customers, findall(x -> x == id, model[old_partner].firms_customers)) model[id].belongToBank = new_partner push!(model[new_partner].firms_customers, id) end end return model end """ hhs_matching!(model) → model Updates households' matching in the credit market. """ function hhs_matching!(model) for id in ids_by_type(Household, model) # Select potential partners potential_partners = filter(i -> model[i] isa Bank && i != model[id].belongToBank && model[i].type == :commercial, collect(allids(model)))[1:model.χ] # Select new partner with the best interest rate among potential partners new_partner = rand(model.rng, filter(i -> i in potential_partners && model[i].il_rate == minimum(model[a].il_rate for a in potential_partners), potential_partners)) # Select interest rate of the new potential partner inew = model[new_partner].il_rate # Pick up old partner old_partner = model[id].belongToBank # PICK UP THE INTEREST OF THE OLD PARTNER iold = model[old_partner].il_rate # COMPARE OLD AND NEW INTERESTS if rand(model.rng) < (1 - exp(model.λ * (inew - iold)/inew)) # THEN SWITCH TO A NEW PARTNER deleteat!(model[old_partner].hh_customers, findall(x -> x == id, model[old_partner].hh_customers)) model[id].belongToBank = new_partner push!(model[new_partner].hh_customers, id) end end return model end """ ib_matching!(model) → model Update borrowing banks' matching. """ function ib_matching!(model) for id in ids_by_type(Bank, model) # end function prematurely if there are no surplus banks available isempty([a.id for a in allagents(model) if a isa Bank && a.status == :surplus]) && return if model[id].status == :deficit # interbank matching depends on the scenario implemented potential_partners = if model.scenario == "Maturity" # Select potential partners with the closest preferences for overnight funds filter(i -> model[i] isa Bank && model[i].status == :surplus && abs((1 - model[i].margin_stability) - model[id].am) <= 1e-01, collect(allids(model))) else # Select potenital partners based only on interbank status and supply amount filter(i -> model[i] isa Bank && model[i].status == :surplus && abs(model[i].tot_supply - model[id].tot_demand) <= 1e-06, collect(allids(model))) end if !isempty(potential_partners) # Select new partner new_partner = rand(model.rng, filter(i -> i in potential_partners, potential_partners)) model[id].belongToBank = new_partner push!(model[new_partner].ib_customers, id) model[id].ib_flag = true model[new_partner].ib_flag = true model.ib_flag = true if model.scenario == "Baseline" # because matching is based on total supply model[new_partner].tot_supply -= model[id].tot_demand end end end end return model end """ ib_rates!(model) → model Update interbank rates on overnight and term segments based on disequilibrium dynamics between demand and supply. The function also checks that interest rates fall within the central bank's corridor, otherwise a warning is issued. """ function ib_rates!(model) # Check if there are interbank exchanges between deficit and surplus banks if model.ib_flag # take the set of matching deficit banks deficit_banks = [id for id in ids_by_type(Bank, model) if model[id].status == :deficit && model[id].ib_flag == true] # take the set of matching surplus banks surplus_banks = [id for id in ids_by_type(Bank, model) if model[id].status == :surplus && model[id].ib_flag == true] # define disequilibrium overnight (ON) interbank market diseq_ON = sum(model[id].on_demand for id in deficit_banks) - sum(model[id].on_supply for id in surplus_banks) # compute interbank rate on the overnight market model.ion = model.icbd + ((model.icbl - model.icbd)/(1 + exp(-model.σib * diseq_ON))) # # define disequilibrium term interbank market diseq_Term = sum(model[id].term_demand for id in deficit_banks) - sum(model[id].term_supply for id in surplus_banks) # compute interbank rate on the term market model.iterm = model.icbd + ((model.icbl - model.icbd)/(1 + exp(-model.σib * diseq_Term))) else # or else interbank rates equal the policy target of the central bank model.ion = model.icbt model.iterm = model.icbt end check_corridor!(model) return model.ion, model.iterm end """ check_corridor!(model) → model Check that interbank rates fall within the corridor, otherwise throw a warning. """ function check_corridor!(model; tol::Float64 = 1e-03) if model.ion - model.icbl > tol || model.icbd - model.ion > tol @warn "Interbank ON rate outside the central bank's corridor at step $(model.step)!" elseif model.iterm - model.icbl > tol || model.icbd - model.iterm > tol @warn "Interbank Term rate outside the central bank's corridor at step $(model.step)!" end return model end """ check_clearing!(model; tol::Float64 = 1e-06) → model Check whether supply and demand clear. If not, a warning is issued. """ function check_clearing!(model; tol::Float64 = 1e-06) for id in ids_by_type(Bank, model) # if banks are actually matched in the interbank market if model[id].ib_flag # supply side if model[id].status == :surplus if model[id].deposit_facility + model[id].ON_assets + model[id].Term_assets - model[id].tot_supply > tol @warn "Supply does not clear at step $(model.step)!" end end # demand side if model[id].status == :deficit if model[id].lending_facility + model[id].ON_liabs + model[id].Term_liabs - model[id].tot_demand > tol @warn "Demand does not clear at step $(model.step)!" end end end end return model end """ update_vars!(model) → model Updates model paramaters: overnight interbank rate `model.ion` and term interbank one `model.iterm`. """ function update_vars!(model) model.ib_flag = false model.ion_prev = model.ion model.iterm_prev = model.iterm return model end """ shocks!(model) → model Defines what happens when shocks are called for: 1) Corridor: corridor central bank's rates (`icbt`, `icbl`, `icbd`) are increased symmetrically by 50 bp every `model.shock_incr` steps; 2) Width: ceiling rate is increasead by 50 bp, altering the width every `model.shock_incr` steps; 3) Uncertainty: the degree of perceived uncertainty (`PDU`) is increased by 0.2 every `model.shock_incr` steps. """ function shocks!(model) # end prematurely if shock setting is "Missing" model.shock == "Missing" && return if model.shock == "Corridor" && iszero(model.step % model.shock_incr) model.icbd += 0.005 model.icbl += 0.005 model.icbt = (model.icbl + model.icbd) / 2.0 elseif model.shock == "Width" && iszero(model.step % model.shock_incr) model.icbl += 0.005 model.icbt = (model.icbl + model.icbd) / 2.0 elseif model.shock == "Uncertainty" && iszero(model.step % model.shock_incr) model.PDU += 0.2 end return model end end # module IMS