Files

129 lines
6.7 KiB
Markdown
Raw Permalink Normal View History

2026-02-27 07:48:12 -08:00
# 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.*
2026-02-27 07:48:12 -08:00
| 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](sp15.pine) |
| **Signature** | [sp15_signature](sp15_signature.md) |
2026-02-27 07:48:12 -08:00
- 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).
2026-02-26 22:02:52 -08:00
## 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.