CViM / src / CViM.jl
CViM.jl
Raw
module CViM

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

Define what happens in the model at each step.
"""
function model_step!(model)
    model.step += 1

    #begin: apply shocks
    if model.shock == "Corridor" && iszero(model.step % model.sas)
        model.icbd += 0.005 # 50 basis points
        model.icbl += 0.005
        model.icb = (model.icbl + model.icbd) / 2.0
    elseif model.shock == "Corridor-unique" && model.step == model.sas
        model.icbd += 0.005 # 50 basis points
        model.icbl += 0.005
        model.icb = (model.icbl + model.icbd) / 2.0
    elseif model.shock == "Expansionary" && model.step == model.sas
        model.icbd -= 0.005
        model.icbl -= 0.005 
        model.icb = (model.icbl + model.icbd) / 2.0
    end
    #end: apply shocks

    for id in ids_by_type(Bank, model)
        CViM.prev_vars!(model[id])
        CViM.update_liq_preferences!(model[id], model)
        CViM.credit_rates!(model[id], model.χ1, model.χ2, model.χ3)
        CViM.reset_vars!(model[id])
    end
    CViM.update_model_vars!(model)

    CViM.update_change_rates!(model)
    for id in ids_by_type(Household, model)
        CViM.prev_vars!(model[id])
        CViM.repayment!(model[id], model)
        CViM.interests_payments!(model[id], model)
        CViM.consumption!(model[id], model)
        CViM.update_lev!(model[id], model.change_rates_hh, model.pref)
    end

    CViM.hhs_matching!(model)
    for id in ids_by_type(Household, model)
        CViM.loans_demand!(model[id])
        CViM.loans!(model[id], model)
    end   

    for id in ids_by_type(Firm, model)
        CViM.prev_vars!(model[id])
        CViM.repayment!(model[id], model)
        CViM.networth!(model[id])
        CViM.interests_payments!(model[id], model)
        CViM.update_lev!(model[id], model.change_rates_firms, model.pref)
    end
    
    CViM.firms_matching!(model)
    for id in ids_by_type(Firm, model)
        CViM.loans_demand!(model[id])
        CViM.loans!(model[id], model)
        CViM.deposits!(model[id], model)
        CViM.consumption!(model[id], model)
        CViM.inventories!(model[id])
        CViM.production!(model[id], model.g, model.n_f)
        CViM.wages!(model[id], model)
        CViM.profits!(model[id])
        CViM.current_balance!(model[id])
        CViM.SFC!(model[id], model)
    end
    CViM.GDP_growth_rate(model)

    for id in ids_by_type(Bank, model)
        CViM.interests_payments!(model[id], model)
        CViM.profits!(model[id])
        CViM.current_balance!(model[id])
        CViM.update_status!(model[id]) # updates IB status
    end
    
    banks_profits = sum(a.profits for a in allagents(model) if a isa Bank) 
    firms_profits = sum(a.profits for a in allagents(model) if a isa Firm)
    profits = (banks_profits + firms_profits)/model.n_hh
    for id in ids_by_type(Household, model)
        CViM.taxes!(model[id], model.τ)
        CViM.income!(model[id], profits)
        CViM.SFC!(model[id], model)
    end

    for id in ids_by_type(CentralBank, model)
        CViM.prev_vars!(model[id])
        CViM.profits!(model[id], model)
        CViM.current_balance!(model[id], model)
    end

    for id in ids_by_type(Government, model)
        CViM.SFC!(model[id], model)
    end

    # begin: Interbank Market
    CViM.ib_matching!(model)
    for id in ids_by_type(Bank, model)
        CViM.update_ib_demand_supply!(model[id])
        CViM.ib_on!(model[id], model)
        CViM.ib_term!(model[id], model)
        CViM.lending_facility!(model[id])
        CViM.deposit_facility!(model[id])
        CViM.funding_costs!(model[id], model.icb, model.ion, model.iterm, model.icbl)
        CViM.SFC!(model[id], model)
    end
    CViM.ib_rates!(model)
    # end: Interbank Market
    
    for id in ids_by_type(CentralBank, model)
        CViM.SFC!(model[id], model)
    end

    CViM.SFC_checks!(model) # check for Stock-Flow consistency 
    return model
end

"""
    GDP_growth_rate(model) → model.gy

Updates growth rate of national GDP.
"""
function GDP_growth_rate(model)
    model.gy = sum(a.GDP - a.GDP_prev for a in allagents(model) if a isa Firm) / sum(a.GDP_prev for a in allagents(model) if a isa Firm)
    return model.gy
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].ilf_rate == minimum(model[a].ilf_rate for a in potential_partners), potential_partners))
        #Select interest rate of the new potential partner
        inew = model[new_partner].ilf_rate
        #Pick up old partner
        old_partner = model[id].belongToBank
        #PICK UP THE INTEREST OF THE OLD PARTNER
        iold = model[old_partner].ilf_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].ilh_rate == minimum(model[a].ilh_rate for a in potential_partners), potential_partners))
        #Select interest rate of the new potential partner
        inew = model[new_partner].ilh_rate
        #Pick up old partner
        old_partner = model[id].belongToBank
        #PICK UP THE INTEREST OF THE OLD PARTNER
        iold = model[old_partner].ilh_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 lender banks available
        isempty([a.id for a in allagents(model) if a isa Bank && a.status == :lender]) && return

        if model[id].status == :borrower
            #Select potential partners with the closest liquidity preference
            potential_partners = filter(i -> model[i] isa Bank && model[i].status == :lender && abs(model[i].liq_pref - model[id].liq_pref) <= 1e-01, collect(allids(model)))
            if !isempty(potential_partners) #&& rand(model.rng, Bool)
                #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)
            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)
    if length([a.id for a in allagents(model) if a isa Bank && a.status == :borrower && !ismissing(a.belongToBank)]) > 0 && 
        length([a.id for a in allagents(model) if a isa Bank && a.status == :lender && !isempty(a.ib_customers)]) > 0
 
        ON = sum(a.ib_on_demand for a in allagents(model) if a isa Bank && a.status == :borrower) - 
            sum(a.ib_on_supply for a in allagents(model) if a isa Bank && a.status == :lender)
        Term = sum(a.ib_term_demand for a in allagents(model) if a isa Bank && a.status == :borrower) - 
            sum(a.ib_term_supply for a in allagents(model) if a isa Bank && a.status == :lender)

        model.ion = model.icbd + ((model.icbl - model.icbd)/(1 + exp(-model.σib * ON)))
        model.iterm = model.icbd + ((model.icbl - model.icbd)/(1 + exp(-model.σib * Term)))
    end

    # check corridor
    if model.ion > model.icbl || model.ion < model.icbd
        @warn "Interbank ON rate outside the central bank's corridor!"
    elseif model.iterm > model.icbl || model.iterm < model.icbd
        @warn "Interbank Term rate outside the central bank's corridor!"
    end
    return model.ion, model.iterm
end

"""
    update_model_vars!(model) → model

Update model variables.
"""
function update_model_vars!(model)
    model.ion_prev = model.ion
    model.iterm_prev = model.iterm
    return model
end

"""
    update_change_rates!(model) → model

Update credit rates changes for households and firms. Used to update real sector agents' desired leverage.
"""
function update_change_rates!(model)
    model.change_rates_hh = (mean(a.ilh_rate for a in allagents(model) if a isa Bank && a.type == :commercial) - 
        mean(a.ilh_rate_prev for a in allagents(model) if a isa Bank && a.type == :commercial))
    model.change_rates_firms = (mean(a.ilf_rate for a in allagents(model) if a isa Bank && a.type == :business) - 
        mean(a.ilf_rate_prev for a in allagents(model) if a isa Bank && a.type == :business))
    return model
end

end # module CViM