Add Savitzky-Golay Moving Average (SGMA) Indicator Implementation

- Implemented SgmaIndicator class in C# with properties for Period, Degree, and Source.
- Added unit tests for SgmaIndicator covering constructor defaults, initialization, and various update scenarios.
- Created a new Quantower adapter for the SGMA indicator, including input parameters and line series setup.
- Removed legacy SGMA implementation and tests to streamline the codebase.
- Updated project files to include new indicator and tests in the build process.
- Generated a missing indicators report and outlined a plan for oscillator documentation rewrite.
This commit is contained in:
Miha Kralj
2026-02-13 21:44:45 -08:00
parent 951842acca
commit dfeb23bf3d
81 changed files with 13629 additions and 2041 deletions
+141 -90
View File
@@ -1,138 +1,189 @@
# Stochastic Fast Oscillator (STOCHF)
# STOCHF: Stochastic Fast Oscillator
## Overview
> "Speed kills in traffic. In markets, it merely whipsaws." -- Anonymous
The Stochastic Fast Oscillator is the unsmoothed variant of the classic Stochastic Oscillator. It measures the position of the closing price relative to the high-low range over a lookback period, producing a raw (fast) %K line and its SMA-smoothed %D signal line.
| Property | Value |
|----------|-------|
| **Category** | Oscillator |
| **Inputs** | Bar series (High, Low, Close) |
| **Parameters** | `kLength` (default 5), `dPeriod` (default 3) |
| **Outputs** | Dual series (%K line, %D signal line) |
| **Output range** | $0$ to $100$ |
| **Warmup** | `kLength` bars |
Unlike the standard Stochastic (STOCH), which may apply additional SMA smoothing to %K, StochF outputs the raw %K directly — making it more responsive to price changes but also noisier.
### Key takeaways
The indicator produces two lines:
- **%K** (Fast %K): Raw position within the range, scaled 0100
- **%D** (Signal line): Simple Moving Average of %K
- The unsmoothed (raw) variant of the Stochastic Oscillator. %K has no additional SMA smoothing applied.
- Default `kLength` is 5 (shorter than Stoch's 14), making it more responsive and noisier.
- Uses `MonotonicDeque` pairs for O(1) amortized min/max tracking, identical architecture to Stoch.
- Zero range (all bars identical) returns $0$ for %K.
- Matches TA-Lib's dedicated `STOCHF` function, which separates Fast from Slow Stochastic explicitly.
## Origin and Sources
## Historical Context
George C. Lane introduced the Stochastic Oscillator in the late 1950s. The "Fast" variant is the original unsmoothed form, while the "Slow" variant applies additional SMA smoothing to reduce noise. Most modern platforms offer both versions; TA-Lib specifically separates them as `STOCH` (slow) and `STOCHF` (fast).
George C. Lane's Stochastic Oscillator (late 1950s) was originally this: the raw, unsmoothed position-in-range calculation with a simple SMA signal line. The "Fast" label was applied retroactively when traders began smoothing %K with an additional SMA to create the "Slow" variant. What Lane invented is what we now call Fast Stochastic.
**Key references:**
- Lane, George C. "Lane's Stochastics." *Technical Analysis of Stocks & Commodities*, 1984
- Murphy, John J. *Technical Analysis of the Financial Markets*, 1999
- Appel, Gerald & Hitschler, Fred. *Stock Market Trading Systems*, 1980
TA-Lib codified the distinction by providing separate functions: `STOCH` (slow, with configurable smoothing on %K) and `STOCHF` (fast, raw %K). QuanTAlib follows this convention. The `Stoch` class defaults to `kLength=14`; the `Stochf` class defaults to `kLength=5` for faster response. Both produce raw %K internally; the difference is the default parameterization and the explicit naming that signals intent.
## Mathematical Formula
The shorter default period makes Stochf more reactive to short-term price action. That reactivity is simultaneously its strength (early signals) and its weakness (more false signals in choppy markets). Traders who want the responsiveness of a 5-period lookback but less noise typically apply additional smoothing externally rather than switching to the Slow variant.
### Core Calculation
## What It Measures and Why It Matters
```
%K = 100 × (Close Lowest Low) / (Highest High Lowest Low)
Stochf measures where the current close sits within the highest-high to lowest-low range over the past `kLength` bars, expressed as a percentage from $0$ to $100$. The %D line is the SMA of %K over `dPeriod` bars.
Where:
Lowest Low = min(Low[i]) for i ∈ [0, kLength-1]
Highest High = max(High[i]) for i ∈ [0, kLength-1]
The indicator prioritizes speed over smoothness. Because %K is unsmoothed and the default lookback is only 5 bars, Stochf reacts to price changes faster than its Slow Stochastic counterpart. This makes it useful for short-term trading where early detection of momentum shifts matters more than filtering noise.
%D = SMA(%K, dPeriod)
```
The trade-off is straightforward: faster response means more false signals. In trending markets, Stochf whipsaws through overbought/oversold zones rapidly. In range-bound markets, the quick response helps identify turning points before slower indicators confirm. Knowing which regime you are trading determines whether Stochf helps or hurts.
### Edge Case
## Mathematical Foundation
When `Highest High = Lowest Low` (zero range), `%K = 0`.
### Core Formula
### Signal Line
$$
HH_n = \max(H_i) \quad \text{for } i \in [t - n + 1, \, t]
$$
`%D` is computed as a Simple Moving Average of `%K` values using a circular buffer with a running sum for O(1) per-bar computation.
$$
LL_n = \min(L_i) \quad \text{for } i \in [t - n + 1, \, t]
$$
## Architecture
$$
\%K_t = 100 \times \frac{C_t - LL_n}{HH_n - LL_n}
$$
### Streaming Path
$$
\%D_t = \text{SMA}(\%K, d)
$$
The streaming implementation uses **monotonic deques** for O(1) amortized highest-high and lowest-low tracking:
where $n$ is `kLength` and $d$ is `dPeriod`.
- **MonotonicDeque** (max): Maintains decreasing order of high values; front always holds the current maximum
- **MonotonicDeque** (min): Maintains increasing order of low values; front always holds the current minimum
- **Circular buffer** + running sum for SMA(%K → %D)
### Parameter Mapping
Bar correction (`isNew=false`) triggers deque rebuild from the circular buffer, ensuring correct state without allocation.
| Parameter | Code | Default | Constraints |
|-----------|------|---------|-------------|
| K Length | `kLength` | 5 | `> 0` |
| D Period | `dPeriod` | 3 | `> 0` |
### State Management
### Warmup Period
```
State record struct:
DSum — running sum of %K values in the SMA window
DHead — circular buffer head index for %D SMA
PrevDVal — previous buffer value at DHead (for rollback)
LastValidHigh/Low/Close — NaN/Infinity protection
```
$$
W = n
$$
The standard `_s` / `_ps` pattern enables bar correction:
- `isNew=true`: `_ps = _s`, advance index/count
- `isNew=false`: `_s = _ps`, recalculate from previous state
The indicator requires $n$ bars to fill the sliding window. The %D SMA pre-fills its buffer with the first %K value (PineScript convention), producing output from bar 0.
### Batch Path
## Architecture & Physics
Static `Batch()` methods use `Highest.Batch()` and `Lowest.Batch()` for vectorized min/max computation, with `ArrayPool` for buffers exceeding 256 elements and `stackalloc` for smaller inputs.
### 1. MonotonicDeque Streaming
## Parameters
Identical architecture to `Stoch`: two `MonotonicDeque` instances (max for highs, min for lows) provide O(1) amortized sliding min/max. Circular buffers (`_hBuf`, `_lBuf`) store raw H/L values for deque rebuild on bar correction.
| Parameter | Type | Default | Range | Description |
|-----------|------|---------|-------|-------------|
| `kLength` | int | 5 | ≥ 1 | Lookback period for highest high / lowest low |
| `dPeriod` | int | 3 | ≥ 1 | SMA smoothing period for %D signal line |
### 2. %D Signal Line
## Performance Profile
A circular buffer (`_dBuf`) with running sum computes the SMA of %K in O(1). First bar pre-fills the entire buffer with the initial %K value; subsequent bars replace the oldest entry.
| Metric | Value |
|--------|-------|
| Time complexity (streaming) | O(1) amortized per bar |
| Time complexity (batch) | O(n) |
| Space complexity | O(kLength + dPeriod) |
| Warmup period | kLength bars |
| Output range | 0100 (both %K and %D) |
### 3. Batch Path
## Interpretation
`Batch(ReadOnlySpan, ..., Span, Span, int, int)` delegates to `Highest.Batch()` and `Lowest.Batch()`. Intermediate buffers use `stackalloc` for $\leq 256$ elements and `ArrayPool<double>` beyond that threshold.
### Overbought / Oversold
### 4. Edge Cases
| Zone | %K Level | Interpretation |
|------|----------|----------------|
| Overbought | > 80 | Price near top of range — potential reversal |
| Neutral | 2080 | Normal trading range |
| Oversold | < 20 | Price near bottom of range — potential reversal |
| Condition | Behavior |
|-----------|----------|
| `kLength <= 0` or `dPeriod <= 0` | `ArgumentException` with `nameof()` |
| `NaN` / `Infinity` input | Substitutes last valid value per channel (H/L/C) |
| All NaN (no valid data yet) | Returns `NaN` for both %K and %D |
| Zero range ($HH = LL$) | %K returns $0$ |
| `isNew = false` | Restores `_ps`, rebuilds both deques from circular buffer |
## Interpretation and Signals
### Signal Zones
| Zone | Condition | Interpretation |
|------|-----------|----------------|
| Overbought | `%K > 80` | Close near period high; potential reversal down |
| Neutral | `20 ≤ %K ≤ 80` | Normal trading range |
| Oversold | `%K < 20` | Close near period low; potential reversal up |
### Signal Patterns
- **%K/%D Crossover**: Bullish when %K crosses above %D; bearish when %K crosses below %D
- **Divergence**: Price makes new highs/lows while StochF doesn't — potential reversal
- **Failure Swings**: %K reaches overbought/oversold then reverses before re-reaching the extreme
- **Hook**: Short-term reversal pattern when %K or %D hooks at extremes
- **%K/%D crossover**: Bullish when %K crosses above %D; bearish when %K crosses below %D.
- **Divergence**: Price makes new highs while %K fails to confirm (bearish) or price makes new lows while %K holds (bullish).
- **Hook reversal**: %K reverses sharply at an extreme without completing a full crossover. Common with the fast variant's responsiveness.
### StochF vs STOCH (Slow Stochastic)
### Practical Notes
This implementation is the **Fast Stochastic** where:
- `%K` is the raw (unsmoothed) oscillator
- `%D` is the SMA of `%K`
- Stochf generates more crossover signals than Stoch due to the shorter default period and lack of %K smoothing. Filter with trend context.
- In strong trends, %K oscillates rapidly near 100 (uptrend) or 0 (downtrend). These are not reversal signals; they confirm trend strength.
- Consider using Stochf for entry timing within a trend identified by a slower indicator (ADX, SMA slope).
The "Slow Stochastic" (STOCH) additionally smooths %K with an SMA before computing %D. StochF is more responsive but generates more false signals in choppy markets.
## Related Indicators
- [**Stoch**](../stoch/Stoch.md): Same formula with default `kLength=14`; often used with additional %K smoothing for the "Slow" variant.
- [**Willr**](../willr/Willr.md): Identical math with inverted $[-100, 0]$ scale.
- [**KDJ**](../kdj/Kdj.md): Extended stochastic with J-line divergence amplification.
- [**StochRSI**](../stochrsi/Stochrsi.md): Applies the stochastic formula to RSI output instead of price.
## Validation
| Library | Match | Notes |
|---------|-------|-------|
| Skender | ✔️ | Via `GetStoch(kLength, dPeriod, smoothPeriods=1)` — smoothPeriods=1 produces Fast %K |
| TALib | ✔️ | Via `TALib.Functions.StochF(high, low, close, ...)` — dedicated Fast Stochastic function |
| Library | Status | Notes |
|---------|--------|-------|
| Skender | ✅ | `GetStoch(kLength, dPeriod, smoothPeriods=1)` matches within `1e-6` |
| TA-Lib | | `StochF(high, low, close, kLength, dPeriod)` matches within `1e-6` |
| Tulip | -- | Not directly validated |
| Ooples | -- | Not validated |
## Performance Profile
### Key Optimizations
- **O(1) amortized streaming**: `MonotonicDeque` avoids full-window scans for min/max.
- **O(1) %D SMA**: Circular buffer with running sum.
- **Zero allocation**: `Update` uses pre-allocated circular buffers and `record struct State`.
- **Stackalloc/ArrayPool batch**: Intermediate buffers use `stackalloc` for $\leq 256$ elements, `ArrayPool` beyond.
### Operation Count (Streaming Mode)
| Operation | Count per bar |
|-----------|---------------|
| Comparisons | 2-3 (deque push amortized) |
| Divisions | 2 (range normalization + %D SMA) |
| Multiplications | 1 (`100 *`) |
| Additions/Subtractions | 2 (%D running sum update) |
| NaN checks | 3 (high, low, close) |
| **Total** | **~10 ops** |
### SIMD Analysis (Batch Mode)
| Property | Value |
|----------|-------|
| Vectorizable | Partially (via `Highest.Batch` / `Lowest.Batch`) |
| %K final loop | Scalar: `100 * (close[i] - LL[i]) / (HH[i] - LL[i])` |
| %D computation | Scalar circular buffer with running sum |
## Common Pitfalls
1. **Zero range**: When all bars in the window have identical H/L, range = 0 and %K = 0 (not 50 or NaN)
2. **Fast vs Slow confusion**: Many platforms default to "Slow Stochastic"; StochF outputs the raw unsmoothed %K
3. **Overbought ≠ sell signal**: In strong trends, %K can stay above 80 for extended periods
4. **Short lookback noise**: kLength < 3 creates excessive whipsaws in volatile markets
5. **SMA warmup for %D**: The first dPeriod bars use the PineScript convention of filling the SMA buffer with the first %K value, not NaN
6. **Default period difference**: StochF defaults to kLength=5 (shorter than STOCH's kLength=14) for faster response
1. **Fast vs Slow confusion**: StochF is the unsmoothed variant. TA-Lib's `STOCH` applies %K smoothing; `STOCHF` does not. Comparing outputs without matching smoothing parameters produces mismatches.
2. **Default period difference**: StochF defaults to `kLength=5`, not 14. Comparing directly to `Stoch(14, 3)` produces different results even though the formula is identical.
3. **More whipsaws**: The shorter lookback and lack of smoothing generate more %K/%D crossovers. Most are noise in trending markets.
4. **Zero range returns 0**: When all bars in the window have identical H/L, %K returns $0$. Williams %R returns $-50$ for the same condition.
5. **%D warmup convention**: First %D value pre-fills the SMA buffer with the initial %K, matching PineScript behavior. Other implementations may emit NaN until `dPeriod` bars of %K are available.
6. **Overbought persistence**: In strong trends, %K stays near extremes. The fast response makes this more pronounced than with Slow Stochastic.
## FAQ
**Q: What is the difference between Stochf and Stoch?**
A: Identical formula, different defaults. Stochf defaults to `kLength=5` for faster response. Stoch defaults to `kLength=14`. Neither applies additional smoothing to %K in this implementation. The "Slow Stochastic" convention requires smoothing %K with an SMA, which is a separate operation.
**Q: Why does TA-Lib separate STOCH and STOCHF?**
A: TA-Lib's `STOCH` function includes a `smoothK` parameter that applies SMA smoothing to %K before computing %D (Slow Stochastic). `STOCHF` omits that smoothing step entirely. QuanTAlib's `Stoch` and `Stochf` both output raw %K; the distinction is in default parameters and naming convention.
**Q: When should I prefer Stochf over Stoch?**
A: When you need faster signal detection and can tolerate more false positives. Typical use cases: scalping, intraday mean reversion, or as a timing tool within a larger trend-following system.
## References
- Lane, G. C. (1984). "Lane's Stochastics." *Technical Analysis of Stocks & Commodities*
- Murphy, J. J. (1999). *Technical Analysis of the Financial Markets*. New York Institute of Finance
- Achelis, S. B. (2000). *Technical Analysis from A to Z*. McGraw-Hill
- [TradingView Stochastic](https://www.tradingview.com/support/solutions/43000502332/)
- [StockCharts Stochastic Oscillator](https://school.stockcharts.com/doku.php?id=technical_indicators:stochastic_oscillator_fast_slow_and_full)
- Lane, G. C. "Lane's Stochastics." *Technical Analysis of Stocks & Commodities*, 1984.
- Murphy, J. J. *Technical Analysis of the Financial Markets*. New York Institute of Finance, 1999.
- Achelis, S. B. *Technical Analysis from A to Z*. McGraw-Hill, 2000.