8.6 KiB
SGMA: Savitzky-Golay Moving Average
Least-squares polynomial fitting has been solving signal processing problems since 1964. That most traders still use medieval averaging techniques says more about the industry than the math.
| Property | Value |
|---|---|
| Category | Trend (FIR MA) |
| Inputs | Source (close) |
| Parameters | period (default 9), degree (default 2) |
| Outputs | Single series (Sgma) |
| Output range | Tracks input |
| Warmup | period bars |
| PineScript | sgma.pine |
| Signature | sgma_signature |
- SGMA is a Finite Impulse Response (FIR) filter that uses polynomial fitting to smooth data while preserving higher moments (peaks, valleys, and inf...
- Similar: SGF, LSMA | Trading note: Savitzky-Golay MA; polynomial smoothing that preserves higher moments. Good for derivative estimation.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
SGMA is a Finite Impulse Response (FIR) filter that uses polynomial fitting to smooth data while preserving higher moments (peaks, valleys, and inflection points). Unlike the Simple Moving Average (which flattens everything) or the Exponential Moving Average (which introduces phase lag), SGMA uses polynomial weighting to maintain the original signal's shape characteristics.
Historical Context / The Standard
Abraham Savitzky and Marcel Golay published their seminal paper "Smoothing and Differentiation of Data by Simplified Least Squares Procedures" in Analytical Chemistry in 1964. The context was spectroscopy—scientists needed to smooth noisy absorption spectra without destroying the peaks that identified chemical compounds.
The Savitzky-Golay filter became the gold standard in scientific signal processing because it solved the fundamental trade-off: smoothing reduces noise but also reduces signal amplitude. SG filters preserve the signal's shape by fitting local polynomials—effectively "understanding" the curvature of the data rather than blindly averaging it.
Financial markets present the same challenge. Traders want smooth lines that don't lag, and they want to preserve the actual peaks and troughs that matter for trading decisions. SGMA adapts the Savitzky-Golay approach to price series.
Architecture & Physics
SGMA computes weights based on the polynomial degree parameter. The weight formula is elegantly simple:
w_i = 1 - |x_i|^d
Where:
x_i = \frac{i - \text{half\_window}}{\text{half\_window}}(normalized position from -1 to +1)d= polynomial degree (0 to 4)
The Physics of Polynomial Degree
The degree parameter controls the weight distribution:
| Degree | Weight Shape | Trading Behavior |
|---|---|---|
| 0 | Flat (uniform) | Equivalent to SMA. All bars weighted equally. |
| 1 | V-shape (linear) | Mild center-weighting. Moderate smoothing. |
| 2 | Parabolic | Strong center-weighting. Good balance. |
| 3 | Cubic falloff | Aggressive center-weighting. Shape preservation. |
| 4 | Quartic falloff | Extreme center-weighting. Maximum responsiveness. |
Critical Edge Behavior: For degree ≥ 1, the edge positions (oldest and newest values in the window) have weight = 0 because at the edges, |x| = 1, so w = 1 - 1^d = 0. This means the filter effectively ignores the boundary values—a deliberate design choice that prevents edge artifacts from corrupting the polynomial fit.
The Compute Challenge
Unlike recursive filters (EMA, DEMA), SGMA requires a convolution over the entire window. QuanTAlib precomputes the weight vector upon initialization, reducing the runtime operation to a dot product.
\text{Runtime Cost} = O(N) \text{ multiplications}
The fixed-weight approach enables SIMD vectorization for batch processing, recovering much of the performance gap versus recursive filters.
Mathematical Foundation
1. Period Normalization
SGMA requires an odd period to ensure symmetric polynomial fitting:
L = \begin{cases} \text{period} & \text{if period is odd} \\ \text{period} + 1 & \text{if period is even} \end{cases}
2. Weight Calculation
The half-window parameter defines the normalization range:
\text{half} = \lfloor L / 2 \rfloor
For each index i from 0 to L-1:
x_i = \frac{i - \text{half}}{\text{half}}
w_i = 1 - |x_i|^d
3. Weight Normalization
Weights are normalized to sum to 1.0:
\hat{w}_i = \frac{w_i}{\sum_{j=0}^{L-1} w_j}
4. Filter Output
The SGMA value is the weighted sum of prices:
\text{SGMA}_t = \sum_{i=0}^{L-1} P_{t-L+1+i} \cdot \hat{w}_i
Performance Profile
SGMA trades a small amount of CPU cycles for superior shape preservation.
Operation Count (Streaming Mode, Scalar)
Per-bar cost for period L (weights precomputed at construction):
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| MUL | L | 3 | 3L |
| ADD | L | 1 | L |
| Total | 2L | — | ~4L cycles |
For a typical period of 14:
- Total: ~56 cycles per bar
Constructor cost (one-time): ~80L cycles (L power operations at ~80 cycles each + L additions + normalization)
Complexity: O(L) per bar — linear with period. Weights precomputed, runtime is pure dot product.
Batch Mode (SIMD/FMA Analysis)
SGMA's dot product structure enables efficient SIMD vectorization:
| Operation | Scalar Ops | SIMD Ops (AVX2) | Speedup |
|---|---|---|---|
| MUL+ADD (FMA) | 2L | L/4 (FMA256) | 8× |
Batch efficiency (512 bars, L=14):
| Mode | Cycles/bar | Total (512 bars) | Improvement |
|---|---|---|---|
| Scalar streaming | 56 | 28,672 | — |
| SIMD batch (FMA) | ~10 | ~5,120 | ~82% |
Quality Metrics
| Metric | Score | Notes |
|---|---|---|
| Accuracy | 10/10 | Exact polynomial weight calculation |
| Timeliness | 8/10 | Center-weighted, inherent lag = half period |
| Overshoot | 9/10 | Polynomial decay prevents ringing artifacts |
| Smoothness | 9/10 | Adjustable via degree parameter |
Implementation Details
// Weight Precomputation (Constructor)
double half = _period / 2.0;
double wSum = 0;
for (int i = 0; i < _period; i++)
{
double normX = (i - half) / half;
double weight = 1.0 - Math.Pow(Math.Abs(normX), _degree);
_weights[i] = weight;
wSum += weight;
}
// Normalization
for (int i = 0; i < _period; i++)
{
_weights[i] /= wSum;
}
// Runtime (Update) - simplified
double result = 0;
for (int i = 0; i < _period; i++)
{
result += _buffer[i] * _weights[i];
}
return result;
Validation
QuanTAlib validates SGMA against mathematical properties rather than external libraries, as most libraries implement the full Savitzky-Golay convolution coefficients rather than the simplified polynomial weighting approach.
| Library | Status | Notes |
|---|---|---|
| QuanTAlib | ✅ | Reference implementation. Validated against math definition. |
| Self-Validation | ✅ | Degree 0 matches SMA exactly. |
| Mode Consistency | ✅ | Streaming, batch, span, and event modes produce identical results. |
| Skender | ❌ | No SGMA equivalent. |
| TA-Lib | ❌ | Not included. |
| Tulip | ❌ | Not included. |
| Ooples | ❌ | Not included. |
Common Pitfalls
-
Degree 0 Misuse: If you want SMA, use SMA. Degree 0 SGMA is mathematically equivalent but wastes cycles on weight calculation.
-
Even Period Confusion: SGMA silently converts even periods to odd (period + 1). If you specify period=10, you get period=11. This is mathematically necessary for symmetric polynomial fitting, not a bug.
-
Edge Weight Zero: For degree ≥ 1, the oldest and newest values in the window have zero weight. This is intentional—it prevents edge effects. But it means:
- Bar corrections (
isNew=false) have minimal effect at higher degrees - The effective "active" period is shorter than the nominal period
- Bar corrections (
-
Cold Start: SGMA requires a full window (
L) to produce mathematically valid output. The firstL-1bars are warmup noise. CheckIsHotbefore trading on the signal. -
High Degree Instability: Degrees 3-4 concentrate weight heavily in the center. While this preserves shape, it also means a small number of bars dominate the output—approaching the behavior of a very short moving average with extra smoothing on the tails.