Skip to content

EBA calibration, regime discipline and nominal rigidities — consumption falls and the crisis now lasts - #31

Open
adambutlin wants to merge 30 commits into
mainfrom
add-nkpc
Open

EBA calibration, regime discipline and nominal rigidities — consumption falls and the crisis now lasts#31
adambutlin wants to merge 30 commits into
mainfrom
add-nkpc

Conversation

@adambutlin

@adambutlin adambutlin commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

What this adds

Nominal rigidities. Prices are now sticky in both countries, and deposits are nominal contracts. Previously the model was fully real — no inflation, no nominal side at all.

Because Greece and Germany share a currency, the terms of trade is the inflation differential, so the nominal side closes without needing an ECB policy rate: a Greek downturn shows up as Greek deflation (about 93% of the adjustment, on the ECB capital key) rather than German inflation. That matches the internal-devaluation pattern of 2010–14.

Banks now hold real assets against nominal deposits, so that deflation raises the real value of what they owe and deepens the hit to bank capital — a debt-deflation channel the real model had no way to express.

A crisis that lasts. The sovereign-risk shock's persistence was a hardcoded 0.80, implying a 14-month crisis. Our own Markov-switching estimation says otherwise: the Greek crisis state has a 50-month expected duration and the realised episode ran 2010-04 to 2017-12 (92 months), giving a quarterly persistence of 0.94. That parameter is now disciplined by the regime estimate rather than picked by hand, and the amplification dial was re-tuned alongside it so the model still hits its 150bp spread target.

Where the model now stands

Three pillars, all up for review together:

EBA-calibrated bank balance sheets Bilateral Greek/German sovereign exposures from the 2011 stress test, so the doom-loop channel runs on measured holdings rather than a placeholder.
Markov-switching empirical discipline A three-state model on peripheral–Bund spreads dates the ECB's stance and now pins two things: the three backstop regimes, and the persistence of the crisis itself.
Sticky prices and nominal deposits Output responds to demand rather than being pinned by technology and capital alone, and nominal debt revaluation reaches bank balance sheets.

Where to find each: the EBA and regime work went onto main ahead of this branch, so the diff here is the nominal-rigidities and shock-persistence layer on top. The results below are the combined model.

Outcomes

Consumption now falls, and the crisis now lasts

The model previously said a sovereign crisis raised Greek consumption on impact, and that output recovered within a quarter. Both are fixed.

impact response to a 1pp default shock before now
Consumption +0.22% −0.70%
Output −0.01% −0.75%
Investment −0.77% −1.71%
Bank net worth −3.38% −6.27%
Cumulative output loss, 40 quarters −0.05 −2.54

Investment is now negative in all 40 quarters and the spread stays above half its peak until quarter 11. Both columns hit the same 150bp Greece–Germany spread target, so this is like-for-like.

Two capital-adjustment frictions were tried first and both rejected — they only shuffled the impact between investment and consumption. The persistence of the shock was the binding constraint, and it was an empirical question we already had the data to answer.

Distributional incidence is progressive

The crisis costs the top income quintile 1.12% of its own consumption while the bottom quintile gains 0.64%, monotone across the distribution. The backstop's protection runs the same way — worth +5.4% to the poorest against +3.5% to the richest.

The backstop results — one of them needs a decision

backstop spread compression output consumption bank net worth loading
passive −0.75% −0.70% −6.27%
medium 25% +0.18% +0.74% −2.92% 5.48×
aggressive 50% +1.13% +2.24% +0.47% 5.17×

The one thing I'd most like your view on

The self-extinguishing premium claim has weakened, and I don't think I should decide alone how to write it.

The loading schedule — the ECB's compensation per unit of expected loss — still declines monotonically in the size of the backstop, and is still comfortably above 1. But its shape has changed:

before now
schedule across the full γ range 4.43 → 1.49 5.65 → 4.59
decline −66% −19%
minimum 1.49 (near fair value) 4.59

The over-compensation result is now stronger — the floor rises from 1.49 to 4.59, so it no longer depends on where on the schedule you look. But the self-extinguishing story was compelling because the loading fell toward 1: intervene hard enough and the ECB earns roughly the actuarially fair expected loss. It no longer approaches that. A longer crisis accumulates premium over more quarters than it accumulates expected loss, so the ratio stays high even under heavy intervention.

Three readings seem open, and it's your call which the paper takes:

  1. Reframe as "declining but persistent over-compensation" and lean on the stronger Claim 1.
  2. Argue the 19% decline still carries the mechanism.
  3. Treat the tension as evidence that reading a Markov regime duration as an AR(1) persistence is too aggressive — the mapping is a first pass, not an identity.

Two smaller flags

  • The default-loading split moved from 3.4%/96.6% to 8.6%/91.4% fundamental versus collateral friction. The constrained-seller claim survives in direction, but "essentially all of it" should become "roughly nine tenths of it".
  • Some recorded psi_lambda_B sweeps in the repo may be unreliable. A shortcut that patches the parameter onto a solved steady state misses one of the two places it enters, and was off by 10bp where it was checked. The adopted values were re-derived without it; the brackets around them were not.

All figures and results are regenerated on the current model, and the steady state is unchanged throughout, so the calibration targets you've already signed off on still hold.

🤖 Generated with Claude Code

adambutlin and others added 27 commits August 5, 2026 14:51
Two-country MU HANK gains genuine nominal price and wage Phillips curves,
closed without any policy rate: the terms-of-trade identity
p/p(-1) = (1+pi_F)/(1+pi_D) pins the inflation differential off the existing
p unknown, and a union-inflation normalisation pins the level. Financial
contracts stay real, so no Fisher relation or deposit-market rework.

Markups are subsidy-neutralised, so the steady state is bit-identical and all
five new residuals are exactly zero at it. Flex is the exact kappa -> inf
limit, which supplies the regression gate and removes the need for a STICKY
code switch.

Scope: sticky becomes the paper's new baseline; flex demotes to an appendix
robustness run. Union monetary channel explicitly out of scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FRBSF WP 2025-10 is the closest published analogue (two-country MU, GK
intermediaries, endogenous default, cross-border sovereign holdings; IT/DE 2012).
Three corrections and one benchmark section:

- kappa_p = 0.0871 agrees with their Calvo-equivalent 0.0846 to within 3%; the
  gap is entirely the discount factor. No recalibration needed.
- mu_p is free to first order under subsidy-neutralisation (the gap linearises to
  mc_hat regardless), so their theta^c=11 vs eps=6 is a non-issue.
- add-nkwpc's stated motivation is wrong for GHH: there is no wealth effect on
  labour supply by construction. Goal restated as adjustment moving off the real
  wage onto hours, which is what the distributional goal needs.
- omega_pi_D = 0.071 retained over their 0.5/0.5: the closure gives
  pi_D = -(1-omega)*dlog p, so 0.071 puts 93% of terms-of-trade adjustment into
  Greek deflation, the 2010-12 pattern. 0.5 splits it evenly.

Also records the live risk: their nominal side does little propagation (inflation
+/-0.1% vs investment 9%), their output contraction comes from a loan-in-advance
constraint this model lacks, and they never publish a flexible-price
counterfactual -- which the kappa -> inf gate produces here for free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author decision 2026-08-05. Scope narrows to price stickiness; the labour block
is untouched, so labor_market_D/F stays in every block list and keeps
labor_mkt_res_D/F as the target for N.

System count is unchanged at 27x27 -- the wage curve had replaced
labor_mkt_res one-for-one, so removing it swaps a target name back rather than
changing the count. Four new residuals (two price NKPCs, ToT identity, union
normalisation), all exactly zero at the existing steady state. Flex is now the
kappa_p -> inf limit of a single curve.

kappa_w and epsilon_w drop out of the calibration; mu_w stays 1.0 and untouched.
Scope now matches Bi-Foerster-Traum on the labour side (both price-sticky,
flexible wages), which removes the need to defend a wage slope with no benchmark
in this model class.

A Taylor rule was scoped and rejected the same day; the reasoning is recorded
under "Explicitly out of scope" so it is not re-derived: a policy rule only bites
if it pins a real rate, which frees both deposit-market conditions and requires
either a cross-border banking claim (rewriting external_account_D) or an ECB
reserve asset inside the GK incentive constraint.

Also records that wage rigidity's loss costs the hours-rationing component of
distributional incidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two additions, both surfaced by asking whether the spec fixes C_D[0] = +0.2164%.

Nominal deposits. rdep_{D,F} is reinterpreted as a nominal rate i_dep_{D,F} and
stays the free unknown clearing deposit_mkt_{D,F} -- no absorber, no cross-border
claim, external_account_D untouched, still 27x27. The absorber problem belonged
to the Taylor rule alone, not to nominal contracts. deposit_return already has the
right shape (predetermined rate x period-t deflator) so it is a one-line change,
and T-2 is not reopened. Two derived real rates, ex-post and ex-ante, substitute
into bank_return, capital_fund, P1 and the bond FOCs per a stated table; all three
collapse to today's rdep at pi = 0, so the SS stays bit-identical.

Banks hold real assets against nominal liabilities, so deflation deepens the
net-worth loss -- Fisher-Bernanke, aimed at Y_D[0] being two orders of magnitude
below the benchmark.

Markup rent routing, which was a genuine gap: with the markup in labour demand
only, w*N = mu_p*mc*(1-alpha)Y while mpk is untouched, leaving an unrouted rent
off steady state -- a Walras leak of the W-1/W-2 class. Distributed proportional
to productivity e (Auclert-Rognlie-Straub), so household labour+profit income is
(1-alpha)Y*e exactly as in the flex model, the wedge affects hiring only, and the
marginal wage stays w so labor_market is unchanged. The textbook lump-sum rebate
was rejected: markups are countercyclical, so it would push C_D[0] further up.

Rollout is now staged so price stickiness and the Fisher channel are separately
attributable, and the spec states plainly that C_D[0] may not flip -- BFT get
consumption rising on impact too, with a Taylor rule, nominal debt and a
loan-in-advance constraint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 tasks, 87 steps, across three phases: a behaviour-preserving refactor, price
Phillips curves with real deposits, then nominal deposits, followed by
recalibration and regeneration.

Adds fast unit tests (code/test_nkpc_blocks.py) that evaluate SSJ @simple blocks
directly via .steady_state(), so sign and identity errors surface in seconds
rather than after a 10-minute pipeline run.

One refinement to the spec, adopted for a strictly smaller diff: rdep_i keeps its
name and becomes the derived EX-ANTE real rate, which is already exactly what
intermediation_P1, divert_bond_foc and divert_portfolio_adj mean by it. Those
three blocks and steady_state.py's eight rdep reads therefore need no changes at
all; only deposit_return, bank_return and capital_fund do, plus one new block per
country.

Three hard gates: main.py output byte-identical after the refactor; IRFs matching
the pre-change baseline at kappa_p = 1e4; and n_inter_D[0] strictly more negative
once deposits go nominal, which catches a reversed Fisher channel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Read .claude/hooks/require-docs-before-commit.sh: it denies any commit staging
code/** or *.py unless STATE.md, PROGRESS.md AND HANDOFF.md are all staged.
Every intermediate commit in the plan staged only PROGRESS.md and would have
been blocked. All 13 code-staging git add lines now list all three; the two
docs-only commits are untouched since the gate does not fire on them.

Also records that main.py (~8-12 min) and regime_model.py --force exceed the
Bash tool's 10-minute cap and must run in the background, so a timeout is not
mistaken for a model failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CLAUDE.md's goods_mkt_D <= 1e-14 refers to the DYNAMIC solve, where it is an
explicit solver target and never printed on its own. What main.py prints under
"SS goods residuals" are the steady-state values, which sit at ~4e-7 on
unmodified code. A subagent checking the doc line against the printed line would
report a false failure.

Records the full measured baseline from the verified Task 1 run at f3711bd --
SS residuals, the block-table verdict, b_gov_D[499] stability, rho_b, both
impact signs, and the TPI per-gamma maxima -- so every later task compares
against reality.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No-op. Verified main.py output is byte-identical before and after.
2/3 tests green. test_firm_profit_restores_factor_exhaustion_off_steady_state
is intentionally red until Task 4 adds mu_p/mc to labor_demand_D.
Turns test_firm_profit_restores_factor_exhaustion_off_steady_state green.
Full nkpc block test file now 8 passed, 0 failed.
Two new global blocks: terms_of_trade turns the fixed-exchange-rate
identity p/p(-1) = (1+pi_F)/(1+pi_D) into a residual on p (already an
unknown via goods_mkt_D), pinning pi_D - pi_F. union_inflation supplies
the level normalisation (omega_pi_D*pi_D + (1-omega_pi_D)*pi_F = 0, the
ECB's phi_pi -> inf union-PPI-stabilisation limit) with no policy rate
or Fisher relation anywhere in the model. At omega_pi_D = 0.071 this is
a 93/7 split of any terms-of-trade move into Greek deflation vs German
inflation -- the internal-devaluation pattern.

Also fixes a bug found in the new test itself:
test_closure_puts_93pct_of_tot_move_into_D_deflation used dlog_p=1e-4,
but the closed-form pi's are only first-order accurate against the
exact nonlinear tot_res, leaving an O(dlog_p^2) gap (~4e-5 relative at
that scale) that misses the test's own rel=1e-6 tolerance. Narrowed to
dlog_p=1e-6, verified numerically to clear tolerance with margin; the
block implementations were correct as specified throughout.

code/test_nkpc_blocks.py: 11 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous form asserted log((1+pi_F)/(1+pi_D)) == dlog_p, which holds only
to first order; the O(dlog_p^2) truncation is 0.429*dlog_p relative and forced
an artificially small dlog_p to pass. Asserting the net-rate split instead is
exact and survives changes to omega_pi_D, which is swept later in this plan.
Solved steady state verified byte-identical to the pre-change baseline.
…e gate

build_block_list gains firm_profit_D/F, price_nkpc_D/F, terms_of_trade and
union_inflation; build_and_solve goes 23x23 -> 27x27 (+mc_D, pi_D, mc_F, pi_F /
+nkpc_p_res_D/F, tot_res, union_pi_res). No existing unknown or target renamed
or dropped.

Gate: as kappa_p -> inf the NKPC forces mu_p*mc -> 1 and labour demand collapses
to competitive, so the system must reproduce the pre-change 23x23 IRFs. It does,
with textbook O(1/kappa_p) convergence -- worst relative deviation 2.925e-03 at
kappa_p=1e4, 2.925e-04 at 1e5 (threshold 1e-3, PASSED), 2.925e-05 at 1e6, with
every one of the 30 IRF series shrinking by exactly 10.00x per decade and the SS
levels bit-identical. Harness: code/dump_irfs.py.

Also fixes an SSJ 1.0.0 defect this system is the first to hit.
CombinedBlock._jacobian only visits a block whose inputs intersect the shock
list, so a target that is a pure function of the solver's own unknowns never
enters H_Z. All four new targets are exactly that, so SSJ handed a 23-row H_Z to
a 27x27 H_U and numpy raised a core-dimension mismatch. full_model.
solve_jacobian_padded() restores the missing rows as zeros -- exact, not an
approximation, since dH/dZ at fixed unknowns is identically zero when the shock
does not appear in the equation -- and otherwise mirrors Block.solve_jacobian
line-for-line. tpi.py routes through it too, and it prints the padded row names
on every solve so the padding cannot go silent.

Full code/main.py at the flex limit reproduces every monitored baseline number,
including both TPI sanity checks at exactly 0.00e+00. test_nkpc_blocks.py: 11
passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Task 9 hit a real SSJ 1.0.0 defect. CombinedBlock._jacobian seeds total_Js from
the shock list and returns total_Js[original_outputs & total_Js.outputs, :], so
a target reachable from no shock is silently dropped from H_Z; solve_jacobian
then hands mismatched shapes to np.linalg.solve. All four new targets are pure
functions of the solver's own unknowns, so H_Z came back with 23 rows against a
27x27 H_U. Fixed by full_model.solve_jacobian_padded(), which restores the rows
as zeros -- exact, since dH/dZ at fixed unknowns is identically zero when the
shock never appears in the equation.

Records why the proposed 25x25 rewrite does NOT avoid this: nkpc_p_res would
still depend only on unknowns, so it would still be dropped, giving 23 rows
against 25x25. Same defect, at the cost of rewriting committed work.

Adds Task 9b to convert the seven remaining call sites. regime_model.py blocks
the E1-E4 cache rebuild and e4_distribution.py blocks E4, so this is not
optional cleanup.

Gate kappa corrected to 1e5: at 1e4 the worst deviation was 2.925e-03. The
deviation falls exactly 10x per decade with an identical 2.925 mantissa at 1e4,
1e5 and 1e6 -- a clean O(1/kappa_p) limit, which is the convergent-limit
diagnostic the gate was designed to test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SSJ 1.0.0 drops H_Z rows for targets reachable from no shock. Seven call sites
would have hit the core-dimension mismatch on first contact with the 27x27
system; regime_model.py blocks the E1-E4 cache rebuild.
At the calibrated kappa_p = 0.0871 with deposits still real:

  Y_D[0]        -0.0149%  ->  -0.4923%   (33x)
  C_D[0]        +0.2164%  ->  -0.4904%   SIGN FLIP
  I_D[0]        -0.7718%  ->  -0.9907%
  n_inter_D[0]  -3.3804%  ->  -4.0140%

Steady state byte-identical; both impact signs negative; all four TPI gammas
inside 1e-7. The flex column reproduces STATE.md's own E1 passive row to every
printed digit, which is the check that the SS divisors are right.

Y_D[0] is now in range of Bi-Foerster-Traum's -0.6% without their loan-in-advance
constraint; the pre-change model was two orders of magnitude below it.

Caveat recorded prominently: this is a ONE-QUARTER SPIKE, not a downturn. Output
and consumption are both positive from quarter 1, and flex consumption is more
persistently negative from quarter 2 on. The claim is that sticky prices fix the
impact quarter, not that they resolve the investment-bust counterfactual.

kappa_p sweep {0.03, 0.0871, 0.2} is monotone and stable throughout, so the sign
flip is not knife-edge -- C_D[0] is negative across the whole sticky range.

Also fixes dump_irfs.py to store SS levels for every series with an assertion.
I_D had no ss__ entry, so a consumer fell back to a divisor of 1.0 and reported a
level deviation as a percentage -- the mislabelling class CLAUDE.md records for
n_inter and K.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Splits rdep_D/F's two meanings without a wide rename. New i_dep_D/F is the
nominal deposit rate (Task 13 wires it as the solver unknown). rdep_D/F keeps
its name and its existing ex-ante meaning -- the real rate for t -> t+1 locked
at t -- so intermediation_P1_D/F, divert_bond_foc_D/F and divert_portfolio_adj
need zero changes. New rdep_expost_D/F is the realised real rate on deposits
placed at t-1, carrying the inflation surprise that drives the Fisher-Bernanke
channel (banks hold real assets against nominal liabilities, so deflation
deepens the net-worth loss). T-2 not reopened: deposit_return_D/F still locks
the rate at i_dep(-1); only the deflator is period-t. At pi=0 all three
collapse to i_dep, so the steady state stays bit-identical.

code/test_nkpc_blocks.py: 11 -> 15 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rices

Sticky prices (NKPC blocks, Task 9-10) and nominal deposit contracts
(Task 11-13) both raised GR-DE spread transmission on a 1pp default
shock from the pre-change 150.4bp to 162.0bp at psi_lambda_B=8.5, an
8% overshoot of the paper's 150bp calibration target. Re-bisected
psi_lambda_B_D/F holding everything else fixed: 8.5 -> 162.14bp,
7.0 -> 136.21bp, 7.8 -> 149.16bp, 7.85 -> 150.14bp (adopted, within
1bp). b_gov_D[499] stayed in the ~1e-5..1e-4 band throughout, no
instability opened up while descending from 8.5.

Full-pipeline re-verification: SS residuals byte-identical to every
prior task's baseline (the dial only touches dynamics). gamma=0 peak
spread = 150.0bp on target; gamma=2/5/10 still monotone declining;
n_inter_D[0]/Y_D[0] both negative (correct doom-loop sign). 17/17
block tests still pass.

E1-E4 in docs/experiments_results.md are now stale until the regime
cache is rebuilt (Task 15).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…model

Cache rebuilt (psilam7p85_cal685f7838) BEFORE run_all.py — experiments read
cached Jacobians and never re-solve, so the reverse order silently reports the
flexible-price model.

E1: loading 4.00/3.17 -> 3.82/2.90, still monotone decreasing and above 1
throughout (schedule 4.43 -> 1.49). Live Claims 1 and 5 survive. Y_D[0] and
C_D[0] still positive under medium/aggressive.
E2: market_clearing_D closes at 2e-16 vs the 1e-07 assertion — nothing leaked
into the resource constraint. Headline-vs-channels ordering reverses.
E3: full writeoff now inverts Live Claim 1 only at aggressive (0.26); medium
holds at 2.46.
E4: stale decile cache rebuilt; paper figures and tables re-emitted.

run_all.py: two hardcoded flex-price captions in the GENERATED document now
compute live — E3's psi_lambda_B was stale, E2's was stating the opposite of
the table above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Task 16 of the nominal-rigidities plan: consolidate the living docs, which
had accumulated one appended section per task across STATE.md, PROGRESS.md
and HANDOFF.md, and correct several places where the prose no longer matched
the numbers.

What the docs now record. The model is sticky-price with nominal deposit
contracts. Rotemberg price Phillips curves in both countries with a markup
wedge in labour demand and a markup rent distributed proportional to
productivity e; wages stay flexible. The nominal side closes with no policy
rate: the monetary-union terms-of-trade identity pins the inflation
differential off the existing unknown p, and a union-inflation normalisation
at omega_pi_D = 0.071 pins the level, putting 93% of any terms-of-trade
adjustment into Greek deflation. Deposits are nominal (i_dep is the solver
unknown, rdep stays ex-ante, rdep_expost carries the inflation surprise into
bank_return and capital_fund); sovereign bonds stay real, a deliberate
asymmetry that maximises banks' Fisher exposure and is now flagged in SPEC.md
as something the paper must state. Solver system 23x23 -> 27x27. The steady
state is bit-identical to pre-change, because markups are subsidy-neutralised
and pi_ss = 0.

Headline numbers, impact on a 1pp default shock, % of own SS, both columns on
the same 150bp peak-spread moment:

                flex/real deposits    sticky+nominal (psi_lambda_B 8.5 -> 7.85)
  peak spread   150.4 bp              150.0 bp
  Y_D[0]        -0.0149               -0.5064
  C_D[0]        +0.2164               -0.5103
  I_D[0]        -0.7718               -1.0114
  n_inter_D[0]  -3.3804               -4.2962

Live Claim 5 (self-extinguishing premium) survives: the loading is monotone
decreasing, 4.43 -> 1.49 over 59 grid points on gamma in [0.51, 30.00], above
1 throughout. Live Claim 1 (over-compensation) survives at 3.82 / 2.90. E2's
identity closes at 3.5e-17 against its 1e-07 assertion.

Caveat recorded prominently and not to be dropped: this is a one-quarter
spike, not a downturn. Output and consumption are both positive from quarter 1
and flexible-price consumption is more persistently negative from quarter 2
on. The honest claim is that the model fixes the impact quarter, not that it
resolves the investment-bust counterfactual.

New watch item: n_inter_D[0] is now positive (+0.924) under the aggressive
backstop, where it was -1.099, so with Y_D[0] = +0.8721 and C_D[0] = +1.5143
the aggressive regime produces an impact boom rather than a cushioned bust.

Also documented as a hard requirement: full_model.solve_jacobian_padded() and
the SSJ 1.0.0 defect it works around, with the grep invariant that must stay
empty.

Three prose corrections. SPEC.md's E2 caution now rests on the channels
cancelling rather than on the headline being the smaller object (the magnitude
ordering reversed: largest channel is 0.25x the headline, was ~4x). CLAUDE.md's
S-1 appendix framing now says full writeoff inverts Live Claim 1 only at
aggressive (0.26), with medium holding at 2.46. CLAUDE.md now states that E4 is
not wired into experiments/run_all.py.

Further stale prose found and either fixed or logged: SPEC.md's calibration
strategy and Live Claims 1 and 5 quoted a superseded psi_lambda_B and loading;
HANDOFF.md's E1/E2/E3/E4 headlines and its 2026-07-31 state blockquote were
flex-price. All corrected. Not fixed, and logged as a blocking open item:
experiments/paper_outputs.py's CAPTIONS dict is stale and in two places
inverted -- fig08_deciles contradicts Table 4 of the document it generates --
which needs a code change plus a figure regeneration and two re-derived
numbers.

Tests: 40 passed (code/test_nkpc_blocks.py 17, code/test_eba_calibration.py
10, experiments/ 13). Grep invariant holds. No code changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
experiments/paper_outputs.py carried a module-level CAPTIONS dict of literal
prose written against the flexible-price model. The sticky-price conversion and
the psi_lambda_B 8.5 -> 7.85 re-tune left every caption stale and three of them
inverted. Because captions are baked into the PNGs, the repo was shipping eight
tracked figures and a generated docs/paper_draft_results.md whose prose
contradicted its own tables — fig08_deciles claimed the lowest income quintile
"gains 0.95%" and the highest loses 0.59%, against a Table 4 in the same file
reading +0.4250 and -0.9073. Same hazard Task 15 fixed inside run_all.py.

CAPTIONS is now empty at import and filled at run time. save() takes the caption
as a required argument; each figure builds it from the arrays it just plotted via
a _caption_figNN helper. Directional claims are selected from the data by
_monotone, _first_quarter and sign tests, so a sign flip rewrites the sentence
instead of lying inside it: fig02 will print "does NOT fall — the
self-extinguishing-premium claim fails at this calibration" if the loading
schedule stops declining, and fig05 will refuse the German-ledger reading if
exposure and loading stop opposing. main() gained a prose-vs-table assertion:
fig01's caption and Table 3 must agree on impact bank net worth via their two
independent routes (the cache directly vs e1.run()'s payload).

Corrections, all now derived:
  fig01  net worth 3.4% / investment -0.77%  ->  -4.3% / -1.0%, plus the
         reversal quarters (net worth q5, spread q8)
  fig02  4.5x ... 2.1x  ->  4.43x at gamma=0.51 -> 1.49x at gamma=30,
         monotone, above 1 throughout
  fig03  INVERTED: "each roughly four times the headline" -> consumption
         carries 0.99x the headline, investment +0.25x, net exports -0.21x
  fig04  3% / 97%  ->  3.4% / 96.6%, re-derived (below)
  fig05  qualitative -> exposure 0 -> 0.92% of quarterly Y_D, loading
         4.43x -> 1.49x
  fig06  INVERTED: net path smaller than its components "at every horizon" is
         false in the impact quarter; true in 14 of the first 16
  fig07  hardcoded 23/52/25 -> read from the npz (22.9/52.4/24.7); hawk span
         2010-2014 derived. Estimated from market data, so model-independent —
         confirmed rather than assumed.
  fig08  INVERTED: Q1 +0.4250 / Q5 -0.9073, backstop gain +2.01 / +1.34;
         consumption FALLS ~0.51% in every quintile on impact, and the minimum
         over 40 quarters is the impact quarter, not quarter five.

fig04 re-derivation. Default loading per unit of default probability is
EL_price_D + psi_spread_D (bond-pricing FOC, equations_D.py:566).
EL_price_D = (1-0.30)*0.0777006/0.968941 = 0.056134 and does not depend on
psi_lambda_B. psi_spread_D = lambda_gk_D*psi_lambda_B_D/(beta_inter_D*Omega_D)
(steady_state.py:104) is LINEAR in psi_lambda_B, so 8.5 -> 7.85 took it
1.737724 -> 1.604839. Split 0.056134/1.660973 = 3.4% fundamental expected loss,
96.6% collateral-friction wedge (was 3.1%/96.9% at 8.5).

No model, calibration or equation change: the result tables are numerically
identical to the pre-fix run. 8 figures and docs/paper_draft_results.md
regenerated; every caption cross-checked against its table, no contradictions
remain. Tests: 40 passed. Removes the standing "do not quote any figure caption"
warning from STATE.md and HANDOFF.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adambutlin adambutlin changed the title Sticky prices and nominal deposits become the baseline Sticky prices and nominal deposits: consumption now falls in a crisis Aug 6, 2026
@adambutlin adambutlin changed the title Sticky prices and nominal deposits: consumption now falls in a crisis EBA calibration, regime discipline and nominal rigidities — consumption now falls in a crisis Aug 6, 2026
adambutlin and others added 3 commits August 6, 2026 16:20
capital_adj_D/F gain S(x) = (omega_I/2)(x-1)^2 with x = I/I(-1); effective
investment (1-S)*I drives the installation technology, and the FOC picks up
the intertemporal term. S(1) = S'(1) = 0 makes this exactly SS-neutral, and
omega_I is calibrated to 0 so the committed model is provably unchanged:
the equivalence gate reproduces the pre-change IRFs to 1.08e-13 across all
45 dumped arrays.

Discounted at beta, not SDF. That is first-order exact here (S'(1) = 0, so
only SDF_ss = beta survives linearisation -- the same argument price_nkpc_D
already uses) and it is required, since taking SDF_D makes SSJ's topological
sort fail: hh_D -> capital_fund_D -> capital_adj_D -> sdf_D -> ghh_composite_D.

Sweeping omega_I in {0,2,5,10} does NOT fix the one-quarter-contraction
problem (issue I-1): it shrinks the contraction toward zero rather than
lengthening it, and at omega_I >= 5 Y_D[0] turns positive. Same failure mode
as the earlier rejected chi1 diagnostic -- both reallocate the impact between
I and C without deepening it. Recorded in STATE.md as a negative result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…da_B

rho_def and rho_Z were hardcoded in code/full_model.py. Both are now
calibration entries (with the old literals as a fallback), and rho_def is set
to 0.9408 -- the quarterly equivalent (0.9798499^3) of the crisis-state monthly
persistence in Empirics/outputs/ms_regime_GRC.npz. The old 0.80 implied a
14-month crisis against an estimated 50-month expected duration and a realised
92-month episode. rho_Z is deliberately left at 0.80.

psi_lambda_B re-tuned 7.85 -> 2.92 to hold the 150bp GR-DE peak-spread moment
(470.62bp at the old value). Full pipeline re-solve per bisection point; the
patch-the-SS shortcut is wrong and is documented as such.

Consequences: cumulative 40q Y goes -0.049 -> -2.542, negative-Y quarters 5 ->
37, all four impact signs still negative. The default-loading split moves
3.4%/96.6% -> 8.6%/91.4% fundamental/friction, which weakens the paper's
constrained-seller claim quantitatively. SS bit-identical; b_gov_D[499] falls
4.63e-05 -> 2.04e-05; TPI loading 5.55/5.37/5.13 still monotone and above 1.

E1-E4 and the paper artefacts are now stale and must be regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cache, E1-E4 and the eight paper figures rebuilt at rho_def=0.9408,
psi_lambda_B=2.92. E2 closes at 1.1e-16 against its 1e-7 assertion.
fig07_ms_regimes correctly unchanged -- it is estimated from market data.

LIVE CLAIM 5 IS MATERIALLY WEAKER. The loading schedule over gamma in
[0.51, 30] was 4.43 -> 1.49 (a 66% decline, approaching the fair-value floor
of 1); it is now 5.65 -> 4.59 (19%). Monotone decreasing still holds, so the
claim survives literally, but the economic punchline -- that the premium
extinguishes itself as the backstop grows -- does not: it bottoms at 4.6x
over-compensation. A longer crisis accumulates premium over more quarters than
expected loss, so the ratio stays high even under heavy intervention.

Live Claim 1 is correspondingly STRONGER: the floor rises 1.49 -> 4.59, so
over-compensation no longer depends on where on the schedule you look.

E1 also shifts: investment is negative even under the aggressive backstop
(-0.0508, was +0.3977) and the aggressive net-worth boom is milder (+0.469,
was +0.924) with a larger consumption effect (+2.24, was +1.51).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adambutlin adambutlin changed the title EBA calibration, regime discipline and nominal rigidities — consumption now falls in a crisis EBA calibration, regime discipline and nominal rigidities — consumption falls and the crisis now lasts Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant