mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 02:28:05 +00:00
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:
+157
-77
@@ -1,106 +1,186 @@
|
||||
# KDJ: Enhanced Stochastic Oscillator
|
||||
|
||||
> "K leads, D confirms, J exaggerates — three perspectives on momentum."
|
||||
> "K leads, D confirms, J exaggerates — three perspectives on momentum condensed into one indicator."
|
||||
|
||||
KDJ is an enhanced Stochastic Oscillator popular in Asian markets. It extends the classic Stochastic by adding a J line that amplifies divergence between K and D, providing earlier reversal signals. Uses Wilder's RMA (Exponential Moving Average with `α = 1/signal`) instead of SMA for smoother K and D lines.
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Category** | Oscillator |
|
||||
| **Inputs** | High, Low, Close (TBar) |
|
||||
| **Parameters** | `length` (default 9), `signal` (default 3) |
|
||||
| **Outputs** | K line, D line, J line (`Last`) |
|
||||
| **Output range** | K: [0, 100], D: [0, 100], J: unbounded |
|
||||
| **Warmup period** | `length + signal - 1` |
|
||||
|
||||
## Calculation
|
||||
### Key takeaways
|
||||
|
||||
1. Compute highest high and lowest low over the lookback period using monotonic deques.
|
||||
2. Calculate the Raw Stochastic Value (RSV).
|
||||
3. Smooth RSV with RMA to get K; smooth K with RMA to get D.
|
||||
4. Compute J as the amplified divergence.
|
||||
- KDJ extends the classic Stochastic by adding a **J line** that amplifies K/D divergence, giving earlier reversal signals.
|
||||
- Uses **Wilder's RMA** (α = 1/signal) instead of SMA for smoother K and D lines, producing less whipsaw than the standard Stochastic.
|
||||
- Popular in **Asian markets** (standard indicator on Chinese exchanges) where the J line's overbought/oversold extremes drive position sizing.
|
||||
- Monotonic deques deliver **O(1) amortized** highest-high/lowest-low tracking without re-scanning the lookback window.
|
||||
- Exponential warmup compensators eliminate the typical initialization bias that plagues recursive filters from bar one.
|
||||
|
||||
Formula:
|
||||
## Historical Context
|
||||
|
||||
```
|
||||
RSV = 100 × (Close - LowestLow) / (HighestHigh - LowestLow)
|
||||
K = RMA(RSV, signal) // α = 1/signal
|
||||
D = RMA(K, signal) // α = 1/signal
|
||||
J = 3K - 2D
|
||||
```
|
||||
The KDJ indicator originated in Asian financial markets as an extension of George Lane's Stochastic Oscillator. Chinese and Japanese traders found the classic %K/%D pair insufficient for capturing the acceleration of momentum reversals, so they added a third component, the J line, defined as 3K - 2D. This amplification makes J break above 100 or below 0 well before K and D reach their own extreme zones, providing an early-warning system that the standard Stochastic lacks.
|
||||
|
||||
If the price range is zero, RSV defaults to `50.0` (neutral). K and D are clamped to `[0, 100]`. J is unbounded and can exceed 100 or go below 0.
|
||||
The QuanTAlib implementation departs from the traditional SMA-based smoothing found in most charting platforms. By substituting Wilder's RMA (equivalent to an EMA with α = 1/period), the K and D lines respond more quickly to recent price changes while maintaining the exponentially-weighted memory that prevents sudden jumps on window entry/exit. This choice trades some of the SMA version's visual smoothness for faster signal generation, which matters when the J line's purpose is precisely to detect reversals early.
|
||||
|
||||
Exponential warmup compensators ensure accurate K and D values from the first bar, avoiding the typical initialization bias of recursive filters.
|
||||
## What It Measures and Why It Matters
|
||||
|
||||
## Interpretation
|
||||
KDJ measures where the current close sits within the recent high-low range, then smooths that position twice (K smooths RSV, D smooths K) and amplifies the gap between the two smoothed lines into J. The K line answers "where is price relative to its range?", the D line answers "where has that relative position been trending?", and the J line answers "is the trend accelerating or decelerating?"
|
||||
|
||||
- **K > D** → bullish momentum (K crosses above D = buy signal)
|
||||
- **K < D** → bearish momentum (K crosses below D = sell signal)
|
||||
- **J > 100** → strongly overbought, potential reversal down
|
||||
- **J < 0** → strongly oversold, potential reversal up
|
||||
- **K > 80** → overbought zone
|
||||
- **K < 20** → oversold zone
|
||||
The J line's unbounded nature is its defining feature. While K and D are clamped to [0, 100], J routinely exceeds 100 during strong uptrends and drops below 0 during strong downtrends. These excursions signal exhaustion before the bounded lines reach their own overbought/oversold thresholds. Traders use J > 100 as a warning that bullish momentum is overextended, and J < 0 as a warning that bearish momentum cannot sustain itself.
|
||||
|
||||
## Parameters
|
||||
## Mathematical Foundation
|
||||
|
||||
| Name | Type | Default | Range | Description |
|
||||
| :--- | :--- | :------ | :---- | :---------- |
|
||||
| `length` | `int` | `9` | `>0` | Lookback period for highest high / lowest low. |
|
||||
| `signal` | `int` | `3` | `>0` | RMA smoothing period for K and D lines. |
|
||||
### Core Formula
|
||||
|
||||
## API
|
||||
$$RSV = 100 \times \frac{Close - LL_n}{HH_n - LL_n}$$
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Kdj {
|
||||
+Name : string
|
||||
+WarmupPeriod : int
|
||||
+IsHot : bool
|
||||
+K : TValue
|
||||
+D : TValue
|
||||
+Last : TValue (J line)
|
||||
+Update(TBar input, bool isNew) TValue
|
||||
+Update(TBarSeries source) (TSeries K, TSeries D, TSeries J)
|
||||
+Prime(TBarSeries source) void
|
||||
+Reset() void
|
||||
+Batch(TBarSeries source, int length, int signal) (TSeries K, TSeries D, TSeries J)
|
||||
+Batch(ReadOnlySpan~double~ high, low, close, Span~double~ kOut, dOut, jOut, int length, int signal) void
|
||||
+Calculate(TBarSeries source, int length, int signal) ((TSeries K, TSeries D, TSeries J) Results, Kdj Indicator)
|
||||
}
|
||||
```
|
||||
where $HH_n$ is the highest high and $LL_n$ is the lowest low over the lookback period $n$. When the range is zero, RSV defaults to 50.0 (neutral).
|
||||
|
||||
## Usage Example
|
||||
$$K = \text{RMA}(RSV, \, signal) = \alpha \cdot RSV + (1 - \alpha) \cdot K_{prev}$$
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
$$D = \text{RMA}(K, \, signal) = \alpha \cdot K + (1 - \alpha) \cdot D_{prev}$$
|
||||
|
||||
// Initialize
|
||||
var kdj = new Kdj(length: 9, signal: 3);
|
||||
$$J = 3K - 2D$$
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
kdj.Update(bar, isNew: true);
|
||||
where $\alpha = 1 / signal$.
|
||||
|
||||
if (kdj.IsHot)
|
||||
{
|
||||
Console.WriteLine($"{bar.Time}: K={kdj.K.Value:F2} D={kdj.D.Value:F2} J={kdj.Last.Value:F2}");
|
||||
}
|
||||
}
|
||||
```
|
||||
### Parameter Mapping
|
||||
|
||||
## Performance Profile
|
||||
| Parameter | Formula role | Default | Constraint |
|
||||
|-----------|-------------|---------|------------|
|
||||
| `length` | Lookback window for $HH_n$ / $LL_n$ | 9 | > 0 |
|
||||
| `signal` | RMA period for K and D smoothing | 3 | > 0 |
|
||||
|
||||
| Metric | Score | Notes |
|
||||
| :--- | :--- | :--- |
|
||||
| **Throughput** | 9 | O(1) amortized via monotonic deques. |
|
||||
| **Allocations** | 0 | Zero allocations in hot path. |
|
||||
| **Complexity** | O(1) | Amortized constant time per update. |
|
||||
| **Accuracy** | 10 | Exact match with PineScript reference. Exponential warmup compensators. |
|
||||
| **Timeliness** | 8 | RMA smoothing provides faster response than SMA-based Stochastic. |
|
||||
| **Overshoot** | 7 | J line intentionally unbounded for early signals. |
|
||||
| **Smoothness** | 8 | Double RMA smoothing eliminates noise. |
|
||||
### Warmup Period
|
||||
|
||||
$$W = length + signal - 1$$
|
||||
|
||||
Default configuration (9, 3) warms up in 11 bars.
|
||||
|
||||
## Architecture & Physics
|
||||
|
||||
### 1. Three-Output Design
|
||||
|
||||
KDJ produces three correlated outputs per bar. [`K`](lib/oscillators/kdj/Kdj.cs:49) and [`D`](lib/oscillators/kdj/Kdj.cs:50) are stored as separate `TValue` properties; [`Last`](lib/oscillators/kdj/Kdj.cs:48) holds the J line. The `Update(TBarSeries)` method returns a named tuple `(TSeries K, TSeries D, TSeries J)`.
|
||||
|
||||
### 2. Monotonic Deque Min/Max
|
||||
|
||||
Instead of scanning the entire lookback window on each bar, [`MonotonicDeque`](lib/oscillators/kdj/Kdj.cs:30) maintains sorted candidates so that highest-high and lowest-low queries are O(1). On correction (`isNew=false`), the deque is rebuilt from the circular buffer without heap allocation.
|
||||
|
||||
### 3. RMA with Warmup Compensator
|
||||
|
||||
The exponential warmup compensator tracks the geometric decay factor $e_K = e_K \times (1 - \alpha)$ and divides raw RMA output by $(1 - e_K)$ during the transient phase. This bias correction ensures accurate K and D values from the first bar rather than waiting for the filter to converge.
|
||||
|
||||
### 4. FMA Hot Path
|
||||
|
||||
Both RMA updates use [`Math.FusedMultiplyAdd`](lib/oscillators/kdj/Kdj.cs:131) for the `decay * prev + alpha * input` pattern, and the J computation uses FMA for `3.0 * K + (-2.0 * D)`.
|
||||
|
||||
### 5. Edge Cases
|
||||
|
||||
- **Zero range**: RSV defaults to 50.0 (midpoint).
|
||||
- **NaN/Infinity inputs**: Last-valid substitution per channel (high, low, close independently).
|
||||
- **All NaN**: Returns NaN for all three outputs until a finite bar arrives.
|
||||
- **K/D clamping**: `Math.Clamp(value, 0.0, 100.0)` enforces bounds post-computation.
|
||||
|
||||
## Interpretation and Signals
|
||||
|
||||
### Signal Zones
|
||||
|
||||
| Zone | K value | J value | Meaning |
|
||||
|------|---------|---------|---------|
|
||||
| Overbought | > 80 | > 100 | Momentum exhaustion, potential reversal down |
|
||||
| Neutral | 20 - 80 | 0 - 100 | Trend continuation likely |
|
||||
| Oversold | < 20 | < 0 | Selling exhaustion, potential reversal up |
|
||||
|
||||
### Signal Patterns
|
||||
|
||||
- **K crosses above D**: Bullish signal, especially when both are below 20.
|
||||
- **K crosses below D**: Bearish signal, especially when both are above 80.
|
||||
- **J > 100**: Strongly overbought, reversal probability increases.
|
||||
- **J < 0**: Strongly oversold, reversal probability increases.
|
||||
- **J divergence from price**: J making lower highs while price makes higher highs warns of trend weakness.
|
||||
|
||||
### Practical Notes
|
||||
|
||||
- The J line generates more false signals than K/D crossovers in ranging markets. Combine with trend confirmation (ADX, moving average slope) to filter.
|
||||
- Shorter `length` values (5-7) suit intraday timeframes; longer values (14-21) suit daily/weekly analysis.
|
||||
- The RMA smoothing makes this variant less noisy than SMA-based KDJ but slightly slower to react to sharp reversals.
|
||||
|
||||
## Related Indicators
|
||||
|
||||
- [**Stoch**](../stoch/Stoch.md): Classic Stochastic %K/%D with SMA smoothing.
|
||||
- [**Stochf**](../stochf/Stochf.md): Fast Stochastic without secondary smoothing.
|
||||
- [**SMI**](../smi/Smi.md): Stochastic Momentum Index, uses distance from midpoint rather than from lowest low.
|
||||
- [**Williams %R**](../willr/Willr.md): Inverted raw stochastic without smoothing.
|
||||
|
||||
## Validation
|
||||
|
||||
No direct TA-Lib/Tulip/Skender equivalent exists for KDJ with Wilder's RMA smoothing. Validation is performed against the PineScript reference and internal consistency checks:
|
||||
- Streaming vs Batch vs Span cross-mode consistency
|
||||
- Mathematical identity: J = 3K − 2D
|
||||
- K/D bounded in [0, 100]
|
||||
- Parameter sensitivity across multiple configurations
|
||||
No direct TA-Lib, Skender, Tulip, or Ooples equivalent exists for KDJ with Wilder's RMA smoothing. Validation is performed via internal consistency checks.
|
||||
|
||||
## Sources
|
||||
| Check | Status | Notes |
|
||||
|-------|--------|-------|
|
||||
| Streaming vs Batch | ✅ | All three outputs match within 1e-10 |
|
||||
| Span vs TBarSeries Batch | ✅ | K, D, J identical across APIs |
|
||||
| J = 3K - 2D identity | ✅ | Mathematical identity holds for all bars |
|
||||
| K/D bounded [0, 100] | ✅ | Verified across 500 bars with high-volatility GBM |
|
||||
| Parameter sensitivity | ✅ | Different length/signal values produce distinct outputs |
|
||||
| Constant price convergence | ✅ | K = D = J = 50.0 after warmup |
|
||||
|
||||
## Performance Profile
|
||||
|
||||
### Key Optimizations
|
||||
|
||||
- **O(1) amortized streaming**: Monotonic deques eliminate window re-scan.
|
||||
- **FMA in RMA updates**: `Math.FusedMultiplyAdd` for both K and D smoothing plus J computation.
|
||||
- **Precomputed constants**: `_alpha` and `_decay` calculated once in constructor.
|
||||
- **Circular buffer**: Fixed-size `double[]` arrays for high/low with modulo indexing.
|
||||
- **Zero-allocation correction**: Deque rebuild operates on existing buffer memory.
|
||||
|
||||
### Operation Count (Streaming Mode)
|
||||
|
||||
| Operation | Count per bar |
|
||||
|-----------|--------------|
|
||||
| Comparisons | 3 (input validation) + O(1) amortized deque |
|
||||
| Multiplications | 4 (2× RMA + J) |
|
||||
| Additions | 4 (2× RMA + J + range) |
|
||||
| FMA calls | 3 (`K`, `D`, `J`) |
|
||||
| Divisions | 1 (RSV) + up to 2 (warmup compensators) |
|
||||
| Clamp | 2 (K, D) |
|
||||
|
||||
### SIMD Analysis (Batch Mode)
|
||||
|
||||
| Aspect | Status |
|
||||
|--------|--------|
|
||||
| Highest/Lowest | Delegated to `Highest.Batch` / `Lowest.Batch` (SIMD-capable) |
|
||||
| RSV computation | Scalar (data-dependent division) |
|
||||
| RMA smoothing | Scalar (IIR recursion, sequential dependency) |
|
||||
| J computation | FMA scalar per element |
|
||||
| Buffer strategy | `stackalloc` ≤ 256, `ArrayPool` above |
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Confusing J with a bounded oscillator.** J routinely exceeds [0, 100]. Applying overbought/oversold thresholds designed for K to the J line produces premature signals.
|
||||
2. **Using SMA-based KDJ formulas.** Many charting platforms use SMA smoothing. This implementation uses RMA (Wilder's), so values will not match SMA-based references exactly.
|
||||
3. **Ignoring warmup bias.** Without the exponential compensator, early K and D values are biased toward zero. The compensator fixes this, but comparing against implementations without it will show discrepancies in the first `signal` bars.
|
||||
4. **Short lookback in ranging markets.** A 5-bar length produces rapid oscillation between 0 and 100, generating excessive crossover signals. Increase `length` or add a trend filter.
|
||||
5. **Treating K/D crossovers as standalone signals.** In strong trends, K and D can remain above 80 (or below 20) for extended periods. Crossovers in the direction of the trend are continuations, not reversals.
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why does J go above 100 or below 0?**
|
||||
A: By design. J = 3K - 2D amplifies the gap between K and D. When K leads D strongly (fast momentum), the 3:2 weighting pushes J beyond the [0, 100] range. This is the indicator's primary edge over standard Stochastic.
|
||||
|
||||
**Q: Why use RMA instead of SMA for smoothing?**
|
||||
A: RMA gives exponentially-weighted memory, so old data fades gradually rather than dropping off a cliff when it exits the window. This produces smoother K and D lines with fewer whipsaws at the cost of slightly more lag.
|
||||
|
||||
**Q: How does this compare to the standard Stochastic (%K/%D)?**
|
||||
A: The standard Stochastic uses SMA smoothing and lacks the J line. KDJ with RMA smoothing responds faster to price changes and provides the additional J line for early reversal detection, making it a strict superset of Stochastic functionality.
|
||||
|
||||
## References
|
||||
|
||||
- Chinese securities analysis (KDJ is a standard indicator on Chinese exchanges)
|
||||
- [PineScript reference](kdj.pine)
|
||||
- Lane, G. C. "Lane's Stochastics." *Technical Analysis of Stocks and Commodities*, 1984.
|
||||
|
||||
Reference in New Issue
Block a user