2026-03-16
The Key Question
Why do countries ever repay their debts? Without legal enforcement, what prevents default? And what determines the price of sovereign bonds?
Connection to Previous Lectures
The sovereign default model combines: dynamic programming (McCall, Rust, KP), Markov chains (income process), value function iteration (KP), and equilibrium pricing (DMP).
\[ \log y_{t+1} = \rho \, \log y_t + \sigma_\varepsilon \, \varepsilon_{t+1}, \qquad \varepsilon_{t+1} \sim N(0, 1) \]
\[ \mathbb{E}_0 \sum_{t=0}^{\infty} \beta^t u(c_t) \]
where \(u(c) = \frac{c^{1-\sigma}}{1-\sigma}\) is CRRA utility
\[ c = y + b - q(b', y) \cdot b' \]
Reading the Budget Constraint
If the government has debt (\(b < 0\)), it must pay \(|b|\) to old creditors. It can borrow \(q \cdot |b'|\) from new creditors. The net resources available for consumption are \(y + b - q \cdot b'\).
\[ h(y) = \min\{y, \; \hat{y}\} \]
\[ V^R(b, y) = \max_{b'} \left\{ u(y + b - q(b', y) \cdot b') + \beta \, \mathbb{E}\left[V(b', y') \mid y\right] \right\} \]
\[ V^D(y) = u(h(y)) + \beta \, \mathbb{E}\left[\lambda \, V(0, y') + (1-\lambda) \, V^D(y') \mid y\right] \]
\[ \boxed{V(b, y) = \max\left\{V^R(b, y), \; V^D(y)\right\}} \]
\[ D(b) = \left\{ y : V^D(y) > V^R(b, y) \right\} \]
\[ \delta(b', y) = \sum_{y' \in D(b')} \Pr(y' \mid y) \]
When Does Default Happen?
Default is more likely when:
\[ q(b', y) = \frac{1 - \delta(b', y)}{1 + r} \]
\[ \text{spread}(b', y) = \frac{1}{q(b', y)} - (1 + r) \]
Feedback Loop
Higher spreads → borrowing is more expensive → debt grows faster → default risk rises → even higher spreads. This can generate debt crises that spiral out of control.
Recursive Equilibrium
A recursive equilibrium consists of:
such that:
| McCall | Rust | KP | Sovereign Default | |
|---|---|---|---|---|
| State | Wage offer | Mileage | Capital \(\times\) TFP | Debt \(\times\) Income |
| Choice | Accept/Reject | Replace/Keep | Savings | Repay/Default + Borrowing |
| State space | Finite | Finite | Continuous \(\times\) Finite | Continuous \(\times\) Finite |
| Prices | Exogenous | — | Factor prices | Bond price \(q(b', y)\) |
| Equilibrium | Partial | — | First Welfare Theorem | Fixed point: \(V\) and \(q\) |
The New Challenge
The bond price \(q(b', y)\) depends on the government’s future default decision, which depends on the bond price. We need to iterate on both \(V\) and \(q\) simultaneously until they are consistent.
function rouwenhorst(N, ρ, σ_ε)
σ_z = σ_ε / sqrt(1 - ρ^2)
p = (1 + ρ) / 2
P = [p 1-p; 1-p p]
for n in 3:N
z = zeros(n - 1)
P = p * [P z; z' 0] + (1-p) * [z P; 0 z'] +
(1-p) * [z' 0; P z] + p * [0 z'; z P]
P[2:end-1, :] ./= 2
end
z_grid = collect(LinRange(-sqrt(N-1) * σ_z,
sqrt(N-1) * σ_z, N))
return z_grid, P
endrouwenhorst (generic function with 1 method)
SovDefaultModel Struct@kwdef mutable struct SovDefaultModel
β::Float64 = 0.980 # discount factor (calibrated to 0.75% quarterly default)
σ::Float64 = 2.0 # risk aversion (CRRA)
r::Float64 = 0.017 # risk-free interest rate
ρ::Float64 = 0.945 # income persistence
σ_ε::Float64 = 0.025 # income innovation std dev
λ::Float64 = 0.282 # re-entry probability
ŷ::Float64 = 0.969 # default output ceiling
τ::Float64 = 0.001 # taste shock scale (smoothing)
N_y::Int = 21 # number of income states
N_b::Int = 101 # sparse grid (value function storage)
N_b_fine::Int = 251 # fine grid (optimization search)
b_min::Float64 = -0.4 # minimum bond holdings (max debt)
b_max::Float64 = 0.4 # maximum bond holdings (max savings)
y_grid::Vector{Float64} = Float64[] # filled by setup!
P_y::Matrix{Float64} = zeros(0, 0) # filled by setup!
b_grid::Vector{Float64} = Float64[] # sparse grid, filled by setup!
b_grid_fine::Vector{Float64} = Float64[] # fine grid, filled by setup!
endSovDefaultModel
setup! Functionsetup! (generic function with 1 method)
m = setup!(SovDefaultModel())
iy_set = round.(Int, LinRange(1, m.N_y, 5))
println("Income grid: ", round.(m.y_grid[iy_set], digits=3))
println("Sparse grid ($(m.N_b) pts): [$(round(m.b_grid[1], digits=3)), ..., $(round(m.b_grid[end], digits=3))]")
println("Fine grid ($(m.N_b_fine) pts): [$(round(m.b_grid_fine[1], digits=3)), ..., $(round(m.b_grid_fine[end], digits=3))]")Income grid: [0.71, 0.843, 1.0, 1.186, 1.408]
Sparse grid (101 pts): [-0.4, ..., 0.4]
Fine grid (251 pts): [-0.4, ..., 0.4]
u (generic function with 1 method)
Two Nested Fixed Points
Unlike KP (where we only iterated on \(V\)), here we iterate on both \(V\) and \(q\). The bond price depends on future default decisions, which depend on the bond price.
Where Interpolation Helps
We store \(V\) and \(q\) on a sparse grid (101 points) and build linear_interpolation per income state. In the Bellman operator, we search over a fine grid (251 points), evaluating interpolated \(V\) and \(q\). This separates approximation accuracy from optimization accuracy. V_itp[iy](0.0) cleanly evaluates \(V(0, y')\) for the default value.
\[ V(b, y) = \tau \, \log\!\left[\exp\!\left(\frac{V^R}{\tau}\right) + \exp\!\left(\frac{V^D}{\tau}\right)\right] \]
\[ \delta(b, y) = \frac{1}{1 + \exp\!\left(\frac{V^R(b, y) - V^D(y)}{\tau}\right)} \]
The Role of \(\tau\)
As \(\tau \to 0\), we recover the original hard-max model. We set \(\tau = 0.001\) as our baseline: this is close to the hard-max model while still providing a smooth default probability for numerical stability. The parameter \(\tau\) plays the same role as the implicit scale \(= 1\) in the Rust lecture.
smooth_max and default_probdefault_prob (generic function with 1 method)
smooth_max: numerically stable log-sum-exp (subtracts the max before exponentiating)default_prob: logistic sigmoid — \(P(\text{default}) = \frac{1}{1 + e^{(V^R - V^D)/\tau}}\)\[ V^D(y_j) = u(h(y_j)) + \beta \sum_{j'} P_{j,j'}\left[\lambda \, V(0, y_{j'}) + (1-\lambda) \, V^D(y_{j'})\right] \]
\[ V^R(b_i, y_j) = \max_{b'} \left\{ u(y_j + b_i - q(b', y_j) \cdot b') + \beta \sum_{j'} P_{j,j'} V(b', y_{j'}) \right\} \]
\[ \delta(b', y_j) = \sum_{j'} P_{j,j'} \cdot \frac{1}{1 + \exp\!\left(\frac{V^R(b', y_{j'}) - V^D(y_{j'})}{\tau}\right)} \]
\[ q(b', y_j) = \frac{1 - \delta(b', y_j)}{1 + r} \]
optimal_default: Pseudocodeiy, the current value function, and VD, compute the value of defaulting todayoptimal_default: Full Functionfunction optimal_default(m, iy, V_itp, VD)
(; β, σ, λ, ŷ, N_y, y_grid, P_y) = m
y = y_grid[iy]
c_def = h(y, ŷ) # income under default
EV_def = sum(P_y[iy, iy2] * (λ * V_itp[iy2](0.0) + # regain access at b=0
(1 - λ) * VD[iy2]) for iy2 in 1:N_y) # or stay in autarky
return u(c_def, σ) + β * EV_def
endoptimal_default (generic function with 1 method)
V_itp[iy2](0.0) evaluates the continuation value at zero debt using interpolation — no need to find a grid indexfind_max Helperoptimal_repayment, let’s build a tiny utility: grid search over a vector to maximize a functionfind_max (generic function with 1 method)
f at every point in grid and returns the best (val, x)b_grid_fine, 251 points) for accuracy, while storing values on the sparse grid (b_grid, 101 points)optimal_repayment: Pseudocodeiy, and interpolated \(V\) and \(q\), find the optimal new borrowing \(b'\)optimal_repayment: Full Functionfunction optimal_repayment(m, b, iy, V_itp, q_itp)
(; β, σ, N_y, y_grid, P_y, b_grid_fine) = m
y = y_grid[iy]
function payoff(bp)
c = y + b - q_itp[iy](bp) * bp
c <= 0 && return -Inf
EV = sum(P_y[iy, iy2] * V_itp[iy2](bp) for iy2 in 1:N_y)
return u(c, σ) + β * EV
end
result = find_max(payoff, b_grid_fine)
return (V = result.val, bp = result.x)
endoptimal_repayment (generic function with 1 method)
payoff(bp) computes the flow utility + discounted continuation for any candidate \(b'\)find_max searches the fine grid (251 points) — more resolution than the sparse grid where we store \(V\)(V, bp) — the optimal value and borrowing levelfunction bellman_operator(m, V, VR, VD, q)
(; N_y, N_b, b_grid) = m
VR_new = similar(VR)
VD_new = similar(VD)
bp_policy = zeros(N_b, N_y)
V_itp = [linear_interpolation(b_grid, V[:, iy]) for iy in 1:N_y]
q_itp = [linear_interpolation(b_grid, q[:, iy]) for iy in 1:N_y]
for iy in 1:N_y
VD_new[iy] = optimal_default(m, iy, V_itp, VD)
end
for iy in 1:N_y
for ib in 1:N_b
result = optimal_repayment(m, b_grid[ib], iy, V_itp, q_itp)
VR_new[ib, iy] = result.V
bp_policy[ib, iy] = result.bp
end
end
return VR_new, VD_new, bp_policy
endbellman_operator (generic function with 1 method)
V_itp and q_itp are built once per iteration (not once per state) for efficiencyupdate_bond_price (generic function with 1 method)
default_prob(VR, VD, τ) returns the smooth logistic probability — no sharp 0/1 discontinuityfunction solveSovDefault(m; tol=1e-6, maxiter=1500, verbose=true, α=0.5)
# Initialize value functions and bond prices
# Iterate: Bellman update + damped bond price update
# V uses smooth_max (log-sum-exp with τ)
# q updated with mixing: q = α·q_new + (1-α)·q_old (prevents oscillation)
# Build interpolated policies for simulation
# Return solution tuple with smooth default probabilities
endfunction solveSovDefault(m; tol=1e-6, maxiter=1500, verbose=true, α=0.5)
(; N_b, N_y, r, τ, b_grid) = m
VR = zeros(N_b, N_y)
VD = zeros(N_y)
V = zeros(N_b, N_y)
q = fill(1 / (1 + r), N_b, N_y) # initial guess: risk-free price
bp_policy = zeros(N_b, N_y)
for iter in 1:maxiter
VR_new, VD_new, bp_policy = bellman_operator(m, V, VR, VD, q)
V_new = [smooth_max(VR_new[ib, iy], VD_new[iy], τ)
for ib in 1:N_b, iy in 1:N_y]
q_lenders = update_bond_price(m, VR_new, VD_new)
q_new = α .* q_lenders .+ (1 - α) .* q # damped update
dist_V = maximum(abs.(V_new - V))
dist_q = maximum(abs.(q_new - q))
if max(dist_V, dist_q) < tol
verbose && println("Converged in $iter iterations (V: $(round(dist_V, sigdigits=3)), q: $(round(dist_q, sigdigits=3)))")
VR = VR_new; VD = VD_new; V = V_new; q = q_new
break
end
VR = VR_new; VD = VD_new; V = V_new; q = q_new
if verbose && iter % 25 == 0
println(" iter $iter: dist_V = $(round(dist_V, sigdigits=3)), dist_q = $(round(dist_q, sigdigits=3))")
end
if iter == maxiter
println("WARNING: did not converge after $maxiter iterations")
end
end
def_prob = [default_prob(VR[ib, iy], VD[iy], τ) for ib in 1:N_b, iy in 1:N_y]
VR_itp = [linear_interpolation(b_grid, VR[:, iy]) for iy in 1:N_y]
bp_itp = [linear_interpolation(b_grid, bp_policy[:, iy]) for iy in 1:N_y]
q_itp = [linear_interpolation(b_grid, q[:, iy]) for iy in 1:N_y]
return (V=V, VR=VR, VD=VD, q=q, bp=bp_policy, def_prob=def_prob,
VR_itp=VR_itp, bp_itp=bp_itp, q_itp=q_itp)
endsolveSovDefault (generic function with 1 method)
Why Damping?
The bond price \(q\) depends on future default, which depends on \(q\) itself. With near-hard-max taste shocks (\(\tau = 0.001\)), small changes in \(V\) near the default boundary cause large swings in \(q\). Mixing old and new prices (\(\alpha = 0.5\)) smooths this feedback loop.
| Parameter | Value | Source |
|---|---|---|
| \(\sigma\) | 2.0 | Standard CRRA |
| \(r\) | 0.017 | US T-bill rate (quarterly) |
| \(\rho\) | 0.945 | Argentine GDP AR(1) persistence |
| \(\sigma_\varepsilon\) | 0.025 | Argentine GDP innovation std dev |
| \(\lambda\) | 0.282 | Avg. exclusion \(\approx\) 3.5 quarters |
| \(\hat{y}\) | 0.969 | Arellano (2008) asymmetric output cost |
| \(\tau\) | 0.001 | Taste shock scale for smooth default |
| \(\beta\) | 0.953 | Arellano (2008), matches \(\approx\) 3% annualized default rate |
Why \(\beta < 1\)?
A discount factor of 0.953 is low compared to standard macro models (\(\beta \approx 0.99\)). This reflects the impatience needed to generate realistic borrowing and default. A very patient government would rarely accumulate enough debt to default.
simulateSovDefault (generic function with 1 method)
iter 25: dist_V = 0.683, dist_q = 0.0158
iter 50: dist_V = 0.384, dist_q = 8.46e-5
iter 75: dist_V = 0.228, dist_q = 3.94e-6
iter 100: dist_V = 0.137, dist_q = 1.81e-7
iter 125: dist_V = 0.0825, dist_q = 8.17e-9
iter 150: dist_V = 0.0498, dist_q = 3.7e-10
iter 175: dist_V = 0.03, dist_q = 1.69e-11
iter 200: dist_V = 0.0181, dist_q = 9.18e-13
iter 225: dist_V = 0.0109, dist_q = 2.6e-13
iter 250: dist_V = 0.0066, dist_q = 3.82e-13
iter 275: dist_V = 0.00398, dist_q = 6.5e-13
iter 300: dist_V = 0.0024, dist_q = 1.54e-12
iter 325: dist_V = 0.00145, dist_q = 1.29e-12
iter 350: dist_V = 0.000876, dist_q = 3.84e-13
iter 375: dist_V = 0.000528, dist_q = 1.05e-12
iter 400: dist_V = 0.000319, dist_q = 4.18e-13
iter 425: dist_V = 0.000192, dist_q = 6.33e-13
iter 450: dist_V = 0.000116, dist_q = 3.05e-13
iter 475: dist_V = 7.01e-5, dist_q = 1.22e-12
iter 500: dist_V = 4.23e-5, dist_q = 3.82e-13
iter 525: dist_V = 2.55e-5, dist_q = 8.79e-13
iter 550: dist_V = 1.54e-5, dist_q = 1.75e-12
iter 575: dist_V = 9.29e-6, dist_q = 1.17e-12
iter 600: dist_V = 5.61e-6, dist_q = 4.14e-13
iter 625: dist_V = 3.38e-6, dist_q = 4.26e-13
iter 650: dist_V = 2.04e-6, dist_q = 6.34e-13
iter 675: dist_V = 1.23e-6, dist_q = 4.68e-13
Converged in 686 iterations (V: 9.87e-7, q: 6.19e-13)
Model: β = 0.98, τ = 0.001
Patience
This takes longer than the KP model because we iterate on both value functions and bond prices. Each iteration optimizes over \(b'\) for every \((b, y)\) state — \(N_b \times N_y\) evaluations per iteration.
iy_set = round.(Int, LinRange(1, m.N_y, 5))
plot(legend=:topleft, size=(700, 400))
for iy in iy_set
plot!(m.b_grid, sol.V[:, iy], linewidth=2,
label="y = $(round(m.y_grid[iy], digits=3))")
end
xlabel!("Bond Holdings b (negative = debt)")
ylabel!("V(b, y)")
title!("Value Function")
vline!([0.0], color=:gray, linestyle=:dash, label="")\[ V^R(b, y) = \max_{b'} \underbrace{\left\{ u\!\left(y + b - q(b', y) \cdot b'\right) + \beta \, \mathbb{E}\left[V(b', y') \mid y\right] \right\}}_{\text{obj}(b'; \, b, y)} \]
iy_mid = (m.N_y + 1) ÷ 2
y_mid = m.y_grid[iy_mid]
V_itp_plot = [linear_interpolation(m.b_grid, sol.V[:, iy]) for iy in 1:m.N_y]
q_itp_plot = [linear_interpolation(m.b_grid, sol.q[:, iy]) for iy in 1:m.N_y]
b_show = [-0.3, -0.2, -0.1, 0.0]
plot(legend=:topleft, size=(700, 400))
for bv in b_show
obj = [let c = y_mid + bv - q_itp_plot[iy_mid](bp) * bp
c > 0 ? u(c, m.σ) + m.β * sum(m.P_y[iy_mid, j] * V_itp_plot[j](bp)
for j in 1:m.N_y) : NaN
end for bp in m.b_grid]
plot!(m.b_grid, obj, linewidth=2, label="b = $bv")
end
hline!([sol.VD[iy_mid]], color=:red, linestyle=:dash, linewidth=2,
label="V^D(y) — default value")
xlabel!("New Debt b'")
ylabel!("Objective Value")
title!("Repayment Objective at Median Income (y = $(round(y_mid, digits=3)))")bv = -0.15 # moderate debt
flow = [let c = y_mid + bv - q_itp_plot[iy_mid](bp) * bp
c > 0 ? u(c, m.σ) : NaN end for bp in m.b_grid]
cont = [m.β * sum(m.P_y[iy_mid, j] * V_itp_plot[j](bp)
for j in 1:m.N_y) for bp in m.b_grid]
p1 = plot(m.b_grid, flow, linewidth=2, color=:blue,
label="u(y + b - q·b')", legend=:topright, size=(700, 250),
title="Flow Utility (b = $bv, y = $(round(y_mid, digits=3)))")
p2 = plot(m.b_grid, cont, linewidth=2, color=:orange,
label="β·E[V(b', y')]", legend=:topright, size=(700, 250),
title="Continuation Value (b = $bv)")
plot(p1, p2, layout=(2, 1), size=(700, 500))iy_set = round.(Int, LinRange(1, m.N_y, 5))
plot(legend=:topright, size=(700, 400))
for iy in iy_set
plot!(m.b_grid, sol.q[:, iy], linewidth=2,
label="y = $(round(m.y_grid[iy], digits=3))")
end
hline!([1 / (1 + m.r)], color=:gray, linestyle=:dash, label="Risk-free price")
xlabel!("New Debt b'")
ylabel!("Bond Price q(b', y)")
title!("Bond Price Schedule")iy_set = round.(Int, LinRange(1, m.N_y, 5))
p1 = plot(legend=:topleft, size=(700, 300), title="Sovereign Spreads")
for iy in iy_set
spread = (1 ./ max.(sol.q[:, iy], 1e-8)) .- (1 + m.r)
spread = clamp.(spread, 0, 2.0) # cap at 200%
plot!(p1, m.b_grid, 100 .* spread, linewidth=2,
label="y = $(round(m.y_grid[iy], digits=3))")
end
xlabel!(p1, "New Debt b'")
ylabel!(p1, "Spread (percentage points)")
p2 = plot(legend=:topleft, size=(700, 300), title="Default Probability by Income")
for iy in iy_set
plot!(p2, m.b_grid, sol.def_prob[:, iy], linewidth=2,
label="y = $(round(m.y_grid[iy], digits=3))")
end
xlabel!(p2, "Bond Holdings b")
ylabel!(p2, "P(default)")
plot(p1, p2, layout=(2, 1), size=(700, 550))iy_set = round.(Int, LinRange(1, m.N_y, 5))
plot(legend=:topleft, size=(700, 400))
for iy in iy_set
plot!(m.b_grid, sol.bp[:, iy], linewidth=2,
label="y = $(round(m.y_grid[iy], digits=3))")
end
plot!(m.b_grid, m.b_grid, color=:gray, linestyle=:dash, label="45° line")
xlabel!("Current Bond Holdings b")
ylabel!("Next Period Bond Holdings b'")
title!("Borrowing Policy Function")function simulateSovDefault(m, sol, T; seed=42)
# Initialize at zero debt and median income
# Each period: compute smooth default probability, draw Bernoulli shock
# If not in default: use interpolated policy bp_itp for borrowing
# If in default: consume h(y), try to regain access
# Return named tuple
endfunction simulateSovDefault(m, sol, T; seed=42)
Random.seed!(seed)
(; r, λ, τ, ŷ, σ, y_grid, P_y, b_grid, N_y) = m
b_sim = zeros(T); y_sim = zeros(T); c_sim = zeros(T)
spread_sim = zeros(T); default_sim = zeros(Int, T)
b = 0.0 # start at zero debt
iy = (N_y + 1) ÷ 2 # start at median income
in_default = false
for t in 1:T
y = y_grid[iy]
b_sim[t] = b; y_sim[t] = y
if in_default
c_sim[t] = h(y, ŷ)
default_sim[t] = 1
spread_sim[t] = NaN
in_default = rand() > λ # try to regain access
if !in_default; b = 0.0; end
else
# Stochastic default: Bernoulli draw (as in Rust simulation)
δ_prob = default_prob(sol.VR_itp[iy](b), sol.VD[iy], τ)
if rand() < δ_prob # draw default shock
c_sim[t] = h(y, ŷ)
default_sim[t] = 1; in_default = true
spread_sim[t] = NaN
else # government repays
bp = clamp(sol.bp_itp[iy](b), b_grid[1], b_grid[end])
qval = sol.q_itp[iy](bp)
c_sim[t] = y + b - qval * bp
spread_sim[t] = max((1 / max(qval, 1e-8)) - (1 + r), 0.0)
b = bp
end
end
iy = rand(Categorical(P_y[iy, :]))
end
return (t=1:T, b=b_sim, y=y_sim, c=c_sim, spread=spread_sim, default=default_sim)
endsimulateSovDefault (generic function with 1 method)
p1 = plot(sim.b, linewidth=1.5, color=:steelblue, label="Debt b",
title="Bond Holdings", size=(800, 200))
hline!(p1, [0.0], color=:gray, linestyle=:dash, label="")
p2 = plot(sim.y, linewidth=1.5, color=:darkgreen, label="Income y",
title="Income", size=(800, 200))
hline!(p2, [m.ŷ], color=:gray, linestyle=:dash, label="ŷ")
plot(p1, p2, layout=(2, 1), size=(800, 450))Default episodes at periods: [262, 267, 280, 302, 306]
Total defaults in 500 periods: 8
plot(legend=:topright, size=(700, 400), title="Bond Prices: Effect of σ")
for σ_val in [1.5, 2.0, 3.0]
m_trial = setup!(SovDefaultModel(β=0.980, σ=σ_val, N_b=51, N_b_fine=101, N_y=11))
sol_trial = solveSovDefault(m_trial, verbose=false)
iy_mid = (m_trial.N_y + 1) ÷ 2
plot!(m_trial.b_grid, sol_trial.q[:, iy_mid], linewidth=2, label="σ = $σ_val")
end
xlabel!("New Debt b'")
ylabel!("Bond Price q(b', y)")plot(legend=:topright, size=(700, 400), title="Bond Prices: Effect of λ")
for λ_val in [0.1, 0.282, 0.5]
m_trial = setup!(SovDefaultModel(β=0.980, λ=λ_val, N_b=51, N_b_fine=101, N_y=11))
sol_trial = solveSovDefault(m_trial, verbose=false)
iy_mid = (m_trial.N_y + 1) ÷ 2
plot!(m_trial.b_grid, sol_trial.q[:, iy_mid], linewidth=2, label="λ = $λ_val")
end
xlabel!("New Debt b'")
ylabel!("Bond Price q(b', y)")plot(legend=:topright, size=(700, 400), title="Bond Prices: Effect of τ")
for τ_val in [0.001, 0.01, 0.05]
m_trial = setup!(SovDefaultModel(β=0.980, τ=τ_val, N_b=51, N_b_fine=101, N_y=11))
sol_trial = solveSovDefault(m_trial, verbose=false)
iy_mid = (m_trial.N_y + 1) ÷ 2
plot!(m_trial.b_grid, sol_trial.q[:, iy_mid], linewidth=2, label="τ = $τ_val")
end
hline!([1 / (1 + m.r)], color=:gray, linestyle=:dash, label="Risk-free price")
xlabel!("New Debt b'")
ylabel!("Bond Price q(b', y)")Concepts
Julia Tools
setup! and @kwdefsmooth_max and default_prob (log-sum-exp + logistic, from Rust)Remember
The sovereign default model shows why countries face higher borrowing costs when they carry more debt or have weaker economies. The key mechanism is that bond prices are set by risk-neutral lenders who anticipate the government’s future default decisions — creating a feedback loop between borrowing costs and default incentives. Unlike domestic models, there is no enforcement mechanism — the government repays only because default is costly (output loss + market exclusion).
EC 410 | University of Oregon