6.7 KiB
SP15: Spencer 15-Point Moving Average
John Spencer designed 15 weights that zero out quarterly and quintile seasonality from economic data. Eighty years later, statisticians still reach for them when they need a quick seasonal adjustment that does not require the German engineering of X-13ARIMA.
| Property | Value |
|---|---|
| Category | Trend (FIR MA) |
| Inputs | Source (close) |
| Parameters | None |
| Outputs | Single series (SP15) |
| Output range | Tracks input |
| Warmup | Period bars |
| PineScript | sp15.pine |
| Signature | sp15_signature |
- SP15 is a fixed-coefficient symmetric FIR filter with 15 weights:
[-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3]divided by 320. - No configurable parameters; computation is stateless per bar.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
SP15 is a fixed-coefficient symmetric FIR filter with 15 weights: [-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3] divided by 320. The weights were designed by John Spencer to have zero frequency response at periods 4 and 5 (frequencies 2\pi/4 and 2\pi/5), making the filter effective at removing quarterly and quintile seasonal components from economic time series. The negative edge weights provide bandpass-like characteristics, and the fixed design requires no parameters beyond the source series.
Historical Context
John Spencer published the 15-point and 21-point weighted moving averages in 1904 for use in actuarial graduation (smoothing mortality tables). The weights were constructed to satisfy two constraints simultaneously: (1) preserve polynomial trends up to degree 3 (cubic), and (2) have zero response at specific seasonal frequencies. The 15-point variant zeros out periods 4 and 5; the 21-point variant zeros out periods 4, 5, and 7.
Spencer's filters predated Henderson's (1916) by twelve years and were widely used in actuarial science and economic statistics before the X-11 method standardized on Henderson filters. The Spencer 15-point filter was the default seasonal adjustment tool at the U.K. Office for National Statistics until the adoption of X-11 in the 1960s. In modern practice, it remains useful as a quick-and-dirty seasonal smoother when full X-13ARIMA decomposition is overkill.
The fixed 15-bar length creates a natural centered lag of 7 bars, which is appropriate for quarterly data (4 observations per year, so a 15-point filter spans nearly 4 quarters). For financial time series, the filter useful for removing intra-week (5-bar) and intra-month patterns from daily data.
Architecture & Physics
1. Fixed Weight Vector
The 15 weights are hardcoded constants, symmetric around the center:
\mathbf{w} = \frac{1}{320}[-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3]
No weight computation is needed; the coefficients are compile-time constants.
2. Symmetric Convolution
The symmetric structure allows folded computation: pair the $i$-th and $(14-i)$-th bars (which share the same weight), sum them, then multiply by the weight once. This halves the multiplication count from 15 to 8.
3. Negative Edge Weights
Three weights at each edge are negative (-3, -6, -5), giving the filter its seasonal-nulling property. The output can exceed the input range when edge bars have extreme values relative to the center.
4. Zero-Parameter Design
SP15 takes no period parameter. The filter length is always 15, and the weights are always Spencer's original values. This is both a strength (no tuning required) and a limitation (no adaptation to different data characteristics).
Mathematical Foundation
The Spencer 15-point filter output:
\text{SP15}_t = \frac{1}{320}\sum_{j=0}^{14} w_j \cdot x_{t-j}
Exploiting symmetry (w_j = w_{14-j}):
\text{SP15}_t = \frac{1}{320}\left[w_7 \cdot x_{t-7} + \sum_{j=0}^{6} w_j \left(x_{t-j} + x_{t-14+j}\right)\right]
Frequency response zeros:
H\left(e^{j2\pi/4}\right) = 0, \quad H\left(e^{j2\pi/5}\right) = 0
These zeros ensure complete suppression of periodicities at 4 and 5 bars.
Weight sum: -3-6-5+3+21+46+67+74+67+46+21+3-5-6-3 = 320
Polynomial preservation: The filter preserves polynomials up to degree 3:
\sum_{j=0}^{14} w_j \cdot (j-7)^k = 320 \cdot \delta_{k0}, \quad k = 0, 1, 2, 3
Default parameters: None (fixed 15-point filter).
Pseudo-code (streaming):
// Fixed symmetric weights (compile-time constants)
w = [-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3]
// Symmetric folded computation
total = w[7] * src[7]
for j = 0 to 6:
total += w[j] * (src[j] + src[14-j])
return total / 320
Resources
- Spencer, J. (1904). "On the Graduation of the Rates of Sickness and Mortality." Journal of the Institute of Actuaries, 38, 334-343.
- Macaulay, F.R. (1931). The Smoothing of Time Series. NBER. Chapter 4: Spencer-Type Formulas.
- Kendall, M.G. & Stuart, A. (1976). The Advanced Theory of Statistics, Vol. 3, 3rd ed. Griffin. Section 46.13: Spencer's Formulae.
- Kenny, P.B. & Durbin, J. (1982). "Local Trend Estimation and Seasonal Adjustment of Economic and Social Time Series." JRSS Series A, 145(1).
Performance Profile
Operation Count (Streaming Mode)
SP15 is a fixed 15-tap FIR filter with hard-coded Spencer weights (sum = 320). At construction, 15 normalized doubles are computed once. Each Update() is a pure 15-element dot product.
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| Ring buffer push | 1 | 3 | ~3 |
| FIR dot product: 15 FMA | 15 | 4 | ~60 |
| Total | 16 | — | ~63 cycles |
O(1) per bar (N is fixed at 15). The dot product takes ~60 cycles on modern x86. WarmupPeriod = 15. No parameters to validate.
Batch Mode (SIMD Analysis)
| Operation | Vectorizable? | Notes |
|---|---|---|
| 15-tap FIR convolution | Yes | AVX2: 4 VFMADD231PD passes cover 16 taps (1 unused) |
| Symmetric weights [−3,−6,−5,3,21,46,67,74,…] | Yes | Symmetric: fold to 8 unique weights; 8 FMADs per bar |
| Negative edge weights | Yes | Signed FMA; no special masking |
| Fixed-N: 15 taps | Yes | Compiler can fully unroll the 15-FMA loop at O3 |
With symmetric folding (8 unique weight pairs), the 15-tap dot product reduces to ~8 FMAs. AVX2 processes 4 output bars per outer iteration. Batch throughput: ~2 cycles per output bar at peak. Unrolled codegen fits entirely in instruction cache.