8.9 KiB
AFIRMA: Autoregressive FIR Moving Average
Standard Moving Averages assume linear or exponential weights. AFIRMA asks: what if we used signal processing window functions instead?
| Property | Value |
|---|---|
| Category | Forecast |
| Inputs | Source (close) |
| Parameters | period, window (default WindowType.BlackmanHarris), leastSquares (default false) |
| Outputs | Single series (Afirma) |
| Output range | Tracks input |
| Warmup | period bars |
| PineScript | afirma.pine |
- AFIRMA is a Windowed Weighted Moving Average that replaces standard linear weighting with weights derived from signal processing window functions (...
- Similar: TSF, LinReg | Complementary: Error metrics for accuracy | Trading note: Adaptive FIR Moving Average for forecasting; projects price using optimized FIR coefficients.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
AFIRMA is a Windowed Weighted Moving Average that replaces standard linear weighting with weights derived from signal processing window functions (Hanning, Hamming, Blackman, Blackman-Harris). This approach achieves specific frequency response characteristics tailored to noise reduction.
The optional Least Squares mode fits a linear regression to recent bars and blends the fitted values with original data, producing a hybrid smoothed-predicted output.
Historical Context
Moving averages traditionally use Simple (rectangular window), Weighted (triangular window), or Exponential (recursive) forms. The DSP community solved finite filter design decades ago using window functions to minimize spectral leakage (ringing artifacts at discontinuities).
AFIRMA applies these well-understood coefficients directly to price series. It is effectively an FIR filter where coefficients are determined solely by the chosen window function—no manual coefficient calculation required.
Architecture & Physics
AFIRMA maintains a sliding window of the last P prices and computes a weighted average using pre-calculated window coefficients.
Window Functions
Instead of linear weights (1, 2, 3...), AFIRMA generates weights using cosine-sum series:
w_k = a_0 + a_1 \cos\left(\frac{2\pi k}{P}\right) + a_2 \cos\left(\frac{4\pi k}{P}\right) + a_3 \cos\left(\frac{6\pi k}{P}\right)
where P is the period and k is the index from 0 to P-1.
| Window | Coefficients | Main Lobe | Side Lobe |
|---|---|---|---|
| Rectangular | a_0=1 |
Narrowest | −13 dB |
| Hanning | a_0=0.5, a_1=-0.5 |
Medium | −32 dB |
| Hamming | a_0=0.54, a_1=-0.46 |
Medium | −43 dB |
| Blackman | a_0=0.42, a_1=-0.5, a_2=0.08 |
Wide | −58 dB |
| Blackman-Harris | a_0=0.35875, a_1=-0.48829, a_2=0.14128, a_3=-0.01168 |
Widest | −92 dB |
The default Blackman-Harris provides maximum side-lobe suppression (−92 dB), ideal for financial data with non-Gaussian noise spikes.
Least Squares Mode
When leastSquares=true, AFIRMA performs an additional step after the base weighted average:
- Determine regression window:
n = \min\left(\lfloor(P-1)/2\rfloor, 50\right) - Fit linear regression to the most recent
nbars (lags 0 ton-1) - Create hybrid buffer: Use fitted values for lags
0ton-1, original values for lagsntoP-1 - Average the hybrid buffer: Simple mean of all
Pvalues
This produces a smoothed estimate that incorporates short-term trend extrapolation. The fitted portion projects recent momentum while the original portion anchors to historical context.
Note: Despite some references calling this "cubic polynomial fitting," the actual implementation uses linear regression (first-degree polynomial: y = \text{intercept} + \text{slope} \times x).
Mathematical Foundation
Base Filter Equation
\text{AFIRMA}_t = \frac{\sum_{k=0}^{P-1} w_k \cdot x_{t-k}}{\sum_{k=0}^{P-1} w_k}
where x is the input series and w_k are the window weights.
Window Coefficient Formulas
Hanning:
w_k = 0.5 - 0.5 \cos\left(\frac{2\pi k}{P}\right)
Hamming:
w_k = 0.54 - 0.46 \cos\left(\frac{2\pi k}{P}\right)
Blackman:
w_k = 0.42 - 0.5 \cos\left(\frac{2\pi k}{P}\right) + 0.08 \cos\left(\frac{4\pi k}{P}\right)
Blackman-Harris:
w_k = 0.35875 - 0.48829 \cos\left(\frac{2\pi k}{P}\right) + 0.14128 \cos\left(\frac{4\pi k}{P}\right) - 0.01168 \cos\left(\frac{6\pi k}{P}\right)
Least Squares Regression
Given regression window size n:
S_x = \frac{(n-1)n}{2}, \quad S_{x^2} = \frac{(n-1)n(2n-1)}{6}
S_y = \sum_{i=0}^{n-1} x_{t-i}, \quad S_{xy} = \sum_{i=0}^{n-1} i \cdot x_{t-i}
\text{slope} = \frac{n \cdot S_{xy} - S_x \cdot S_y}{n \cdot S_{x^2} - S_x^2}
\text{intercept} = \frac{S_y - \text{slope} \cdot S_x}{n}
Fitted value at lag i: \hat{x}_i = \text{intercept} + \text{slope} \cdot i
Final LS output:
\text{AFIRMA}_{LS} = \frac{1}{P} \left( \sum_{i=0}^{n-1} \hat{x}_i + \sum_{i=n}^{P-1} x_{t-i} \right)
Parameters
| Parameter | Default | Range | Description |
|---|---|---|---|
| Period | — | ≥ 1 | Window length (number of taps) |
| Window | BlackmanHarris | Enum | Window function for weight generation |
| LeastSquares | false | bool | Enable linear regression blending |
Performance Profile
Operation Count (Streaming Mode)
Windowed FIR convolution — O(P) per bar where P = period (window length).
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| RingBuffer write | 1 | ~2 cy | ~2 cy |
| FIR convolution (P FMA ops) | P | ~1 cy | ~P cy |
| Weight normalization | 1 | ~1 cy | ~1 cy |
| LS regression (if enabled) | n | ~2 cy | ~2n cy |
| NaN guard + state update | 1 | ~2 cy | ~2 cy |
| Total (P=20) | O(P) | — | ~25 cy |
O(P) per bar where P = window length. FMA-fused dot product dominates; LS regression adds O(n) where n = min(⌊(P−1)/2⌋, 50).
| Metric | Value | Notes |
|---|---|---|
| Complexity | O(P) | Convolution per bar |
| Allocations | 0 | Zero-allocation in Update and Batch spans |
| Warmup | P bars | WarmupPeriod = period |
Quality Metrics
| Metric | Score | Notes |
|---|---|---|
| Accuracy | 9/10 | Excellent noise suppression |
| Timeliness | 6/10 | Inherent FIR lag (~P/2 bars) |
| Overshoot | 2/10 | Minimal; no recursive amplification |
| Smoothness | 10/10 | Exceptional with Blackman-Harris |
Validation
| Library | Status | Notes |
|---|---|---|
| Pine Script | ✅ | Matches afirma.pine reference |
| Internal | ✅ | Batch, Streaming, Span modes consistent |
| TA-Lib | N/A | Not implemented |
| Skender | N/A | Not implemented |
Window Comparison
For identical period, different windows trade smoothness for responsiveness:
| Window | Smoothness | Lag | Best For |
|---|---|---|---|
| Rectangular | Low | Lowest | Equivalent to SMA |
| Hanning | Medium | Medium | Balanced general use |
| Hamming | Medium-High | Medium | Better spectral properties than Hanning |
| Blackman | High | Higher | Noisy trending markets |
| BlackmanHarris | Highest | Highest | Maximum noise rejection |
Common Pitfalls
-
Lag Increases with Smoothness: Blackman-Harris has the best noise rejection but also the most lag. For fast signals, consider Hanning or even Rectangular (which degrades to SMA).
-
Least Squares Is Not Magic: LS mode adds trend extrapolation but can overshoot during reversals. It works best in trending markets, not choppy conditions.
-
Large Periods Amplify Lag: FIR filters have inherent delay of approximately
P/2bars. Period 50 means ~25 bars of lag regardless of window choice. -
isNew Parameter Matters: When processing live ticks within the same bar, use
Update(value, isNew: false). When a new bar opens, useisNew: true(default). Incorrect usage corrupts internal state. -
NaN Handling: Non-finite inputs (NaN, ±Infinity) are replaced with the last valid value. Consecutive NaN inputs maintain the last known good value. After
Reset(), the first valid input establishes the baseline. -
Warmup Period: AFIRMA requires
periodbars beforeIsHotbecomes true. During warmup, it uses available data with proportionally adjusted weights.
References
- Harris, F. J. (1978). "On the use of windows for harmonic analysis with the discrete Fourier transform." Proceedings of the IEEE, 66(1), 51-83.
- Nuttall, A. H. (1981). "Some windows with very good sidelobe behavior." IEEE Transactions on Acoustics, Speech, and Signal Processing, 29(1), 84-91.