Find the dates at which the mean of every asset’s series shifts, with the Bai and Perron (1998, 2003) method for multiple structural breaks. For every number of breaks the break dates that minimize the sum of squared residuals around a mean per segment are found exactly (by dynamic programming), and the number of breaks is chosen by the Bayesian information criterion. Every segment covers at least a share of the observations (the integer part of that share times the number of observations, as in Bai and Perron), so a break is not fitted to a few outliers.

The BIC works well for series without serial correlation, such as returns, but tends to choose too many breaks when the series is serially correlated (Bai and Perron, 2003), as levels such as inflation or interest rates are: a persistent swing then looks like a shift in the mean. For such series, compare the segments for a few values of breaks rather than relying on the chosen number alone.

Breaks show when a series changed character, such as the regimes of inflation or of interest rates, or a change in the average return of an asset. Unlike the Chow test, the break dates do not have to be known in advance.

The formula is as follows:

\[\text{yₜ} = \text{muⱼ} + \text{epsilonₜ} \;\text{for the dates of segment}\; j = 1,\;\; \text{...},\;\; \text{m + 1},\;\; \text{with yₜ the column series (Return by default) and m breaks}\] \[\operatorname{SSR}(m) = \text{min over the break dates of}\; \text{Σⱼ} \text{Σ} (t \;\text{in segment}\; j) (\text{yₜ} - \text{ȳⱼ}) ^{2},\;\; \text{found exactly by dynamic programming},\;\; \text{with every segment at least}\; h = \max(\text{⌊minimum\_segment} \cdot \text{T⌋},\; 2) \text{observations} (\text{minimum\_segment} = 0.15 \;\text{by default})\] \[\operatorname{BIC}(m) = T \cdot \ln(\operatorname{SSR}(m) / T) + (2m + 1) \cdot \ln(T),\;\; \text{for} \;\; m = 0,\;\; \text{...},\;\; \text{max\_breaks (5 by default); the m with the lowest BIC is chosen unless breaks is given}\]
  • Per segment: its Start, End, Observations, Mean ȳⱼ and Standard Deviation (ddof=1)

For more information, see the following papers:

  • Bai, J. and Perron, P. (1998). “Estimating and Testing Linear Models with Multiple Structural Changes.” Econometrica, 66(1), 47-78.
  • Bai, J. and Perron, P. (2003). “Computation and Analysis of Multiple Structural Change Models.” Journal of Applied Econometrics, 18(1), 1-22.

Also known as: Bai-Perron test, multiple structural breaks, change point detection, regime shifts.

Structural Breaks (Bai-Perron) in Python or via the MCP

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_structural_breaks as shown below.

from financetoolkit import Economics, Econometrics

economics = Economics(start_date="1960-01-01", end_date="2025-12-31")

inflation = economics.get_inflation_rate(countries="United States", period="yearly")

econometrics = Econometrics(data=inflation)

econometrics.get_structural_breaks(column="Level", max_breaks=3)

Or, after setting up the Finance Toolkit MCP, ask:

At which dates did the mean of yearly US inflation shift from 1960 to 2025, allowing for up to three breaks?

Which returns:

  Start End Observations Mean Standard Deviation BIC
(‘United States’, 1) 1960-12-31 1972-12-31 13 0.0282 0.0169 -519.037
(‘United States’, 2) 1973-12-31 1982-12-31 10 0.0876 0.0269 -519.037
(‘United States’, 3) 1983-12-31 2025-12-31 43 0.0285 0.0143 -519.037

Using Your Own Data

get_structural_breaks runs on any data, not only prices: ratios, economic indicators, yield curves or your own dataset. Pass a DataFrame, a Series or a dictionary of them to Econometrics as data, with the dates in the rows or the columns, and set changes to how its changes are calculated: "percentage", "log", "difference" for rates and ratios, or None when the data are changes already. For example, for the monthly unemployment rates of the United States and Germany:

from financetoolkit import Economics, Econometrics

economics = Economics(start_date="2000-01-01")

unemployment = economics.get_unemployment_rate(
    countries=["United States", "Germany"], period="monthly"
)

econometrics = Econometrics(data=unemployment, changes="difference")

econometrics.get_structural_breaks()

Parameters

get_structural_breaks accepts the following parameters.

ParameterTypeDescriptionExample
periodstrThe data frequency (daily, weekly, monthly, quarterly, or yearly). Defaults to “daily”."weekly"
columnstrThe historical data column to analyze. Defaults to “Return”; use “Adj Close” (or “Level” with standalone data) for the series itself, such as an inflation rate."Return"
max_breaksintThe most breaks considered. Defaults to 5.5
minimum_segmentfloatThe smallest segment as a share of the observations (the trimming). Defaults to 0.15.0.15
breaksint | NoneThe number of breaks to find instead of choosing it by the BIC. Defaults to None.None
include_benchmarkboolWhether to include “Benchmark” among the assets. Defaults to False.True
roundingint | NoneThe number of decimals to round the results to. Defaults to None.2
Share