| Title: | General State-Space Modelling, Filtering and Change-Point Detection |
|---|---|
| Description: | A compact, dependency-light toolkit for linear-Gaussian and discrete-state state-space models. Provides a vector-state Kalman filter and Rauch-Tung-Striebel smoother in closed form, a native Gaussian-mixture (regime-switching) filter, hidden Markov model filtering and Viterbi decoding, a likelihood-ratio change-point detector, and a minimal single-case interrupted-time-series causal contrast built on the smoother. Mean-reverting spread estimation for pairs trading is included as one documented application of the general machinery, building on the scalar Ornstein-Uhlenbeck spread model of Elliott, van der Hoek and Malcolm (2005) <doi:10.1080/14697680500149370>. Methods follow Kalman (1960) <doi:10.1115/1.3662552>, Rauch, Tung and Striebel (1965) <doi:10.2514/3.3166> and Durbin and Koopman (2012) <doi:10.1093/acprof:oso/9780199641178.001.0001>. |
| Authors: | Max Moldovan [aut, cre] (ORCID: <https://orcid.org/0000-0001-9680-8474>), Johannes van der Hoek [aut] (Co-developer; foundational mean-reverting spread model in Elliott, van der Hoek & Malcolm (2005).), Robert J. Elliott [ctb] (Foundational theory of the scalar mean-reverting spread filter in Elliott, van der Hoek & Malcolm (2005).), William P. Malcolm [ctb] (Foundational theory of the scalar mean-reverting spread filter in Elliott, van der Hoek & Malcolm (2005).) |
| Maintainer: | Max Moldovan <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.6.0.9000 |
| Built: | 2026-10-15 23:25:53 UTC |
| Source: | https://github.com/max578/kalmix |
Runs the closed-form Kalman filter and Rauch-Tung-Striebel smoother of
kalman_filter() and rts_smoother() over an ssm, then reads the
time-resolved causal information series off the two passes following Andreou,
Chen and Bollt (2026). The causal information at each step is the relative
entropy of the smoothing distribution of the latent state from its filtering
distribution,
the information the future of the series adds to the present estimate of the state (their eq. 7). The series is summarised by its time-average, the mean causal information.
aci( model, y, lead_time = TRUE, dt = 1, eval_points = NULL, max_lag = NULL, n_lag = NULL, engine = c("online", "expanding"), functional = c("objective", "objective_exact"), mechanism = NULL, alpha = 0.05, transition = NULL, init_prob = NULL )aci( model, y, lead_time = TRUE, dt = 1, eval_points = NULL, max_lag = NULL, n_lag = NULL, engine = c("online", "expanding"), functional = c("objective", "objective_exact"), mechanism = NULL, alpha = 0.05, transition = NULL, init_prob = NULL )
model |
An ssm, or a list of two or more ssm regimes for a regime-switching read-out. |
y |
A numeric vector or |
lead_time |
Logical scalar – whether to compute the objective causal
information rate and lead-time as well as the causal information series.
Defaults to |
dt |
Numeric scalar – the sampling interval, so the lead-time is in
meaningful time units (for example years for an annual ENSO index).
Defaults to |
eval_points |
An optional integer vector of step indices at which to
anchor the lead-time computation; passed to |
max_lag |
Integer scalar – the largest future-window lag, in steps, used for the lead-time. Defaults to one fifth of the series length. |
n_lag |
Integer scalar or |
engine |
Character – the lead-time engine, |
functional |
Character – |
mechanism |
Optional declared provenance for the model as the
data-generating mechanism: |
alpha |
Numeric scalar – the significance level the model-adequacy
diagnostics abstain at. Defaults to |
transition, init_prob
|
The regime-transition matrix and initial
regime distribution when |
By default the objective causal information rate and the decision lead-time
are also computed (lead_time = TRUE), through causal_information_rate():
the divergence of the complete smoother from an expanding-future-window
smoother is integrated into a single threshold-free lead-time in the time
units of the series (their eqs. 8–9). Set lead_time = FALSE to return the
causal information series alone, which is cheaper for long series.
Under a Student-t observation family (obs_family = "student_t") the
filter pass is the outlier-robust variational update, whose per-step
posteriors are Gaussian approximations. The relative-entropy chain treats
those approximations as exact Gaussians, so the causal information and
lead-time inherit the variational approximation; the Gaussian family
involves no approximation.
A regime-switching model is supplied as a list of ssm regimes (with the
optional transition and init_prob of mixture_filter()): the read-out
then compares the Kim smoother's collapsed posteriors with the GPB1
filter's. Both are the model class's own Gaussian collapses of the true
mixtures, so the causal information inherits that approximation; the
verdict is grounded through the mixture-PIT diagnostics, and the
lead-time uses the expanding-window construction (the online recursion is
linear-Gaussian only).
The recovered state is interpretable as a cause of the observed series only
under the maintained assumption that the ssm is the data-generating
mechanism. That assumption is not taken on trust: the read-out is grounded
through innovation_diagnostics(), and a model whose standardised
innovations are not white, not correctly scaled, or not Gaussian is declared
inadequate, so the verdict abstains with an "ungrounded" grounding token
and the returned aci_fit additionally carries the
c("orchestra_refusal", "kalmix_abstention") classes (as does a fit whose
adequacy could not be judged at all, or whose lead-time is censored or
unconverged – see censored/the quadrature convergence check), so a
cross-member caller can recognise the read-out as unreliable without
parsing the grounding string. Passing the diagnostics establishes
self-consistency only; the verdict is labelled "grounded" solely when an
adequate model is accompanied by declared, dated mechanism provenance,
and is "ungrounded" otherwise – that ordinary, expected case is not
itself abstained on.
An aci_fit.
Andreou, M., Chen, N. and Bollt, E. (2026). Assimilative causal inference. Nature Communications, 17, 1854.
Kalman, R. E. (1960). A new approach to linear filtering and prediction problems. Journal of Basic Engineering, 82(1), 35–45.
Rauch, H. E., Tung, F. and Striebel, C. T. (1965). Maximum likelihood estimates of linear dynamic systems. AIAA Journal, 3(8), 1445–1450.
causal_information_rate() for the lead-time alone;
kalman_filter() and rts_smoother() for the underlying passes.
Other causal:
aci_fit(),
causal_information_rate(),
estimate_obs_df(),
innov_diag(),
innovation_diagnostics(),
its_causal(),
its_fit()
## A latent oscillation seen through a slow, noisy observation channel: the ## future of the series is informative about the hidden state, so the causal ## information is positive. model <- ssm( transition = matrix(c(0.6, -0.5, 0.5, 0.6), nrow = 2), observation = matrix(c(1, 0), nrow = 1), state_cov = diag(c(0.2, 0.2)), obs_cov = 1, init_state = c(0, 0), init_cov = diag(c(5, 5)) ) set.seed(1) x <- numeric(200) s <- c(0, 0) for (t in seq_len(200)) { s <- as.numeric(model@transition[[1]] %*% s) + rnorm(2, sd = 0.45) x[t] <- s[1] + rnorm(1) } fit <- aci(model, x, lead_time = FALSE) fit@mean_causal_information## A latent oscillation seen through a slow, noisy observation channel: the ## future of the series is informative about the hidden state, so the causal ## information is positive. model <- ssm( transition = matrix(c(0.6, -0.5, 0.5, 0.6), nrow = 2), observation = matrix(c(1, 0), nrow = 1), state_cov = diag(c(0.2, 0.2)), obs_cov = 1, init_state = c(0, 0), init_cov = diag(c(5, 5)) ) set.seed(1) x <- numeric(200) s <- c(0, 0) for (t in seq_len(200)) { s <- as.numeric(model@transition[[1]] %*% s) + rnorm(2, sd = 0.45) x[t] <- s[1] + rnorm(1) } fit <- aci(model, x, lead_time = FALSE) fit@mean_causal_information
An S7 object holding the output of aci(): the per-step causal information
series, its time aggregate (the mean causal information), the objective
causal information rate and the implied decision lead-time, alongside the
filter and smoother passes the read-out was computed from.
aci_fit( causal_information = numeric(0), mean_causal_information = numeric(0), causal_information_rate = NA_real_, lead_time = NA_real_, dt = 1, filter = NULL, smoother = NULL, grounding = "ungrounded", grounding_reason = NA_character_, adequacy = NULL, censored = FALSE, monotone = NA )aci_fit( causal_information = numeric(0), mean_causal_information = numeric(0), causal_information_rate = NA_real_, lead_time = NA_real_, dt = 1, filter = NULL, smoother = NULL, grounding = "ungrounded", grounding_reason = NA_character_, adequacy = NULL, censored = FALSE, monotone = NA )
causal_information |
A numeric vector of length |
mean_causal_information |
Numeric scalar – the time-average of
|
causal_information_rate |
Numeric scalar – the objective,
threshold-free causal information rate (a lead-time in the time units of
the series), or |
lead_time |
Numeric scalar – the decision lead-time, an alias of the
causal information rate in time units, or |
dt |
Numeric scalar – the sampling interval the lead-time is expressed
in. Defaults to |
filter |
The forward pass the read-out was computed from: a kalman_fit, or a regime_fit for a regime-switching model. |
smoother |
The smoothing pass: an rts_fit, or a regime_smooth for a regime-switching model. |
grounding |
Character scalar – the grounding token of the causal
verdict, |
grounding_reason |
Character scalar – a short label for why the verdict
carries that token ( |
adequacy |
The innov_diag model-adequacy diagnostics the grounding was read from. |
censored |
Logical scalar – whether any lead-time anchor was right-censored, that is whether its future window ran past the end of the record or its divergence profile had not decayed by the last lag it could be evaluated at. A censored lead-time is a lower bound, not an estimate, and degrades the grounding token. |
monotone |
Logical scalar – whether every anchor's divergence profile decreases with lag, the condition under which the efficient and exact range functionals are the same functional. |
The causal information at step t is the relative entropy of the smoothing
distribution from the filtering distribution of the latent state,
the information the future of the series adds to the present estimate of the
state. It is non-negative and zero only where the future is uninformative
about the state at t.
An S7 object of class aci_fit.
Other causal:
aci(),
causal_information_rate(),
estimate_obs_df(),
innov_diag(),
innovation_diagnostics(),
its_causal(),
its_fit()
Computes the objective, threshold-free causal influence range of Andreou, Chen and Bollt (2026, eqs. 8–9) for a state-space model: the effective horizon over which the future of the observed series keeps informing the estimate of the latent state, returned as a decision lead-time in the time units of the series.
causal_information_rate( model, y, dt = 1, eval_points = NULL, max_lag = NULL, n_lag = NULL, engine = c("online", "expanding"), functional = c("objective", "objective_exact"), epsilon = .cir_epsilon_grid(), margin = 0.1, tol = 0.02, transition = NULL, init_prob = NULL )causal_information_rate( model, y, dt = 1, eval_points = NULL, max_lag = NULL, n_lag = NULL, engine = c("online", "expanding"), functional = c("objective", "objective_exact"), epsilon = .cir_epsilon_grid(), margin = 0.1, tol = 0.02, transition = NULL, init_prob = NULL )
model |
An ssm. |
y |
A numeric vector or matrix of observations. |
dt |
Numeric scalar – the sampling interval; the returned lead-time is
in these time units. Defaults to |
eval_points |
An optional integer vector of anchor step indices.
Defaults to |
max_lag |
Integer scalar – the largest future-window lag in steps. Defaults to one fifth of the series length, capped so an anchor still has future to integrate over. |
n_lag |
Integer scalar or |
engine |
Character – |
functional |
Character – |
epsilon |
Numeric vector – the tolerance grid the exact functional
averages the subjective range over, in nats. Defaults to the 129-point
logarithmic grid |
margin |
Numeric scalar in |
tol |
Numeric scalar – the relative disagreement between the quadrature
and the same quadrature on half the grid points above which a warning is
issued. Defaults to |
transition, init_prob
|
The regime-transition matrix and initial
regime distribution when |
At an anchor step the smoother is recomputed on an expanding future
window: the relative entropy of the complete smoother from the
smoother that has seen the series only up to (their eq. 8,
with the complete smoother as the integrating density) falls from its
filter value at – the per-step causal information of eq. 7 –
towards zero as the window grows.
Two functionals reduce that profile to a range, and they are different
functionals rather than two quadratures of one. The subjective range at
tolerance is the last lag at which the profile still
exceeds ,
, and
functional = "objective_exact" averages it over the tolerance,
with
. The default functional = "objective" is the
computationally efficient underestimate the source paper gives,
The two coincide when the profile decreases with lag, by the layer-cake
identity, and the integral form is strictly the smaller as soon as it does
not; a profile that rises before it decays is the ordinary case near the
start of a record. Monotonicity is assumed for convenience in the source
paper and is not required by the theory, so "objective" is reported as a
lower bound and attr(x, "monotone") records whether the two agree. The
range is averaged over the anchors and scaled to time units by dt.
Two engines compute the same divergence profile. The default "online"
engine grows the future window through a fixed-point smoother recursion: the
smoothed estimate of the anchor state is updated in place as each later
observation is assimilated, by accumulating the Rauch-Tung-Striebel gains, so
the whole profile costs one filter-smoother pass plus length(eval_points)
light forward recursions and no re-smoothing. It is exact and applies to
time-varying models. The "expanding" engine instead reruns the filter and
smoother on each truncated series – the direct construction of the published
equations, retained as a cross-check on the smoother recursion and restricted
to time-invariant models. The two share the quadrature and the reduction, so
their agreement grades the recursion and not the range functional; the
independent grading of the functional is the conformance suite against
acir.
An anchor whose future window runs past the end of the record cannot be
resolved. The profile is then integrated over the lags the record does
support and no further – the value is a lower bound, not an estimate – and
the anchor is marked censored. So is an anchor whose profile has not decayed
to within margin of its peak by the last lag it could be evaluated at,
since the window was too short to see the influence expire. attr(x, "censored") is TRUE when any anchor is censored and attr(x, "censored_fraction") gives the proportion that are.
Numeric scalar – the objective causal influence range (a lead-time
in the time units set by dt), 0 where no future information is
recoverable, carrying the attributes censored (logical),
censored_fraction (numeric), monotone (logical, whether every anchor's
profile decreases with lag), n_lag (the grid size used) and converged
(logical, whether a thinned grid agreed with this one to within tol;
NA when the check does not apply, for example a fully resolved grid).
Andreou, M., Chen, N. and Bollt, E. (2026). Assimilative causal inference. Nature Communications, 17, 1854.
Other causal:
aci(),
aci_fit(),
estimate_obs_df(),
innov_diag(),
innovation_diagnostics(),
its_causal(),
its_fit()
model <- ssm( transition = matrix(c(0.6, -0.5, 0.5, 0.6), nrow = 2), observation = matrix(c(1, 0), nrow = 1), state_cov = diag(c(0.2, 0.2)), obs_cov = 1, init_state = c(0, 0), init_cov = diag(c(5, 5)) ) set.seed(1) x <- numeric(200) s <- c(0, 0) for (t in seq_len(200)) { s <- as.numeric(model@transition[[1]] %*% s) + rnorm(2, sd = 0.45) x[t] <- s[1] + rnorm(1) } causal_information_rate(model, x)model <- ssm( transition = matrix(c(0.6, -0.5, 0.5, 0.6), nrow = 2), observation = matrix(c(1, 0), nrow = 1), state_cov = diag(c(0.2, 0.2)), obs_cov = 1, init_state = c(0, 0), init_cov = diag(c(5, 5)) ) set.seed(1) x <- numeric(200) s <- c(0, 0) for (t in seq_len(200)) { s <- as.numeric(model@transition[[1]] %*% s) + rnorm(2, sd = 0.45) x[t] <- s[1] + rnorm(1) } causal_information_rate(model, x)
An S7 object holding the output of detect_changepoint(): the detected
change-point locations, the per-segment means, and the penalised criterion
the search used.
changepoint_fit( changepoints = integer(0), n_segments = integer(0), segment_means = numeric(0), penalty = numeric(0), method = character(0) )changepoint_fit( changepoints = integer(0), n_segments = integer(0), segment_means = numeric(0), penalty = numeric(0), method = character(0) )
changepoints |
An integer vector of the detected change-point indices (the last index of each segment but the last); empty if no change is supported. |
n_segments |
Integer scalar — the number of segments,
|
segment_means |
A numeric vector of the fitted mean of each segment. |
penalty |
Numeric scalar — the per-split penalty that was applied. |
method |
Character scalar — the detection method used. |
An S7 object of class changepoint_fit.
Other change-point:
detect_changepoint()
Finds shifts in the mean of a Gaussian sequence by binary segmentation on the single-change-point likelihood-ratio statistic. At each stage the segment is split at the location whose two-mean fit most improves the Gaussian fit over a single-mean fit; the split is kept only when the improvement clears a penalty, otherwise the segment is declared homogeneous.
detect_changepoint(x, penalty = "mbic", min_segment = 5L)detect_changepoint(x, penalty = "mbic", min_segment = 5L)
x |
A numeric vector — the sequence to scan. |
penalty |
Numeric scalar, |
min_segment |
Integer scalar — the shortest admissible segment length,
so a single outlier is not declared its own segment. Defaults to |
The single-change statistic at split is the drop in the Gaussian
twice-negative-profile-log-likelihood, computed in closed form from the
segment sums and sums of squares,
the variance being estimated within each candidate segment.
The penalty matters more than the statistic. Under the null of one
homogeneous segment the best-split statistic is far from zero – on a
length-100 series its null mean is near 7 and its 95th percentile near
13 – so the plain Schwarz penalty (about 4.6 at that
length) over-segments badly. The default "mbic" penalty here is an
empirically calibrated , strengthened in the spirit of the
modified Bayesian information criterion of Zhang and Siegmund (2007) to
account for the change location being estimated (their criterion is a
different functional; the name is kept for the shared motivation). On
homogeneous Gaussian series it was calibrated to a false-detection rate at
or below 6\% from n = 100 upward while retaining full power against a
one-standard-deviation mean shift. The weaker "sic" (,
alias "bic") penalty and any numeric value are offered for callers who
want to tune sensitivity deliberately, accepting more false positives.
Scott, A. J. and Knott, M. (1974). A cluster analysis method for grouping means in the analysis of variance. Biometrics, 30(3), 507–512. (Binary segmentation.) Killick, R., Fearnhead, P. and Eckley, I. A. (2012). Optimal detection of changepoints with a linear computational cost. Journal of the American Statistical Association, 107(500), 1590–1598.
Zhang, N. R. and Siegmund, D. O. (2007). A modified Bayes information
criterion with applications to the analysis of comparative genomic
hybridization data. Biometrics, 63(1), 22–32. (The motivation for
strengthening the per-split penalty; the "mbic" value here is an
empirically calibrated , not their functional.)
Other change-point:
changepoint_fit()
set.seed(1) x <- c(rnorm(50, 0), rnorm(50, 4), rnorm(50, 1)) fit <- detect_changepoint(x) fit@changepointsset.seed(1) x <- c(rnorm(50, 0), rnorm(50, 4), rnorm(50, 1)) fit <- detect_changepoint(x) fit@changepoints
Profiles the one-step-ahead predictive log-likelihood of a state-space model
over a grid of Student-t degrees of freedom and returns the maximiser. The
grid includes Inf (the Gaussian model), so a series whose innovations are
not heavy-tailed selects Inf and the model collapses to the linear-Gaussian
filter – the estimator never imposes heavy tails that the data do not show.
estimate_obs_df( model, y, df_grid = c(3, 4, 5, 7, 10, 15, 30, Inf), lrt_margin = stats::qchisq(0.95, 1)/2 )estimate_obs_df( model, y, df_grid = c(3, 4, 5, 7, 10, 15, 30, Inf), lrt_margin = stats::qchisq(0.95, 1)/2 )
model |
An ssm giving the dynamics and the observation scale. Its own
|
y |
A numeric vector or |
df_grid |
A numeric vector of candidate degrees of freedom to profile
over. Every value must exceed |
lrt_margin |
The parsimony margin in log-likelihood units. The Gaussian
model ( |
The supplied model provides the dynamics (transition, process noise, prior)
and the observation scale obs_cov; only the observation family and its
degrees of freedom are varied. The log-likelihood is the Student-t predictive
density accumulated by kalman_filter() (an approximation that treats the
one-step-ahead distribution as Student-t; see kalman_filter()).
A list with df (the selected degrees of freedom), family
("gaussian" if df is Inf, else "student_t"), log_lik (the
maximised log-likelihood), grid (the grid evaluated) and profile (the
log-likelihood at each grid value).
Roth, M., Ozkan, E. and Gustafsson, F. (2013). A Student's t filter for heavy tailed process and measurement noise. ICASSP, 5770–5774.
ssm() for building the chosen model; aci() for the causal
read-out the chosen family grounds.
Other causal:
aci(),
aci_fit(),
causal_information_rate(),
innov_diag(),
innovation_diagnostics(),
its_causal(),
its_fit()
## A heavy-tailed series: the profile prefers a finite df over the Gaussian. model <- ssm( transition = 1, observation = 1, state_cov = 0.04, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) level <- cumsum(rnorm(300, sd = 0.2)) y <- level + rt(300, df = 3) est <- estimate_obs_df(model, y) est$df## A heavy-tailed series: the profile prefers a finite df over the Gaussian. model <- ssm( transition = 1, observation = 1, state_cov = 0.04, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) level <- cumsum(rnorm(300, sd = 0.2)) y <- level + rt(300, df = 3) est <- estimate_obs_df(model, y) est$df
Returns the time for a deviation from the long-run mean to halve in
expectation, , in the time units of the fit.
half_life(model)half_life(model)
model |
A spread_model. |
Numeric scalar.
Other classes:
spread_model()
x <- as.numeric(stats::arima.sim(list(ar = 0.8), n = 500)) half_life(ou_fit(x))x <- as.numeric(stats::arima.sim(list(ar = 0.8), n = 500)) half_life(ou_fit(x))
An S7 object specifying a discrete-state hidden Markov model with univariate
Gaussian emissions, as built by the constructor of the same name. The hidden
state follows a Markov chain with the given initial distribution and
transition matrix; in state j the observation is Gaussian with mean
emission_mean[j] and standard deviation emission_sd[j].
hmm( init_prob = numeric(0), transition = numeric(0), emission_mean = numeric(0), emission_sd = numeric(0) )hmm( init_prob = numeric(0), transition = numeric(0), emission_mean = numeric(0), emission_sd = numeric(0) )
init_prob |
A length- |
transition |
A |
emission_mean |
A length- |
emission_sd |
A length- |
An S7 object of class hmm.
Other hidden-markov:
hmm_filter(),
hmm_fit(),
hmm_viterbi()
## A two-state mean-shift model: a low state and a high state. model <- hmm( init_prob = c(0.5, 0.5), transition = matrix(c(0.95, 0.05, 0.05, 0.95), nrow = 2, byrow = TRUE), emission_mean = c(0, 3), emission_sd = c(1, 1) ) model## A two-state mean-shift model: a low state and a high state. model <- hmm( init_prob = c(0.5, 0.5), transition = matrix(c(0.95, 0.05, 0.05, 0.95), nrow = 2, byrow = TRUE), emission_mean = c(0, 3), emission_sd = c(1, 1) ) model
Runs the forward-backward recursion over an hmm. The forward pass gives
the filtered state posteriors and the model
log-likelihood; the backward pass combines with it to give the smoothed
posteriors . The recursion is carried in the
log domain and renormalised each step, so it is stable over long series.
hmm_filter(model, y)hmm_filter(model, y)
model |
An hmm. |
y |
A numeric vector of observations. |
An hmm_fit.
Rabiner, L. R. (1989). A tutorial on hidden Markov models and selected applications in speech recognition. Proceedings of the IEEE, 77(2), 257–286.
hmm_viterbi() for the most likely path.
Other hidden-markov:
hmm(),
hmm_fit(),
hmm_viterbi()
model <- hmm( init_prob = c(0.5, 0.5), transition = matrix(c(0.95, 0.05, 0.05, 0.95), nrow = 2, byrow = TRUE), emission_mean = c(0, 3), emission_sd = c(1, 1) ) set.seed(1) y <- c(rnorm(40, 0), rnorm(40, 3)) fit <- hmm_filter(model, y) fit@log_likmodel <- hmm( init_prob = c(0.5, 0.5), transition = matrix(c(0.95, 0.05, 0.05, 0.95), nrow = 2, byrow = TRUE), emission_mean = c(0, 3), emission_sd = c(1, 1) ) set.seed(1) y <- c(rnorm(40, 0), rnorm(40, 3)) fit <- hmm_filter(model, y) fit@log_lik
An S7 object holding the output of hmm_filter(): the filtered and smoothed
state posteriors, the smoothed most-likely state at each step, and the model
log-likelihood.
hmm_fit( filtered = numeric(0), smoothed = numeric(0), state_path = integer(0), log_lik = numeric(0), model = hmm() )hmm_fit( filtered = numeric(0), smoothed = numeric(0), state_path = integer(0), log_lik = numeric(0), model = hmm() )
filtered |
An |
smoothed |
An |
state_path |
An integer vector of the per-step smoothed modal state. |
log_lik |
Numeric scalar — the model log-likelihood. |
model |
The hmm that was filtered. |
An S7 object of class hmm_fit.
Other hidden-markov:
hmm(),
hmm_filter(),
hmm_viterbi()
Returns the single most likely sequence of hidden states given the whole
observed series, by the Viterbi (1967) dynamic-programming recursion in the
log domain. Unlike the per-step smoothed mode from hmm_filter(), the
Viterbi path is jointly optimal: it is the state sequence that maximises the
joint posterior, and so respects the transition structure across steps.
hmm_viterbi(model, y)hmm_viterbi(model, y)
model |
An hmm. |
y |
A numeric vector of observations. |
An integer vector of length length(y), the most likely state
index at each step.
Viterbi, A. (1967). Error bounds for convolutional codes and an asymptotically optimum decoding algorithm. IEEE Transactions on Information Theory, 13(2), 260–269.
Other hidden-markov:
hmm(),
hmm_filter(),
hmm_fit()
model <- hmm( init_prob = c(0.5, 0.5), transition = matrix(c(0.95, 0.05, 0.05, 0.95), nrow = 2, byrow = TRUE), emission_mean = c(0, 3), emission_sd = c(1, 1) ) set.seed(1) y <- c(rnorm(40, 0), rnorm(40, 3)) table(hmm_viterbi(model, y))model <- hmm( init_prob = c(0.5, 0.5), transition = matrix(c(0.95, 0.05, 0.05, 0.95), nrow = 2, byrow = TRUE), emission_mean = c(0, 3), emission_sd = c(1, 1) ) set.seed(1) y <- c(rnorm(40, 0), rnorm(40, 3)) table(hmm_viterbi(model, y))
An S7 object holding the model-adequacy diagnostics of
innovation_diagnostics(): the standardised one-step-ahead innovations and
the three residual tests read off them – whiteness, scale calibration and
normality – together with the combined adequacy verdict.
innov_diag( standardised = numeric(0), whiteness_p = NA_real_, calibration_p = NA_real_, normality_p = NA_real_, p_value = NA_real_, adequate = NA, alpha = 0.05, lags = NA_integer_, n_obs = NA_integer_, obs_dim = NA_integer_, obs_family = "gaussian", reason = NA_character_ )innov_diag( standardised = numeric(0), whiteness_p = NA_real_, calibration_p = NA_real_, normality_p = NA_real_, p_value = NA_real_, adequate = NA, alpha = 0.05, lags = NA_integer_, n_obs = NA_integer_, obs_dim = NA_integer_, obs_family = "gaussian", reason = NA_character_ )
standardised |
An |
whiteness_p |
Numeric scalar – the Ljung-Box whiteness p-value, Bonferroni-combined across the observation dimensions. |
calibration_p |
Numeric scalar – the two-sided p-value of the normalised-innovation-squared scale test. |
normality_p |
Numeric scalar – the Jarque-Bera normality p-value of the pooled standardised innovations. |
p_value |
Numeric scalar – the combined adequacy p-value, the Bonferroni minimum across the three test families. |
adequate |
Logical scalar – |
alpha |
Numeric scalar – the per-battery significance level. |
lags |
Integer scalar – the Ljung-Box lag. |
n_obs |
Integer scalar – the series length. |
obs_dim |
Integer scalar – the observation dimension. |
obs_family |
Character scalar – the observation-noise family the
diagnostics were taken under ( |
reason |
Character scalar – a short label for the verdict
( |
An S7 object of class innov_diag.
Other causal:
aci(),
aci_fit(),
causal_information_rate(),
estimate_obs_df(),
innovation_diagnostics(),
its_causal(),
its_fit()
Runs the standard innovation diagnostics of linear-Gaussian state-space model
checking over a completed Kalman pass and reduces them to a single adequacy
verdict. Under a correctly specified model the standardised one-step-ahead
innovations are independent standard Gaussian, so three departures from that
are tested: serial correlation (a Ljung-Box whiteness test on each
observation dimension), a mis-scaled noise level (a two-sided test of the
total normalised innovation squared against its reference) and
non-Gaussianity (a Jarque-Bera test on the pooled innovations). The model is
declared inadequate when any family rejects at alpha, combined
conservatively across the families by the Bonferroni rule.
innovation_diagnostics(object, lags = NULL, alpha = 0.05)innovation_diagnostics(object, lags = NULL, alpha = 0.05)
object |
A kalman_fit, or a regime_fit from |
lags |
Integer scalar – the Ljung-Box lag. Defaults to |
alpha |
Numeric scalar – the per-battery significance level the
adequacy verdict is taken at. Defaults to |
The diagnostics are what aci() uses to decide whether to trust its causal
read-out: a model whose innovations reject it is not a credible
data-generating mechanism, so the read-out is labelled "ungrounded" and the
decision abstains. Passing the diagnostics establishes self-consistency only,
which is necessary but not sufficient for a grounded causal claim (see
aci() on declaring mechanism provenance).
An innov_diag.
Harvey, A. C. (1989). Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press.
Durbin, J. and Koopman, S. J. (2012). Time Series Analysis by State Space Methods. 2nd ed. Oxford University Press.
Jarque, C. M. and Bera, A. K. (1980). Efficient tests for normality, homoscedasticity and serial independence of regression residuals. Economics Letters, 6(3), 255–259.
aci(), which calls this to ground its causal verdict.
Other causal:
aci(),
aci_fit(),
causal_information_rate(),
estimate_obs_df(),
innov_diag(),
its_causal(),
its_fit()
## A correctly specified local level: the innovations pass the diagnostics. model <- ssm( transition = 1, observation = 1, state_cov = 0.04, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) level <- cumsum(rnorm(300, sd = 0.2)) y <- level + rnorm(300) innovation_diagnostics(kalman_filter(model, y))## A correctly specified local level: the innovations pass the diagnostics. model <- ssm( transition = 1, observation = 1, state_cov = 0.04, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) level <- cumsum(rnorm(300, sd = 0.2)) y <- level + rnorm(300) innovation_diagnostics(kalman_filter(model, y))
Estimates the causal effect of an intervention on a single time series by
contrasting the observed post-intervention path with the counterfactual a
state-space model forecasts from the pre-intervention segment. A local-level
or local-linear-trend ssm is fitted to x[1:(intervention - 1)], its
state is projected forward across the post-intervention window, and the
observed values are differenced against that forecast. The forecast
covariance gives a predictive interval, so the pointwise, cumulative and
average effects each carry a calibrated uncertainty band, and a genuinely
null effect shows an interval that straddles zero.
its_causal(x, intervention, trend = FALSE, level = 0.95)its_causal(x, intervention, trend = FALSE, level = 0.95)
x |
A numeric vector — the full series, pre- and post-intervention. |
intervention |
Integer scalar — the index of the first
post-intervention observation. The pre-period is |
trend |
Logical scalar — whether to use a local-linear-trend model
( |
level |
Numeric scalar in |
The design is the single-case (N-of-1) interrupted time series; it is causal only under the maintained assumption that, absent the intervention, the pre-period dynamics would have continued. That assumption is the user's to defend; the function quantifies the contrast it implies.
An its_fit.
Brodersen, K. H., Gallusser, F., Koehler, J., Remy, N. and Scott, S. L. (2015). Inferring causal impact using Bayesian structural time-series models. Annals of Applied Statistics, 9(1), 247–274.
Other causal:
aci(),
aci_fit(),
causal_information_rate(),
estimate_obs_df(),
innov_diag(),
innovation_diagnostics(),
its_fit()
## A series with a genuine post-intervention lift of about +3. set.seed(1) pre <- cumsum(rnorm(60, sd = 0.2)) post <- pre[60] + cumsum(rnorm(40, sd = 0.2)) + 3 x <- c(pre, post) fit <- its_causal(x, intervention = 61L) fit@avg_effect## A series with a genuine post-intervention lift of about +3. set.seed(1) pre <- cumsum(rnorm(60, sd = 0.2)) post <- pre[60] + cumsum(rnorm(40, sd = 0.2)) + 3 x <- c(pre, post) fit <- its_causal(x, intervention = 61L) fit@avg_effect
An S7 object holding the output of its_causal(): the observed
post-intervention series, the model's counterfactual forecast and its
predictive interval, the pointwise and cumulative effects, and the average
post-intervention effect with an interval.
its_fit( time = numeric(0), observed = numeric(0), counterfactual = numeric(0), counterfactual_lower = numeric(0), counterfactual_upper = numeric(0), pointwise_effect = numeric(0), cumulative_effect = numeric(0), avg_effect = numeric(0), avg_effect_lower = numeric(0), avg_effect_upper = numeric(0), level = numeric(0) )its_fit( time = numeric(0), observed = numeric(0), counterfactual = numeric(0), counterfactual_lower = numeric(0), counterfactual_upper = numeric(0), pointwise_effect = numeric(0), cumulative_effect = numeric(0), avg_effect = numeric(0), avg_effect_lower = numeric(0), avg_effect_upper = numeric(0), level = numeric(0) )
time |
A numeric or integer vector of the post-intervention time indices. |
observed |
A numeric vector of the observed post-intervention values. |
counterfactual |
A numeric vector of the forecast counterfactual. |
counterfactual_lower, counterfactual_upper
|
Numeric vectors — the predictive-interval bounds of the counterfactual. |
pointwise_effect |
A numeric vector — observed minus counterfactual. |
cumulative_effect |
A numeric vector — the running sum of the pointwise effect. |
avg_effect |
Numeric scalar — the average post-intervention effect. |
avg_effect_lower, avg_effect_upper
|
Numeric scalars — the interval on the average effect. |
level |
Numeric scalar — the interval coverage used. |
An S7 object of class its_fit.
Other causal:
aci(),
aci_fit(),
causal_information_rate(),
estimate_obs_df(),
innov_diag(),
innovation_diagnostics(),
its_causal()
Runs the closed-form forward recursion of Kalman (1960) over an ssm. Starting from the model's prior, each step predicts the state through the transition and then updates it with the observation through the Kalman gain
where is the innovation covariance. The model log-likelihood is
accumulated from the prediction error decomposition,
with innovation .
kalman_filter(model, y)kalman_filter(model, y)
model |
An ssm. |
y |
A numeric vector (univariate) or |
The recursion is exact for a linear-Gaussian model. Time-varying system
matrices are honoured: a model whose transition, observation,
state_cov or obs_cov was supplied as a list of n matrices uses the
step-t matrix at step t.
A kalman_fit.
The filtering distribution returned here is one of
the two distributions aci() compares step by step: the time-resolved causal
information is its relative entropy from the smoothing distribution of
rts_smoother() (Andreou, Chen and Bollt 2026, eq. 7). See aci() and
causal_information_rate().
Kalman, R. E. (1960). A new approach to linear filtering and prediction problems. Journal of Basic Engineering, 82(1), 35–45.
rts_smoother() for the backward smoothing pass; ssm() for the
model.
Other state-space:
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
## Recover a smooth signal from a noisy local-level series. model <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) level <- cumsum(rnorm(100, sd = 0.1)) y <- level + rnorm(100) fit <- kalman_filter(model, y) fit@log_lik## Recover a smooth signal from a noisy local-level series. model <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) level <- cumsum(rnorm(100, sd = 0.1)) y <- level + rnorm(100) fit <- kalman_filter(model, y) fit@log_lik
An S7 object holding the output of a forward Kalman pass over an ssm, as
returned by kalman_filter(). The filtered quantities condition on
observations up to and including each step; the predicted quantities
condition on observations strictly before it. The innovations and their
covariances are retained because the log-likelihood and the
interrupted-time-series contrast are read off them.
kalman_fit( filtered_mean = numeric(0), filtered_cov = list(), predicted_mean = numeric(0), predicted_cov = list(), innovation = numeric(0), innovation_cov = list(), log_lik = numeric(0), model = ssm() )kalman_fit( filtered_mean = numeric(0), filtered_cov = list(), predicted_mean = numeric(0), predicted_cov = list(), innovation = numeric(0), innovation_cov = list(), log_lik = numeric(0), model = ssm() )
filtered_mean |
An |
filtered_cov |
A list of |
predicted_mean |
An |
predicted_cov |
A list of |
innovation |
An |
innovation_cov |
A list of |
log_lik |
Numeric scalar — the model log-likelihood from the prediction error decomposition. |
model |
The ssm that was filtered. |
An S7 object of class kalman_fit.
Other state-space:
kalman_filter(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
Filters the latent mean-reverting state of a spread through a finite
Gaussian-mixture Kalman recursion, the regime-switching generalisation of
the scalar filter of Elliott, van der Hoek and Malcolm (2005). The fitted
spread_model is expanded into n_regimes linear-Gaussian regimes that
share the spread's mean-reversion dynamics but differ in observation noise,
from calm to turbulent; the observed spread is filtered through
mixture_filter(), and the returned regime_fit carries both the
filtered spread state and the posterior probability of each volatility
regime at every step.
kalmix_filter(model, y, n_regimes = 2L, vol_multipliers = NULL)kalmix_filter(model, y, n_regimes = 2L, vol_multipliers = NULL)
model |
A spread_model giving the mean-reversion dynamics of the
spread, as returned by |
y |
A numeric vector — the observed spread series to filter. |
n_regimes |
Integer scalar — the number of volatility regimes to track.
Defaults to |
vol_multipliers |
A numeric vector of length |
The recursion runs on the package's native engine and needs no sibling package.
The regime models operate on deviations from the fitted long-run mean: the
discrete-time Ornstein-Uhlenbeck dynamics are the plain autoregressive
contraction of , so the observed spread is de-meaned by
model@theta before filtering and the returned state estimates are
reported back on the observed scale. A spread whose long-run mean is far
from zero is therefore tracked around , not pulled toward zero.
A regime_fit over the spread's volatility regimes. When
n_regimes = 1 the regime machinery is skipped and the kalman_fit of a
plain Kalman pass is returned instead.
Elliott, R. J., van der Hoek, J. and Malcolm, W. P. (2005). Pairs trading. Quantitative Finance, 5(3), 271–276.
mixture_filter() for the general engine; ou_fit() for the model.
x <- as.numeric(stats::arima.sim(list(ar = 0.85), n = 400)) model <- ou_fit(x) fit <- kalmix_filter(model, x) tail(round(fit@regime_prob, 2))x <- as.numeric(stats::arima.sim(list(ar = 0.85), n = 400)) model <- ou_fit(x) fit <- kalmix_filter(model, x) tail(round(fit@regime_prob, 2))
Filters a series generated by a finite set of linear-Gaussian regimes with a Markov chain switching between them. Each regime is supplied as its own ssm; the regimes must share a common state and observation dimension. At each step the filter advances every regime by one Kalman predict-and-update from the shared mixed prior, weights the regimes by their one-step evidence and the regime-transition prior, and collapses the per-regime posteriors back to a single Gaussian (a Gaussian pseudo-Bayesian order-one recursion). The returned regime probabilities are the filtered posterior over the active regime.
mixture_filter(models, y, transition = NULL, init_prob = NULL)mixture_filter(models, y, transition = NULL, init_prob = NULL)
models |
A list of two or more ssm objects, one per regime, sharing a common state and observation dimension. |
y |
A numeric vector or |
transition |
A |
init_prob |
A length- |
A regime_fit.
Bar-Shalom, Y., Li, X. R. and Kirubarajan, T. (2001). Estimation with Applications to Tracking and Navigation. Wiley. (GPB and IMM filters.)
kalman_filter() for the single-regime base case; kalmix_filter()
for the pairs-trading wrapper.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
## Two volatility regimes for a local-level series. calm <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 0.25, init_state = 0, init_cov = 1 ) wild <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 4, init_state = 0, init_cov = 1 ) set.seed(1) y <- c(rnorm(60, sd = 0.5), rnorm(60, sd = 2)) fit <- mixture_filter(list(calm, wild), y) tail(round(fit@regime_prob, 2))## Two volatility regimes for a local-level series. calm <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 0.25, init_state = 0, init_cov = 1 ) wild <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 4, init_state = 0, init_cov = 1 ) set.seed(1) y <- c(rnorm(60, sd = 0.5), rnorm(60, sd = 2)) fit <- mixture_filter(list(calm, wild), y) tail(round(fit@regime_prob, 2))
Smooths a regime-switching series: runs the Gaussian pseudo-Bayesian
(GPB1) forward pass of mixture_filter() and the backward recursion of
Kim (1994), returning the regime probabilities and collapsed state
moments conditioned on the whole series. Smoothed regime probabilities
use the future as well as the past, so a regime that only becomes
apparent in hindsight is attributed to the steps where it held.
mixture_smoother(models, y, transition = NULL, init_prob = NULL)mixture_smoother(models, y, transition = NULL, init_prob = NULL)
models |
A list of two or more ssm objects, one per regime, sharing a common state and observation dimension. |
y |
A numeric vector or |
transition |
A |
init_prob |
A length- |
Each backward step collapses a Gaussian mixture to one Gaussian per regime under the smoothed pair weights, mirroring the forward collapse, so the smoother is the standard approximation for Markov-switching state-space models rather than the (exponentially costly) exact mixture smoother. Missing observations are handled by the forward pass as prediction-only steps and flow through the backward pass unchanged.
Kim, C.-J. (1994). Dynamic linear models with Markov-switching. Journal of Econometrics, 60(1–2), 1–22.
mixture_filter() for the forward pass; rts_smoother() for
the single-regime smoother.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
calm <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 0.25, init_state = 0, init_cov = 1 ) wild <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 4, init_state = 0, init_cov = 1 ) set.seed(1) y <- c(rnorm(60, sd = 0.5), rnorm(60, sd = 2)) sm <- mixture_smoother(list(calm, wild), y) tail(round(sm@smoothed_prob, 2))calm <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 0.25, init_state = 0, init_cov = 1 ) wild <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 4, init_state = 0, init_cov = 1 ) set.seed(1) y <- c(rnorm(60, sd = 0.5), rnorm(60, sd = 2)) sm <- mixture_smoother(list(calm, wild), y) tail(round(sm@smoothed_prob, 2))
Estimates the mean-reverting (Ornstein-Uhlenbeck) dynamics of a spread by fitting the exact discrete-time autoregression
then mapping the autoregressive coefficients back to the continuous-time
parameters: , , and
.
ou_fit(x, dt = 1)ou_fit(x, dt = 1)
x |
Numeric vector — the spread series (for example from
|
dt |
Numeric scalar — the sampling interval, in the time units the
reported |
A spread that is not mean-reverting has an autoregressive coefficient
outside (0, 1); the function refuses such a series rather than report a
meaningless negative half-life.
A spread_model.
Other spread:
spread_series()
x <- as.numeric(stats::arima.sim(list(ar = 0.85), n = 800)) ou_fit(x)x <- as.numeric(stats::arima.sim(list(ar = 0.85), n = 800)) ou_fit(x)
An S7 object holding the output of mixture_filter(). The regime
probabilities give the filtered posterior over which regime is active at
each step; the collapsed state mean and covariance are the
mixture-marginalised state estimate.
regime_fit( regime_prob = numeric(0), state_mean = numeric(0), state_cov = list(), innovation = numeric(0), innovation_cov = list(), innovation_pit = numeric(0), log_lik = numeric(0), n_regimes = integer(0) )regime_fit( regime_prob = numeric(0), state_mean = numeric(0), state_cov = list(), innovation = numeric(0), innovation_cov = list(), innovation_pit = numeric(0), log_lik = numeric(0), n_regimes = integer(0) )
regime_prob |
An |
state_mean |
An |
state_cov |
A list of |
innovation |
An |
innovation_cov |
A list of |
innovation_pit |
For a univariate series, the length- |
log_lik |
Numeric scalar — the mixture model log-likelihood. |
n_regimes |
Integer scalar — the number of regimes |
An S7 object of class regime_fit.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
An S7 object holding the output of mixture_smoother(): the smoothed
regime probabilities and the collapsed smoothed state moments, each
conditioning on the whole observed series.
regime_smooth( smoothed_prob = numeric(0), smoothed_mean = numeric(0), smoothed_cov = list(), log_lik = numeric(0), n_regimes = integer(0) )regime_smooth( smoothed_prob = numeric(0), smoothed_mean = numeric(0), smoothed_cov = list(), log_lik = numeric(0), n_regimes = integer(0) )
smoothed_prob |
An |
smoothed_mean |
An |
smoothed_cov |
A list of |
log_lik |
Numeric scalar — the mixture model log-likelihood, carried through from the forward pass. |
n_regimes |
Integer scalar — the number of regimes |
An S7 object of class regime_smooth.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
An S7 object holding the output of a backward RTS smoothing pass, as
returned by rts_smoother(). Each smoothed quantity conditions on the whole
observed series, and its covariance.
rts_fit( smoothed_mean = numeric(0), smoothed_cov = list(), log_lik = numeric(0), model = ssm() )rts_fit( smoothed_mean = numeric(0), smoothed_cov = list(), log_lik = numeric(0), model = ssm() )
smoothed_mean |
An |
smoothed_cov |
A list of |
log_lik |
Numeric scalar — the model log-likelihood (carried through from the filter pass for convenience). |
model |
The ssm that was smoothed. |
An S7 object of class rts_fit.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_smoother(),
ssm(),
ssm_fit(),
ssm_mle()
Runs the backward recursion of Rauch, Tung and Striebel (1965) over a
completed Kalman pass to obtain the state estimates conditioned on the whole
series, . From the last filtered state backwards,
each step blends the filtered estimate with the smoothed estimate of the
next state through the smoother gain
giving and the matching
covariance update. The result is exact for a linear-Gaussian model.
rts_smoother(object, y = NULL)rts_smoother(object, y = NULL)
object |
A kalman_fit from |
y |
A numeric vector or matrix of observations. Required when |
Either a fitted kalman_fit or an ssm plus observations may be supplied; in the latter case the filter pass is run first.
An rts_fit.
This smoother supplies the second of the two Gaussian distributions aci()
compares: the time-resolved causal information is the relative entropy of the
smoothing distribution from the filtering
distribution (Andreou, Chen and Bollt 2026, eq. 7). See aci().
Rauch, H. E., Tung, F. and Striebel, C. T. (1965). Maximum likelihood estimates of linear dynamic systems. AIAA Journal, 3(8), 1445–1450.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
ssm(),
ssm_fit(),
ssm_mle()
model <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) y <- cumsum(rnorm(100, sd = 0.1)) + rnorm(100) smoothed <- rts_smoother(model, y) head(smoothed@smoothed_mean)model <- ssm( transition = 1, observation = 1, state_cov = 0.01, obs_cov = 1, init_state = 0, init_cov = 10 ) set.seed(1) y <- cumsum(rnorm(100, sd = 0.1)) + rnorm(100) smoothed <- rts_smoother(model, y) head(smoothed@smoothed_mean)
An S7 object holding the Ornstein-Uhlenbeck (OU) parameters of a
cointegration spread, as returned by ou_fit(). The continuous-time
model is
so kappa is the speed of mean reversion, theta the long-run mean,
and sigma the instantaneous volatility. The mean-reversion half-life
is .
spread_model( kappa = numeric(0), theta = numeric(0), sigma = numeric(0), dt = 1, n_obs = NA_integer_, hedge_ratio = NA_real_, call = NULL )spread_model( kappa = numeric(0), theta = numeric(0), sigma = numeric(0), dt = 1, n_obs = NA_integer_, hedge_ratio = NA_real_, call = NULL )
kappa |
Numeric scalar — the mean-reversion speed (per unit time). Strictly positive for a mean-reverting spread. |
theta |
Numeric scalar — the long-run mean the spread reverts to. |
sigma |
Numeric scalar — the instantaneous volatility. Strictly positive. |
dt |
Numeric scalar — the sampling interval the model was fitted at,
in the same time units as |
n_obs |
Integer scalar — the number of spread observations used. |
hedge_ratio |
Numeric scalar — the cointegration hedge ratio the
spread was built with, or |
call |
The matched call to |
An S7 object of class spread_model.
Other classes:
half_life()
x <- as.numeric(stats::arima.sim(list(ar = 0.8), n = 500)) ou_fit(x)x <- as.numeric(stats::arima.sim(list(ar = 0.8), n = 500)) ou_fit(x)
Forms the spread a - alpha - beta * b whose stationarity underlies a
pairs trade, where beta is the ordinary-least-squares hedge ratio of
a on b and alpha the intercept. The hedge ratio and intercept are
attached to the result as attributes.
spread_series(a, b, intercept = TRUE)spread_series(a, b, intercept = TRUE)
a |
Numeric vector — the first asset's price (or log-price) series. |
b |
Numeric vector — the second asset's series, the same length as
|
intercept |
Logical scalar — whether to include an intercept in the
hedge regression. Defaults to |
A numeric vector of the same length as a, the spread, carrying
hedge_ratio and intercept attributes.
Other spread:
ou_fit()
set.seed(1) b <- cumsum(stats::rnorm(300)) a <- 2 * b + stats::rnorm(300) s <- spread_series(a, b) attr(s, "hedge_ratio")set.seed(1) b <- cumsum(stats::rnorm(300)) a <- 2 * b + stats::rnorm(300) s <- spread_series(a, b) attr(s, "hedge_ratio")
An S7 object specifying a discrete-time linear-Gaussian state-space model,
built by the constructor of the same name. The latent state of dimension
m evolves through a transition matrix, an optional intercept (control
input) and Gaussian process noise, and is observed through an observation
matrix, an optional observation intercept and Gaussian observation noise:
The intercepts default to zero, recovering the intercept-free form; a
non-zero is the standard way to give a stationary state a
non-zero long-run mean (for an AR(1) state with coefficient and
mean , set ).
ssm( transition, observation, state_cov, obs_cov, init_state, init_cov, state_intercept = 0, obs_intercept = 0, obs_family = c("gaussian", "student_t"), obs_df = Inf )ssm( transition, observation, state_cov, obs_cov, init_state, init_cov, state_intercept = 0, obs_intercept = 0, obs_family = c("gaussian", "student_t"), obs_df = Inf )
transition |
The state transition matrix |
observation |
The observation matrix |
state_cov |
The process-noise covariance |
obs_cov |
The observation-noise covariance |
init_state |
The prior mean of the initial state |
init_cov |
The prior covariance of the initial state, an |
state_intercept |
The state intercept |
obs_intercept |
The observation intercept |
obs_family |
The observation-noise family, |
obs_df |
The Student-t degrees of freedom |
Each of transition, observation, state_cov and obs_cov is accepted
as a single matrix (static, reused at every step) or as a list of matrices,
one per time step (time-varying). Scalars and length-m vectors are
promoted to matrices, so a univariate local-level model can be written with
bare numbers. Internally every system matrix is stored as a list of one or
more matrices; the filtering and smoothing recursions index it per step.
The dimensions must be mutually consistent: with state dimension m and
observation dimension d, the transition matrices are m by m, the
observation matrices d by m, the process covariances m by m, and the
observation covariances d by d. The initial covariance is m by m.
An S7 object of class ssm.
Durbin, J. and Koopman, S. J. (2012). Time Series Analysis by State Space Methods, 2nd ed. Oxford University Press.
kalman_filter(), rts_smoother(), mixture_filter().
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm_fit(),
ssm_mle()
## A local-level (random-walk-plus-noise) model written with bare numbers. model <- ssm( transition = 1, observation = 1, state_cov = 0.1, obs_cov = 1, init_state = 0, init_cov = 10 ) model ## A constant-velocity tracking model with a vector state. velocity <- ssm( transition = matrix(c(1, 0, 1, 1), nrow = 2), observation = matrix(c(1, 0), nrow = 1), state_cov = diag(c(0.01, 0.01)), obs_cov = 1, init_state = c(0, 0), init_cov = diag(c(10, 10)) ) velocity## A local-level (random-walk-plus-noise) model written with bare numbers. model <- ssm( transition = 1, observation = 1, state_cov = 0.1, obs_cov = 1, init_state = 0, init_cov = 10 ) model ## A constant-velocity tracking model with a vector state. velocity <- ssm( transition = matrix(c(1, 0, 1, 1), nrow = 2), observation = matrix(c(1, 0), nrow = 1), state_cov = diag(c(0.01, 0.01)), obs_cov = 1, init_state = c(0, 0), init_cov = diag(c(10, 10)) ) velocity
Estimates the process covariance, the observation covariance and
(optionally) the transition matrix of an ssm by maximising the
prediction-error log-likelihood of kalman_filter() with stats::optim().
The supplied model is the template: its non-estimated components, its prior
and its observation family are kept as given, and its current values seed
the first optimisation start.
ssm_fit( model, y, estimate = c("state_cov", "obs_cov"), transition = FALSE, n_start = 3L, control = list() )ssm_fit( model, y, estimate = c("state_cov", "obs_cov"), transition = FALSE, n_start = 3L, control = list() )
model |
An ssm — the template supplying structure, prior, family and starting values. Estimated components must be time-invariant. |
y |
A numeric vector or |
estimate |
A character vector naming the components to estimate, from
|
transition |
Logical scalar — whether to estimate the transition
matrix as well (unconstrained; a fit whose spectral radius reaches one
is reported with a warning). Defaults to |
n_start |
Integer scalar — the number of optimisation starts.
Defaults to |
control |
A list passed to |
Covariances are optimised through their log-Cholesky factors (the
logarithm of the factor's diagonal, the off-diagonal entries free), so the
search is unconstrained and every iterate is a valid covariance. The fit
is repeated from n_start starts (the template plus a deterministic
ladder of log-scale shifts, so no seed is involved) and the best optimum
is kept, guarding against local optima at a modest cost.
Standard errors are read from the numerical Hessian at the optimum and
mapped to the covariance entries by a numerical delta method; they are
asymptotic and can be optimistic on short series, where the likelihood
separates the two noise sources weakly (a wide interval there is a finding
about the data, not a defect of the fit).
Missing observations (fully NA rows) are handled by the filter as
prediction-only steps, so a gappy series can be fitted directly. Under a
Student-t observation family the same machinery maximises the robust
filter's likelihood at the template's fixed degrees of freedom; combine
with estimate_obs_df() to choose the degrees of freedom.
An ssm_mle.
Harvey, A. C. (1989). Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press. (Prediction-error decomposition maximum likelihood.)
Durbin, J. and Koopman, S. J. (2012). Time Series Analysis by State Space Methods. 2nd ed. Oxford University Press.
kalman_filter() for the likelihood; estimate_obs_df() for the
Student-t degrees of freedom; ssm() for the template.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_mle()
## Recover the noise split of a local-level series. One start keeps the ## example fast; the default multi-start is the safer everyday setting. set.seed(1) level <- cumsum(rnorm(150, sd = 0.5)) y <- level + rnorm(150) template <- ssm( transition = 1, observation = 1, state_cov = 1, obs_cov = 1, init_state = y[1], init_cov = 10 ) fit <- ssm_fit(template, y, n_start = 1L) fit@estimates$state_cov fit@estimates$obs_cov## Recover the noise split of a local-level series. One start keeps the ## example fast; the default multi-start is the safer everyday setting. set.seed(1) level <- cumsum(rnorm(150, sd = 0.5)) y <- level + rnorm(150) template <- ssm( transition = 1, observation = 1, state_cov = 1, obs_cov = 1, init_state = y[1], init_cov = 10 ) fit <- ssm_fit(template, y, n_start = 1L) fit@estimates$state_cov fit@estimates$obs_cov
An S7 object holding the output of ssm_fit(): the fitted ssm (ready
for every verb that accepts one), the maximised log-likelihood, the
estimates and standard errors of the estimated entries, and the optimiser
diagnostics.
ssm_mle( model = ssm(), log_lik = numeric(0), estimates = list(), std_errors = list(), convergence = integer(0), n_start = integer(0), start_log_lik = numeric(0) )ssm_mle( model = ssm(), log_lik = numeric(0), estimates = list(), std_errors = list(), convergence = integer(0), n_start = integer(0), start_log_lik = numeric(0) )
model |
The fitted ssm. |
log_lik |
Numeric scalar — the maximised prediction-error log-likelihood. |
estimates |
A named list of the estimated matrices ( |
std_errors |
A named list matching |
convergence |
Integer scalar — the |
n_start |
Integer scalar — the number of optimisation starts run. |
start_log_lik |
A numeric vector with the best log-likelihood reached from each start. |
An S7 object of class ssm_mle.
Other state-space:
kalman_filter(),
kalman_fit(),
mixture_filter(),
mixture_smoother(),
regime_fit(),
regime_smooth(),
rts_fit(),
rts_smoother(),
ssm(),
ssm_fit()