ESTAR#

class cultivars.univariate.smooth_transition.ESTAR(endog, *, order, delay=1)[source]

Bases: _SmoothTransitionModel[STARResult]

Exponential smooth-transition autoregression.

The weight \(G(z) = 1 - \exp(-\gamma (z - c)^2 / s^2)\) is symmetric about the threshold, so both tails share one regime and the middle is the other. This is the natural form for mean reversion that strengthens with distance from equilibrium – the canonical application is a real exchange rate under transaction costs, where small deviations persist and large ones are arbitraged away. In the result’s vocabulary the “lower” regime is the middle (weight near 0 at \(z = c\)) and the “upper” regime is the tails.

Variables:
  • _endog – The validated series.

  • _order – Autoregressive order within each regime.

  • _delay – Delay of the transition variable.

  • _transition – "exponential".

Parameters:
  • endog (ArrayLike) – The series.

  • order (int) – Autoregressive order within each regime.

  • delay (int) – Delay of the transition variable y_{t-d}.

Raises:
  • SpecificationError – If order or delay is not a positive integer.

  • DimensionError – If endog is not one-dimensional or has fewer than 2(order + 2) + delay observations.

  • NumericalError – If endog contains non-finite values, or (at fit()) the transition variable has zero variance.

See also

References

Terasvirta, T. (1994). Specification, estimation, and evaluation of smooth transition autoregressive models. Journal of the American Statistical Association, 89(425), 208-218.

Kapetanios, G., Shin, Y., & Snell, A. (2003). Testing for a unit root in the nonlinear STAR framework. Journal of Econometrics, 112(2), 359-379.

Example

Near-unit-root persistence close to equilibrium, fast reversion far from it:

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> z = np.zeros(800)
>>> for t in range(1, 800):
...     w = 1.0 - np.exp(-3.0 * z[t - 1] ** 2)
...     z[t] = (1 - w) * 0.95 * z[t - 1] + w * 0.2 * z[t - 1] + 0.3 * rng.standard_normal()
>>> res = ESTAR(z, order=1).fit()
>>> res._comparison_label(), bool(abs(res.threshold) < 0.3)
('ESTAR(1, d=1)', True)
>>> bool(res.lower_params[1] > 0.8), bool(res.upper_params[1] < 0.5)
(True, True)
>>> near = np.abs(res.threshold_values - res.threshold) < 0.2
>>> bool(res.regime_weight[near].mean() < res.regime_weight[~near].mean())
True