GK structural refactor: no sovereign spread wedge + canonical CB mechanism - #33
Open
adambutlin wants to merge 43 commits into
Open
GK structural refactor: no sovereign spread wedge + canonical CB mechanism#33adambutlin wants to merge 43 commits into
adambutlin wants to merge 43 commits into
Conversation
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>
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>
The model normalised Y_D_ss = Y_F_ss = 1, making Greece and Germany the same size, while every EBA moment is a ratio to its own country's net worth. Cross- border stocks built as `phi * n_holder / q` landed in the holder's units, so the model could match the portfolio-composition moment (phi_bD_F = 0.0075) or the market-structure moment (foreigners hold 12.72% of the bank-held Greek stock), never both — joint consistency needs n_F/n_D = 8.85 against the model's 0.761, a gap equal to the Germany/Greece GDP ratio. size_F = 11.697 (Eurostat 2010, from data/eba_moments.json raw_EURm). Convention: every F variable is per F capita and O(1); every D variable is a D aggregate. The weight appears only where the two countries meet — trade_balance, external_account_D, global_goods_mkt, domestic_bond_clearing, and the three _tpi overrides. omega splits into omega_D = 0.85 / omega_F = 0.98717 by symmetric bilateral trade intensity, since a shared home bias makes the larger country's imports from the smaller size_F times too large. Exposes a TPI bug present since the block was written: budget_residual_F_tpi paid rem_cb_F, a D-aggregate ECB cash flow, into a per-F-capita budget. At equal country size the missing weight was exactly 1.0, so goods_mkt_F sat at 2e-10 and it passed every check. Under size_F it leaked 1.98e-2 of F GDP at gamma=10 while gamma=0 stayed clean. Every TPI number computed before this carries the error, growing with deployment. psi_lambda_B re-tuned 2.92 -> 3.01 (145.20 -> 149.93 bp) since size_F damps cross-border amplification. Verified (code/main.py, exit 0): foreign shares 0.1274 / 0.001298 against EBA 0.1272 / 0.001301 — both moments hold jointly for the first time; phi_bD_F exact; K_D 10.800, K_F 10.824 vs the 10.8 check; goods_mkt_D/F ~4.2e-07; ca_res_D -2.8e-17; max|goods_mkt_F| 2.06e-10..2.12e-10 across gamma; IC residuals machine-zero; GK well-posed; n_inter_D[0] -6.7366%, Y_D[0] -0.8521%; peak spread 150.0 bp. 48/48 fast tests, including a new test_cross_border_units.py that locks the per-capita/aggregate convention and the direction of the weight. E1-E4 and all paper artefacts are STALE and must be regenerated before use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ssion bracket
Audit of two existing mechanisms, prompted by whether the fiscal block could
generate foreign retrenchment without new wedges or shocks. It cannot. No model
equations changed; what changed is what can be claimed about the parameters.
phi_lamb_D (Bohn rule), swept {0.15, 0.10, 0.07, 0.05}: the stability boundary is
between 0.10 and 0.07, not the 0.05 the printed rho_b gate predicts. That gate is
partial-equilibrium, omits def_scale_D, and is optimistic enough to land a user on
a divergent calibration; it is now labelled as not being the stationarity test.
b_D_F turns negative only in the divergent region, so no stationary calibration of
the Bohn rule produces retrenchment. phi_lamb_D = 0.15 keeps ~1.5x margin and stays.
mv_rule_D = 0 justified rather than assumed: Maastricht debt is nominal face value,
and the par and market-value gaps move in opposite directions in a crisis (par
positive 39/40 quarters, market-value negative 40/40 and 2.88x larger, which would
cut taxes 0.75% of quarterly GDP at impact). The "market-value rule REQUIRED"
comment was stale from the CT1 scope and is retired.
Fiscal limit estimated (Empirics/fiscal_limit.py, new): BFT's logistic on Eurostat
gov_10q_ggdebt plus the repo's Greek-Bund spreads. Preferred pre-OMT sample
eta0 = -14.80 (0.55), eta_s = 7.67 (0.47), R2 = 0.849, n = 50 — same family as their
Italian -10.70/5.25. Post-2012 must be excluded: OMT severed the debt-spread link
(debt 152->181%, spread 13.4->6.3->1.4pp), which would build the studied policy into
the parameter. def_scale_D stays 0.25, now with provenance — it sits inside the
estimated range (0.04 full / 0.19 crisis / 0.63 pre-OMT), and the best-fitting 0.633
is unusable because psi_lambda_B is not continuously calibratable there.
TPI is materially less effective after the size_F and rem_cb_F fixes: the same
compression needs ~8x the intervention. Named regimes are now gamma = 0 / 11.47 /
33.66 (were 0 / ~1.6 / ~5.1). The 50% target stopped bracketing on [0,25] and
aborted run_all.py; gamma_for_compression now uses hi=40. There is a CLOSED-LOOP
SINGULARITY at gamma ~ 42 with a separate branch beyond it — a coarse scan sampling
40 and 50 reads the two as one smooth decline and hides it. The aggressive regime
now sits ~8 gamma-units below that pole.
All downstream artefacts regenerated in the documented order against the live
calibration: regime cache, E1-E3, E4, paper outputs. 48/48 fast tests pass.
Also adds a UserPromptSubmit hook that reminds Claude to gloss model variable names
with a short definition on first use in prose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l audit Integrates the add-nkpc workstream and the fix-cross-border-units work (32 commits). main was last at the pre-sticky-price state; this brings it current. Model changes: - Rotemberg price NKPC in both countries, subsidy-neutralised so the steady state is bit-identical to the flex-price model; wages stay flexible. 27x27 solver system. - Nominal deposit contracts: i_dep is the unknown, rdep the derived ex-ante real rate, rdep_expost carries the inflation surprise (the Fisher channel on bank liabilities). - rho_def disciplined at 0.9408 by the repo's Markov-switching regime estimate. - Investment-flow adjustment cost S(I/I(-1)), inactive at omega_I = 0. - COUNTRY-SIZE ASYMMETRY: size_F = 11.697. Every F variable is per F capita, every D variable a D aggregate. This is what lets the model match the EBA portfolio- composition moment and the market-structure moment at the same time — previously impossible, and the reason foreigners held 1.25% of the bank-held Greek stock against 12.72% in the data. Home bias splits into omega_D/omega_F accordingly. - Fixes a TPI conduit bug that size_F exposed: budget_residual_F_tpi paid a D-aggregate ECB cash flow into a per-F-capita budget. Invisible at equal country size; leaked 2e-2 of F GDP at gamma=10. Every TPI number computed before this carried it, growing with deployment. - psi_lambda_B re-tuned to 3.01 to hold the 150bp peak-spread moment. Calibration provenance added for phi_lamb_D, mv_rule_D and def_scale_D; see docs/STATE.md. All artefacts regenerated against the live calibration. Known open items on merge: foreign banks still do not retrench on a default shock (contrary to 2010-12 and to Bi-Foerster-Traum), and F is Germany rather than the core aggregate because the EBA sample is GR+DE by construction. Both in docs/HANDOFF.md.
Audit of the sovereign-risk-to-bank-financing block, then the first of five
stages. No behavioural change: IRFs are bit-identical to main.
AUDIT. The intended chain p_def -> Delta_bD_eff -> IC -> lambda_gk/Omega ->
intermediation_P1_D -> q_b_D is broken at the third arrow. Delta_bD_eff enters
intermediation_IC_D only, moves theta_D, and dead-ends. The Greek spread is set
entirely by divert_bond_foc_D, a hand-written rule referencing no endogenous GK
object -- only the frozen scalar psi_spread_D computed once at the SS.
bond_price_ss_D, steady_auxilliary_D and smart_steady_D are SS-only and absent
from build_block_list().
GK portfolio optimality is violated at the steady state, which is why the wedge
exists: nu_bD_D/nu_K_D = 0.2491 against Delta_bD_D = 0.20, and nu_bD_D equals
nu_bF_D bit-identically (0.02696043) while Delta_bD_D = 0.20 vs Delta_bF_D =
0.40. steady_auxilliary_D defines the marginal values from returns and never
restricts them; the portfolio FOCs are imposed nowhere, in neither the SS nor
the dynamics.
STAGE 1. New collateral_quality_D/F export Delta_bD_eff_D, Delta_bF_eff_D,
Delta_bF_eff_F, Delta_bD_eff_F under a bounded map
z = psi_lambda_B*def_rate(+1)/(1-Delta); Delta_eff = Delta + (1-Delta)*z/(1+z)
Range [Delta,1), monotone, and d Delta_eff/d def_rate(+1) at 0 is psi_lambda_B
exactly (SSJ Jacobian: 3.0100000000), so the linearisation is untouched and
psi_lambda_B keeps its calibrated meaning. Closes the domain hole: the old
linear form left [0,1] at def_rate(+1) > 0.266 and drove 1-Delta_eff negative.
NEW SSJ GOTCHA. np.exp in a @simple block raises "TypeError: loop of ufunc does
not support argument 0 of type AccumulatedDerivative" -- simple blocks
differentiate through a dual-number type supporting arithmetic operators only.
Hence rational, not exponential, saturation. Residual caveat: a pole at
def_rate(+1) = -0.2658, unreachable here, would bind in code/global/.
DOC DRIFT CORRECTED. The eba_calibration.md ledger said Delta_own committed at
0.80 / cross at 0.90; CLAUDE.md's GK-1 row said Delta=0.85/0.90 ->
lambda_gk_D=+0.927. Live values are 0.20 / 0.40 with lambda_gk_D = 2.2129. Both
described the CT1 scope; GK-2's broad scope cut phi_own 2.39 -> 0.456, which
satisfies feasibility at the inherited Delta, so the raise was never adopted.
~4x error for anyone computing the collateral channel from the old numbers.
VERIFICATION. code/main.py exit 0, bit-identical to baseline: n_inter_D[0] =
-6.7366%, Y_D[0] = -0.8521%, peak spread +0.375 pp, goods_mkt_D =
-4.2493163257550925e-07, max|goods_mkt_F| 2.06e-10..2.12e-10 across the gamma
grid, K_D/K_F = 10.800/10.824. 35 fast tests pass; the solve_jacobian_padded
invariant grep is empty.
Stages 2-5 (not started) will move the steady state and need a full
recalibration: author decision is option (i), with EL_price_D retained (option
(b), S-1 stands). psi_bD_F/psi_bF_D must survive stage 4 -- they are the
coexistence and stationarity device, not spread wedges.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…CB mechanism
Sovereign pricing is now generated by the bond's state-contingent payoff inside the
genuine Gertler-Karadi portfolio FOC. There is no free spread parameter left in the
model.
The root cause was the PAYOFF, not the pricing block. zeta_writeoff_D = 0 wrote down
only the current coupon on default and left the perpetuity's continuation value whole,
understating the loss on a 12.9-quarter claim by [delta_b + (1-delta_b)q_b]/delta_b =
12.6x (EL 0.0561 against the contract's 0.7014). psi_spread_D = 0.615 was standing in
for almost exactly that gap.
Deleted, not recalibrated: psi_spread_D/F, EL_price_D/F as a pricing wedge,
divert_bond_foc_D/F, divert_portfolio_adj, bond_price_ss_D/F, domestic_bond_foc_D/F,
portfolio_adj_cost, and the excess_return_*_ss anchors. An AST scanner
(test_no_ad_hoc_sovereign_spread_wedge_anywhere) fails if any of those names reappears
in live code under code/.
What replaces them:
* gk_bond_foc_D/F impose nu_own/nu_K = Delta_own_eff, which with intermediation_P1
is rb_exp(+1) - rdep = Delta_eff * (rk(+1) - rdep). q_b_D and q_b_F are now SS
unknowns pinned by these residuals.
* gk_cross_border_foc imposes the same condition on the cross legs plus a portfolio
adjustment cost, so the own legs pin PRICES and the cross legs pin QUANTITIES.
* collateral_quality_D/F export a bounded, forward-looking pledgeability map with
local slope psi_lambda_B, replacing the unbounded inline linear form.
* bond_return_D/F are the single source of truth for default loss, emitting rb_exp
(prices), rb_actual (realised on the traced branch) and EL_load (diagnostic only).
Calibration: zeta_writeoff = 1, psi_lambda_B = 0, all four Delta = 0.20. The
150bp-moment tuning history for psi_lambda_B is void -- it was conditional on a payoff
that under-priced default 12.6x.
Steady state: q_b_D = 0.974906, q_b_F = 0.965974, both yields 80.0bp annualised, SS
spread exactly 0, lambda_gk_D = 2.1087, Omega_D = 10.3462, EL_load_D = 0.7014. All
four GK portfolio FOC legs verify to <= 2.1e-13 on every solved SS.
Dynamics on a 1pp default shock, none of it tuned to a moment:
peak spread 205.87 bp Y_D -1.9742% C_D -2.5108% I_D -3.0046%
N_D -3.0010% w_D -5.9102% n_inter_D -11.4073% q_b_D -5.9498%
TPI loading 0.520 / 0.504 / 0.482 at gamma = 2/5/10 -- below 1, so the ECB
over-compensation headline is reversed, and near-invariant across all three arms.
Canonical central-bank mechanism (docs/cb_mechanism.md, new)
------------------------------------------------------------
Promoted from the CB-block audit in diagnostics/cb_audit/, which changed no model
source. The CB block came through the refactor untouched and correct: the four TPI
blocks are byte-identical to their pre-refactor versions, all mark exclusively at the
endogenous q_b_D, cb_flow_D matches bond_return_D term for term, and SS neutrality is
exact -- every TPI block output is bit-identical to its non-TPI counterpart.
* The spread identity. With psi_lambda_B = 0, SDF_banker and Omega_p1 cancel in
nu_bD_D/nu_K_D, leaving rb_exp_D(+1) - rdep_D = 0.20*(rk_D(+1) - rdep_D) exactly.
The CB has no direct lever on the spread; it compresses only by lowering rk_D.
TPI's spread effect and its investment effect are ONE effect.
* The 2x2 sovereign-holdings matrix (new diagnostic, probe_portfolio.py; both
clearing identities close to <= 1.2e-15 at 26 checkpoints). ~72% of the CB book in
market value -- 84% in quantity at impact, ~99% from t=1 -- is bought from GERMAN
banks, 17% is new Greek issuance, only 10% comes off Greek banks.
* The relief is a denominator effect. TPI at gamma=10 undoes 82.9% of the crisis
rise in phi_bD_D while the numerator moves -0.4% and n_inter_D recovers +6.6%.
Price support and mark-to-market recapitalisation, not balance-sheet relief.
* The "closed-loop pole at gamma ~ 27.3" is a T=500 terminal-truncation artefact.
The resonant eigenvector carries 0.0000 of its mass in the first 100 quarters and
0.9922 in t=400-499; A_cb[499,499] = +1.080 is the only positive diagonal in the
matrix. Dropping five columns removes every pole below gamma=36 and changes the
reported peak spread by nothing at gamma=2/5/10. No reported number is affected;
the guard and the documented claim are wrong. Fix proposed, not implemented.
Docs: STATE.md, PROGRESS.md and HANDOFF.md updated, including the pole correction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Framing objections only -- the correspondence between the coauthor's global nonlinear model in Sections 2-3 and the sequence-space code is a separate reconciliation and is parked. Ten findings, of which the load-bearing ones are: the exogenous risk process rules out the multiplicity channel the introduction spends two paragraphs attributing to OMT/TPI, and "rollover risk" in Section 2 is a misuse of the term; TPI's eligibility criteria exclude Greece 2010-12, so the counterfactual needs restating as a mechanism question; paragraph 1 promised a closed doom loop the model cannot deliver; the 87% holdings statistic was a model artefact presented as a fact and wrong by roughly five times; "who would have paid for it" was answered with capital-key exposure rather than expected cost, on a path where the operation is profitable; and the heterogeneous-agent block was advertised as a contribution while producing none of the reported findings, despite the quintile incidence results already existing. The introduction has since been rewritten against this report (Overleaf 866a537). Sections 2-4, the abstract and the appendices are unaddressed.
New Empirics/motivation_figures.py emits two paper figures with captions baked
into the PNGs:
fig_greece_motivation 2x2 -- Greek debt level, debt/GDP, the ten-year
yield against the Bund, and real GDP against real
investment indexed to 2007Q4.
fig_euro_yield_decoupling four periphery ten-year yields against the Bund,
1995-2026, with ECB/OMT/TPI markers.
Sources: Eurostat gov_10q_ggdebt (S13, na_item GD) and namq_10_gdp (B1GQ, P51G,
CLV10_MEUR, SCA); the FRED yield panel already cached by graph_spreads.py.
Outputs land in Empirics/outputs/, which is gitignored -- the tracked copies are
in the Overleaf project under VIVA/figures/.
Palette is the repo's existing Okabe-Ito order, validated for CVD separation
(worst adjacent pair dE 11.0 deutan, 15.6 normal). Germany is drawn in ink and
dashed rather than given a categorical hue, since it is the benchmark.
Numbers in the baked captions were checked against the series: PSI cut EUR 75bn
and 33.7 points of debt ratio; the ratio regained its 2011Q4 level in 2013Q2 and
the stock not until 2021Q2; investment fell 69.7% and GDP 27.4% from 2007Q4 to
the 2015Q3 trough.
No model source changed. Docs updated per project policy: STATE.md records the
figures, the two data facts and the Delta-gloss correction; PROGRESS.md carries
the changelog entry; HANDOFF.md carries session status and the next paper items.
The parameter is 0 today and will never be non-zero again: it and the risk-sensitive branch of collateral_quality_D/F are to be removed from code/, leaving Delta_*_eff constant. This matters beyond tidiness. psi_lambda_B is the analogue of Bi-Foerster-Traum's liquidity risk channel (their eq 2.24, phi^eta = 0.0125 calibrated to the 2012 Italian episode), and their own Table 1 shows that channel carrying most of both financial moments -- peak spread 6.4 -> 2.0pp without it against 5.0 in the data, excess return 5.1 -> 0.9 against 5.1. Removing ours outright is therefore the paper's sharpest separation from the nearest published analogue, and the introduction now asserts it as an absence rather than as a switched-off dial (Overleaf eda9e55). Records the open gap: the paper claims the device does not exist while collateral_quality_D still contains it at coefficient zero. The fix is the deletion, not a hedge in the text. Also corrects CLAUDE.md's GK-1 wording, which said Delta_bD_D = 0.20 "makes Greek paper worse collateral than capital". Backwards: from the IC, theta_tgt = value/lambda_gk + (1-Delta)*phi_b, so lower Delta is LESS divertable, hence BETTER collateral, which is why sovereign paper earns a fifth of the capital premium via nu_bD/nu_K = Delta. The code comment in intermediation_IC_D already had it right.
Reviews the rewritten introduction against Sections 2 and 3 of the Overleaf draft only -- no comparison to the sequence-space implementation, since Section 2 describes the coauthors' global model and the two are separate objects. Ten findings. The load-bearing ones: the introduction justifies Greece on a measurement argument (EBA exposures, maturity ladders, bilateral cross-holdings) that Section 3 does not carry out, calibrating Core/Periphery blocs from Bocola (2016)'s Italian posterior instead; the introduction under-credits Bocola three times while depending on him throughout; and the TPI experiment it describes exists nowhere in the document -- Section 2 has no central bank and Section 4 is a heading. Also records the ordering problem (the constraint channel is given primacy while Section 3 calibrates a barely-binding steady state where the anticipation channel does the work), the asset-neutrality of the constraint channel under a single divertibility parameter, Section 2's "rollover risk" against the introduction's correct statement that the model admits no self-fulfilling component, the undeclared "monetary union" in a model with no nominal side, and a notation ledger. The Disposition section records what was fixed in the Overleaf commit and what was deliberately left: the results paragraph (marked [ADD RESULTS] rather than filled with sequence-space numbers that would contradict Sections 2-3), Section 4, and the Section 2/3 notation and compile defects, which belong to the coauthors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The C8 disposition recorded a placeholder awaiting Section 4. The placeholder has been removed from the introduction at the author's instruction; the finding itself stands unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hostile review of VIVA/sections/02-model.tex read against Sections 1 and 3, with every equation in the section re-derived. Nine major findings. Three are load-bearing. Section 2 defines no equilibrium: there is no market clearing of any kind in it, no resource constraint, no parity condition, no process for Z_X (country-X TFP), and no household first-order condition. The bank's portfolio problem is over-determined, with four Euler conditions per bank against two bond prices, and the cross-border position b^F_D (D-bank holdings of F government paper) is determined by nothing. And the sentence at l. 411 explaining the risk premium contradicts its own premise and inverts the sign of E[Omega] (the expected augmented banker discount factor), which by eq. (40) rises with the priced default probability rather than falling. M9 connects the first two to the policy section. Bond-market clearing and the cross-border Euler conditions are exactly where a TPI purchase has to attach, and "which of the two banking systems supplies the bonds", one of the paper's three stated questions, is answered by the equation l. 290 omits. There is no central bank anywhere in the paper. Also recorded: what is correct and should not be touched. The CES demand system, the Jermann adjustment-cost block, the excess-return representation, Proposition 1, the pricing conditions and the two government equations all check out, and Section 3's closed-form GK steady state reproduces Section 2 exactly. I get mu_bar (steady-state constraint multiplier) = 0.000996 against 0.0010 reported, and the omega_X > 0 bound (entrant transfer positivity) at 3.8667% against 3.87% reported. Doc-only commit; no code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fresh review of the introduction, read against the equation-by-equation audit of Section 2 in referee_report_4.md rather than against Section 2's own account of itself. The central finding is that the introduction's three stated research questions map one-to-one onto the three places Section 2 is incomplete. "What the backstop does to the equilibrium" needs an equilibrium, which Section 2 never defines. "Which banking system supplies the bonds" is answered by the cross-border Euler conditions, the equations Section 2 omits. "How the gains fall across households" needs a distribution that moves aggregates, and the state vector carries none. Also recorded: the introduction opens on a 22-point output fall against a transmission channel worth roughly 0.12% per 100bp of credit spread; the motivating statistics are not model objects, and the paper disclaims the bank's empirical counterpart 130 lines after using it as evidence; the "occasionally binding constraint" justification for the global solution is not exercised, since the constraint binds at the steady state and tightens from there; and a web edit had removed the sentence saying the model cannot speak to unwarranted premia while leaving the ECB's rationale stated. Three findings run the other way. On the sign of the expected augmented discount factor, on rollover risk, and on the status of the recessionary-default claim, the introduction is correct and Section 2 is wrong. Those repairs belong in Section 2. A separate expositional review followed and is not in this file; its conclusions were applied to the introduction directly. Doc-only commit; no code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Hatchondo-Martinez bullet said duration "is what generates MTM capital losses on bank balance sheets". Duration alone generates none of them. Measured on the calibrated model, holding duration fixed at 17 quarters and varying the persistence of the latent risk factor, the reported impulse gives: rho_s = 0.00 perpetuity -1.01% one-quarter bill -1.01% ratio 1.0x rho_s = 0.50 perpetuity -1.27% one-quarter bill -1.01% ratio 1.3x rho_s = 0.95 perpetuity -5.58% one-quarter bill -1.01% ratio 5.5x Under a transitory risk shock a perpetuity is marked down no more than a bill. The channel is duration interacting with rho_s: a persistent rise keeps the default hazard elevated across the claim's whole remaining life, so it is priced into payments years ahead rather than into the next one only. The bullet now says that, records the numbers, and notes the second error the old wording invited: a short bond's principal is marked down as well, so the distinction is never coupon against principal. Surfaced while drafting the same explanation for the paper's introduction, where two successive drafts had made both mistakes. Doc-only commit; no code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two things, in one branch because the second could not be established before the first.
psi_spread→ GK. The sovereign spread is no longer a calibrated object. It is generated by the bond's state-contingent payoff inside the genuine Gertler–Karadi portfolio FOC. Every free spread parameter is deleted, not recalibrated.docs/cb_mechanism.md), established by a diagnose-only audit that changed no model source.1. The
psi_spread→ GK refactorThe root cause was the payoff, not the pricing block
zeta_writeoff_D(continuation-value write-down switch)= 0wrote down only the current coupon on default and left the perpetuity's continuation value whole. On a 12.9-quarter claim that understates the loss bypsi_spread_D(the free sovereign-spread loading)= 0.615was standing in for almost exactly that gap. The earlier conclusion that the GK mechanism was too weak to generate the Greek spread was wrong — it was being fed the wrong payoff.Deleted, not recalibrated
psi_spread_D/F,EL_price_D/Fas a pricing wedge,divert_bond_foc_D/F,divert_portfolio_adj,bond_price_ss_D/F,domestic_bond_foc_D/F,portfolio_adj_cost, and theexcess_return_*_ssanchors.code/test_nkpc_blocks.py::test_no_ad_hoc_sovereign_spread_wedge_anywhereAST-scanscode/*.pyand fails if any of those names reappears in live code. There must be no equation of the formspread += parameter * default_probability.What replaces them
gk_bond_foc_D/Fnu_own/nu_K = Delta_own_eff— withintermediation_P1,rb_exp(+1) − rdep = Delta_eff·(rk(+1) − rdep).q_b_Dandq_b_Fare now SS unknowns pinned by these residuals.gk_cross_border_foccollateral_quality_D/Fpsi_lambda_B(collateral-friction amplification dial), replacing an unbounded inline linear form that left the economic domain atdef_rate(+1) > 0.266.bond_return_D/Frb_exp(prices),rb_actual(realised on the traced branch),EL_load(diagnostic only, read by nothing that prices).Calibration consequences
zeta_writeoff = 1,psi_lambda_B = 0, all fourDelta = 0.20(the cross-border pair moved 0.40 → 0.20 because GK optimality at a riskless SS withrk_D = rk_Fandrdep_D = rdep_F = 0forces it).The 150 bp-moment tuning history for
psi_lambda_Bis void — it was conditional on a payoff that under-priced default by 12.6×.Verification
Steady state:
q_b_D = 0.974906,q_b_F = 0.965974, both yields 80.0 bp annualised, SS spread exactly 0,lambda_gk_D(GK incentive-constraint multiplier)= 2.1087,Omega_D(banker marginal value of net worth)= 10.3462,EL_load_D = 0.7014. All four GK portfolio FOC legs verify to ≤ 2.1e−13 on every solved SS (report_gk_steady_stateraises otherwise). Walras residuals:goods_mkt_D−4.23e−07,ca_res_D1.67e−16, the four sticky-price targets exactly 0. Fast tests 22/22.2. Headline dynamics — 1pp default shock, nothing tuned to a moment
Y_D(D output)C_D(D consumption)I_D(D investment)N_D(D hours)w_D(D real wage)n_inter_D(D-bank net worth)q_b_D(D sovereign bond price)K_D(D capital)Peak spread 205.87 bp annualised. German yield −16.2 bp (flight to quality).
b_DD+2.116% againstK_D−0.067% — balance-sheet crowding out.b_DF−2.101%, German banks retrenching.Ordering is the mechanism, not a coincidence: investment falls hardest, consumption next, output least. The doom loop runs through the bank balance sheet (
n_inter_D−11.4%) into the capital stock, and the household absorbs the rest through hours and wages.TPI loading 0.520 / 0.504 / 0.482 at γ = 2/5/10 — below 1, and declining. The ECB over-compensation headline is therefore reversed, and the loading is near-invariant across all three arms (within 4% across a 12.6× change in the priced loss). That invariance is a property of the GK pricing structure rather than of the risk's size, and is the most robust number in the exercise.
3. The canonical central-bank mechanism
docs/cb_mechanism.md(new, canonical) — promoted from the audit indiagnostics/cb_audit/, which was diagnose-only and changed no model source.The CB block survived the refactor untouched, and is correct
The four TPI blocks (
domestic_bond_clearing_tpi,budget_residual_D/F_tpi,external_account_D_tpi) are byte-identical to their pre-refactor versions — they were written against the payoff in coupon/survival form, never against the old FOC's price decomposition. All mark exclusively at the endogenousq_b_D;cb_flow_D(CB net cash flow) matchesbond_return_Dterm for term. Zero livepsi_spreadreferences in the CB block, clearing condition or residual equations. SS neutrality is exact — every TPI block output bit-identical (difference0.000e+00) to its non-TPI counterpart, andcb_flow_D = rem_cb_D = rem_cb_F = 0.The spread identity — the single most important structural fact
At
psi_lambda_B = 0,SDF_bankerandOmega_p1cancel in the rationu_bD_D/nu_K_D, leavingexactly (verified at SS:
0.20 × 0.010000 = 0.002000). The CB has no direct lever on the spread. It compresses only by loweringrk_D(D return on capital), i.e. by crowding capital back in — so TPI's spread effect and its investment effect are one effect and must never be reported as two channels.The 2×2 sovereign-holdings matrix
New decisive diagnostic (
probe_portfolio.py); both clearing identities close to ≤ 1.2e−15 at 26 checkpoints. Aggregate market value in D goods:Under the shock alone, Greek banks' share of their own sovereign rises — retrenchment concentrates the risk on the balance sheet least able to carry it. That is the doom loop as a portfolio fact.
Sourcing of the CB book, Δ(γ=10 − γ=0), identity closing exactly:
In pure quantity (price effect stripped out) the German share is 84% at impact and ~99% from t=1. The ECB is not sharing Greek exposure with German banks — it is buying them out of it, at a price its own purchases are raising.
The relief is a denominator effect
phi_bD_D(D-bank sovereign concentration, what the incentive constraint reads): 0.452489 SS → 0.486757 at γ=0 → 0.458345 at γ=10. TPI undoes 82.9% of the crisis spike — with the numerator moving −0.4% whilen_inter_Drecovers +6.6%. The channel is price support → mark-to-market recapitalisation, not balance-sheet or quantity relief. German banks' Greek exposure meanwhile falls to 27% below SS: TPI accelerates cross-border retrenchment rather than reversing it.Fiscal conduit
Capital key
kappa_cb_F = 0.929; measured aggregate remittance ratio F/D = 13.059 against 13.085 implied, the gap being the endogenous terms of trade. Both remittances negative at impact — a capital call each treasury funds at its own sovereign terms. German cumulative taxes rise monotonically with γ and German welfare falls monotonically (ΔW_F = −0.0195 / −0.0482 / −0.0944) against Greek gains of +0.1956 / +0.5155 / +1.1290. Not self-financing; the creditor side is real and correctly signed.Things this PR corrects but does not fix
code/tpi.py,CLAUDE.mdandHANDOFF.mdis a T=500 terminal-truncation artefact: the resonant eigenvector carries 0.0000 of its mass in the first 100 quarters and 0.9922 in t = 400–499, andA_cb[499,499] = +1.080is the only positive diagonal entry in the matrix. Dropping five columns removes every pole below γ = 36 and changes the reported peak spread by nothing at γ = 2, 5, 10. The 0.25-step condition-number scan also steps clean over a nearer apparent singularity at γ = 2.2116. No reported number is affected — every statistic is computed on[:100], where the artefact has no mass — but the γ = 19.88 cap is imposed for a spurious reason. Docs corrected; code fix proposed as R-1, not implemented.writeoff_enabled = 0, so no credit loss ever flows through the conduit. The realised German transfer and the printed "F bears EL PV" are different objects living in different places and must never be netted.diagnostics/scripts still execute against the deletedpsi_spread.solve_configs.pyis the dangerous one: it does not crash, it silently produces apsi_lambda_B = 0arm identical to its own baseline.All proposed fixes are written up in
diagnostics/cb_audit/recommended_fix.mdand none are applied.Review pointers
docs/cb_mechanism.md— start here; §4 (the identity), §6 (the matrix), §10 (reporting rules).docs/STATE.md→ GK structural refactor — the model change and its numbers.diagnostics/cb_audit/VERDICT.md— audit findings, most severe first.code/equations_D.py::gk_bond_foc_D— the docstring derives the new condition and records the deleted one.Verification run
probe_pipeline.pyrunsmain.py's stages 1-5 plusrun_tpi()— the same calls in the sameorder, with only
generate_irf_plots/generate_tpi_plotsskipped — so the printed residualtables, the four-leg GK FOC check and the TPI closed-loop diagnostics are all main's own.
Captured verbatim in
diagnostics/cb_audit/probe_pipeline_stdout.txt.code/main.pyitselfwas not invoked (it would have rewritten the untracked
outputs/figure set); the figureregeneration on this branch predates the audit.
🤖 Generated with Claude Code