Interval coverage¶
A confidence interval is a promise about repeated samples: across
independent draws from the same data-generating process, a nominal 95% interval
should contain the truth 95% of the time. Every other validation tier in this
repository proves that tsecon's point estimates and standard-error algebra
match an independent reference. None of them proves the promise itself.
This page is that proof, and it is an audit rather than an advertisement. 63 interval-valued outputs across 35 functions — a function paired with the option and regime that change its answer — were re-estimated on thousands of seeded draws from data-generating processes whose truth is known in closed form, and the containment rate was counted. Where an interval covers at its nominal rate, the number is here. Where it does not, the number is also here, with the Monte Carlo standard error of the measurement next to it, an attribution of why, and what to do instead.
The three headline results, before any table
- 14 of the 50 frequentist intervals miss their nominal rate even in the design they are entitled to do well on. That list is below, and it is the most important output of this work.
- The delta-method VAR IRF band loses coverage monotonically in the horizon — 90% nominal, 89.7% at impact, 67.3% at h=12 with T=100 — and the standard error is not the problem. It is the normal approximation to a badly skewed statistic. The table is here.
- A pointwise band is not a joint band, and the gap is enormous. A nominal 90% pointwise IRF band contains the whole 13-horizon path in 72.2% of samples at T=500; nominal 95% marginal VAR forecast bands contain every horizon and series at once in 40.9% at T=100 — and still only 48.1% at T=800, because the joint rate does not converge to the marginal one. ~~No function in this library reports a simultaneous band.~~ A simultaneous (sup-t) band now exists — scored against a pointwise arm on the same replications it reaches 85.2% ± 1.1 (IRF, nominal 90%) and 90.5% ± 0.7 (forecast, nominal 95%). That is the largest repair on this page and neither VAR arm reaches nominal: what it buys and what it cannot.
Fixed in 0.2.0¶
This audit was written against 0.1.0. Four of its findings were defects rather
than approximation error, and 0.2.0 closed all four. The rows they came from
are still in the tables below, now measuring the shipped 0.2.0 behaviour —
the before-numbers live in this table, because an audit that deletes its
findings once they are fixed leaves nothing to check later, and the sizes of
these gaps are the argument for measuring coverage at all.
| finding | what shipped | measured effect |
|---|---|---|
ols had no hc2/hc3 |
both added; match statsmodels to 2.96e-15 | T=25 leverage design: 0.682 → 0.863. Still short of nominal |
iv_gmm(weight="hac") was a silent no-op at its default bandwidth=0.0 |
default is now the Newey-West rule; explicit 0.0 raises; the truncation used is returned as hac_bandwidth |
0.632 → 0.842. A working default, not a remedy — bandwidth=10 still only reaches 0.868 |
iv_gmm reported no first-stage F |
first_stage returned per instrumented regressor |
diagnostic only; coverage unchanged. F > 10 is still not a safe threshold (0.915 at median F = 10.5) |
arima_fit(d=1) omitted the drift-uncertainty term |
drift_uncertainty=True adds it via the delta method; default unchanged so the statsmodels golden survives |
h=24, T=60: 0.902 → 0.945 |
None of these repaired an approximation. Each removed a case where the library returned something other than what the caller asked for, or withheld a number they needed to judge it. The approximation gaps on this page — the delta-method IRF band decaying in the horizon, pointwise-is-not-joint, HAC under persistence — are all still here, and are not bugs to be fixed.
Two of the four were breaking. Anyone who called iv_gmm(weight="hac") before
0.2.0 received White standard errors; their published numbers will move.
Why a golden fixture cannot prove this¶
A golden test loads one dataset, runs the estimator, and asserts the output matches a reference implementation to 1e-8. That is a strong claim about arithmetic and a null claim about statistics. Two calls can each reproduce their reference to machine precision and still have completely different coverage, because coverage is a property of the sampling distribution — it depends on the sample size, the persistence, the horizon, and the leverage of the design, none of which appear in a fixture.
Concretely: iv_gmm(weight="hac") with its default bandwidth is
bit-identical to weight="robust" — max absolute difference 0.000e+00
across 3000 replications. Both are computed exactly as documented. One of them
covers 63.2% when the caller believes they asked for serial-correlation
robustness. No fixture can see that; only simulation can.
This is Tier 6 of the testing map, and it sits next to Tier 5, Monte Carlo validation, which does the same job for test size and estimator consistency.
Reproducing every number¶
# the whole audit: eight families, one consolidated report
.venv/bin/python docs/examples/coverage/run_all.py # 2396 s here
# just the consolidated tables, without the eight per-family reports
.venv/bin/python docs/examples/coverage/run_all.py --summary
# a smoke run; the MC standard errors are 2-4x larger, so do not quote it
.venv/bin/python docs/examples/coverage/run_all.py --quick # ~250 s
# or one family at a time
.venv/bin/python docs/examples/coverage/regression_se.py # 114 s
.venv/bin/python docs/examples/coverage/irf_bands.py # 627 s
.venv/bin/python docs/examples/coverage/lp_family.py # 212 s
.venv/bin/python docs/examples/coverage/forecast_intervals.py # 249 s
.venv/bin/python docs/examples/coverage/bayes_and_sets.py # 289 s
.venv/bin/python docs/examples/coverage/quantile_panel_lp.py # 693 s
.venv/bin/python docs/examples/coverage/factor_midas.py # 35 s
.venv/bin/python docs/examples/coverage/proxy_garch_tail.py # 175 s
# the page-vs-registry row inventory (no Monte Carlo; runs in the test suite)
.venv/bin/python docs/examples/coverage/check_page.py # <1 s
# regenerate Table 1 / Table 2 below from the harvested results -- the rows
# on this page are pasted from this output, never typed
.venv/bin/python docs/examples/coverage/run_all.py --markdown tables.md
Every family draws from one master seed, 20260729, and prints it. Every draw
is default_rng([seed, experiment, replication]), so the numbers do not depend
on call ordering, on PYTHONHASHSEED, or on whether you run one family or all
eight — verified: every measured line from the standalone runs appears
identically in the consolidated run. Each family asserts its own qualitative
findings and exits non-zero if they stop holding, so a statistical
regression fails a build rather than quietly rotting in this page.
module assertions runtime result
----------------------------------------------------------------------
regression_se.py 110 pass 114.1s OK
irf_bands.py 67 pass 627.3s OK
lp_family.py 16 pass 212.4s OK
forecast_intervals.py 13 pass 248.9s OK
bayes_and_sets.py 23 pass 289.4s OK
quantile_panel_lp.py 17 pass 693.2s OK
factor_midas.py 9 pass 34.6s OK
proxy_garch_tail.py 27 pass 175.4s OK
63 probes harvested from 8 families in 2396.2s
The coverage numbers are byte-reproducible; the runtimes are wall clock and will not be.
How to read the tables¶
Monte Carlo standard error. Every coverage number is printed as
p ± se with se = sqrt(p(1−p)/reps). At reps=3000 and p=0.95 that is
0.0040, so 0.93 and 0.95 are five standard errors apart and can be told
apart honestly; at reps=400 it is 0.015 and they cannot. The dev column
is (coverage − nominal) / se. A verdict of UNDER means dev < −3,
OVER means dev > +3. That is a statement about statistical
distinguishability, not about importance — which is why gap pp is printed
next to it. At reps=3000, both 0.934 and 0.588 are "UNDER"; they are 1.6pp
and 36pp, and they are not the same finding.
kind — not everything shaped like an interval makes a coverage promise.
| kind | what it is | is nominal coverage owed? |
|---|---|---|
| CI | frequentist confidence interval for a parameter | yes |
| PRED | predictive interval for a future realisation | yes |
| CRED | Bayesian credible band — a statement about the posterior | no. A shortfall measures the prior, not a defect |
| SET | set-identified bounds (sign restrictions) — not an interval about a point at all | no. The question is whether the identified set contains the truth |
Measuring frequentist coverage of a credible band is still informative — it tells you how much work the prior is doing — but it answers a different question, and this page labels it as such everywhere. Those rows are segregated into group C.
cause — a miss is one of five things, and they need different responses.
| cause | meaning | who can fix it |
|---|---|---|
| APPROXIMATION | the formula is right; its asymptotics have not arrived at this sample size, horizon, or persistence | nobody. Widen deliberately, or use a different kind of interval |
| ESTIMATOR | the estimator is wrong for the job, or is off-centre, so no standard error rescues it | the caller — or nobody, when it is inconsistency |
| CONVENTION | a deliberate library default (a bandwidth, a degrees-of-freedom choice, a discreteness padding) with a measured coverage cost | the caller, by overriding it |
| API GAP | the interval that would fix it is not exposed at all | the library. These are the actionable recommendations |
| READING | the interval is correct and the reader's question was different | the reader |
Coverage is DGP-specific. Every number below is conditional on the data-generating process that produced it. The processes were chosen to be canonical — the textbook cases a reader will recognise, plus the stress cases applied macroeconomics actually lives in — and they are not exhaustive. A number here is evidence about a mechanism, not a universal constant for the function. The DGPs are stated in full below, and each family module's docstring derives its truth in closed form.
The headline: 63 measured interval-valued outputs¶
Each row names two measured cells. favourable is a design the interval is
entitled to do well on; stress is a design that pushes the same interval
where applied work goes. Reading them together is what separates "this
asymptotic approximation degrades, as asymptotic approximations do" from "this
interval does not work".
Table 1 — the favourable case¶
A miss here is not the user's data.
| surface | interval / option | kind | nom | design measured | coverage ± MC se | dev | gap pp | verdict |
|---|---|---|---|---|---|---|---|---|
tsecon.ols |
se_type="nonrobust" | CI | 0.95 | iid Gaussian, T=200 | 0.946 ± 0.004 | -1.0 | -0.4 | at nominal |
tsecon.ols |
se_type="hc1" | CI | 0.95 | heteroskedastic sd=|x|, T=200 | 0.942 ± 0.004 | -1.9 | -0.8 | at nominal |
tsecon.ols |
se_type="hc1"; small T, leverage | CI | 0.95 | x~chi2(1), sd(e|x)=x, T=1600 | 0.940 ± 0.004 | -2.4 | -1.0 | at nominal |
tsecon.ols |
se_type="hc3"; small T, leverage | CI | 0.95 | x~chi2(1), sd(e|x)=x, T=1600 | 0.945 ± 0.004 | -1.3 | -0.5 | at nominal |
tsecon.ols |
se_type="hac" | CI | 0.95 | x,e AR(1) phi=0.0, T=200 | 0.944 ± 0.004 | -1.5 | -0.6 | at nominal |
tsecon.iv_gmm |
2sls / 2step / iterated | CI | 0.95 | median first-stage F = 90.1 | 0.946 ± 0.004 | -1.0 | -0.4 | at nominal |
tsecon.iv_gmm |
weight="hac" | CI | 0.95 | AR(1) errors phi=0.8, hac bw=10 | 0.868 ± 0.006 | -13.2 | -8.2 | UNDER |
tsecon.har_rv |
HAC SEs on the three slopes | CI | 0.95 | iid innovations, maxlags=5, b_daily | 0.943 ± 0.004 | -1.6 | -0.7 | at nominal |
tsecon.har_rv |
HAC SE on the CONSTANT | CI | 0.95 | het innovations, maxlags=0, const | 0.917 ± 0.005 | -6.5 | -3.3 | UNDER |
tsecon.recession_probit |
Wald interval | CI | 0.95 | probit common phi=0.9, T=250 | 0.952 ± 0.004 | +0.5 | +0.2 | at nominal |
tsecon.quantile_regression |
Powell sandwich, slope | CI | 0.95 | location-scale T=200, tau=0.5 | 0.940 ± 0.004 | -2.3 | -1.0 | at nominal |
tsecon.quantile_regression |
Powell sandwich, intercept | CI | 0.95 | homoskedastic T=200, tau=0.5 | 0.958 ± 0.004 | +2.3 | +0.8 | at nominal |
tsecon.var_irf_bands |
method="asymptotic" | CI | 0.90 | n=500, h=0 | 0.909 ± 0.006 | +1.5 | +0.9 | at nominal |
tsecon.var_irf_bands |
method="bootstrap" | CI | 0.90 | n=100, h=0 | 0.848 ± 0.008 | -6.5 | -5.2 | UNDER |
tsecon.var_irf_bands |
method="bootstrap", bias_correct=True | CI | 0.90 | n=100, h=12 | 0.900 ± 0.007 | +0.0 | +0.0 | at nominal |
tsecon.var_irf_bands |
cumulative=True | CI | 0.90 | orth=True,cumulative=True, h=12 | 0.884 ± 0.007 | -2.2 | -1.6 | at nominal |
tsecon.var_irf_bands |
with the lag order wrong | CI | 0.90 | n=500, fit as VAR(4) [correct], h=4 | 0.905 ± 0.007 | +0.8 | +0.5 | at nominal |
tsecon.var_irf_bands |
pointwise band read as a JOINT band | CI | 0.90 | n=500, h=0 | 0.909 ± 0.006 | +1.5 | +0.9 | at nominal |
tsecon.lp |
se="lag_augmented" (the default) | CI | 0.95 | lag_augmented, h=0 | 0.945 ± 0.004 | -1.3 | -0.5 | at nominal |
tsecon.lp |
se="hac" | CI | 0.95 | T=800 hac, h=12 | 0.939 ± 0.005 | -2.1 | -1.1 | at nominal |
tsecon.lp_iv |
strong instrument | CI | 0.95 | strong iv, best h (1) | 0.930 ± 0.005 | -4.2 | -2.0 | UNDER |
tsecon.lp_iv |
weak instrument | CI | 0.95 | weak iv, closest h (0) | 0.969 ± 0.003 | +6.1 | +1.9 | OVER |
tsecon.lp_state |
per-regime response | CI | 0.95 | state0 lag_augmented, best h (1) | 0.953 ± 0.005 | +0.6 | +0.3 | at nominal |
tsecon.lp_multiplier |
integral multiplier | CI | 0.95 | multiplier, h=0 | 0.934 ± 0.005 | -3.5 | -1.6 | UNDER |
tsecon.smooth_lp |
lam="cv" (the default) | CI | 0.95 | lam=0, h=0 | 0.936 ± 0.009 | -1.5 | -1.4 | at nominal |
tsecon.arima_fit |
forecast_lower / forecast_upper | PRED | 0.95 | h=1 | 0.944 ± 0.009 | -0.7 | -0.6 | at nominal |
tsecon.arima_fit |
d=1 (random walk with drift) | PRED | 0.95 | h=1 | 0.939 ± 0.006 | -1.8 | -1.1 | at nominal |
tsecon.var_forecast |
lower / upper | PRED | 0.95 | h=1 | 0.948 ± 0.002 | -0.9 | -0.2 | at nominal |
tsecon.var_forecast |
marginal bands read as a JOINT band | PRED | 0.95 | h=1 | 0.944 ± 0.002 | -2.8 | -0.6 | at nominal |
tsecon.bvar_irf_draws |
5th/95th posterior percentile band | CRED | 0.90 | prior 'oracle-tight', h=4 | 0.906 ± 0.011 | +0.5 | +0.6 | n/a |
tsecon.bvar_ssvs |
spike-and-slab credible band | CRED | 0.90 | prior 'SSVS spike-slab', h=0 | 0.899 ± 0.011 | -0.1 | -0.1 | n/a |
tsecon.bvar_irf_draws |
impact band vs an EXACT interval | CRED | 0.90 | exact chi-square interval for the same scalar | 0.904 ± 0.006 | +0.6 | +0.4 | n/a |
tsecon.sign_restricted_svar |
pointwise 5-95 band over rotations | CRED | 0.90 | lambda1=5.0, h=3 | 0.860 ± 0.017 | -2.3 | -4.0 | n/a |
tsecon.robust_svar_bounds |
Giacomini-Kitagawa robust region | CRED | 0.90 | lambda1=5.0, h=3 | 0.935 ± 0.012 | +2.8 | +3.5 | n/a |
tsecon.sign_restricted_svar |
set envelope (min/max over draws) | SET | 0.90 | lambda1=5.0, h=3 | 0.983 ± 0.007 | +12.6 | +8.3 | n/a |
tsecon.zero_sign_svar |
band at a TRUE point-identifying zero | CRED | 0.90 | lambda1=5.0, h=3 | 0.875 ± 0.017 | -1.5 | -2.5 | n/a |
tsecon.bai_perron |
break-date CI, conditional on detection | CI | 0.95 | break/sigma=1.0, T=800, cond. on detection | 0.970 ± 0.005 | +3.9 | +2.0 | OVER |
tsecon.bai_perron |
break-date CI, UNconditional | CI | 0.95 | break/sigma=3.0, T=200, detection 0.97 | 0.967 ± 0.004 | +4.1 | +1.7 | OVER |
tsecon.bai_perron |
break-date CI at a LARGE break | CI | 0.95 | break/sigma=1.0, T=200, cond. on detection | 0.957 ± 0.005 | +1.5 | +0.7 | at nominal |
tsecon.quantile_lp |
Powell sandwich, identified iid shock | CI | 0.95 | iid T=400 tau=0.50, h=0 | 0.939 ± 0.008 | -1.5 | -1.1 | at nominal |
tsecon.quantile_lp |
persistent regressor (phi=0.8) | CI | 0.95 | p=4, worst h (6) | 0.909 ± 0.009 | -4.5 | -4.1 | UNDER |
tsecon.panel_lp |
se_type="driscoll_kraay" (the default) | CI | 0.95 | N=50 T=80 dk, h=0 | 0.916 ± 0.006 | -6.2 | -3.4 | UNDER |
tsecon.panel_lp |
bias_correction="spj" at short T | CI | 0.95 | SPJ, N=50, T=40, h=2 | 0.843 ± 0.007 | -14.7 | -10.7 | UNDER |
tsecon.lp |
cumulative="both" (default se resolves to "hac") | CI | 0.95 | T=400, h=12 (the pre-fix 0.507 cell) | 0.920 ± 0.006 | -4.9 | -3.0 | UNDER |
tsecon.favar + tsecon.var_irf_bands |
two-step bands conditioned on F-hat | CI | 0.90 | N=100 T=200 F-hat, h=0 | 0.887 ± 0.007 | -1.8 | -1.3 | at nominal |
tsecon.umidas |
se_type="hac" (the default) | CI | 0.95 | iid errors, T=300, k=1 (most recent lag) | 0.947 ± 0.004 | -0.8 | -0.3 | at nominal |
tsecon.growth_at_risk |
bse (Newey-West at horizon-1 lags, the default) | CI | 0.95 | tau=0.5, h=1, T=240 | 0.955 ± 0.005 | +1.0 | +0.5 | at nominal |
tsecon.growth_at_risk |
bse_powell (uncorrected, kept for replication) | CI | 0.95 | tau=0.5, h=1, T=240 | 0.955 ± 0.005 | +1.0 | +0.5 | at nominal |
tsecon.proxy_svar_bands |
bands="moving_block" (the default), Hall | CI | 0.90 | impact (var 1), strong instrument, T=300 | 0.891 ± 0.010 | -0.9 | -0.9 | at nominal |
tsecon.proxy_svar_bands |
bands="wild" (reproduction arm, labelled invalid) | CI | 0.90 | moving-block reference, impact (var 1) | 0.891 ± 0.010 | -0.9 | -0.9 | at nominal |
tsecon.proxy_ar_sets |
rf_method="delta" (the default) | CI | 0.95 | card VAR(2) T=300, h=1 | 0.952 ± 0.004 | +0.4 | +0.2 | at nominal |
tsecon.proxy_ar_sets |
rf_method="second_order" | CI | 0.95 | card VAR(2) T=300, h=12 | 0.974 ± 0.003 | +8.3 | +2.4 | OVER |
tsecon.proxy_ar_sets |
rf_method="second_order_bc" | CI | 0.95 | routine VAR(1) T=250, h=12 | 0.966 ± 0.003 | +4.7 | +1.6 | OVER |
tsecon.garch_fit |
se_mle (inverse Hessian) | CI | 0.95 | GARCH(1,1) normal z, T=2000, alpha | 0.951 ± 0.007 | +0.1 | +0.1 | at nominal |
tsecon.garch_fit |
se_robust (Bollerslev-Wooldridge, QMLE) | CI | 0.95 | GARCH(1,1) normal z, T=2000, alpha | 0.946 ± 0.007 | -0.6 | -0.4 | at nominal |
tsecon.flp |
per-element se on functional_pca scores | CI | 0.95 | external scores, persistent curves, impact | 0.953 ± 0.005 | +0.6 | +0.3 | at nominal |
tsecon.flp_scenario |
w'beta scenario band | CI | 0.95 | in-span scenario, persistent curves, impact | 0.932 ± 0.007 | -2.8 | -1.8 | at nominal |
tsecon.theta_forecast / tsecon.backtest |
no interval is returned | NONE | — | the library returns a point path only | — | — | — | no band |
tsecon.weighted_midas |
no interval is returned | NONE | — | NLS point fit, weights and fit diagnostics only | — | — | — | no band |
tsecon.dfm_nowcast |
no interval is returned | NONE | — | point nowcast + smoothed factor path only | — | — | — | no band |
tsecon.nelson_siegel |
no interval is returned | NONE | — | factors, fitted lambda, residuals and R^2 only | — | — | — | no band |
tsecon.nongaussian_svar |
no interval is returned | NONE | — | point B, IRF and kurtosis diagnostics only | — | — | — | no band |
tsecon.garch_fit |
variance_forecast | NONE | — | analytic point path only; no interval is implied | — | — | — | no band |
Table 2 — the stress case¶
Sorted worst first. Every stress design was chosen to be stressful, so a miss here is expected; the deliverable is its size, not its existence.
| surface | interval / option | kind | nom | design measured | coverage ± MC se | dev | gap pp | verdict |
|---|---|---|---|---|---|---|---|---|
tsecon.var_irf_bands |
with the lag order wrong | CI | 0.90 | n=500, fit as VAR(1) [misspecified], h=4 | 0.061 ± 0.005 | -156.1 | -83.9 | UNDER |
tsecon.bai_perron |
break-date CI, UNconditional | CI | 0.95 | break/sigma=0.25, T=200, detection 0.29 | 0.233 ± 0.009 | -76.0 | -71.7 | UNDER |
tsecon.proxy_svar_bands |
bands="wild" (reproduction arm, labelled invalid) | CI | 0.90 | impact: the identifying moment is frozen | 0.193 ± 0.020 | -35.9 | -70.8 | UNDER |
tsecon.var_forecast |
marginal bands read as a JOINT band | PRED | 0.95 | every horizon and series inside simultaneously | 0.409 ± 0.006 | -85.2 | -54.1 | UNDER |
tsecon.flp |
per-element se on functional_pca scores | CI | 0.95 | estimated scores, persistent curves, worst h (0) | 0.421 ± 0.013 | -41.5 | -52.9 | UNDER |
tsecon.var_irf_bands |
method="bootstrap" | CI | 0.90 | n=100, h=12 | 0.410 ± 0.011 | -44.6 | -49.0 | UNDER |
tsecon.ols |
se_type="hac" | CI | 0.95 | x,e AR(1) phi=0.95, T=200 | 0.588 ± 0.009 | -40.3 | -36.2 | UNDER |
tsecon.bvar_ssvs |
spike-and-slab credible band | CRED | 0.90 | prior 'SSVS spike-slab', h=12 | 0.594 ± 0.019 | -16.5 | -30.6 | n/a |
tsecon.smooth_lp |
lam="cv" (the default) | CI | 0.95 | lam=cv, h=0 | 0.646 ± 0.018 | -16.8 | -30.4 | UNDER |
tsecon.bvar_irf_draws |
5th/95th posterior percentile band | CRED | 0.90 | prior 'default', h=4 | 0.610 ± 0.018 | -15.7 | -29.0 | n/a |
tsecon.ols |
se_type="hc1"; small T, leverage | CI | 0.95 | x~chi2(1), sd(e|x)=x, T=25 | 0.682 ± 0.009 | -31.6 | -26.8 | UNDER |
tsecon.favar + tsecon.var_irf_bands |
two-step bands conditioned on F-hat | CI | 0.90 | N=20 T=800, worst h (7) | 0.673 ± 0.010 | -21.6 | -22.7 | UNDER |
tsecon.var_irf_bands |
method="asymptotic" | CI | 0.90 | n=100, h=12 | 0.673 ± 0.010 | -21.6 | -22.7 | UNDER |
tsecon.ols |
se_type="nonrobust" | CI | 0.95 | heteroskedastic sd=|x|, T=200 | 0.732 ± 0.008 | -27.0 | -21.8 | UNDER |
tsecon.ols |
se_type="hc1" | CI | 0.95 | AR(1) errors+regressor .7, T=200 | 0.735 ± 0.008 | -26.7 | -21.5 | UNDER |
tsecon.sign_restricted_svar |
pointwise 5-95 band over rotations | CRED | 0.90 | lambda1=0.2, h=3 | 0.698 ± 0.023 | -8.8 | -20.2 | n/a |
tsecon.garch_fit |
se_mle (inverse Hessian) | CI | 0.95 | GARCH(1,1) t(5) z, T=2000, alpha | 0.750 ± 0.014 | -14.5 | -20.0 | UNDER |
tsecon.quantile_lp |
persistent regressor (phi=0.8) | CI | 0.95 | p=0, worst h (6) | 0.758 ± 0.014 | -14.2 | -19.2 | UNDER |
tsecon.panel_lp |
bias_correction="spj" at short T | CI | 0.95 | SPJ, N=50, T=20, h=2 | 0.761 ± 0.009 | -22.1 | -18.9 | UNDER |
tsecon.var_irf_bands |
pointwise band read as a JOINT band | CI | 0.90 | n=500, all of h=0..12 at once | 0.722 ± 0.010 | -17.8 | -17.8 | UNDER |
tsecon.growth_at_risk |
bse (Newey-West at horizon-1 lags, the default) | CI | 0.95 | tau=0.05, h=12, T=240 | 0.793 ± 0.010 | -15.0 | -15.7 | UNDER |
tsecon.proxy_svar_bands |
bands="moving_block" (the default), Hall | CI | 0.90 | T=300, worst (h, variable), worst h (12) | 0.749 ± 0.014 | -11.0 | -15.1 | UNDER |
tsecon.panel_lp |
se_type="driscoll_kraay" (the default) | CI | 0.95 | N=50 T=40, worst h (4) | 0.818 ± 0.008 | -17.1 | -13.2 | UNDER |
tsecon.growth_at_risk |
bse_powell (uncorrected, kept for replication) | CI | 0.95 | tau=0.5, h=12, T=240 | 0.826 ± 0.010 | -12.7 | -12.4 | UNDER |
tsecon.proxy_ar_sets |
rf_method="delta" (the default) | CI | 0.95 | routine VAR(1) T=250, h=12 | 0.828 ± 0.007 | -17.7 | -12.2 | UNDER |
tsecon.umidas |
se_type="hac" (the default) | CI | 0.95 | AR(1) errors phi=0.7, T=150, intercept | 0.829 ± 0.007 | -17.6 | -12.1 | UNDER |
tsecon.iv_gmm |
2sls / 2step / iterated | CI | 0.95 | median first-stage F = 1.2 | 0.839 ± 0.007 | -16.5 | -11.1 | UNDER |
tsecon.var_irf_bands |
cumulative=True | CI | 0.90 | orth=True,cumulative=False, h=12 | 0.789 ± 0.009 | -12.2 | -11.1 | UNDER |
tsecon.iv_gmm |
weight="hac" | CI | 0.95 | AR(1) errors phi=0.8, hac auto (NW rule) | 0.842 ± 0.007 | -16.3 | -10.8 | UNDER |
tsecon.zero_sign_svar |
band at a TRUE point-identifying zero | CRED | 0.90 | lambda1=0.2, h=3 | 0.797 ± 0.020 | -5.1 | -10.3 | n/a |
tsecon.robust_svar_bounds |
Giacomini-Kitagawa robust region | CRED | 0.90 | lambda1=0.2, h=3 | 0.800 ± 0.020 | -5.0 | -10.0 | n/a |
tsecon.ols |
se_type="hc3"; small T, leverage | CI | 0.95 | x~chi2(1), sd(e|x)=x, T=25 | 0.863 ± 0.006 | -13.9 | -8.7 | UNDER |
tsecon.quantile_regression |
Powell sandwich, slope | CI | 0.95 | location-scale T=200, tau=0.05 | 0.866 ± 0.006 | -13.5 | -8.4 | UNDER |
tsecon.lp |
se="hac" | CI | 0.95 | T=100 hac, h=12 | 0.870 ± 0.008 | -10.7 | -8.0 | UNDER |
tsecon.har_rv |
HAC SE on the CONSTANT | CI | 0.95 | het innovations, maxlags=22, const | 0.873 ± 0.006 | -12.7 | -7.7 | UNDER |
tsecon.flp_scenario |
w'beta scenario band | CI | 0.95 | in-span scenario, persistent curves, worst h (8) | 0.873 ± 0.009 | -9.0 | -7.7 | UNDER |
tsecon.bai_perron |
break-date CI, conditional on detection | CI | 0.95 | break/sigma=0.5, T=200, cond. on detection | 0.888 ± 0.008 | -8.0 | -6.2 | UNDER |
tsecon.var_irf_bands |
method="bootstrap", bias_correct=True | CI | 0.90 | n=100, h=0 | 0.842 ± 0.008 | -7.2 | -5.8 | UNDER |
tsecon.quantile_lp |
Powell sandwich, identified iid shock | CI | 0.95 | iid T=200 tau=0.25, worst h (0) | 0.893 ± 0.010 | -5.8 | -5.7 | UNDER |
tsecon.arima_fit |
forecast_lower / forecast_upper | PRED | 0.95 | worst h (10) | 0.899 ± 0.011 | -4.5 | -5.1 | UNDER |
tsecon.arima_fit |
d=1 (random walk with drift) | PRED | 0.95 | worst h (22) | 0.902 ± 0.008 | -6.3 | -4.8 | UNDER |
tsecon.lp_multiplier |
integral multiplier | CI | 0.95 | multiplier, worst h (8) | 0.903 ± 0.005 | -8.7 | -4.7 | UNDER |
tsecon.lp_state |
per-regime response | CI | 0.95 | state1 lag_augmented, worst h (7) | 0.907 ± 0.007 | -5.7 | -4.3 | UNDER |
tsecon.lp_iv |
strong instrument | CI | 0.95 | strong iv, worst h (5) | 0.909 ± 0.005 | -7.8 | -4.1 | UNDER |
tsecon.garch_fit |
se_robust (Bollerslev-Wooldridge, QMLE) | CI | 0.95 | GARCH(1,1) t(5) z, T=2000, beta | 0.909 ± 0.009 | -4.5 | -4.1 | UNDER |
tsecon.har_rv |
HAC SEs on the three slopes | CI | 0.95 | het innovations, maxlags=22, b_monthly | 0.923 ± 0.005 | -5.5 | -2.7 | UNDER |
tsecon.bvar_irf_draws |
impact band vs an EXACT interval | CRED | 0.90 | credible band, lambda1=5.0 | 0.874 ± 0.007 | -3.9 | -2.6 | n/a |
tsecon.var_forecast |
lower / upper | PRED | 0.95 | worst h (11) | 0.925 ± 0.003 | -9.2 | -2.5 | UNDER |
tsecon.lp |
se="lag_augmented" (the default) | CI | 0.95 | lag_augmented, worst h (6) | 0.934 ± 0.004 | -4.1 | -1.6 | UNDER |
tsecon.proxy_ar_sets |
rf_method="second_order" | CI | 0.95 | routine VAR(1) T=250, h=12 | 0.935 ± 0.004 | -3.3 | -1.5 | UNDER |
tsecon.recession_probit |
Wald interval | CI | 0.95 | probit rare phi=0.9, T=100, 25% no MLE | 0.968 ± 0.004 | +4.9 | +1.8 | OVER |
tsecon.lp |
cumulative="both" (default se resolves to "hac") | CI | 0.95 | T=1600, most extreme h (4) | 0.971 ± 0.004 | +5.4 | +2.1 | OVER |
tsecon.lp_iv |
weak instrument | CI | 0.95 | weak iv, worst h (4) | 0.985 ± 0.002 | +15.5 | +3.5 | OVER |
tsecon.proxy_ar_sets |
rf_method="second_order_bc" | CI | 0.95 | card VAR(2) T=300, h=12 | 0.990 ± 0.002 | +22.0 | +4.0 | OVER |
tsecon.quantile_regression |
Powell sandwich, intercept | CI | 0.95 | location-scale T=200, tau=0.5 | 0.993 ± 0.002 | +28.2 | +4.3 | OVER |
tsecon.bai_perron |
break-date CI at a LARGE break | CI | 0.95 | break/sigma=3.0, T=200, cond. on detection | 0.998 ± 0.001 | +54.2 | +4.8 | OVER |
tsecon.sign_restricted_svar |
set envelope (min/max over draws) | SET | 0.90 | lambda1=0.2, h=3 | 0.953 ± 0.011 | +4.9 | +5.2 | n/a |
tsecon.theta_forecast / tsecon.backtest |
no interval is returned | NONE | — | the library returns a point path only | — | — | — | no band |
tsecon.weighted_midas |
no interval is returned | NONE | — | NLS point fit, weights and fit diagnostics only | — | — | — | no band |
tsecon.dfm_nowcast |
no interval is returned | NONE | — | point nowcast + smoothed factor path only | — | — | — | no band |
tsecon.nelson_siegel |
no interval is returned | NONE | — | factors, fitted lambda, residuals and R^2 only | — | — | — | no band |
tsecon.nongaussian_svar |
no interval is returned | NONE | — | point B, IRF and kurtosis diagnostics only | — | — | — | no band |
tsecon.garch_fit |
variance_forecast | NONE | — | analytic point path only; no interval is implied | — | — | — | no band |
Where the intervals miss¶
This is the section to act on. Each caveat links to the model card for the function, so it is reachable from the function's own documentation as well as from here.
A. Misses even in the favourable design¶
14 of 50. These are off nominal in the design they are
entitled to do well on, so a caller cannot fix them by having better data.
(The last two are the page's only deliberately conservative members: the
two opt-in proxy_ar_sets repairs, which sit over nominal in their
favourable cells by design.)
| surface | favourable design | measured (nominal 0.95 / 0.90) | cause | what to do | documented in |
|---|---|---|---|---|---|
iv_gmm(weight="hac") |
AR(1) errors φ=0.8, bandwidth=10 supplied |
0.868 ± 0.006 (automatic default: 0.842 ± 0.007) | APPROXIMATION (was CONVENTION — fixed in 0.2.0) | the original finding: bandwidth defaulted to 0.0, and a Bartlett kernel truncated at 0 lags is the White estimator, so weight="hac" alone changed nothing (verified bit-identical, max |Δse| = 0.000e+00 over 3000 reps) and covered 0.632 ± 0.009. 0.2.0 made the default the Newey-West rule and made an explicit 0.0 an error. That lifts coverage to 0.842 and no further — at this persistence T=250 cannot estimate the long-run variance, and even bandwidth=10 reaches only 0.868 |
GMM card |
var_irf_bands(method="bootstrap") |
impact, persistent VAR, T=100 | 0.848 ± 0.008 (h=12: 0.410 ± 0.011) | ESTIMATOR | the percentile band sits below an already downward-biased point estimate — a second dose of the same bias. Pass method="bootstrap", bias_correct=True on a persistent VAR: it lifts h=12 from 0.410 to 0.900. (bias_correct is a property of the bootstrap resampling loop; setting it on the default asymptotic arm now raises rather than being silently ignored, which is how it shipped before 0.3.0) |
VAR/SVAR card |
har_rv — the constant |
heteroskedastic innovations, maxlags=0 |
0.917 ± 0.005 (maxlags=22: 0.873 ± 0.006) |
ESTIMATOR | not the SE: the least-squares persistence bias at Σb = 0.95 is absorbed entirely by the intercept (measured bias −0.0990 against the mechanical prediction −0.0994). The three slopes are at nominal; do not read the HAR intercept as if it were | Realized-vol card |
lp_iv — strong instrument |
median first-stage F ≈ 134, best horizon | 0.930 ± 0.005 (worst horizon: 0.909 ± 0.005) | CONVENTION | the kernel covariance follows linearmodels' debiased=False convention (which is what makes the point estimates match the golden) and applies p Bartlett lags even at h=0, where the score has nothing to smooth. Subtract two to four points from the nominal level before quoting an LP-IV interval |
LP card |
lp_multiplier |
impact, median F ≈ 159 | 0.934 ± 0.005 (widest window: 0.903 ± 0.005) | CONVENTION | well centred (|bias|/sd ≤ 0.08) and strongly instrumented at every horizon, but se/sd ≈ 0.9: the honest critical value at T=240 is nearer 2.2 than 1.96 | LP card |
lp_iv — weak instrument |
kindest horizon of the weak arm itself | 0.969 ± 0.003 (worst: 0.985 ± 0.002) | APPROXIMATION | it over-covers while the median interval width explodes about 5×. That is the correct symptom, not a lucky escape: Dufour (1997) — under weak identification no bounded confidence set can be honest, so a Wald set stays honest only by becoming uninformative. Report first_stage_f |
LP card |
bai_perron — unconditional |
break/σ = 3, T=200 | 0.967 ± 0.004 (break/σ = 0.25: 0.233 ± 0.009) | ESTIMATOR | at a small break, detection itself collapses to 0.29, so the rate a user actually faces is 0.233. A break-date CI is meaningful only once the break is detectable | Structural-breaks card |
bai_perron — conditional on detection |
break/σ = 1, T=800 | 0.970 ± 0.005 (break/σ = 0.5, T=200: 0.888 ± 0.008) | APPROXIMATION / CONVENTION | over-covers at a large break because the half-width is ceil(c/scale) plus one index on each side, and that discreteness padding dominates (0.998 at break/σ = 3). Under-covers at a small break because Bai's argmax limit distribution is a finite-sample approximation — it improves with T (0.877 → 0.914 → 0.944 at T = 200/400/800) while the interval width does not shrink at all (26.4 → 26.5 → 26.1), exactly as fixed-break asymptotics predict |
Structural-breaks card |
quantile_lp — persistent regressor |
default lag controls (p=4), worst (τ, h) cell | 0.909 ± 0.009 (no lag controls, worst cell: 0.758 ± 0.014) | ESTIMATOR | the Powell sandwich is heteroskedasticity-robust, not HAC — exactly as the card says. The default lag controls whiten an AR(1) regressor and hold every cell at 0.91–0.95; with n_lag_controls=0 nothing whitens the score and the growth_at_risk-shaped decay appears in full (se/sd 0.66 at the far horizon, |bias|/sd ≈ 0.4). Keep the lag controls at least as long as the regressor's AR order — and note the canonical iid-shock design is measured near nominal everywhere |
Quantile card |
panel_lp(se_type="driscoll_kraay") — the default |
N=50, T=80, impact | 0.916 ± 0.006 (T=40, worst horizon: 0.818 ± 0.008) | APPROXIMATION | with a common shock the effective sample is T, not N·T: quintupling N moves pooled coverage by under a point while doubling T buys ~5pp. Driscoll-Kraay is a T-asymptotic estimator; at T=40 read the bands as indicative. And never swap in se_type="cluster" under a common factor — on the same draws it covers 0.20 (se/sd = 0.12) |
Panel-LP cookbook |
panel_lp(bias_correction="spj") |
T=40, h=2 (a short panel — the correction's home turf) | 0.843 ± 0.007 (T=20: 0.761 ± 0.009) | APPROXIMATION | the SPJ removes most of the Nickell bias (−0.141 → +0.015 at T=20, h=2 — corroborating its card almost exactly) and recomputes the SEs for the corrected estimator, but Driscoll-Kraay remains a short-T approximation, so neither SPJ nor uncorrected FE reaches nominal at T=20. The measured T=20 coverage gain over FE is +2.5pp paired (se 0.6pp) — real, and smaller than the card's 300-replication point numbers (0.743 → 0.823) suggest; at T=40 the paired gain is −1.5pp | Panel card |
lp(cumulative="both") — the post-fix HAC default |
T=400, h=12 (the repaired defect cell) | 0.920 ± 0.006 (T=1600, most extreme h: 0.971 ± 0.004, over) | APPROXIMATION (the 0.507 defect itself was fixed in 0.3.0) | the official repair numbers: the pre-fix lag-augmented/HC1 default covered 0.507 at h=12 and was flat in T; the mode-dependent HAC default restores h=12 to 0.920 at T=400 and 0.956 at T=1600, with the residual deviation flipping mildly conservative at T=1600 mid-horizons — the Bartlett bandwidth h + p is generous once T is large relative to the MA(h) overlap. se="lag_augmented" with this mode now raises, and the suite asserts it |
LP card |
proxy_ar_sets(rf_method="second_order") |
card VAR(2), T=300, h=12 — the cell the opt-in repair was shipped for | 0.974 ± 0.003, over (routine VAR(1), h=12: 0.935 ± 0.004, under) | APPROXIMATION | the shipped long-horizon repair, measured in-registry: on the card DGP it crosses ~2pp past nominal at h=12, on the harder routine VAR(1) it stops ~1.5pp short — the exact residual roadmap note 21 recorded (0.964/0.932 on its own 500-rep harness). No single evaluation point calibrates both DGPs; the width price is ~1.6x the delta set at h=12. It remains the best point-calibration choice, and the default stays "delta" |
Identification card |
proxy_ar_sets(rf_method="second_order_bc") |
routine VAR(1), T=250, h=12 — the residual-gap cell it was built for | 0.966 ± 0.003, over (card VAR(2), h=12: 0.990 ± 0.002, over) | CONVENTION | deliberately conservative: the same seeded simulation centred at Pope-bias-corrected coefficients is the only arm at-or-above nominal at every horizon on both DGPs, and that floor is bought with over-coverage (up to +4pp at the card DGP's h=12) and a ~2x width price at h=12. Choose it when long-horizon under-coverage is the error you must rule out; boundedness is bit-identical across all three rf_methods (asserted every run) |
Identification card |
B. At nominal when entitled, off under stress¶
36 of 50. These behave the way asymptotic approximations behave. Nothing here is an alarm; the number to quote is the size of the loss in the regime you are actually in.
| surface | stress design | measured | cause | what to do | documented in |
|---|---|---|---|---|---|
var_irf_bands with the wrong lag order |
VAR(4) truth fitted as VAR(1), h=4, T=500 | 0.061 ± 0.005 | ESTIMATOR | inconsistency, not a band problem: coverage gets worse as T grows — 17.8% at T=200, 6.2% at T=500. Choose the lag order on the data before reading any band; with the correct order the same cell covers 0.905 | VAR/SVAR card |
var_forecast marginal bands read as a joint band |
12 horizons × 2 series simultaneously, T=100 | 0.409 ± 0.006 | READING | see pointwise is not joint | VAR/SVAR card |
ols(se_type="hac") on a slope |
regressor and errors AR(1) at φ=0.95, T=200 | 0.588 ± 0.009 | APPROXIMATION | the reported HAC SE is 0.43 of the true sampling sd. Lengthening the bandwidth helps and does not close it (0.703 at 12 lags, 0.728 at 24): at T=200 the sample does not contain enough independent information to estimate a long-run variance this large. Report such a slope with a bandwidth chosen for the persistence and treat the interval as indicative | HAC cookbook |
smooth_lp(lam="cv") |
impact response, T=200 | 0.646 ± 0.018 | ESTIMATOR | by design, and worst exactly where an applied reader looks first. At impact the cross-validated penalty pulls the estimate off the peak (|bias|/sd = 1.23 — the bias exceeds a whole sampling sd). A smooth-LP band is a band around the penalized estimand; the unpenalized lam=0 anchor covers 0.936 at the same cell. Separately, se conditions on the selected λ, so mean se/sd falls from 0.907 to 0.813 |
LP card |
ols(se_type="hc1"), small T with leverage |
x ~ chi2(1), sd(e|x)=x, T=25 | 0.682 ± 0.009 | ESTIMATOR (was API GAP — fixed in 0.2.0) | hc1's n/(n−k) factor buys 0.015 at k=2; the leverage correction 1/(1−h_i) is what matters. 0.2.0 added hc2/hc3, and tsecon's own hc3 now covers 0.863 ± 0.006 on these draws — 18 points recovered, and still short of nominal, so prefer hc3 at small n without treating it as a cure (its own row is next) |
Inference guide |
ols(se_type="hc3"), small T with leverage |
x ~ chi2(1), sd(e|x)=x, T=25 | 0.863 ± 0.006 | APPROXIMATION | the estimator 0.2.0 added because of this audit, measured on its own row: at nominal in the favourable design (0.945 ± 0.004 at T=1600) and still 8.7pp short at T=25 — the leverage correction recovers most of the small-T gap but not all of it. The SE distribution is skewed, so the mean se/sd of 0.942 overstates the typical interval; at a small, high-leverage n, widen deliberately or bootstrap |
Inference guide |
var_irf_bands(method="asymptotic") |
h=12, T=100 | 0.673 ± 0.010 | APPROXIMATION | the horizon table | VAR/SVAR card |
ols(se_type="nonrobust") |
heteroskedastic sd=|x|, T=200 | 0.732 ± 0.008 | ESTIMATOR | inconsistent, so data does not help: in the high-leverage design it is stuck at 0.428 even at T=1600 and slides downward with T. hc0/hc1 repair almost all of it (0.732 → 0.942) |
Inference guide |
ols(se_type="hc1") under serial correlation |
AR(1) errors, AR(1) regressor φ=0.7, T=200 | 0.735 ± 0.008 | ESTIMATOR | HC repairs nothing here — statistically indistinguishable from nonrobust's 0.744, with se/sd 0.569 vs 0.579. HC is heteroskedasticity-robust, not serial-correlation robust. Only hac moves the number (0.876) |
HAC cookbook |
var_irf_bands pointwise read as joint |
all of h=0..12 at once, T=500 | 0.722 ± 0.010 | READING | see pointwise is not joint | VAR/SVAR card |
iv_gmm with weak instruments |
median first-stage F = 1.2, T=250 | 0.839 ± 0.007 | ESTIMATOR + API GAP | the mean reported se/sd of 1.27 is a mirage: the median reported SE is only 0.456 of the true sampling sd, and the mean is dragged above 1 by a handful of replications with enormous SEs. IQR/(1.349 sd) = 0.42 says the sampling law is nothing like normal. A fixed-width interval at the true sd covers 0.968, so the damage is done by how the SE varies across samples (corr(|error|, SE) = +0.83 — it is smallest in exactly the samples where the estimate is worst). And the rule-of-thumb F of 10 is not safe: at median F = 10.5 coverage is already 0.915 with a median se/sd of 0.841. 0.2.0 added the first_stage diagnostic so the caller can at least see the strength; no Anderson-Rubin set is exposed, and that remains the real answer here |
GMM card |
var_irf_bands per-horizon vs cumulative=True |
per-horizon band, h=12, T=200 | 0.789 ± 0.009 (cumulative: 0.884 ± 0.007) | APPROXIMATION | on this DGP the running sum is dominated by the early, well-estimated horizons, so it is a much more nearly linear function of the estimated slopes. Measured here, not a general theorem | VAR/SVAR card |
quantile_regression, extreme τ |
τ=0.05, location-scale, T=200 | 0.866 ± 0.006 | APPROXIMATION | se/sd is 0.818 while the point-estimate bias is +0.008, so it is squarely the SE: the Powell sandwich needs a conditional density at the fitted quantile, estimated from the handful of observations near an extreme quantile. It shrinks with T (0.916 at T=1000) but does not close. τ=0.50 is fine (0.940). Bootstrap the quantile process for extreme τ at a few hundred observations | Quantile card |
lp(se="hac") |
h=12, T=100 | 0.870 ± 0.008 | APPROXIMATION | the default se="lag_augmented" covers better at every horizon on the same draws (paired gap +0.027 pooled over h≥6, se 0.0014). Newey-West at bandwidth h+p spends its degrees of freedom estimating autocovariances that lag augmentation has already removed. The gap closes in T (0.870 at T=100 → 0.939 at T=800) |
LP card |
har_rv slopes at a long bandwidth |
heteroskedastic, maxlags=22, b_monthly |
0.923 ± 0.005 | APPROXIMATION | bandwidth is not free. On a correctly specified HAR with iid innovations, b_daily's se/sd falls 0.990 (maxlags=0) → 0.986 (5, the default) → 0.973 (22), and coverage 0.947 → 0.943 → 0.940. The default of 5 is a sensible compromise; 22 is a real cost | Realized-vol card |
arima_fit forecast band |
AR(1) φ=0.9, worst horizon, T=100 | 0.899 ± 0.011 | APPROXIMATION | a plug-in band: the identical formula at the true parameters covers 0.946 on the same draws (paired plug-in cost +4.7pp ± 1.0). The formula is right; the gap is the price of not knowing φ and σ | ARIMA card |
arima_fit(d=1) forecast band |
random walk with drift, h=24, T=60 | 0.902 ± 0.008 | APPROXIMATION | forecast_se is exactly σ̂·√h (to 2.7e-15), i.e. the h²/(T−1) drift-uncertainty term is omitted entirely. The shortfall is therefore predictable in closed form: 2Φ(z/√(1+h/(T−1)))−1 gives 90.2% at h=24, measured 90.3%. Restoring the term recovers 94.5% |
ARIMA card |
lp_state, the persistent regime |
state 1, worst horizon, T=300 | 0.907 ± 0.007 | ESTIMATOR | se/sd is close to 1, so this is centring, not scale: the interacted design identifies each regime off roughly half a persistent sample and |bias|/sd reaches 0.27. The quiet regime is at nominal (0.937–0.953). State-dependent LP needs more data than linear LP for the same interval to mean the same thing | LP card |
var_forecast band |
worst horizon, T=100 | 0.925 ± 0.003 | APPROXIMATION | the same plug-in story, and it is estimation error rather than bias: at T=800 the paired gap to the oracle band is +0.2pp and coverage is 0.948; at T=100 the gap is +2.4pp | VAR/SVAR card |
lp(se="lag_augmented") — the default |
worst horizon, T=200 | 0.934 ± 0.004 | APPROXIMATION | the best-calibrated interval in the family, and the reason lag augmentation is the default. se/sd sits within a couple of percent of 1 at every horizon; the residual 1.6pp is ordinary finite-sample dynamic-regression bias, and it shrinks in T | LP card |
recession_probit |
rare events (rate 0.055), T=100 | 0.968 ± 0.004, on survivors | ESTIMATOR | over-covers, and the number is selected: 25.0% of replications have no finite MLE and the library correctly raises (16.2% no recession months at all, 8.8% quasi-complete separation). Read the failure share with the coverage, always. Among survivors 12.4% carry an SE more than 3× the median, and the MLE is biased away from zero (median bias +0.117 at T=100, +0.012 at T=1000). Note se/sd = 0.496 with 0.968 coverage is an outlier signature, not a narrow interval |
Recession card |
quantile_regression intercept |
x ~ U(0,2), so x=0 is at the edge, τ=0.50 | 0.993 ± 0.002 | APPROXIMATION | over-covers because the intercept is an extrapolation to the edge of the support and its sandwich SE is conservative. Per-coefficient coverage in a quantile regression is not uniform, and the intercept is usually not the quantity of interest | Quantile card |
var_irf_bands(bias_correct=True) at impact |
h=0, persistent VAR, T=100 | 0.842 ± 0.008 | APPROXIMATION | the cost side of a good trade: Kilian's correction buys ~49 coverage points at h=12 (0.410 → 0.900) and costs 5.8pp at impact. Take the trade on a persistent VAR | VAR/SVAR card |
quantile_lp — identified iid shock |
worst (τ, h, T) cell of the whole grid: τ=0.25, h=0, T=200 | 0.893 ± 0.010 | APPROXIMATION | the audit's cleanest good news: the quantile card's transferred growth_at_risk warning does not bind on an identified iid shock, because the check-loss score is serially uncorrelated by construction (the same mechanism that makes lag-augmented mean-LP inference work). The worst cell is the location-scale tail impact — a Powell kernel density-estimation cost, not an overlap one — and it shrinks in T (0.923 at T=400) | Quantile card |
favar + var_irf_bands — two-step bands |
small noisy panel (N=20), T=800, worst horizon | 0.673 ± 0.010 | ESTIMATOR | favar ships no band; this is the guide's own construction and its warned generated-regressor hazard, priced against a true-factor oracle on the same draws. On a rich clean panel the two are within 0.3pp; on the small noisy one the F̂ band loses a further 6–14pp at long horizons — and more T makes it worse (0.747 → 0.673 at h=7) while the oracle improves, because the band shrinks around an O(1/N) factor-measurement distortion. Bootstrap the two-step procedure, or grow N before T |
Multivariate guide |
umidas — the intercept |
AR(1) errors φ=0.7, persistent HF regressor, T=150 | 0.829 ± 0.007 | APPROXIMATION | the HF-lag coefficients hold ~0.92+ even here; the intercept inherits the error's full serial correlation (se/sd = 0.71) — the har_rv-constant mechanism on a new surface. Quote the constant with care or lengthen maxlags |
Nowcasting/MIDAS card |
growth_at_risk — bse (the NW default) |
τ=0.05, h=12, T=240 | 0.793 ± 0.010 (τ=0.5, h=12: 0.913 ± 0.007; h=1: 0.955 ± 0.005) | APPROXIMATION | the Newey-West correction at hac_lags = horizon−1 is the whole story at the median and half the story in the tail, exactly as the card's own table says: the residual is the Powell kernel density estimate at an extreme quantile (se/sd falls to 0.73 by h=12 at τ=0.05), which nothing you can pass fixes. Quote the fitted quantile path, not a tail coefficient interval, at h ≥ 8 |
Quantile card |
growth_at_risk — bse_powell (replication arm) |
τ=0.5, h=12, T=240 | 0.826 ± 0.010 (identical to bse at h=1, asserted exact) |
ESTIMATOR | the uncorrected Powell sandwich assumes a martingale-difference score; the h-step overlap makes it an MA(h−1), and the cost is ~9pp at the median by h=12 (se/sd 0.72 vs bse's 0.95). It exists for statsmodels replication and serially uncorrelated conditioners — use the default bse |
Quantile card |
proxy_svar_bands — moving-block Hall band |
worst (h, variable) cell, h=12, T=300 | 0.749 ± 0.014 (impact: 0.891 ± 0.010) | APPROXIMATION | the long-horizon decay is inherited from the reduced-form VAR bootstrap (no Kilian correction runs on the proxy path) — the card's documented cost, reproduced in-registry. On this DGP the Efron band beats the recommended Hall band at h=12 (pooled 0.885 vs 0.787), because the bootstrap distribution is right-skewed exactly where Hall's reflection hurts; both endpoints ship, so read both at long horizons | Identification card |
proxy_ar_sets(rf_method="delta") — the default |
routine VAR(1), T=250, h=12 | 0.828 ± 0.007 (h=1: 0.952 ± 0.004; card VAR(2) h=12: 0.881 ± 0.006) | APPROXIMATION | the audit's one-sided long-horizon decline (misses 515/0 above/below at h=12), reproduced in-registry: the delta variance is evaluated at the estimated coefficients and shrinks in exactly the under-persistent draws that miss. The two opt-in repairs are its group-A rows above; the default stays "delta" |
Identification card |
proxy_svar_bands(bands="wild") — the reproduction arm |
impact, strong instrument | 0.193 ± 0.020 | READING | not an interval at impact: the common-Rademacher draw leaves the identifying moment bit-identical in every draw, so the band carries no identification uncertainty at all — the card's frozen-moment defect, measured in-registry (the valid moving-block arm covers 0.891 at the same cell). It exists to reproduce published Mertens-Ravn / Gertler-Karadi bands and labels itself asymptotically_valid=False (asserted every run) |
Identification card |
garch_fit — se_mle |
t(5) innovations fit with dist="normal" (QMLE), T=2000 |
0.750 ± 0.014 (Gaussian innovations: 0.951–0.959) | ESTIMATOR | the inverse Hessian is only valid when the innovation distribution is correct; under fat tails its se/sd falls to ~0.54 for every parameter while the point estimates stay consistent. se_robust on the same fits holds 0.909–0.919. Quote se_robust unless you have a reason to believe the distribution |
Volatility card |
garch_fit — se_robust (Bollerslev-Wooldridge) |
t(5) innovations (QMLE), T=2000, worst parameter | 0.909 ± 0.009 (Gaussian: 0.946–0.957; T=500 Gaussian: 0.877–0.901) | APPROXIMATION | the sandwich fat-tailed fits need: it holds most of nominal where se_mle collapses, a few points short in finite samples (se/sd ~0.86). Boundary fits carry NaN SEs with se_valid=False rather than an invented number — 0.7–0.8% of draws here, counted and excluded; read that flag before either SE |
Volatility card |
flp — per-element se on functional_pca scores |
persistent yield-curve-like design, impact | 0.421 ± 0.013 (external true scores, same draws: 0.953 ± 0.005) | ESTIMATOR | the card's generated-regressor warning, priced: se conditions on the scores, and estimated eigenfunctions carry O_p(T^-1/2) rotation error the HAC sandwich cannot see — se/sd is 0.27 at impact. External scores are exempt (measured at nominal); on the canonical iid-impulse design the hazard is confined to impact and mild (0.860). Report flp_scenario's w'beta contrasts |
Functional-shocks card |
flp_scenario — w'beta scenario band |
in-span scenario, worst horizon (8) | 0.873 ± 0.009 (impact: 0.932 ± 0.007) | APPROXIMATION | the documented reporting route, and the immunity is real: at impact it covers at nominal on the same draws where the per-element se collapses to 0.42. The long-horizon decline is the ordinary LP-HAC cost this page documents for lp(se="hac") — se/sd drifts from 0.97 to 0.85 across the horizons |
Functional-shocks card |
theta_forecast, backtest |
— | no interval at all | READING | both return point paths only; backtest returns no interval-bearing key. Any band you report around them is your own construction and its coverage is your claim, not the library's. (For reference, a DIY interval built from backtest errors on a random walk with drift covers 93.0% at h=1 falling to 90.6% at h=6 — our construction, not a library promise) |
Forecasting card |
nongaussian_svar, garch_fit's variance_forecast |
— | no interval at all | READING | nongaussian_svar returns point B, IRF and kurtosis diagnostics only, and the GARCH variance_forecast is a documented analytic point path ("no interval or coverage level … none is implied") — both verified by per-run key-set tripwires, so a future se breaks an assertion rather than silently outdating this row. Any band you draw around either is your own construction |
Identification card / Volatility card |
C. Objects that make no frequentist promise¶
7 rows. Nothing here is a defect. A shortfall measures the prior or the identified set, and the reason these are reported at all is that a reader who expects 90% has misread the object.
| object | design | measured (nominal 0.90) | what the number means | documented in |
|---|---|---|---|---|
bvar_ssvs credible band |
true-but-small cross coefficient, h=12 | 0.594 ± 0.019 | the spike prior does exactly what it is for and zeroes a true 0.03 cross lag; the band then sits around zero. Compare the diffuse NIW band's 0.809 at the same cell | Bayesian card |
bvar_irf_draws credible band |
library-default Minnesota prior (δ=0), h=4 | 0.610 ± 0.018 | the prior mean is white noise and the truth has own lags 0.85, so the band is in the wrong place (bias −0.182 against width 0.39). A well-centred, tighter prior (δ=0.85, λ₁=0.05) reaches 0.906 at a narrower width of 0.32 — higher coverage from a smaller band, which is the signature of a centring problem rather than a scale one. bvar_hierarchical loosens the badly centred prior (λ₁ 0.2 → 0.60) and tightens the well-centred one (0.2 → 0.16), exactly as marginal-likelihood logic says — but tuning tightness cannot fix a prior mean in the wrong place (0.784 at h=12) |
Bayesian card |
sign_restricted_svar pointwise 5–95 band |
λ₁=0.2, h=3 | 0.698 ± 0.023 | a Haar-rotation posterior summary that mixes mutually inconsistent structural models — neither a confidence interval nor the identified set. That is precisely what fry_pagan_svar exists to complain about. At λ₁=5.0 it is 0.860 |
Identification card |
zero_sign_svar band at a true point-identifying zero |
λ₁=0.2, h=3 | 0.797 ± 0.020 | here the zero pins the rotation, so the band is about a point — which makes this the cleanest available reading of what the Minnesota prior costs a frequentist reader. 0.875 at λ₁=5.0 | VAR/SVAR card |
robust_svar_bounds (Giacomini-Kitagawa) |
λ₁=0.2, h=3 | 0.800 ± 0.020 | the one set-identified object that does aim at 1−α containment, and it delivers under a diffuse reduced-form prior (0.935 at λ₁=5.0, and ≥0.930 across every cell). It is robust to the rotation prior; it inherits the Minnesota prior on the reduced form, and with δ=0 that prior pulls a persistent response down and takes the whole set with it | Identification card |
bvar_irf_draws impact band vs an exact interval |
prior mean exactly right (white noise, δ=0) | 0.874 ± 0.007 vs the exact chi-square interval's 0.904 ± 0.006 on the same samples | even a perfect prior mean leaves ~3pp, and the mechanism is the conjugate convention: the inverse-Wishart posterior has df v₀+T = 104 while the residual sampling df is T_eff−k = 96. This is the cleanest statement on the page of why a credible band is not a confidence interval | Bayesian card |
sign_restricted_svar set envelope |
λ₁=0.2, h=3 | 0.953 ± 0.011 | the union over the reduced-form posterior of the identified set — wider than any credible object, and near-total containment certifies very little. At impact, where a sign restriction leaves the set open down to zero, every object covers ≈1.000 and measures nothing | Identification card |
Pointwise is not joint¶
This is the largest reading error available on this page, and it is not a defect in any function. A pointwise band promises that this horizon's true response is inside this horizon's band with probability 1−α. It promises nothing about the whole path, and the two rates are far apart:
| object | nominal, pointwise | measured pointwise (impact) | measured jointly, whole path |
|---|---|---|---|
var_irf_bands, asymptotic, T=100, h=0..12 |
90% | 0.897 | 0.567 ± 0.011 |
var_irf_bands, asymptotic, T=200, h=0..12 |
90% | 0.900 | 0.650 ± 0.011 |
var_irf_bands, asymptotic, T=500, h=0..12 |
90% | 0.910 | 0.722 ± 0.010 |
var_irf_bands, bootstrap, T=500, h=0..12 |
90% | 0.903 | 0.735 ± 0.010 |
var_forecast, T=100, 12 horizons × 2 series |
95% | 0.944 | 0.409 ± 0.006 |
var_forecast, T=800, 12 horizons × 2 series |
95% | 0.948 | 0.481 ± 0.006 |
arima_fit, AR(1) φ=0.9, T=100, h=1..12 |
95% | 0.933 | 0.639 ± 0.018 |
Note the T=800 row: the joint rate is 0.481 even where the marginal rate is at nominal (0.948 ± 0.002). Joint coverage does not converge to the marginal level as the sample grows — it is a different quantity, and a band that contains the whole path 95% of the time has to be materially wider.
The remedy, and the two places it stops¶
Added after 0.2.0. The finding above is left exactly as it was measured — it
is still what a pointwise band delivers, and it is still the default.
When this page was written, tsecon reported no simultaneous band for any
object, and that was the largest open recommendation
on it. A simultaneous (sup-t) band now exists: var_irf_bands,
var_forecast, lp and smooth_lp take band="sup-t" (or "sidak" /
"bonferroni"), and the two VAR functions take a band_scope as well. It
changes exactly one thing: the multiplier. Same point estimate, same standard
errors, and a constant c in point ± c·se chosen so that every cell of a
declared family is covered at once — the construction of Montiel Olea and
Plagborg-Møller. Scored against a pointwise arm on the same replications:
| object | nominal | design | pointwise, joint | sup-t, joint | Šidák | Bonferroni | measured by |
|---|---|---|---|---|---|---|---|
var_forecast |
95% | T=100, 12 horizons × 2 series, K=24, 2000 reps | 42.0% ± 1.1 | 90.5% ± 0.7 | 91.3% ± 0.6 | 91.5% ± 0.6 | forecast_intervals.py |
var_irf_bands, asymptotic |
90% | T=500, h=0..12, K=13, 1000 reps | 71.7% ± 1.4 | 85.2% ± 1.1 | 91.7% ± 0.9 | 92.0% ± 0.9 | irf_bands.py |
lp, lag-augmented |
90% | T=240, 13 horizons, 400 reps | 36.5% | 81.8% | — | — | the crate's own tests |
lp, lag-augmented |
90% | T=720, 13 horizons, 400 reps | 42.7% | 89.5% | — | — | the crate's own tests |
The two VAR rows are measured by this page's own modules, and both arms are read
off the same call: asking for a simultaneous band leaves point, se,
lower and upper bit-identical — those modules assert it — so the comparison
is paired rather than two independent runs, and every replication that gains the
path gains it because the multiplier grew. Their pointwise rates reproduce this
page's 0.409 and 0.722 on fresh seeds, and their sup-t rates reproduce what the
crates' own tests measure at higher replication counts (41.2% → 90.5% at 6000
reps; 70.4% → 84.8% at 3000). The LP rows are the crate tests' — lp has no
simultaneous-band arm in the eight modules below (its cumulative-mode
pointwise intervals are measured in quantile_panel_lp.py).
Neither VAR simultaneous rate reaches nominal — 85.2% against 90%, 90.5% against 95% — and that is the honest headline, not a footnote. A sup-t band fixes multiplicity and inherits every other defect on this page, because it reuses the same standard errors:
- the IRF band's own marginal coverage on the same 1000 draws is 91.0% at h=0 and 85.3% at h=12, so the residual belongs to the delta-method problem. What that cell needs is a better standard error, not a bigger multiplier;
var_forecastis a plug-in band that ignores coefficient sampling error, so its pooled per-cell marginal rate is 93.4%, not 95% — see the oracle column. A simultaneous band cannot hand back coverage the marginal band never had;- LP is the clean case, and it is the one that proves the mechanism. At T=720, where the marginal rates sit on nominal, sup-t lands on nominal (89.5% against 90%). At T=240 it does not (81.8%) — for the same reason the marginals are off there.
The LP rows also settle what kind of problem this is. Tripling T moved the pointwise joint rate from 36.5% to 42.7%. It is not converging: multiplicity is not a small-sample caveat that data cures.
Simultaneous over what? — this is a user-visible choice, not a detail.
Every cell added to the family widens the band for every other cell in it, so
the same alpha over a different family is a different band, and a band whose
scope is ambiguous is worse than no band. The scopes are:
| object | scope | the family | K on the designs above (k=2 series; IRF h=0..12, forecast 12 steps) |
|---|---|---|---|
var_irf_bands |
"horizon" (default) |
one response-shock pair's path — the object measured above | 13 |
var_irf_bands |
"shock" |
every response to one shock, all horizons | 26 |
var_irf_bands |
"all" |
the whole IRF grid | 52 |
var_forecast |
"all" (default) |
every horizon of every series — the object measured above | 24 |
var_forecast |
"horizon" |
one series' whole forecast path | 12 |
lp / smooth_lp |
— | the horizons of the one response | 13 |
Every result reports its scope and its K. Report them too.
Which multiplier, and what the fallbacks cost. The closed forms depend on
nothing but K: at K=13 and α=0.10 they are Šidák 2.6490 and Bonferroni
2.6653, against a pointwise 1.6449. The sup-t multiplier is a property
of the path, because it is the only route that uses the dependence across
cells — adjacent horizons of an impulse response are strongly positively
correlated, while Šidák (exact under independence) and Bonferroni (valid under
any dependence) pay for a worst case a smooth response path does not present. On
this page's BASE VAR(1) it averages 2.0742 over 1000 replications: 22%
below Šidák, and only 26% above the pointwise z. On more persistent paths the
crates' tests see it run up to about 2.65 — no saving at all. Read the critical
value your own fit reports; do not assume the saving.
And read the Šidák and Bonferroni columns above before treating them as safe fallbacks. On the IRF row they land at 91.7% and 92.0% against a nominal 90% — over the line, because over-widening happened to cancel a marginal shortfall of an entirely different origin. That is a coincidence on one DGP, not a calibration.
Three things this does not do. lp_iv, lp_multiplier and lp_state have
no cross-horizon covariance, so they get Šidák and Bonferroni only — sup-t
is refused with an error naming the reason, and their bands must never be
described as sup-t (panel_lp joined this closed-form-only list after the
audit; its own seeded joint-coverage table lives on the
panel card). The bootstrap simultaneous band is a different shape
from the bootstrap percentile band — symmetric point ± c·se against asymmetric
Efron percentiles — so it is not guaranteed to sit outside the percentile band
cell by cell, only outside the symmetric point ± z·se. And nothing that
already reads these functions changed meaning: var_irf_bands and var_forecast return
pointwise lower/upper whatever you pass — the simultaneous edges arrive
as extra sim_lower/sim_upper keys — and lp returns no band at all unless
asked. Every number in the tables above is a pointwise band and stays one, so
a fan chart you did not ask a simultaneous band for is still read one horizon
at a time.
The delta-method IRF band, horizon by horizon¶
The single most actionable table in the audit. DGP: a stationary VAR(1) with
largest root 0.758 and Σ = [[1, 0.4], [0.4, 2]]; the population orthogonalised
IRF is exactly A**h @ chol(Σ), so the truth is closed-form at every horizon.
Cell: the response of y1 to orthogonalised shock 0. Nominal 90%, 2000
replications, 399 bootstrap draws per replication.
n = 100
h truth med bias mean se mc sd |bias|/sd cov asym cov boot
0 0.4000 -0.0094 0.1386 0.1424 0.07 89.7±0.7 88.8±0.7
1 0.3500 -0.0237 0.1207 0.1270 0.19 87.8±0.7 86.7±0.8
2 0.2860 -0.0340 0.1238 0.1281 0.27 87.2±0.7 84.4±0.8
3 0.2260 -0.0367 0.1134 0.1155 0.32 85.5±0.8 81.7±0.9
4 0.1753 -0.0374 0.0981 0.0987 0.38 83.5±0.8 80.0±0.9
5 0.1347 -0.0330 0.0827 0.0826 0.40 81.2±0.9 78.7±0.9
6 0.1029 -0.0285 0.0688 0.0685 0.42 79.0±0.9 77.5±0.9
7 0.0784 -0.0243 0.0569 0.0567 0.43 76.6±0.9 76.5±0.9
8 0.0596 -0.0200 0.0469 0.0469 0.43 74.4±1.0 75.8±1.0
9 0.0452 -0.0166 0.0387 0.0389 0.43 72.5±1.0 75.3±1.0
10 0.0343 -0.0136 0.0319 0.0324 0.42 70.7±1.0 74.7±1.0
11 0.0260 -0.0111 0.0263 0.0270 0.41 68.7±1.0 74.6±1.0
12 0.0197 -0.0091 0.0217 0.0226 0.40 67.3±1.0 74.3±1.0
simultaneous coverage of the whole h=0..12 path (pointwise bands make NO such promise): asym 56.7±1.1 boot 62.5±1.1
standardised statistic t = (point - truth)/se, asymptotic arm. The Wald band
covers exactly when |t| <= 1.645, so these four rows ARE the coverage row above.
h=0 h=1 h=2 h=4 h=6 h=8 h=12
skewness 0.00 -0.17 -0.29 -0.81 -2.00 -3.84 -9.48
5th pct -1.67 -1.96 -2.14 -2.68 -3.74 -5.50 -13.26
median -0.07 -0.20 -0.28 -0.38 -0.44 -0.48 -0.57
95th pct 1.65 1.48 1.36 1.14 0.98 0.90 0.76
se / mc sd 0.97 0.95 0.97 0.99 1.00 1.00 0.96
Read the last row first. mean se / mc sd = 0.96 at h=12: the reported
standard error tracks the true sampling standard deviation to within 4%. The
standard error is not the problem. The problem is the row above it: the
standardised statistic has skewness −9.48, with 5th and 95th percentiles of
−13.26 and +0.76 against the ±1.645 the Wald band assumes. The band is not
too narrow — it is one-sidedly wrong. Its lower edge is far too high and its
upper edge is never reached.
That distinction matters because it tells you what to do. Scaling the interval up would not fix a skew; changing the shape would. The measurements confirm it:
| n | h=0 | h=4 | h=8 | h=12 | joint, h=0..12 |
|---|---|---|---|---|---|
| 100, asymptotic | 89.7 ± 0.7 | 83.5 ± 0.8 | 74.4 ± 1.0 | 67.3 ± 1.0 | 56.7 ± 1.1 |
| 100, bootstrap | 88.8 ± 0.7 | 80.0 ± 0.9 | 75.8 ± 1.0 | 74.3 ± 1.0 | 62.5 ± 1.1 |
| 200, asymptotic | 90.0 ± 0.7 | 88.1 ± 0.7 | 82.5 ± 0.8 | 77.2 ± 0.9 | 65.0 ± 1.1 |
| 200, bootstrap | 90.0 ± 0.7 | 86.2 ± 0.8 | 84.0 ± 0.8 | 83.1 ± 0.8 | 70.0 ± 1.0 |
| 500, asymptotic | 91.0 ± 0.6 | 87.8 ± 0.7 | 86.6 ± 0.8 | 84.7 ± 0.8 | 72.2 ± 1.0 |
| 500, bootstrap | 90.3 ± 0.7 | 87.1 ± 0.8 | 86.7 ± 0.8 | 86.3 ± 0.8 | 73.5 ± 1.0 |
Three practical conclusions. (i) The bootstrap band is the better choice at
long horizons at every sample size measured here (74.3 vs 67.3 at T=100 h=12),
because it does not impose symmetry. (ii) The shortfall shrinks with T but is
still 5.3pp at T=500, h=12. (iii) cumulative=True holds up far better than the
per-horizon band on this DGP — 88.4% vs 78.9% at h=12, T=200 — because the
running sum is dominated by the early, well-estimated horizons.
And a fourth, from a different DGP: on a persistent VAR (largest root 0.950) the plain percentile bootstrap is worse than the Wald band, because the band centre itself sits below an already downward-biased estimate. Kilian's bias correction is the fix and it is dramatic:
n = 100 h=0 h=1 h=2 h=4 h=6 h=8 h=10 h=12
true response 1.000 0.950 0.902 0.815 0.735 0.663 0.599 0.540
cov asymptotic 87.9±0.7 81.1±0.9 77.8±0.9 73.7±1.0 70.6±1.0 68.1±1.0 65.5±1.1 64.0±1.1
cov bootstrap 84.8±0.8 67.2±1.0 55.1±1.1 45.6±1.1 41.6±1.1 41.4±1.1 41.1±1.1 41.0±1.1
cov bootstrap+bc 84.2±0.8 85.0±0.8 86.7±0.8 88.6±0.7 89.4±0.7 89.5±0.7 89.8±0.7 90.0±0.7
med bias asymptotic -0.005 -0.044 -0.072 -0.117 -0.151 -0.177 -0.193 -0.202
med bias bootstrap+bc -0.005 -0.006 0.001 0.012 0.019 0.025 0.029 0.032
centre-point bootstrap -0.019 -0.060 -0.091 -0.122 -0.125 -0.113 -0.093 -0.072
centre-point bootstrap+bc -0.021 -0.029 -0.037 -0.045 -0.042 -0.029 -0.011 0.014
centre-point is the median of (lower+upper)/2 minus the point estimate. It is
0 for the symmetric Wald band by construction; the percentile bootstrap's
−0.072 means the band sits below an estimate that is already −0.202 from
the truth. Coverage 0.410 → 0.900 from one keyword.
Family detail¶
Regression standard errors¶
reps=3000, nominal 95%, z = 1.959964, MC se at p=0.95 is 0.0040.
Designs: y = 1 + 2x + e with (row 1) iid Gaussian errors; (row 2) sd(e) = |x|;
(row 3) AR(1) errors with an AR(1) regressor at φ=0.7; (row 4) t(3) errors.
error structure nonrobust hc0 hc1 hac auto hac lag8
---------------------------------------------------------------------------------------------
iid Gaussian 0.946+-0.004 0.941+-0.004 0.941+-0.004 0.938+-0.004 0.933+-0.005
heteroskedastic sd=|x| 0.732+-0.008 0.940+-0.004 0.942+-0.004 0.939+-0.004 0.930+-0.005
AR(1) errors+regressor .7 0.744+-0.008 0.733+-0.008 0.735+-0.008 0.876+-0.006 0.891+-0.006
t(3) errors (inf kurtosis) 0.951+-0.004 0.944+-0.004 0.946+-0.004 0.943+-0.004 0.934+-0.005
se/sd ratio (mean SE / MC sd of the estimate; <1 = SE too small)
iid Gaussian 0.999 0.988 0.993 0.981 0.968
heteroskedastic sd=|x| 0.572 0.960 0.965 0.954 0.941
AR(1) errors+regressor .7 0.579 0.566 0.569 0.804 0.839
t(3) errors (inf kurtosis) 0.987 0.960 0.965 0.953 0.941
Row 4 is a small but genuine reversal of the usual advice: under t(3) errors the robust standard errors lose slightly more coverage than the naive one (0.944 vs 0.951, se/sd 0.960 vs 0.987). t(3) has finite variance but no fourth moment, which is exactly what the sandwich's asymptotics assume.
The next table is the cleanest decomposition in the audit. Design:
x ~ chi2(1) (high leverage), sd(e|x) = x. hc2/hc3 are tsecon output as of
0.2.0; the hc2*/hc3* columns are independent NumPy references on the
same draws, kept as a cross-check (the two agree to 1.04e-14 across every
replication and sample size).
oracle is the sandwich at the true per-observation error variances; with
Gaussian errors and a fixed design the estimate is exactly normal about it, so
the oracle column must be exactly 0.95 at every T. It is — which proves that
every shortfall to its left is the variance estimate, not the normal
approximation.
T nonrobust hc0 hc1 hc2* hc3* oracle
------------------------------------------------------------------------------------
25 0.493+-0.009 0.667+-0.009 0.682+-0.009 0.773+-0.008 0.863+-0.006 0.951+-0.004
50 0.482+-0.009 0.780+-0.008 0.789+-0.007 0.838+-0.007 0.893+-0.006 0.945+-0.004
100 0.459+-0.009 0.849+-0.007 0.853+-0.006 0.885+-0.006 0.910+-0.005 0.950+-0.004
400 0.440+-0.009 0.923+-0.005 0.924+-0.005 0.932+-0.005 0.942+-0.004 0.954+-0.004
1600 0.428+-0.009 0.940+-0.004 0.940+-0.004 0.943+-0.004 0.945+-0.004 0.951+-0.004
nonrobust never converges — it is inconsistent here, so more data does not
help, and it slides downward with T. hc0 converges but slowly. hc1's
n/(n−k) factor is nearly worthless at k=2. HC2/HC3 target the leverage
directly and recover most of the small-T gap — which is why 0.2.0 added
them. Note the word most: hc3 reaches 0.863 at T=25, not 0.95, and its mean
se/sd of 0.942 overstates the typical interval because the SE distribution is
skewed. The remaining gap is the same one the oracle column isolates.
And the worst single number in the audit — a slope with a persistent regressor
and persistent errors, hac auto = ⌊4(T/100)^(2/9)⌋ = 4 lags, T=200:
phi score AC nonrobust hc1 hac auto hac lag12 hac lag24
---------------------------------------------------------------------------------
0.00 0.00 0.949+-0.004 0.947+-0.004 0.944+-0.004 0.930+-0.005 0.911+-0.005
0.50 0.25 0.875+-0.006 0.871+-0.006 0.926+-0.005 0.922+-0.005 0.902+-0.005
0.80 0.64 0.648+-0.009 0.634+-0.009 0.841+-0.007 0.873+-0.006 0.864+-0.006
0.95 0.90 0.358+-0.009 0.340+-0.009 0.588+-0.009 0.703+-0.008 0.728+-0.008
phi nonrobust hc1 hac auto hac lag12 hac lag24 (se/sd)
---------------------------------------------------------------------------------
0.00 1.005 0.996 0.982 0.956 0.919
0.50 0.789 0.778 0.922 0.927 0.897
0.80 0.476 0.465 0.737 0.814 0.806
0.95 0.239 0.223 0.432 0.562 0.602
This extends experiment 2 of the Monte Carlo suite, which covers a mean. A slope with a persistent regressor is materially worse and was previously unmeasured. Note also the φ=0 column: a 24-lag bandwidth costs 3.8 coverage points when there is no serial correlation to soak up. Bandwidth is not free in either direction.
Full report, including iv_gmm × instrument strength (with the Hansen J size at
a true null), har_rv, recession_probit and quantile_regression per τ:
docs/examples/coverage/regression_se.py.
VAR impulse-response bands¶
DGPs: BASE VAR(1) root 0.758; PERSIST VAR(1) root 0.950; LAG4 VAR(4) root
0.900, all with Σ = [[1, 0.4], [0.4, 2]]. Nominal 90%, reps=2000,
n_boot=399, horizons 0..12, n ∈ {100, 200, 500}.
Structural zeros are excluded from every claim and verified as exact facts
instead: under a Cholesky ordering the impact response of variable 0 to shock 1
is identically zero and var_irf_bands correctly reports se = 0 with
lower = upper = 0. The truth is also zero, so that cell "covers" 100% of the
time by construction and measures nothing. Same for the whole orth=False
impact matrix, which is the identity with zero width.
The misspecification experiment deserves its own reading, because it is the one
place where more data makes coverage worse. LAG4 truth, fitted as a
VAR(1), target held at the true VAR(4) path:
n = 200 h=0 h=1 h=2 h=3 h=4 h=5 h=6 h=8 h=12
true response 0.400 0.220 0.109 0.052 0.188 0.175 0.122 0.104 0.067
cov asymptotic, misspecified 82.7±0.8 68.8±1.0 69.1±1.0 72.5±1.0 17.8±0.9 7.0±0.6 7.1±0.6 1.2±0.2 0.1±0.0
cov asymptotic, correct 88.1±0.7 88.8±0.7 88.5±0.7 90.5±0.7 89.1±0.7 87.2±0.7 87.5±0.7 87.0±0.8 81.7±0.9
|bias|/mc_sd, misspecified 0.40 0.67 0.68 0.65 2.62 4.14 4.35 8.35 21.55
|bias|/mc_sd, correct 0.02 0.08 0.08 0.06 0.16 0.21 0.28 0.26 0.38
n = 500
cov asymptotic, misspecified 77.5±0.9 48.9±1.1 47.1±1.1 47.5±1.1 6.2±0.5 0.5±0.2 0.7±0.2 0.0±0.0 0.0±0.0
cov asymptotic, correct 90.4±0.7 90.2±0.7 90.1±0.7 90.0±0.7 90.5±0.7 89.1±0.7 88.8±0.7 89.0±0.7 86.2±0.8
|bias|/mc_sd, misspecified 0.70 1.29 1.27 1.23 3.49 5.69 6.12 12.90 42.35
17.8% → 6.2% at h=4 as T goes 200 → 500. That is the signature of
inconsistency: the interval shrinks around the wrong number. |bias|/mc_sd of
42 at h=12 says the band is nowhere near the truth in units of its own width.
The correct-lag arm is at nominal throughout. No band can substitute for
choosing the lag order.
Full report: docs/examples/coverage/irf_bands.py.
Local projections¶
DGP: y_t = Σ_{j<J} θ_j s_{t-j} + nuisance_t with θ_j = 0.7^j, J=25, and
s_t iid standard normal. Because s_t is orthogonal in population to
everything else in the horizon-h projection, the population LP coefficient on
s_t is exactly θ_h — for any number of lag controls and whatever serial
correlation the nuisance term has. There is no approximation in the truth, so
any miss belongs to the interval. Nominal 95%; T=200 unless stated.
The headline is that the library's default is the right default, and the comparison is paired on the same draws so the difference carries its own standard error:
paired coverage difference, lag_augmented minus hac (same draws):
h diff se_diff diff/se
---------------------------------
0 +0.0060 0.0019 3.15
4 +0.0217 0.0029 7.54
8 +0.0220 0.0034 6.49
12 +0.0340 0.0037 9.14
pooled over h >= 6 (per-draw average, so cross-horizon correlation is handled): +0.0271 (se 0.0014)
se="lag_augmented" (Montiel Olea & Plagborg-Møller 2021) wins at every
horizon, the mechanism is visible in se/sd (1.000 vs 0.926 averaged over h≥6),
and the gap closes in T — HAC's h=12 coverage goes 0.870 (T=100) → 0.939
(T=800) as its se/sd rises 0.863 → 0.979.
smooth_lp is the largest frequentist miss in the family, and it is worst
exactly where an applied reader looks first:
arm h truth bias sd_est mean_se se/sd |b|/sd cov95 mcse
lam=0 0 1.0000 -0.0003552 0.03993 0.04024 1.01 0.01 0.936 0.009
lam=cv 0 1.0000 -0.07812 0.06334 0.05076 0.80 1.23 0.646 0.018
lam=100 0 1.0000 -0.03906 0.04482 0.0451 1.01 0.87 0.861 0.013
|b|/sd = 1.23 at impact: the shrinkage bias exceeds a whole sampling standard
deviation, so the interval is centred in the wrong place and no standard error
saves it. The lam=100 arm isolates the two effects — se/sd is 1.01 there (the
SE is correctly sized) while |b|/sd is already 0.87 (pure shrinkage). The
library's own model card says se "conditions on lam and does not account for
shrinkage bias"; 0.646 against 0.95 is the size of what that sentence is
hiding. Read a smooth-LP band as a band around the penalized estimand, and
read the lam=cv column against the lam=0 column rather than against 0.95.
(This cell moved 0.640 → 0.646 — a third of its own MC standard error — when
0.3.0 made the default CV λ grid scale-relative; every other pre-existing cell
on this page reproduces the earlier run byte-for-byte.)
Full report, including lp_iv strong vs weak, lp_state per regime, and
lp_multiplier, plus a decomposition of every arm's worst horizon into
d_se / d_bias / d_other:
docs/examples/coverage/lp_family.py.
Predictive intervals¶
A predictive interval targets a future realisation, not a parameter, but the promise has the same form. DGPs: AR(1) at φ ∈ {0.9, 0.5}, T=100; ARMA(1,1) φ=0.6 θ=0.4; a stationary VAR(1) with A = [[.7,.15],[.1,.6]] and Σ = [[1,.4],[.4,1]] at T ∈ {100, 800}; a random walk with drift 0.1 at T ∈ {100, 60}. Nominal 95%.
Two devices carry the argument, and both are stronger than a bare coverage number.
The oracle column. Every library interval here is a plug-in interval: it evaluates the textbook Gaussian formula at the estimated parameters and ignores the sampling error in them. Run the identical formula at the true parameters and it covers at nominal. The gap is therefore not a wrong standard error — it is the price of not knowing the parameters, measured rather than asserted:
h | library | replicated | oracle | width(lib)
1 | 93.3 (0.95) | 93.3 (0.95) | 94.6 (0.86) | 3.867
4 | 91.3 (1.07) | 91.3 (1.07) | 94.4 (0.87) | 6.397
8 | 90.3 (1.12) | 90.3 (1.12) | 94.3 (0.88) | 7.418
12 | 90.7 (1.10) | 90.7 (1.10) | 95.9 (0.75) | 7.784
plug-in cost (oracle minus library, PAIRED on the same reps, pp): h1:+1.3+/-0.6 h4:+3.1+/-0.8 h8:+4.0+/-0.9 h12:+5.1+/-1.0
(replicated is the shipped band rebuilt by hand as mean ± z·se; it agrees to
4.4e-15 over 700 fits, which is how we know the interval is the classical
conditional-on-parameters interval and nothing else.)
A closed form. For the I(1) case the omitted term is available exactly, so the coverage the shipped band must attain is computable in advance:
h | library | oracle | corrected | width(lib)
1 | 93.3 (0.64) | 94.3 (0.60) | 93.5 (0.64) | 3.868
12 | 91.7 (0.71) | 94.9 (0.57) | 94.5 (0.59) | 13.401
24 | 90.3 (0.76) | 95.9 (0.51) | 94.5 (0.59) | 18.951
closed-form prediction for the shipped band: h1:94.8% h12:92.6% h24:90.2%
measured minus predicted (pp): h1:-1.5 h12:-0.9 h24:+0.2
arima_fit(0,1,0) reports forecast_se = σ̂·√h exactly (to 2.7e-15) — the
h²/(T−1) drift-uncertainty term is omitted. The prediction
2Φ(z/√(1+h/(T−1)))−1 gives 90.2% at h=24 and the measurement is 90.3%. When
measurement matches prediction, the shortfall is not merely observed, it is
explained. Restoring the term recovers 94.5%.
The nominal level is a real level, not a knob — var_forecast coverage is
strictly increasing in the requested level at every horizon:
h | 50% nom 80% nom 90% nom 95% nom 99% nom
1 | 49.1 (-0.9) 79.6 (-0.4) 89.2 (-0.8) 94.2 (-0.8) 98.8 (-0.2)
4 | 46.9 (-3.1) 76.6 (-3.4) 87.2 (-2.8) 92.9 (-2.1) 98.2 (-0.8)
6 | 47.4 (-2.6) 77.1 (-2.9) 87.1 (-2.9) 92.9 (-2.1) 98.2 (-0.8)
Full report: docs/examples/coverage/forecast_intervals.py.
Bayesian bands and identified sets¶
This family exists mostly to get the question right. DGPs: a PERSIST VAR(1)
with own lags 0.85 (largest root 0.905) at T=100; white noise fitted as a VAR(1)
so the δ=0 prior mean is exactly correct; an SVAR at T=200 whose true impact
matrix satisfies every imposed sign; a truly recursive VAR at T=200; and
y_t = e_t + δ·1{t ≥ T/2} for the break experiments. Nominal 90%.
The prior sweep is the whole story for bvar_irf_draws — and note that the
best row is both better-covering and narrower than the default, which is
the signature of a centring problem rather than a scale one:
coverage of the y0 <- shock0 orthogonalised IRF, nominal 0.90
design h=0 h=1 h=2 h=4 h=8 h=12
default 0.790+-0.015 0.770+-0.016 0.686+-0.018 0.610+-0.018 0.600+-0.019 0.624+-0.018
random walk 0.883+-0.012 0.844+-0.014 0.844+-0.014 0.857+-0.013 0.867+-0.013 0.873+-0.013
oracle 0.877+-0.012 0.794+-0.015 0.801+-0.015 0.813+-0.015 0.826+-0.014 0.827+-0.014
oracle-tight 0.887+-0.012 0.881+-0.012 0.887+-0.012 0.906+-0.011 0.897+-0.011 0.883+-0.012
over-tight 0.889+-0.012 0.874+-0.013 0.831+-0.014 0.730+-0.017 0.561+-0.019 0.403+-0.019
diffuse 0.869+-0.013 0.780+-0.016 0.777+-0.016 0.789+-0.015 0.806+-0.015 0.807+-0.015
emp-Bayes d=0 0.886+-0.012 0.779+-0.016 0.770+-0.016 0.767+-0.016 0.779+-0.016 0.784+-0.016
emp-Bayes d=1 0.883+-0.012 0.847+-0.014 0.849+-0.014 0.850+-0.013 0.874+-0.013 0.880+-0.012
SSVS spike-slab 0.899+-0.011 0.877+-0.012 0.814+-0.015 0.711+-0.017 0.621+-0.018 0.594+-0.019
WHY: median bias / mean band width / MC sd of the posterior median (y0 <- shock0)
design h=0 h=2 h=8
default +0.050/0.24/0.08 -0.117/0.33/0.13 -0.194/0.37/0.12
oracle-tight -0.010/0.23/0.07 -0.047/0.26/0.07 -0.094/0.34/0.08
over-tight -0.003/0.23/0.07 -0.048/0.20/0.06 -0.101/0.18/0.03
over-tight is the mirror-image warning: a correctly centred prior pushed to
λ₁=0.02 collapses to 0.403 at h=12, because being right about the own lags is
not enough when the true cross lags are crushed to zero.
And the three set-identified objects, which answer three different questions and should never be read as one:
y0 <- shock0 structural IRF (truth: h0=+1.000 h1=+0.660 h2=+0.431 h3=+0.279 h4=+0.180 h6=+0.074)
h=0 is sign-restricted: the set is open down to 0, so everything covers there.
design h=0 h=1 h=2 h=3 h=4 h=6
pointwise band, l1=0.2 0.993+-0.004 0.760+-0.021 0.688+-0.023 0.698+-0.023 0.710+-0.023 0.728+-0.022
pointwise band, l1=5.0 0.988+-0.006 0.860+-0.017 0.848+-0.018 0.860+-0.017 0.873+-0.017 0.885+-0.016
set envelope, l1=0.2 1.000+-0.000 0.970+-0.009 0.950+-0.011 0.953+-0.011 0.948+-0.011 0.945+-0.011
robust CI, l1=0.2 1.000+-0.000 0.887+-0.016 0.815+-0.019 0.800+-0.020 0.792+-0.020 0.777+-0.021
robust CI, l1=5.0 0.998+-0.002 0.948+-0.011 0.930+-0.013 0.935+-0.012 0.932+-0.013 0.930+-0.013
The ordering pointwise band < robust CI < set envelope is what the three
objects mean; only the middle one aims at 0.90. And the h=0 column certifies
nothing at all: a weak sign restriction leaves the identified set open down to
zero, so the truth is inside whatever the data say. Impact coverage of a
sign-restricted band is arithmetic, not calibration.
Full report, including narrative_svar (true statements tighten the h=1 band
from 0.486 to 0.368 while coverage moves 0.724 → 0.736 — and the ARW bands are
not nested, because importance reweighting can move an individual quantile
outward) and the bai_perron break-date experiments:
docs/examples/coverage/bayes_and_sets.py.
Quantile, panel and cumulative local projections¶
The three LP surfaces the original audit did not measure. DGPs: a
location-scale MA whose truncation at J = p + 1 makes the conditional
quantile exactly linear in quantile_lp's design (so the per-tau truths
are closed-form: impact slope θ₀ + 0.4·z_τ, later slopes θ_h); a dynamic
panel with a common shock, a common factor, and truth 0.8·0.8^h; and the
lp_family house MA for the cumulative modes. Nominal 95% throughout;
1000–2500 replications by experiment.
quantile_lp is the audit's cleanest good news, and the reason is
structural. Its own model card warns that these Powell-sandwich standard
errors are "not HAC" and that growth_at_risk's measured overlap
under-coverage (0.72 at h=8, τ=0.5, T=200) "is the right order of magnitude
to expect here too". Measured, that transfer does not bind on the
canonical design: with an identified i.i.d. shock the check-loss score is
serially uncorrelated at every horizon — the overlapping windows correlate
the ψ factors, but each is multiplied by an independent shock draw, the same
mechanism that makes lag-augmented inference work for mean LP. Coverage sits
at 0.89–0.97 across every (τ, h, T) cell, worst at the location-scale impact
tail (0.893 ± 0.010 at τ=0.25, T=200 — a density-estimation cost, not an
overlap one). The hazard is real where nothing whitens the regressor:
persistent regressor (phi_s = 0.8), tau = 0.50, T = 200, nominal 0.95
h=0 h=2 h=4 h=6
p=4 (the default) 0.946 0.933 0.946 0.931 se/sd 1.00-1.01
p=0 (no controls) 0.899 0.881 0.859 0.785 se/sd 0.88 -> 0.67
paired difference, p=4 minus p=0, pooled h >= 3: +0.106 (se 0.008)
The default lag controls include the regressor's own lags, so the
residualised impulse is (nearly) the AR innovation and the sandwich survives;
strip them and the growth_at_risk-shaped decay appears in full, driven by the
SE (se/sd falls to 0.66 while |bias|/sd stays near 0.4). Keep
n_lag_controls at least the regressor's AR order.
panel_lp's Driscoll-Kraay default: the effective sample is T, and now
that is a number. With a common shock and a common factor
(y_it = α_i + 0.8·y_{i,t−1} + 0.8·s_t + 0.9·f_t + e_it):
nominal 0.95, Driscoll-Kraay (the default), h = 0 / 2 / 4
N=10 T=40 0.875 0.841 0.824 N=10 T=80 0.904 0.884 0.887
N=50 T=40 0.889 0.839 0.818 N=50 T=80 0.916 0.887 0.894
Quintupling N moves pooled coverage by under a point; doubling T buys ~5pp.
The Nickell bias is visible and behaves exactly as Nickell says (−0.071 at
h=4, T=40 → −0.043 at T=80, untouched by N). And the cookbook's "wrong
reflex" is priced: se_type="cluster" on the same draws covers 0.20
(se/sd = 0.12) — clustering by entity assumes independence across entities in
the presence of a common factor, and the paired coverage difference to
Driscoll-Kraay is +0.70 (se 0.004). The dramatic size is this DGP's strong
factor loading; the sign is general.
The split-panel jackknife, re-measured on the panel card's own design at ~8× its replications (γ_f = 0, N=50, bandwidth=2; the card's 300-rep table claims FE 0.743 → SPJ 0.823 at T=20, h=2):
T=20 h=2 bias fe -0.141 -> spj +0.015 cov95 fe 0.713 -> spj 0.761
T=40 h=2 bias fe -0.059 -> spj +0.011 cov95 fe 0.846 -> spj 0.843
paired coverage difference, spj minus fe: T=20 +0.025 (se 0.006),
T=40 -0.015 (se 0.005)
The bias claim is corroborated almost exactly (the card's −0.137 → +0.009). The coverage gain at T=20 is real but smaller than the card's point numbers suggest — +2.5pp paired, not +8pp — and at T=40 SPJ covers very slightly worse than FE, consistent with the card's own T=40 cells. Neither route approaches nominal at T=20; Driscoll-Kraay is itself a short-T approximation, exactly as the card warns. Read that table with its mcse (~0.025) in mind.
lp(cumulative="both"): the official post-fix numbers. The audit's most
serious open finding was that this mode's old lag-augmented/HC1 default
covered 0.507 at h=12, flat in T; 0.3.0 made se=None resolve to
"hac" here and made an explicit se="lag_augmented" raise (both asserted
every run):
cumulative="both", default se -> "hac", nominal 0.95
h=0 h=4 h=8 h=12
T=400 0.943 0.960 0.950 0.920 se/sd 0.94-1.05
T=1600 0.936 0.971 0.965 0.956 se/sd 0.97-1.10
h=12 goes 0.507 → 0.920 ± 0.006 at T=400 and 0.956 at T=1600 — the
repair, measured on the audit's DGP (the fix's own probe reported 0.920 at
the matching cell). The residual deviation flips mildly conservative at
T=1600 mid-horizons (0.971 at h=4): the Bartlett bandwidth h + p is
generous once T is large relative to the MA(h) overlap. The
cumulated-outcome mode (cumulative=True) keeps its lag-augmented default
and stays at 0.93–0.96 everywhere, as the LP card states.
Full report:
docs/examples/coverage/quantile_panel_lp.py.
Factor models and mixed frequency¶
Split by an honest question asked first: does the function ship an interval
at all? favar, weighted_midas, dfm_nowcast and nelson_siegel do not —
verified against their live key sets on every run, so a future se key
breaks an assertion rather than silently outdating this page. umidas ships
HAC bse, and favar's guide documents how bands get built in practice
(var_irf_bands on [F̂, policy]) while warning that "bands that condition
on F̂ as if it were data are too narrow". Both measurable claims are
measured.
The FAVAR generated-regressor hazard, priced. The guide's own transmission DGP (two latent factors + a policy rate forming a VAR(1) with diagonal Σ); the measured cell is the policy rate's response to the recursive policy shock — the one FAVAR band cell invariant to factor rotation, so its truth is closed-form. Each replication fits the two-step band and, on the same draw, the infeasible band on the true factors:
nominal 0.90, h = 0 / 4 / 8, F-hat band vs oracle band (paired)
N=100 T=200 (rich, clean) F-hat 0.887 0.903 0.816 oracle 0.889 0.898 0.824
N=20 T=200 (small, noisy) F-hat 0.902 0.920 0.733 oracle 0.894 0.895 0.833
N=20 T=800 F-hat 0.897 0.877 0.728 oracle 0.893 0.905 0.892
paired F-hat minus oracle, pooled h >= 4:
N=100 T=200: -0.003 (se 0.001) N=20 T=200: -0.058 (se 0.004) N=20 T=800: -0.138 (se 0.006)
On the rich panel the two are within 0.3pp — Bai-Ng negligibility (√T/N → 0) has effectively arrived, and what remains is the delta-method horizon decay this page already documents. On the small noisy panel the F̂ band loses a further 6–14pp at long horizons, and the T-growth column is the diagnosis: from T=200 to T=800 the oracle improves (0.833 → 0.892 at h=8) while the F̂ band gets worse (0.733 → 0.728, and 0.747 → 0.673 at h=7, with |bias|/sd reaching 1.03) — the band shrinks like 1/√T around a factor-measurement distortion that is O(1/N) and does not shrink in T at all. No standard error that conditions on F̂ can see it; bootstrap the two-step procedure, or grow N before T.
umidas's HAC intervals: the slopes hold, the intercept is the casualty.
On a known-weights mixed-frequency DGP (truth β·w_k per lag, exactly):
nominal 0.95 intercept worst HF-lag coefficient
iid errors, T=300 0.943 0.940
AR(1) errors phi_u=0.7,
HF phi_h=0.9, T=150 0.829 0.918 (intercept se/sd 0.71)
The lag coefficients stay ~0.92+ even under persistent errors and
near-collinear lag columns; the intercept inherits the error's full serial
correlation — the same constant-under-persistence mechanism this page
documents for har_rv's constant. Quote the constant with care or lengthen
maxlags.
Full report:
docs/examples/coverage/factor_midas.py.
Proxy-SVAR inference, GARCH, growth-at-risk and functional LP¶
The five families the original audit listed as unmeasured, split by the same
question factor_midas asks first: does the surface ship an interval at all?
nongaussian_svar and the GARCH variance_forecast do not — verified by
key-set tripwires every run. Everything else is measured on closed-form-truth
DGPs (stated below).
proxy_svar_bands: the valid arm decays, the reproduction arm is not an
interval. Card VAR(2), T=300, strong instrument, nominal 90%,
n_boot=2000, 1000 replications (wild arm 400):
pooled over the three variables (the degenerate (norm_var, h=0) cell
asserted exact and excluded) h=0* h=1 h=4 h=8 h=12
moving-block, Hall 0.881 0.873 0.853 0.817 0.787
moving-block, Efron 0.865 0.875 0.851 0.866 0.885
wild, Hall 0.218 0.856 0.848 0.819 0.787
(* h=0 excludes the normalized variable; wild h=0 is its two free cells)
The moving-block Hall band starts ~1–3pp short at impact and gives up
~10pp more by h=12 — the reduced-form bootstrap decay the card documents (no
Kilian correction runs on the proxy path). Two findings are sharper than the
card's numbers: the Efron percentile band beats the recommended Hall band
at h=12 on this DGP (0.885 vs 0.787 pooled — the bootstrap distribution is
right-skewed exactly where Hall's reflection hurts; read both endpoints at
long horizons), and the wild arm collapses to 0.19–0.24 at impact
while looking almost reasonable at h ≥ 1 — because the frozen identifying
moment is the whole uncertainty at h=0 and only part of it later. It
self-reports asymptotically_valid=False, asserted every run.
proxy_ar_sets: the audit's decline, its shipped repair, and the repair's
conservative variant, paired on the same draws. Nominal 95%, 1000
replications, mean over non-degenerate cells:
h=1 h=4 h=8 h=12 misses at h=12 median width
card VAR(2), T=300 (above/below) vs delta, h=12
delta (the default) 0.952 0.941 0.916 0.881 357 / 0 1.00
second_order 0.958 0.942 0.952 0.974 78 / 0 1.58
second_order_bc 0.958 0.952 0.970 0.990 30 / 0 1.94
routine VAR(1), T=250
delta 0.957 0.933 0.884 0.828 515 / 0 1.00
second_order 0.961 0.945 0.930 0.935 194 / 0 1.61
second_order_bc 0.961 0.954 0.953 0.966 103 / 0 2.02
Every miss is one-sided (the truth exits above the set) and the boundedness
decision is bit-identical across the three rf_methods on every draw (the
corrections enter v0 only — asserted). second_order recovers most of the
decline and leaves the ~1.5pp residual on the routine VAR(1) that roadmap
note 21 recorded; second_order_bc (the note-21 follow-up: the same
simulation centred at Pope-bias-corrected coefficients) closes that residual
from the conservative side — at-or-above nominal at every horizon on both
DGPs, over-covering where second_order already sufficed, at ~2x the delta
width. A floor, not a calibration; the default stays "delta".
growth_at_risk: the Newey-West correction is the whole story at the
median and half the story in the tail. Exact Gaussian state-space truth,
T=240, 1500 replications, the slope on the conditioning variable:
nominal 0.95 h=1 h=4 h=8 h=12 se/sd at h=12
bse, tau=0.50 0.955 0.933 0.924 0.913 0.95
bse_powell tau=0.50 0.955 0.892 0.849 0.826 0.72
bse, tau=0.05 0.888 0.845 0.794 0.793 0.73
bse_powell tau=0.05 0.888 0.830 0.784 0.783 0.68
At h=1 the two are bit-identical (asserted exact — nothing overlaps). At the median the correction holds ~0.91+ through h=12 where the uncorrected sandwich drops to 0.83. In the 5% tail the corrected interval still sits at 0.79 by h=8 — the Powell kernel density estimate at an extreme quantile, the card's documented residual, which no argument fixes. The card's own advice stands, measured: at h ≥ 8 in the tail, quote the fitted quantile path.
garch_fit: the QMLE story, per standard error. GARCH(1,1),
(ω, α, β) = (0.05, 0.10, 0.85), 1000 replications each:
nominal 0.95, worst parameter se_mle se_robust
normal z, T=2000 0.951 0.946
normal z, T=500 0.902 0.877
t(5) z fit as normal, T=2000 0.750 0.909
Under Gaussian innovations at T=2000 both routes are at nominal. Under t(5)
innovations — the QMLE case every fat-tailed financial series is in — the
inverse-Hessian se_mle collapses (se/sd ≈ 0.54 on every parameter) while
Bollerslev-Wooldridge se_robust holds ~0.91. Boundary fits are excluded
and counted (≤ 0.8% here); the library marks them se_valid=False with NaN
rather than inventing a number.
flp / flp_scenario: the generated-regressor warning, priced against
both exempt routes. Persistent yield-curve-like design, T=400, 1500
replications, nominal 95%:
h=0 h=2 h=4 h=8 se/sd h=0
est. scores, per-element se 0.421 0.733 0.846 0.881 0.27
TRUE scores, per-element se 0.953 0.925 0.895 0.883 0.99
flp_scenario w'beta band 0.932 0.923 0.893 0.873 0.97
The card's warned collapse is real and dramatic — 0.42 at impact with se/sd 0.27 — and both documented exemptions hold on the same draws: external scores are at nominal at impact, and the scenario contrast's rotation invariance keeps it at 0.93 where the per-element band fails. What remains at long horizons (~0.87–0.88 for all three arms) is the ordinary LP-HAC persistence cost, not the generated-regressor one. On the canonical iid-impulse design the hazard is confined to impact and mild (0.860 vs 0.952 true-scores).
Full report:
docs/examples/coverage/proxy_garch_tail.py.
Check one number yourself¶
Three short, seeded programs. Each one runs in under a second and reproduces a finding from the tables above without the surrounding harness.
1. The delta-method IRF band loses coverage in the horizon. This is a 500-replication sketch of the 2000-replication measurement above, so expect third-decimal differences (and note it starts the series at zero with a burn-in, where the module draws an exactly-stationary initial condition):
import numpy as np
import tsecon
# The BASE DGP of docs/examples/coverage/irf_bands.py: a stationary VAR(1)
# whose population orthogonalised IRF is exactly A**h @ chol(Sigma).
A = np.array([[0.70, 0.10], [0.15, 0.50]])
P = np.linalg.cholesky(np.array([[1.0, 0.4], [0.4, 2.0]]))
truth = np.array([(np.linalg.matrix_power(A, h) @ P)[1, 0] for h in range(13)])
reps, T, burn = 500, 200, 200
hits = np.zeros(13)
for r in range(reps):
rng = np.random.default_rng([20260729, r])
y = np.zeros((burn + T, 2))
for t in range(1, burn + T):
y[t] = A @ y[t - 1] + P @ rng.standard_normal(2)
b = tsecon.var_irf_bands(y[burn:], lags=1, horizon=12, orth=True,
method="asymptotic", alpha=0.10)
lo = np.asarray(b["lower"])[:, 1, 0]
hi = np.asarray(b["upper"])[:, 1, 0]
hits += (lo <= truth) & (truth <= hi)
cov = hits / reps
se = np.sqrt(cov * (1.0 - cov) / reps)
print(f"nominal 90% delta-method band, response of y1 to shock 0, T={T}, reps={reps}")
for h in (0, 4, 8, 12):
print(f" h={h:<3d} truth {truth[h]:.4f} coverage {cov[h]:.3f} +- {se[h]:.3f}")
nominal 90% delta-method band, response of y1 to shock 0, T=200, reps=500
h=0 truth 0.4000 coverage 0.930 +- 0.011
h=4 truth 0.1753 coverage 0.878 +- 0.015
h=8 truth 0.0596 coverage 0.826 +- 0.017
h=12 truth 0.0197 coverage 0.784 +- 0.018
2. weight="hac" used to be the White estimator, and no longer is. No
Monte Carlo needed — the old behaviour was an identity, and the fix is visible
in one draw. This audit found it; 0.2.0 closed it. The "robust" and
bandwidth=10 numbers below are unchanged from the original run, which is the
point: only the default moved.
import numpy as np
import tsecon
rng = np.random.default_rng(20260729)
T = 250
z = rng.standard_normal((T, 2)) # two instruments
e = np.zeros(T) # AR(1) errors, phi = 0.8
u = rng.standard_normal(T)
for t in range(1, T):
e[t] = 0.8 * e[t - 1] + u[t]
x = (0.6 * z.sum(axis=1) + 0.7 * e + rng.standard_normal(T)).reshape(-1, 1)
y = 1.0 * x[:, 0] + e
robust = tsecon.iv_gmm(x, z, y, method="2step", weight="robust")
hac_auto = tsecon.iv_gmm(x, z, y, method="2step", weight="hac")
hac_bw10 = tsecon.iv_gmm(x, z, y, method="2step", weight="hac", bandwidth=10.0)
print(f'weight="robust" se = {robust["bse"][0]:.6f}')
print(f'weight="hac" (auto: {hac_auto["hac_bandwidth"]:.0f} lags) se = {hac_auto["bse"][0]:.6f}')
print(f'weight="hac", bandwidth=10.0 se = {hac_bw10["bse"][0]:.6f}')
print(f'|hac(auto) - robust| = {abs(hac_auto["bse"][0] - robust["bse"][0]):.3e}')
try:
tsecon.iv_gmm(x, z, y, method="2step", weight="hac", bandwidth=0.0)
except ValueError as exc:
print(f'\nbandwidth=0.0 -> ValueError: {str(exc)[:72]}...')
print(f'\nfirst-stage F on the endogenous regressor: '
f'{hac_auto["first_stage"][0]["fstat"]:.1f}')
weight="robust" se = 0.204701
weight="hac" (auto: 4 lags) se = 0.198534
weight="hac", bandwidth=10.0 se = 0.193519
|hac(auto) - robust| = 6.167e-03
bandwidth=0.0 -> ValueError: bandwidth=0.0 with weight="hac" is a no-op: a Bartlett kernel truncated ...
first-stage F on the endogenous regressor: 22.1
Before 0.2.0 the second line read 0.204701 and the fourth read
0.000e+00 — bit-identical to "robust", in every one of 3000 replications.
Now the default resolves to the Newey-West rule of thumb
floor(4 (n/100)^(2/9)) = 4 lags at T=250, reports that choice back as
hac_bandwidth, and refuses an explicit 0.0 rather than honouring it.
The fix does not repair the coverage, and the numbers say so: 0.632 at the
old no-op default, 0.842 at the automatic rule, 0.868 at bandwidth=10
against a nominal 0.95. A working default is not a remedy — under moments this
persistent, T=250 does not contain enough independent information to estimate
the long-run variance, and the automatic rule picks fewer lags than the
setting that reached 0.868. The last line is the other half of the fix: the
first-stage F is now reported, so the caller can see the instrument strength
their interval rests on.
3. The omitted drift-uncertainty term in an I(1) forecast band. Also an identity:
import numpy as np
import tsecon
rng = np.random.default_rng(20260729)
T, H = 100, 12
y = np.cumsum(0.1 + rng.standard_normal(T)) # random walk with drift 0.1
fit = tsecon.arima_fit(y, p=0, d=1, q=0, constant=True,
forecast_steps=H, conf_alpha=0.05)
se = np.asarray(fit["forecast_se"])
sigma = se[0] # forecast_se at h=1 IS sigma
h = np.arange(1, H + 1)
shipped = sigma * np.sqrt(h) # what the library reports
correct = sigma * np.sqrt(h + h**2 / (T - 1)) # + drift uncertainty
print(f"max |forecast_se - sigma*sqrt(h)| = {np.abs(se - shipped).max():.2e}")
print(" h shipped se with drift term ratio")
for i in (0, 3, 7, 11):
print(f"{h[i]:2d} {shipped[i]:9.4f} {correct[i]:15.4f} "
f"{shipped[i] / correct[i]:.4f}")
max |forecast_se - sigma*sqrt(h)| = 4.44e-16
h shipped se with drift term ratio
1 0.9666 0.9715 0.9950
4 1.9332 1.9719 0.9804
8 2.7340 2.8423 0.9619
12 3.3484 3.5456 0.9444
The data-generating processes¶
Every number on this page is conditional on the process that produced it. These are the canonical textbook cases plus the stress regimes applied work lives in; they are not an exhaustive sweep, and a number here is evidence about a mechanism rather than a constant for the function.
| family | processes | nominal | replications |
|---|---|---|---|
regression_se |
y = 1 + 2x + e with iid Gaussian / sd(e)=|x| / AR(1) errors with an AR(1) regressor at φ=0.7 / t(3) errors. A high-leverage design x ~ chi2(1), sd(e|x)=x at T ∈ {25,…,1600}. A HAC slope design with x and e both AR(1) at φ ∈ {0, 0.5, 0.8, 0.95}. IV over-identified by 1 with corr(e,v)=0.7 and π ∈ {0.6, 0.2, 0.05}. A Corsi HAR with truth [−0.2, 0.35, 0.35, 0.25], Σb = 0.95. A probit/logit with an AR(1) index at φ=0.9 in a common (rate 0.46) and a rare (rate 0.055) regime. A quantile design y = a + bx + (s₀ + s₁x)u, x ~ U(0,2) |
95% | 3000 |
irf_bands |
BASE VAR(1) root 0.758; PERSIST VAR(1) root 0.950; LAG4 VAR(4) root 0.900 fitted as a VAR(1); all Σ = [[1, 0.4], [0.4, 2]], n ∈ |
90% | 2000 (399 bootstrap draws each) |
lp_family |
y_t = Σ_j 0.7^j s_{t−j} + nuisance_t truncated at J=25, T ∈ {100, 200, 400, 800}; a heteroskedastic variant; strong vs weak LP-IV; a two-state Markov design with P(stay)=0.9; a persistent-impulse multiplier design at ρ_x=0.8 |
95% | 700–4000 by experiment |
forecast_intervals |
AR(1) at φ ∈ {0.9, 0.5}, T=100; ARMA(1,1) φ=0.6 θ=0.4; VAR(1) A = [[.7,.15],[.1,.6]], Σ = [[1,.4],[.4,1]], T ∈ {100, 800}, fitted lags ∈ {1, 4}; random walk with drift 0.1 at (T=100, H=12) and (T=60, H=24) | 95% (plus a 50/80/90/95/99 sweep) | 600–6000 by experiment |
bayes_and_sets |
PERSIST VAR(1) own lags 0.85 at T=100 under nine priors; white noise fitted as a VAR(1) so δ=0 is exactly right; a sign-restricted SVAR at T=200 whose true A₀ satisfies every imposed sign; a truly recursive VAR at T=200; y_t = e_t + δ·1{t ≥ T/2} at T ∈ {200, 400, 800} and δ/σ ∈ {3, 2, 1, 0.5, 0.25} |
90% | 250–2500 by experiment |
quantile_panel_lp |
a location-scale MA y_t = Σ_j 0.7^j s_{t−j} + (1 + 0.4 s_t) e_t truncated at J = p + 1 = 5 so the conditional quantile is exactly linear in the design, s ~ U(−1.5, 1.5) iid, T ∈ {200, 400}, τ ∈ {0.25, 0.5, 0.75}; the same MA driven by a Gaussian AR(1) regressor at φ_s=0.8 (pure location, closed-form slopes), fitted with p ∈ {4, 0}; a dynamic panel y_it = α_i + 0.8 y_i,t−1 + 0.8 s_t + 0.9 f_t + e_it with a common shock and a common factor, N ∈ {10, 50}, T ∈ {40, 80}, plus the panel card's own SPJ design (γ_f = 0, N=50, T ∈ {20, 40}); the lp_family house MA for cumulative at T ∈ |
95% | 1000–2500 by experiment |
factor_midas |
the guide's FAVAR transmission DGP — 2 latent factors + a policy rate forming a VAR(1) with diagonal Σ, panel X = ΛF + idio with (N, idio sd, T) ∈ {(100, 0.5, 200), (20, 1.0, 200), (20, 1.0, 800)}, Λ redrawn per replication; a mixed-frequency DGP y = 0.5 + 1.5 Σ_k w_k hf[t,k] + u with exp-Almon weights, K=12, m=3, HF AR(1) φ_h ∈ {0.5, 0.9}, u iid or AR(1) φ_u=0.7, T ∈ {300, 150} |
90% (favar bands) / 95% (umidas) | 2000–3000 |
proxy_garch_tail |
the proxy card's 3-variable VAR(2) (T=300, spectral radius 0.68) and roadmap note 21's routine VAR(1) (T=250, radius ~0.70), u = Hε with a strong proxy m = ε₀ + 1.5ν, truth λ(h,i) = (Ψ_h H[:,0])_i / H[0,0] exactly; a Gaussian state VAR(1) (y, x) with ρ=0.5, β=0.5, φ=0.85 at T=240, whose h-ahead conditional quantile is exactly linear in [1, x_t, y_t] (the GaR design); GARCH(1,1) (ω, α, β) = (0.05, 0.10, 0.85) with standard-normal or standardized t(5) innovations at T ∈ {2000, 500}; curve panels exactly spanned by two orthonormal shapes with iid or AR(1) (0.9/0.7) scores, M=8, T ∈ {300, 400} | 90% (proxy_svar_bands) / 95% (everything else) | 400–1500 by experiment (n_boot=2000) |
What is not measured¶
Stated so you do not have to discover it.
- ~~No simultaneous (sup-t) band exists to measure.~~ One exists now, and
it is measured — but only for two of the four surfaces that offer it.
irf_bands.pyandforecast_intervals.pyeach carry a simultaneous arm, and those numbers are in pointwise is not joint. They are not in Table 1 or Table 2: every row of those tables is a pointwise or marginal band, which is still what a caller gets by default and still what a reader gets by mistake.lpandsmooth_lpalso take a band selector and have no arm inlp_family.py— the LP rows quoted are the crate's own tests. Both VAR arms run at a reducedband_n_sim(20000 against the library default of 100000) to keep the harness runtime modest; that puts simulation noise into the multiplier, not bias into the coverage it delivers. - No weak-instrument-robust set exists to measure for these two rows.
Anderson-Rubin is the right interval for the
iv_gmmandlp_ivweak-instrument rows, and neither function exposes one. The library does now shipproxy_ar_sets— an Anderson-Rubin set for proxy-SVAR impulse responses — so the machinery exists and the gap is that it has not been extended to the IV regression estimators, not that it is unbuilt. - ~~
lp(cumulative=...)intervals are unmeasured.~~ Measured now —quantile_panel_lp.pypublishes the official post-fix numbers for thecumulative="both"HAC default (the audit's 0.507-at-h=12 defect) and re-measures the cumulated-outcome mode's lag-augmented default alongside. - ~~
growth_at_risk,proxy_svar,nongaussian_svar, GARCH forecast intervals, andflp/flp_scenarioare not in this audit.~~ Measured now —proxy_garch_tail.pyadded 13 registry rows:growth_at_risk's two sandwiches on an exact-truth design (corroborating the card's Powell → Newey-West table),proxy_svar_bands' moving-block/Efron/wild arms, all threeproxy_ar_setsrf_methods paired,garch_fit's two standard errors under Gaussian and t(5) QMLE, andflp/flp_scenariopriced against their documented exempt routes.nongaussian_svarand the GARCHvariance_forecastship no interval — now verified by per-run key-set tripwires rather than assumed.proxy_svaritself remains point-estimate only by design (its docstring routes inference toproxy_svar_bands/proxy_ar_sets, both measured above). - The
bvar_*family is measured here as frequentist diagnostics only (group C); its Bayesian calibration — draw from the model's own prior and check the credible sets — was measured by audit round 6, which found the ML-IIhyperprior="none"collapse and the GLP plug-in's 0.82–0.85, and is recorded indocs/roadmap/20-audit-round-6-findings.mdand on the Bayesian card. - The term-structure fits ship no interval at all.
nelson_siegel's key set is verified every run byfactor_midas.py(its row is in the tables);svenssonanddynamic_nsreturn the same interval-free shape (factors, residuals, R², forecasts) but are not under a per-run tripwire. - Only two nominal levels are swept.
var_irf_bandsis measured at 68/90/95 andvar_forecastat 50/80/90/95/99. Everything else is measured at one level; coverage shortfalls are not guaranteed to scale linearly in α, and in fact the IRF sweep shows they do not — at h=12 the loss is 4.9pp at nominal 68%, 13.7pp at 90% and 15.0pp at 95%. - One machine, one build. These are statistical properties, not timings, so they are machine-independent — but they are measured against this working tree's release build of the extension.
- Coverage is not the only thing that matters. An interval can cover at nominal and be useless if it is enormous (the weak-instrument rows are exactly this). Where width is the story, the family reports print widths too.
What this audit recommends¶
Ordered by how much coverage each would buy, and separated into what the library should change and what a caller should change.
For the library — shipped in 0.2.0. Three of the five recommendations
below were acted on. They are kept here, marked, rather than deleted: an audit
that quietly edits out its own findings once they are fixed cannot be checked
against later.
- ~~Add
hc2/hc3tools'sse_typemenu.~~ Done. Both match statsmodels HC2/HC3 to 2.96e-15. Measured on the same design that motivated it: tsecon's ownhc3covers 0.863 ± 0.006 at T=25 wherehc1covers 0.682 — and is still short of nominal, so prefer it without treating it as a cure. The gap closes with n (0.910 at T=100, 0.945 at T=1600). (se_typeremains lower-case only —"HC0"and"HAC"raiseValueError.) - ~~Make
iv_gmm(weight="hac")refuse to silently degrade.~~ Done, and it is a breaking change.bandwidthnow defaults toNone, which selects the Newey-West rule; an explicit0.0raises; the resolved truncation comes back ashac_bandwidth. Coverage moves 0.632 → 0.842, which is the honest number: it buys 21 points and still misses nominal, so this closed a silent-wrongness bug, not the coverage gap. - ~~Expose a simultaneous band.~~ Done, after
0.2.0— and it does not reach nominal on either VAR arm.var_irf_bands,var_forecast,lpandsmooth_lptake a band selector with four routes (sup-t, Šidák, Bonferroni, and the unchanged pointwise default), each reporting the cell family it is simultaneous over. Scored against a pointwise arm on the same replications: the forecast band goes 42.0% → 90.5% at nominal 95%, the IRF band 71.7% → 85.2% at nominal 90%, and LP 42.7% → 89.5% at T=720. So this closed the reading gap and left two coverage gaps standing, both inherited: the IRF band's marginal coverage is itself 85.3% at h=12, andvar_forecast's pooled per-cell marginal rate is 93.4%.lp_iv,lp_multiplierandlp_stateget the closed forms only. The full comparison. - Expose an Anderson-Rubin set for LP-IV and
iv_gmm. Half done.iv_gmmnow reportsfirst_stage, so the caller can at least see the instrument strength — but the audit also showed F > 10 is not a safe threshold (0.915 coverage at median F = 10.5), which is precisely why a weak-IV-robust set is still the real answer. Under weak identification no bounded Wald set can be honest. - A per-τ convergence flag for
quantile_regression. Still open. The single sharedconvergedbool trips on 232/3000 replications at T=200 and is the IRLS iteration cap, not an estimation failure — dropping those replications moves τ=0.05 coverage only from 0.866 to 0.870.
For a caller, in order of how often it will bite.
se_type="hc1"is not serial-correlation robust. Use"hac", and choose the bandwidth for the persistence you actually have.- On a persistent VAR, pass
method="bootstrap", bias_correct=Truetovar_irf_bands— the method matters, and passingbias_correctwithout it now raises. 0.410 → 0.900 at h=12. - Prefer the bootstrap band at long horizons and prefer
cumulative=Truewhere the cumulative response is what you mean. - Keep
lp's defaultse="lag_augmented". It wins at every horizon measured. - Read a smooth-LP band as a band around the penalized estimand, and never as a confidence interval for the impact response.
- Read
first_stage_fbefore you read any IV standard error — and do not treat F = 10 as a green light: coverage there is already 0.915. - Read
recession_probit's failure share with its coverage. A quarter of rare- event samples at T=100 have no finite MLE. - Do not read the HAR intercept as if it were as reliable as the HAR slopes.
- Read a fan chart one horizon at a time — or ask for the simultaneous band and say which family it is simultaneous over. The pointwise default is unchanged, so nothing you already plotted became a joint statement.
See also¶
- Testing & validation — where this tier sits among the nine, and what each of the others can and cannot prove.
- Validation matrix — the per-family table of references, fixtures and tolerances that pins the point estimates.
- Monte Carlo validation — test size, HAC coverage for a mean, and estimator consistency.
- Frontier Monte Carlo — LP vs VAR bias/variance, and weak-instrument LP-IV.
- HAC standard errors and Confidence bands on a VAR IRF — the recipes these caveats attach to.
- The eight modules themselves, each with a derivation of its truth in its
docstring:
regression_se.py,irf_bands.py,lp_family.py,forecast_intervals.py,bayes_and_sets.py,quantile_panel_lp.py,factor_midas.py,proxy_garch_tail.py, the runnerrun_all.py, and the page-sync guardcheck_page.py.