Found while investigating #145's wage/price dynamics. Independent of #145 and arguably more consequential for calibration.
Summary
32.16% of firms (264 of 821, FRA seed 18) price at pure average cost for the entire run, with the markup channel inert. Their effective markup is mu ~ 0.28, which is not a markup but a solver fallback value.
The share is identical across three very different wage regimes and constant through time. Zero firms change binding status between t=10 and t=29.
Chain (each step verified against the code)
1. AC-floor binding is exactly the set of firms with mu < 1
country_config_FRA.yaml:293 sets ac_floor_share = 1.0. The binding test at prices.py:1057 is ac_candidate >= markup_candidate:
phi * (MC + d) >= mu * MC with phi = 1 <=> d >= (mu - 1) * MC
If mu < 1 the right-hand side is negative and d >= 0, so the floor always binds. Predicted vs recorded binding agrees on 100% of 821 firms at t=25; the share with mu < 1 equals the binding share to six decimals (0.321559).
2. The sub-1 markups are produced inside the model, not by the data
| Series |
min |
median |
max |
share < 1 |
Orbis mu_op_weighted_median |
1.042 |
- |
1.529 |
0 |
pricing_markup_base_mu |
1.042 |
1.111 |
1.575 |
0.0000 |
pricing_markup_residual_factor |
0.250 |
1.066 |
1.543 |
- |
pricing_markup_mu (final) |
0.268 |
1.154 |
1.879 |
0.3216 |
markup_mu = base_mu * residual_factor. The base is always above 1. The residual factor is exactly 0.2500 for all 264 affected firms - MARKUP_RESIDUAL_MIN_FACTOR (prices.py:343).
3. Why the solver clamps
Those firms carry status 4 = MARKUP_RESIDUAL_STATUS_AC_FLOOR_UNREACHABLE (prices.py:342). In _sector_markup_residual_factor (prices.py:608-643) the solver searches for a sector factor making the weighted average of max(factor * base_mu * MC, AC) equal the sector's initial observed price. The max means the AC floor lower-bounds that average, so when
AC floor average >= target price
no factor can reach the target. The solver returns min_factor = 0.25 and records status 4.
The model is reporting, correctly and explicitly, that its own cost structure prices above observed prices at initialisation for these sectors. The clamped markup is the symptom, not the disease.
Why it matters
- A third of the economy has no disinflation capacity. For AC-bound firms
price = MC + d exactly, so cost pass-through is one-for-one with no margin to compress.
- It is structural, not dynamic - fixed at calibration from initial conditions and never re-evaluated, which is why no wage-side intervention moves it.
- It bounds what any wage-side fix can achieve.
- Probable common root with the standing sector C/L/O negative-profit flag - "costs above observed prices at initialisation" is the same statement.
mu ~ 0.28 flows into diagnostics as if it were a markup, which is why mean markup reads ~0.93.
What this does NOT explain
It does not explain accelerating CPI. AC-bound firms have pass-through gain exactly 1 - they transmit cost changes, they do not amplify them. Separate 20-seed diagnostics locate the residual drift in real unit labour cost.
Open questions
- Which sectors carry status 4, and do they coincide with the C/L/O negative-profit set?
- Is the initial cost structure too high, or the target price too low? Status 4 cannot distinguish them.
- Should
ac_floor_share = 1.0 be relaxed? At phi = 1 the floor is MC + depreciation with zero margin, forcing binding for any mu < 1.
- Should status 4 surface as a calibration failure rather than clamping silently to a meaningless
mu?
Reproduction
python run_model_mc.py --seeds 18 --t-max 30 --n-jobs 1 --wage-arm baseline \
--consumption-long-run-intercept -0.3 --save-h5-dir <ABSOLUTE PATH>
Compare FRA/firms/pricing_markup_mu, pricing_markup_base_mu, pricing_markup_residual_factor, pricing_markup_residual_status, pricing_ac_floor_binding.
Note: --save-h5-dir resolves relative paths against the worker CWD, not the configured output directory; absolute paths are required.
🤖 Generated with Claude Code
Found while investigating #145's wage/price dynamics. Independent of #145 and arguably more consequential for calibration.
Summary
32.16% of firms (264 of 821, FRA seed 18) price at pure average cost for the entire run, with the markup channel inert. Their effective markup is
mu ~ 0.28, which is not a markup but a solver fallback value.The share is identical across three very different wage regimes and constant through time. Zero firms change binding status between t=10 and t=29.
Chain (each step verified against the code)
1. AC-floor binding is exactly the set of firms with
mu < 1country_config_FRA.yaml:293setsac_floor_share = 1.0. The binding test atprices.py:1057isac_candidate >= markup_candidate:If
mu < 1the right-hand side is negative andd >= 0, so the floor always binds. Predicted vs recorded binding agrees on 100% of 821 firms at t=25; the share withmu < 1equals the binding share to six decimals (0.321559).2. The sub-1 markups are produced inside the model, not by the data
mu_op_weighted_medianpricing_markup_base_mupricing_markup_residual_factorpricing_markup_mu(final)markup_mu = base_mu * residual_factor. The base is always above 1. The residual factor is exactly 0.2500 for all 264 affected firms -MARKUP_RESIDUAL_MIN_FACTOR(prices.py:343).3. Why the solver clamps
Those firms carry status 4 =
MARKUP_RESIDUAL_STATUS_AC_FLOOR_UNREACHABLE(prices.py:342). In_sector_markup_residual_factor(prices.py:608-643) the solver searches for a sector factor making the weighted average ofmax(factor * base_mu * MC, AC)equal the sector's initial observed price. Themaxmeans the AC floor lower-bounds that average, so whenno factor can reach the target. The solver returns
min_factor = 0.25and records status 4.The model is reporting, correctly and explicitly, that its own cost structure prices above observed prices at initialisation for these sectors. The clamped markup is the symptom, not the disease.
Why it matters
price = MC + dexactly, so cost pass-through is one-for-one with no margin to compress.mu ~ 0.28flows into diagnostics as if it were a markup, which is why mean markup reads ~0.93.What this does NOT explain
It does not explain accelerating CPI. AC-bound firms have pass-through gain exactly 1 - they transmit cost changes, they do not amplify them. Separate 20-seed diagnostics locate the residual drift in real unit labour cost.
Open questions
ac_floor_share = 1.0be relaxed? Atphi = 1the floor isMC + depreciationwith zero margin, forcing binding for anymu < 1.mu?Reproduction
Compare
FRA/firms/pricing_markup_mu,pricing_markup_base_mu,pricing_markup_residual_factor,pricing_markup_residual_status,pricing_ac_floor_binding.Note:
--save-h5-dirresolves relative paths against the worker CWD, not the configured output directory; absolute paths are required.🤖 Generated with Claude Code