*! version 1.0.0 17jul2026

help for xtcombreak test version 1.0.0 - 17jul2026

Title

xtcombreak test -- Test the null that the structural break
occurs at a common date across all units of a heterogeneous panel
(Jiang and Kurozumi 2026).

Syntax

xtcombreak test depvar indepvars [if] [in]
[, options]

Model
trimming(#) trimming eps defining Lambda(eps); default trimming(0.1)

noconstant suppress the constant in xit

Critical values
simulate simulate critical values and a p-value from JK's Theorem 1

reps(#) replications for simulate; default 2000

gridpoints(#) grid points approximating the Brownian motions; default 1000

seed(#) seed for simulate

level(#) level flagged in the output: 10, 5 or 1; default 5

Reporting
showindex also report the break as an index 1..T

graph CUSUM-process and shift-direction plots

cusumname(name) name for the CUSUM graph

shiftname(name) name for the shift-direction graph

At least one indepvars is required. The data must be xtset as a
balanced panel.

Description

xtcombreak test implements Jiang and Kurozumi (2026). The model is
their eq.(1),

yit = xit'bi + xit'di*1{t > k0i} + uit

with heterogeneous coefficients bi, di across units and a
possibly unit-specific break date k0i. The hypotheses are

H0: k0i = k0 for all i (the break is common)

HA: k0g1 != k0g2 for some groups (dates differ)

Why this test exists

Every common-break method -- Bai (2010), Baltagi-Feng-Kao (2016), Kim
(2011, 2014), Qian-Su (2016) -- buys its precision by assuming all units break
together. Jiang and Kurozumi open by noting that, before their paper, "no study has
focused on the validity of the common break assumption in panels", while evidence
suggests break points "are likely to vary significantly across individuals". This
test fills that gap.

How it works

The statistic takes khat from the pooled least-squares search (their eq.3),
forms the squared CUSUM of the two-regime OLS residuals, and divides by a
self-normaliser built by refitting the model on four regimes split at
k1 < khat < k2:

SNT(khat) = sup USNT(k,khat) / VNT(k1,khat,k2)

Under H0 khat is consistent, the residuals behave, and the statistic has a
non-degenerate limit. Under HA, khat gets trapped between the true break dates
(their Proposition 1) and can never match all of them, so at least one group is
always fitted at the wrong date, the CUSUM diverges at rate NT while the
denominator stays Op(1), and the test is consistent (their Theorem 2).

The self-normaliser exists for a specific reason. The natural alternative --
a kernel long-run-variance estimate -- is consistent under H0 but badly biased
under HA, so power falls as breaks grow: the non-monotonic power problem. By
constructing a normaliser proportional to sigma2, the long-run variance
cancels and power is monotonic. This extends Shao and Zhang (2010).

Options

Model

trimming(#) sets eps in the admissible set

Lambda(eps) = {[T*eps] <= k <= [T(1-eps)], [T*eps] <= k1 <= khat-[T*eps],
khat+[T*eps] <= k2 <= [T(1-eps)]}

Default trimming(0.1), matching the paper. Changing it invalidates
the published critical values -- JK's Table 1 is computed at eps = 0.1 only. If
you set anything else, xtcombreak refuses the table and requires
simulate, rather than silently reporting wrong critical values.

noconstant removes the constant from xit. JK's sec.2 states
xit "includ[es] a constant term; thus, the first element is unity for all
t", so the default keeps it. Use this only if your indepvars already contains
one.

Critical values

simulate simulates the limiting distribution of Theorem 1 directly
instead of reading the table, using JK's own recipe (approximating the Brownian
motions on a grid, over many replications). Use it when:

o trimming() is not 0.1 -- the table does not apply;

o tau0 = khat/T falls outside [0.20, 0.80] -- outside the table;

o you want a p-value. The published table gives critical values only,
so without simulate r(p) is missing.

reps(#) and gridpoints(#) control the simulation. JK used 10,000
replications and 2,000 grid points. The defaults here (2000/1000) are faster;
raise them for a final result. Use seed(#) for reproducibility.

level(#) chooses which of the three tabulated levels (10, 5, 1) is
flagged. All three are always reported; this only sets the graph threshold and the
headline verdict.

Reporting

graph draws the CUSUM process USNT(k,khat) against k with the
rejection threshold, and the shift-direction plot -- the estimated di by
series, coloured by sign. The latter is a power diagnostic; see
below.

Interpreting the output

Test statistic block

S_NT -- the statistic. Compare to the critical values; it is a
one-sided upper-tail test (reject when S is large).

numerator -- supk USNT. Diverges at rate NT under HA.

denominator -- inf VNT. Op(1) under both H0 and HA
(their Proposition 3), so all the signal is in the numerator.

tau0-hat -- khat/T. The null distribution depends on it, so the
critical value is read at this tau0.

Critical values

Roughly 45 / 58 / 90 at the 10/5/1% levels across most of the tau0 range.
Note how large they are -- this is a ratio of a squared CUSUM to a small
normaliser, not a chi2.

The source line says whether they came from JK Table 1 or were
simulated.

Diagnostics

Sign concordance -- the fraction of estimated shifts di sharing
the majority sign. 1.00 = all the same direction; 0.50 = an even split. Below 0.65
the command warns, because this is the configuration in which JK show the test
loses power. See below.

Warnings also fire when T < 50 (size distortion) and T/N < 1
(Assumption 2(iii)).

Power: when this test fails you

These come from the paper's own simulations. They matter because a
non-rejection is only as informative as the test's power, and this test has
specific, documented blind spots.

1. Breaks in opposite directions -- power collapses

JK sec.5 (p.96): with d1i, d2i ~ U(-0.5, 0.5) "our tests also
lose power in panels" -- the c'delta = 0 problem of Deng and Perron (2008). Their
own conclusion: "our test is useful when changes in individual coefficients are in
the similar direction."

This is why xtcombreak reports sign concordance and warns when it
is near 0.5. If your shifts are half positive and half negative, a non-rejection
tells you very little.

2. Breaks close together -- power falls off fast

JK Table 6 (N=T=50, rho=0.4, 10% level), first break fixed at 0.2T:

distance 0.05T 0.1T 0.2T 0.3T 0.4T 0.5T 0.6T

power 0.116 0.317 0.794 0.932 0.952 0.926 0.869

At 0.1T apart the test rejects only 32% of the time at the 10% level. Their
guidance: "If the distance between two breaks exceeds [0.3T], the rejection
probability reaches at least 90%."

3. Unbalanced groups -- power falls fast

JK Table 7 (N=T=50, breaks at 0.3T and 0.7T, 10% level):

N1:N2 2:N-2 1:9 2:8 3:7 4:6 5:5

power 0.157 0.251 0.538 0.806 0.929 0.976

"If the number of individuals in one group is much less than that in the
second group, the heterogeneity between the two groups cannot be identified."
A handful of deviant units will not be caught.

4. Small T with serial correlation -- SIZE is distorted

JK Table 2, nominal 5%, rho = 0.4:

T=20: 0.145 (N=10), 0.157 (N=50), 0.167 (N=100)

T=50: 0.086, 0.080, 0.073

T=100: 0.060, 0.064, 0.053

T=200: 0.049, 0.043, 0.050

At T=20 the test rejects three times too often, and it worsens with N.
Size only settles near T=200. xtcombreak warns when T < 50. With small T,
read a rejection sceptically -- it may be size distortion, not heterogeneity.

Net advice

The uncomfortable pattern: the test is most likely to wave you through
exactly when break heterogeneity is mild but real -- close dates, a small
deviant group, mixed directions. That is still enough to contaminate khat, but not
enough to be caught. Treat a non-rejection as weak evidence, and lean on the
diagnostics.

What a rejection does not tell you

A rejection is ambiguous between two very different worlds:

(a) there is no common break -- each series or group has its own; or

(b) there are several common breaks along the time dimension.

The test cannot separate them, and JK hit this in their own application
(sec.6). Testing 13 mutual-fund categories over 2005M02-2011M12, they rejected for
12 of 13 -- then had to fall back on the Bai-Perron sequential test to work out
what the rejection meant, finding multiple breaks clustered at similar dates
(early 2006, early 2008, early 2009). Only after splitting the sample did the test
fail to reject over 2008M06-2011M12, supporting one common break during the
sub-prime crisis.

So on a rejection, do what they did:

. xtbreak test y x1 x2, hypothesis(3) sequential

. xtcombreak test y x1 x2 if year <= 2008

. xtcombreak test y x1 x2 if year > 2008

Their DGP.3/H4A simulations confirm the test has high power against multiple
common breaks (Table 12) -- which is precisely why a rejection cannot rule that
out.

The pre-test problem

This test is post-estimation in mechanics -- it needs khat first, and
in fact its power comes from khat being wrong under HA -- but a
pre-test in purpose: you run it to decide whether you may impose a common
break.

That makes it a pre-test in the technical, awkward sense. If you test, fail
to reject, and then report Bai's confidence interval as though the common break
were known, that interval's true coverage is not its nominal level: it is
conditional on having passed a screening on the same data. Neither paper
computes this distortion.

Like Hansen's J or a Hausman test, the null here is a modelling
assumption, so it carries the usual asymmetry: failing to reject is not evidence
the assumption holds. Combined with the power gaps above, that asymmetry bites
hard. xtcombreak never selects a specification for you on the basis of this
test.

Examples

. webuse grunfeld, clear

. xtset company year

Basic

. xtcombreak test invest mvalue kstock

With a p-value

. xtcombreak test invest mvalue kstock, simulate reps(5000) seed(42)

. display "p = " r(p)

Different trimming -- simulate is then required

. xtcombreak test invest mvalue kstock, trimming(0.15) simulate seed(42)

On a rejection: split the sample, as JK did

. xtcombreak test invest mvalue kstock

. xtcombreak test invest mvalue kstock if year <= 1943

. xtcombreak test invest mvalue kstock if year > 1943

Check the power diagnostic before trusting a non-rejection

. xtcombreak test invest mvalue kstock, graph

. display "sign concordance = " r(concord)

near 0.5 means mixed directions -- JK show power collapses there

Full sequence

. xtcombreak all invest mvalue kstock

Stored results

xtcombreak test stores the following in r():

Scalars

r(S) the test statistic S_NT

r(p) p-value (missing unless simulate)

r(cv10) 10% critical value

r(cv05) 5% critical value

r(cv01) 1% critical value

r(khat) estimated common break index (1..T)

r(tau0) khat/T

r(numerator) sup_k US_NT(k,khat)

r(denominator) inf V_NT(k1,khat,k2)

r(concord) sign concordance of the estimated shifts

r(N) number of series

r(T) number of periods

r(TNratio) T/N

Macros

r(cmd) xtcombreak test

r(cvsource) where the critical values came from

r(depvar) dependent variable

r(indepvars) regressors

r(panelvar) panel variable

r(timevar) time variable

Matrices

r(cusum) T x 4: index, date, US_NT(k,khat), threshold

r(breakdate) 2 x 1: date, index

r(delta) N x 2: id, estimated first slope shift

Author

Dr Merwan Roudane

merwanroudane920@gmail.com

github.com/merwanroudane