Files
Miha Kralj 33d20f2a18 feat(dynamics): add PlusDI, MinusDI, PlusDM, MinusDM indicators
Complete thin Dx-composition wrapper indicators with full test coverage:

- PlusDi/MinusDi: Directional Indicator wrappers (DiPlus/DiMinus from Dx)
- PlusDm/MinusDm: Directional Movement wrappers (DmPlus/DmMinus from Dx)
- Individual validation tests per indicator directory (TALib, Skender, bounds)
- Combined unit tests (DiDm.Tests.cs) and validation tests (DiDm.Validation.Tests.cs)
- Quantower wrappers + tests for all 4 indicators
- PineScript v6 implementations with compensated RMA
- Normalized .md documentation for all indicators and categories
- 182 tests passing, 0 failures
2026-03-11 20:21:52 -07:00

10 KiB

STOCH: Stochastic Oscillator

The Stochastic Oscillator doesn't follow price. It follows the speed, or momentum, of price. Momentum changes direction before price.

Property Value
Category Oscillator
Inputs Bar series (High, Low, Close)
Parameters kLength (default 14), dPeriod (default 3)
Outputs Dual series (%K line, %D signal line)
Output range 0 to 100
Warmup kLength bars
PineScript stoch.pine

Key takeaways

  • Measures where the close sits within the highest-high to lowest-low range, scaled to [0, 100].
  • Produces two lines: raw %K (position in range) and %D (SMA of %K, the signal line).
  • Uses MonotonicDeque pairs for O(1) amortized min/max tracking in streaming mode.
  • Zero range (all bars identical) returns 0 for %K, not 50 or NaN.
  • This is the Fast Stochastic variant. %K is unsmoothed; %D is \text{SMA}(\%K, d).

Historical Context

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.

What It Measures and Why It Matters

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.

Mathematical Foundation

Core Formula


HH_n = \max(H_i) \quad \text{for } i \in [t - n + 1, \, t]

LL_n = \min(L_i) \quad \text{for } i \in [t - n + 1, \, t]

\%K_t = 100 \times \frac{C_t - LL_n}{HH_n - LL_n}

\%D_t = \text{SMA}(\%K, d)

where n is kLength and d is dPeriod.

Parameter Mapping

Parameter Code Default Constraints
K Length kLength 14 > 0
D Period dPeriod 3 > 0

Warmup Period


W = n

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.

Architecture & Physics

1. MonotonicDeque Streaming

Two MonotonicDeque instances provide O(1) amortized min/max tracking:

  • Max deque: decreasing order of highs; front is always the window maximum.
  • Min deque: increasing order of lows; front is always the window minimum.
  • Circular buffers (_hBuf, _lBuf): store raw H/L values for deque rebuild on bar correction.

2. %D Signal Line

A separate 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 and update the running sum.

3. Batch Path

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

Signal Patterns

  • %K/%D crossover: Bullish when %K crosses above %D; bearish when %K crosses below %D. Most reliable in overbought/oversold zones.
  • Divergence: Price makes new highs while %K does not (bearish) or price makes new lows while %K does not (bullish).
  • Failure swing: %K reaches an extreme, pulls back, fails to re-reach the extreme, then reverses.
  • Hook: Short-term reversal when %K or %D hooks at an extreme without completing a full crossover.

Practical Notes

  • In strong trends, %K stays overbought or oversold for extended periods. Fading the trend on %K readings alone produces consistent losses.
  • The %D crossover is a lagging signal by design (it's an SMA). Use it for confirmation, not anticipation.
  • Fast Stochastic is noisier than Slow Stochastic. If whipsaws are a problem, either increase kLength or apply additional smoothing.
  • Willr: Identical math with inverted [-100, 0] scale; \text{WillR} = \text{\%K} - 100.
  • Stochf: Fast Stochastic variant (may differ in %D handling).
  • KDJ: Extended stochastic with J-line divergence amplification.
  • SMI: Stochastic Momentum Index, measures distance from range midpoint rather than boundary.

Validation

Library Status Notes
Skender GetStoch(kLength, dPeriod, smoothPeriods=1) matches within 1e-6 after warmup
TA-Lib -- Not directly validated (separate Stochf tests)
Tulip -- Not directly validated
Ooples -- Not validated

Performance Profile

Key Optimizations

  • O(1) amortized streaming: MonotonicDeque avoids full-window scans for min/max on each bar.
  • O(1) %D SMA: Circular buffer with running sum eliminates iteration over the %D window.
  • 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. 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].

References

  • 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.
  • Appel, G.; Hitschler, F. Stock Market Trading Systems. Dow Jones-Irwin, 1980.
  • Achelis, S. B. Technical Analysis from A to Z. McGraw-Hill, 2000.