Make the floor on the government interest rate a parameter - #1203
Make the floor on the government interest rate a parameter#1203marcelolafleur wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1203 +/- ##
=======================================
Coverage 72.74% 72.74%
=======================================
Files 22 22
Lines 5768 5768
=======================================
Hits 4196 4196
Misses 1572 1572
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
arihantlodha-cmd
left a comment
There was a problem hiding this comment.
This one's clean, and it's directly useful for the Japan calibration i've been working on, where general government net interest is genuinely negative, so thanks for it.
Non-blocking notes. The mechanical change is obviously safe: default 0.0 makes np.maximum(x, 0.0) identical to before, the docstring math and the changelog both track the change, and the param_notation is escaped correctly. The tests cover the three cases that matter (the floor lets a negative rate through, the floor still binds above the wedge, and the TPI branch), which is more than I expected for a one-line change.
The one thing I'd sanity-check isn't in this diff: now that r_gov can actually go negative, does anything downstream of get_r_gov quietly assume it's non-negative? Debt service is just r_gov * D so that's fine sign-wise, and the r_p blend should be too, but if any fiscal or household code takes a log/sqrt of a rate or clips on the old assumption that the floor was zero, a negative r_gov would be the first thing to exercise it. If you've already confirmed the budget and r_p handle a negative rate end to end (a Japan run would show it), then this is complete as is.
Nice small fix.
|
Thanks @arihantlodha-cmd. I traced everything downstream of get_r_gov: debt service, the debt path, the r_p blend, and household income are all linear in the rate, and there are no logs, roots, or clips on rates anywhere in the solve. The default floor is 0.0, so nothing changes unless you opt in. The only edge I see is a calibration extreme enough to pull r_p itself negative, which would feed negative capital income into the tax functions, but realistic numbers don't come close. The Japan calibration I sent you uses this change, so you can see it working end to end there. |
|
thanks for tracing it all the way through @marcelolafleur. linear-in-the-rate everywhere with no logs/roots/clips is the reassuring answer, and the r_p-goes-negative case is a fair boundary to flag (negative capital income into the tax functions would be the thing to watch, but as you say realistic numbers don't get close). and agreed, r_gov_floor is exactly what OG-Japan needs, Japan's general government net interest is genuinely negative, so i'll wire it in on that side once this lands. thanks for doing this one. |
arihantlodha-cmd
left a comment
There was a problem hiding this comment.
Strong support for this. I traced every consumer of r_gov to check the claim that it enters linearly, and it holds:
- The government budget constraints in
fiscal.pyuse it only as(1 + r_gov) * D(debt accumulation) andr_gov * D(debt service, lines 199 and 252). get_D_sspinsD = debt_ratio_ss * Y, so debt is exogenous tor_gov, and new borrowing keys off the growth rates, not the rate. There is no division byr_govanywhere in the steady state.- The one spot with a division is the household portfolio return in
aggregates.get_r_p,r_p = (r_gov * D + r_K * K) / (D + K).r_govis linear in the numerator and the denominator is total assets, independent of the rate, so a negativer_govjust drags the blended return down, which is the correct behavior. The[-0.3, 0.3]bound on the parameter keeps1 + r_pcomfortably positive even in the limiting case where debt were the whole portfolio, so the Euler equation is never at risk.
So allowing the rate to go negative is safe, and it does exactly the right thing economically: it turns the interest bill into the subsidy that a negative-real-rate sovereign actually receives.
This matters to me directly. I have been building a Japan calibration on top of OG-Core, and the hardcoded zero is a live obstacle: Japan's general government pays a negative real rate on its debt, so clipping r_gov to zero makes the model overstate its debt-service burden and, with the debt ratio pinned, the primary surplus it has to run to support that debt. Making the floor a parameter removes that, and the 0.0 default keeps every existing calibration bit for bit identical.
The tests are well chosen. The "floor still binds" case (p6) is a nice touch, since it checks that the parameter works as a bound in both directions rather than only letting rates through. Reads well to me.
Fixes #1202.
get_r_govclipped the interest rate on government debt at a hardcoded0.00, so a sovereign that genuinely pays a negative real rate could not be modelled. Japan's general government net interest implies about −0.6% real; the wedge returns it and the clip discards it.The bound is now a parameter,
r_gov_floor, defaulting to0.0. Existing calibrations are unaffected —np.maximum(x, 0.0)is exactly what happened before.r_goventers the model linearly everywhere — no division by it, no powers, no logs — with two consumers,debt_service = r_gov * Dandr_p = (r_gov*D + r_K*K) / (D + K), whose denominator is strictly positive. A negative value means the government is a net receiver on its debt position.Tests
Three cases added to the existing
test_get_r_gov: a lowered floor lets a negative rate through, the floor still binds when set above the wedge, and the same on the TPI path since both branches changed. The suite's existingr_gov < 0case already covers the default clipping to zero, so it doubles as the behaviour-preservation check.The new cases cannot run on
master—r_gov_flooris not a valid parameter there, soupdate_specificationsrejects it at collection.test_fiscal.pyandtest_parameters.pypass, 45 total.Note
r_gov_flooris a scalar, whiler_gov_scaleandr_gov_shiftare time-varying. A floor reads as a structural statement rather than a policy path, but say the word if you would rather it matched its neighbours.cc @jdebacker @rickecon