George C. Lane developed the Stochastic Oscillator in the late 1950s while working at Investment Educators in Chicago. His core observation was deceptively simple: in uptrends, closing prices tend to cluster near the high of the trading range; in downtrends, they cluster near the low. Quantifying that tendency produces a bounded oscillator that measures momentum rather than price.
Lane was careful to distinguish between Fast and Slow variants. The Fast Stochastic uses the raw %K and its SMA as %D. The Slow Stochastic applies additional smoothing: Slow %K equals Fast %D, and Slow %D is an SMA of Slow %K. This implementation produces the Fast variant. Traders who want Slow Stochastic should wrap the output with an additional SMA pass.
The Stochastic Oscillator and Williams %R share identical mathematics. The only difference is scale: $\text{WillR} = \text{Stoch \%K} - 100$. Lane's version scales $[0, 100]$ with overbought at the top; Williams inverts to $[-100, 0]$. Same information, different packaging.
The Stochastic Oscillator measures the closing price's position within the recent high-low range as a percentage. A reading of $100$ means the close equals the highest high over the lookback period. A reading of $0$ means the close equals the lowest low.
The %D signal line smooths %K via a simple moving average, providing crossover signals. When %K crosses above %D, momentum is shifting upward. When %K crosses below %D, momentum is shifting downward. These crossovers are most significant when they occur in overbought ($> 80$) or oversold ($< 20$) territory.
The indicator's real utility is divergence detection. When price makes a new high but %K fails to confirm, buying momentum is weakening. When price makes a new low but %K refuses to follow, selling pressure is exhausting. These divergences often precede reversals by several bars.
The indicator requires $n$ bars to fill the sliding window for highest-high and lowest-low computation. The %D SMA uses the PineScript convention of pre-filling its buffer with the first %K value, so it produces output from bar 0.
`Batch(ReadOnlySpan, ..., Span, Span, int, int)` delegates to `Highest.Batch()` and `Lowest.Batch()` for vectorized sliding min/max. Intermediate buffers use `stackalloc` for $\leq 256$ elements and `ArrayPool<double>` for larger inputs. The %D SMA uses a local circular buffer.
### 4. Edge Cases
| 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 |
1.**Fast vs Slow confusion**: This implementation outputs Fast Stochastic. Many platforms default to Slow Stochastic, which smooths %K before computing %D. Direct comparison will not match without setting `smoothPeriods=1`.
2.**Zero range returns 0**: When all bars in the window share the same high and low, %K returns $0$. Williams %R returns $-50$ for the same condition. The choice is arbitrary but not interchangeable.
3.**Overbought does not equal sell**: In trending markets, %K stays overbought/oversold for extended periods. Counter-trend trades based solely on Stochastic readings produce drawdowns.
4.**Short lookback noise**: `kLength < 5` creates excessive whipsaws. The default 14 balances responsiveness and noise rejection.
5.**%D warmup convention**: The first %D value pre-fills the SMA buffer with the initial %K, matching PineScript behavior. Other implementations may use NaN until `dPeriod` bars of %K are available.
6.**Bar correction cost**: Correcting a bar (`isNew=false`) triggers O(kLength) deque rebuild. Infrequent in normal streaming but visible when batch-correcting thousands of bars.
## FAQ
**Q: What is the difference between Fast and Slow Stochastic?**
A: Fast Stochastic uses raw %K and SMA(%K) as %D. Slow Stochastic sets Slow %K = Fast %D, then Slow %D = SMA(Slow %K). This implementation is Fast Stochastic. Apply an additional SMA to the output for Slow.
**Q: Why does zero range return 0 instead of 50?**
A: Convention. When the range is zero, the close equals both the high and the low, so the "position in range" is undefined. Returning $0$ matches the PineScript and Skender conventions. Williams %R returns $-50$ for the same condition.
**Q: How does Stoch relate to Williams %R?**
A: They are the same formula with different scales. $\text{WillR} = \text{\%K} - 100$. Stoch scales $[0, 100]$; WillR scales $[-100, 0]$.