Time Series Analysis

The smile.timeseries package models and forecasts univariate time series. It supplies diagnostics and transformations alongside autoregressive and autoregressive moving-average models.

Java examples call the static factories on TimeSeries, AR, ARMA, and BoxTest. Scala and Kotlin prefer package helpers such as diff, diffStages, acf, ar, and ljungBox. Financial examples start from smile.datasets.BitcoinPrice, take its log closing prices, and difference them into log returns.

ClassPurpose
TimeSeries Differencing, autocovariance, ACF, and PACF utilities
BoxTest Box–Pierce and Ljung–Box portmanteau tests
AR AR(p) models fitted by Yule–Walker or OLS
ARMA ARMA(p, q) models fitted by Hannan–Rissanen

Foundations

Stationarity

These models assume weak stationarity: a time-invariant mean and autocovariance. Trending prices and seasonal series usually violate that assumption. Differencing removes polynomial trends, while a log transform often stabilizes variance. For asset prices, log(pt) − log(pt−1) is a common stationary starting point.

AR, MA, and ARMA

ModelDefinitionDiagnostic pattern
AR(p) xt = b + Σ φixt−i + εt PACF cuts off; ACF tails off
MA(q) xt = b + εt + Σ θiεt−i ACF cuts off; PACF tails off
ARMA(p, q) Combines AR and MA terms Both ACF and PACF tail off

Here εt is white noise with variance σ² and b is the intercept. ACF/PACF patterns are order-selection heuristics, not proof; compare candidate models out of sample when possible.

TimeSeries Utilities

TimeSeries is an interface containing static methods; it is never instantiated. diff(x, lag) shortens a series by the lag. The three-argument overload performs repeated differencing and returns each intermediate series.

diff(x, lag)
Difference observations separated by lag.
diff(x, lag, differences)
Apply differencing repeatedly; requires lag × differences < x.length.
cov(x, lag)
Raw, unnormalized autocovariance sum; negative lags use their absolute value.
acf(x, lag)
Normalized autocovariance; lag zero is always 1.
pacf(x, lag)
Direct lag correlation after removing intermediate lags with the Yule–Walker equations.
import java.util.Arrays
import smile.timeseries.TimeSeries

double[] x = {1, 2, 4, 8, 16}
double[][] d = TimeSeries.diff(x, 1, 2)
System.out.println(Arrays.toString(d[0]))
System.out.println(Arrays.toString(d[1]))
System.out.printf("cov1=%.2f acf1=%.4f pacf2=%.4f%n",
    TimeSeries.cov(x, 1), TimeSeries.acf(x, 1), TimeSeries.pacf(x, 2))

[1.0, 2.0, 4.0, 8.0]
[1.0, 2.0, 4.0]
      

BoxTest: Residual Diagnostics

Portmanteau tests ask whether the first several autocorrelations are jointly zero. pierce computes Q = nΣr²; ljung applies the small-sample correction n(n+2)Σr²/(n−lag) and is usually preferred for short series.

Read type, df, q, and pvalue directly. A large p-value means the data do not provide evidence against the white-noise null; it does not prove independence.

import smile.timeseries.BoxTest

double[] residuals = {
    0.12, -0.08, 0.03, 0.14, -0.11, 0.02, -0.04, 0.09,
    -0.06, 0.01, 0.05, -0.10, 0.07, -0.02, 0.04, -0.03
}
BoxTest pierce = BoxTest.pierce(residuals, 5)
BoxTest ljung = BoxTest.ljung(residuals, 5)
System.out.println(pierce)
System.out.println(ljung)
System.out.println(ljung.pvalue > 0.05)

AR: Autoregressive Models

Yule–Walker vs OLS

AR.fit(x, p) uses Yule–Walker: it is fast, depends on the ACF, and guarantees a stationary fitted model. AR.ols(x, p) uses least squares and normally provides more accurate coefficients plus standard errors, but does not guarantee stationarity. AR.ols(x, p, false) skips standard errors when only point estimates are needed.

p(), intercept(), ar()
Model order, intercept, and AR coefficients.
fittedValues(), residuals()
In-sample fitted values and errors; each has length x.length − p.
RSS(), variance(), df()
Residual sum of squares, residual variance, and degrees of freedom.
R2(), adjustedR2()
In-sample goodness-of-fit summaries.
ttest()
A p × 4 OLS matrix: estimate, standard error, t-statistic, and p-value. Yule–Walker returns null.
forecast(), forecast(h)
One-step or iterated multi-step forecasts; h must be positive.

Bitcoin Log Returns

import java.util.Arrays
import smile.datasets.BitcoinPrice
import smile.timeseries.AR
import smile.timeseries.BoxTest
import smile.timeseries.TimeSeries

var bitcoin = new BitcoinPrice()
double[] returns = TimeSeries.diff(bitcoin.logPrice(), 1)
AR yw = AR.fit(returns, 6)
AR ols = AR.ols(returns, 6)

System.out.printf("p=%d intercept=%g R2=%g%n",
    ols.p(), ols.intercept(), ols.R2())
System.out.println(Arrays.toString(ols.ar()))
System.out.println(Arrays.toString(ols.forecast(5)))
System.out.println(BoxTest.ljung(ols.residuals(), 20))
System.out.println(yw.ttest() == null)

Inspect PACF lags before choosing p. Multi-step forecasts feed each prediction back as a lagged value and therefore tend toward the model’s long-run mean. For OLS, ttest() has one row per AR coefficient; the intercept is not included.

ARMA: Autoregressive Moving-Average Models

ARMA.fit(x, p, q) uses the two-stage Hannan–Rissanen procedure: first fit a long AR model to obtain proxy residuals, then regress on the requested AR and MA lags with SVD-based least squares. The MA terms can capture short-memory dependence that would otherwise require a high-order AR model.

The series must satisfy x.length > p + q + 20 + max(p, q). Accessors mirror AR, with additional q() and ma(). ttest() has p + q rows: AR rows first, then MA.

import java.util.Arrays
import smile.datasets.BitcoinPrice
import smile.timeseries.ARMA
import smile.timeseries.BoxTest
import smile.timeseries.TimeSeries

double[] returns = TimeSeries.diff(new BitcoinPrice().logPrice(), 1)
ARMA model = ARMA.fit(returns, 6, 3)
System.out.printf("ARMA(%d,%d), variance=%g%n",
    model.p(), model.q(), model.variance())
System.out.println(Arrays.toString(model.ar()))
System.out.println(Arrays.toString(model.ma()))
System.out.println(model.forecast())
System.out.println(Arrays.toString(model.forecast(3)))
System.out.println(BoxTest.ljung(model.residuals(), 20))

A one-step ARMA forecast uses the latest observed values and residuals. Unknown future residuals are assigned their expectation of zero, so the MA contribution disappears after q steps and longer forecasts are driven by the AR recursion.

Modeling Workflow

  1. Plot the raw series and assess stationarity. Apply logs, TimeSeries.diff, or seasonal differencing as needed.
  2. Inspect ACF and PACF. Start with small p and q, then compare plausible candidates.
  3. Use AR when PACF cuts off and ACF tails off; use ARMA when both tail off and an MA component gives a more parsimonious fit.
  4. For AR, prefer OLS when inference and coefficient accuracy matter. Choose Yule–Walker when a stationary fitted model is required.
  5. Inspect coefficients and, where available, their t-tests. Validate forecasts out of sample; information criteria are external to this API.
  6. Run BoxTest.ljung(model.residuals(), lag). If it rejects white noise, revisit transformations or model order.
  7. Forecast only after diagnostics are satisfactory, and communicate uncertainty separately because these methods return point forecasts.
AR estimatorYule–WalkerOLS
FactoryAR.fitAR.ols
Stationary fitted modelGuaranteedNot guaranteed
Coefficient behaviorSlightly biasedConsistent
Standard errors / t-testsNoYes, unless disabled

Common Pitfalls

Non-stationary input
Do not fit directly to trending prices or untreated seasonality. Use logs/differences and check stationarity first.
Excessive lag order
Large p or q overfits, destabilizes coefficients, and consumes observations. Start around 1–3 and increase only when diagnostics justify it.
Short ARMA series
Respect n > p + q + 20 + max(p, q); otherwise ARMA.fit throws IllegalArgumentException.
Unstable OLS AR
AR.ols does not guarantee stationarity. Use AR.fit or check characteristic roots when forecasts must remain stable.
Misreading Ljung–Box
A large p-value only means there is insufficient evidence of autocorrelation at the tested lags; it does not validate every model assumption.
MA forecast memory
Future ARMA residuals are zero in expectation, so MA effects fade after q steps. This is expected.
Overinterpreting ttest()
AR t-tests cover AR lags, not the intercept. ARMA rows list all AR coefficients before MA coefficients.