Add new moving average implementations: LTMA, MCNMA, NLMA, NMA, NYQMA, RAIN, and TRAMA

- LTMA (Linear Trend Moving Average): Introduces a predictive moving average using dual cascaded EMAs for trend estimation.
- MCNMA (McNicholl EMA): Implements a zero-lag TEMA using a cascaded EMA structure for enhanced responsiveness.
- NLMA (Non-Lag Moving Average): Utilizes a damped cosine kernel to achieve reduced lag in moving averages.
- NMA (Natural Moving Average): Adapts smoothing based on volatility profiles using a square-root kernel.
- NYQMA (Nyquist Moving Average): Applies the Nyquist-Shannon theorem to prevent aliasing in cascaded moving averages.
- RAIN (Rainbow Moving Average): Combines multiple SMA layers with weighted averages for multi-scale smoothing.
- TRAMA (Trend Regularity Adaptive Moving Average): Adapts smoothing based on the frequency of new highs and lows in price data.
This commit is contained in:
Miha Kralj
2026-02-20 21:40:32 -08:00
parent cbeefc9d64
commit 90d5638008
121 changed files with 9595 additions and 6315 deletions
+102 -148
View File
@@ -1,195 +1,149 @@
# STC: Schaff Trend Cycle
> "By applying the Stochastic twice to MACD, we reveal the cycle hidden within the trend itself."
The Schaff Trend Cycle is a cyclometric oscillator that improves upon MACD by passing it through a double-Stochastic process. This recursive normalization detects market cycles with greater speed and accuracy, producing a bounded 0-100 indicator that reaches extremes earlier than MACD while avoiding Stochastic jitter.
The Schaff Trend Cycle is a cyclometric oscillator that applies double-Stochastic normalization to MACD, extracting the cyclical phase hidden within the trend itself. The recursive normalization produces a bounded 0100 output that reaches extremes earlier than raw MACD while suppressing Stochastic jitter. Developed for currency markets, STC's tendency to flatline at 0 or 100 during strong trends signals continuation rather than reversal — a feature that distinguishes it from conventional momentum oscillators. Output converges toward a square wave in steady-state trending conditions.
## Historical Context
Doug Schaff developed the STC in the 1990s while trading currency markets. He observed that the MACD, while excellent at identifying trends, suffered from lag—by the time it signaled, much of the move had already occurred. Conversely, the Stochastic oscillator was fast but noisy, generating numerous false signals.
Schaff's insight was that trends themselves move in cycles. By applying the Stochastic normalization formula recursively to MACD values, he could extract the cyclical phase of the trend. The "Stochastic of a Stochastic" creates a self-normalizing oscillator that converges toward a square wave in steady-state conditions.
The STC found particular popularity in forex trading where its speed advantage over MACD proved valuable in the 24-hour market. The indicator's tendency to "flatline" at extremes (0 or 100) during strong trends—initially seen as a limitation—became recognized as a feature: it signals trend continuation rather than reversal.
Doug Schaff developed STC in the 1990s while trading currency markets. His diagnosis: MACD identified trends correctly but with unacceptable lag — by signal time, much of the move had elapsed. The Stochastic oscillator was fast but noisy, generating false signals in trending markets. Schaff's synthesis recognized that trends themselves move in cycles. Rather than choosing between lagging trend detection and noisy cycle extraction, he piped MACD through the Stochastic twice. The first pass normalizes MACD within its recent range, collapsing the unbounded trend signal into a 0100 band. The second pass normalizes the smoothed first pass, further compressing the cycle information and creating a self-normalizing oscillator. The double normalization acts as a nonlinear filter that amplifies transitions and suppresses noise during sustained moves. STC found particular traction in forex trading where the 24-hour market rewarded speed advantages over MACD. The flatline behavior at extremes — initially dismissed as a limitation — became recognized as a defining feature: sustained 0 or 100 readings indicate trend continuation with high confidence, equivalent to a digital "trend on" signal.
## Architecture & Physics
The algorithm implements a deep signal processing pipeline with recursive Stochastic normalization.
### 1. MACD Construction
**Step 1: MACD Construction**
Fast and slow EMAs generate the trend signal:
Fast and slow EMAs generate the raw trend signal:
$$\alpha_f = \frac{2}{\text{fastLength} + 1}, \quad \alpha_s = \frac{2}{\text{slowLength} + 1}$$
$$\text{EMA}_f = \alpha_f P_t + (1 - \alpha_f)\text{EMA}_{f,t-1}$$
$$\text{EMA}_s = \alpha_s P_t + (1 - \alpha_s)\text{EMA}_{s,t-1}$$
$$\text{EMA}_{f,t} = \alpha_f \cdot P_t + (1 - \alpha_f) \cdot \text{EMA}_{f,t-1}$$
$$\text{MACD}_t = \text{EMA}_f - \text{EMA}_s$$
$$\text{EMA}_{s,t} = \alpha_s \cdot P_t + (1 - \alpha_s) \cdot \text{EMA}_{s,t-1}$$
**Step 2: First Stochastic (%K₁)**
$$\text{MACD}_t = \text{EMA}_{f,t} - \text{EMA}_{s,t}$$
Normalize MACD within its recent range:
### 2. First Stochastic (%K₁)
$$\%K_1 = 100 \times \frac{\text{MACD}_t - \min(\text{MACD}_{t-k:t})}{\max(\text{MACD}_{t-k:t}) - \min(\text{MACD}_{t-k:t})}$$
Normalize MACD within its recent $k$-bar range:
**Step 3: First Smoothing (%D₁)**
$$\%K_1 = 100 \times \frac{\text{MACD}_t - \min(\text{MACD}_{t-k+1:t})}{\max(\text{MACD}_{t-k+1:t}) - \min(\text{MACD}_{t-k+1:t})}$$
EMA smooth the first Stochastic:
When $\max = \min$ (flat MACD), $\%K_1$ holds its previous value. This collapses the unbounded MACD into [0, 100].
$$\%D_1 = \alpha_d \cdot \%K_1 + (1 - \alpha_d) \cdot \%D_{1,t-1}$$
### 3. First Smoothing (%D₁)
**Step 4: Second Stochastic (%K₂)**
EMA smooth the first Stochastic to reduce whipsaw:
Apply Stochastic normalization again to %D₁:
$$\alpha_d = \frac{2}{d\text{Period} + 1}$$
$$\%K_2 = 100 \times \frac{\%D_1 - \min(\%D_{1,t-k:t})}{\max(\%D_{1,t-k:t}) - \min(\%D_{1,t-k:t})}$$
$$\%D_{1,t} = \alpha_d \cdot \%K_{1,t} + (1 - \alpha_d) \cdot \%D_{1,t-1}$$
**Step 5: Final Output**
### 4. Second Stochastic (%K₂)
Apply Stochastic normalization again to %D₁, using the same $k$-bar window:
$$\%K_2 = 100 \times \frac{\%D_{1,t} - \min(\%D_{1,t-k+1:t})}{\max(\%D_{1,t-k+1:t}) - \min(\%D_{1,t-k+1:t})}$$
This second pass further compresses the signal, amplifying transitions between trend phases.
### 5. Final Smoothing
Apply selected smoothing method to %K₂:
$$\text{STC}_t = \text{Smooth}(\%K_2)$$
$$\text{STC}_t = \text{Smooth}(\%K_{2,t})$$
Smoothing options: None, EMA, Sigmoid, Digital (threshold-based)
Smoothing options:
## Performance Profile
- **None:** Raw %K₂ output
- **EMA:** Standard EMA smoothing with $\alpha_d$
- **Sigmoid:** $S(x) = \frac{100}{1 + e^{-0.1(x - 50)}}$ — S-curve compression
- **Digital:** Threshold at 50 → output snaps to 0 or 100 (square wave)
### Operation Count (Streaming Mode, per Bar)
### 6. Complexity
| Operation | Count | Cost (cycles) | Subtotal |
|-----------|------:|------:|------:|
| FMA | 8 | 5 | 40 |
| MUL | 12 | 4 | 48 |
| ADD/SUB | 20 | 1 | 20 |
| DIV | 4 | 15 | 60 |
| MIN/MAX scan | 2×k | 2 | ~40 |
| Clamp | 4 | 3 | 12 |
| **Total** | — | — | **~220** |
- **Time:** $O(k)$ per bar for min/max scanning over both Stochastic windows
- **Space:** $O(k)$ — two ring buffers of size kPeriod (MACD values and %D₁ values)
- **Warmup:** slowLength + kPeriod bars before output stabilizes
### Complexity Analysis
## Mathematical Foundation
- **Time:** $O(k)$ per bar for min/max scanning (optimized with incremental tracking)
- **Space:** $O(k)$ — two ring buffers of size kPeriod
- **Latency:** slowLength + kPeriod bars warmup
### Parameters
## Validation
| Symbol | Parameter | Default | Constraint |
|--------|-----------|---------|------------|
| $k$ | kPeriod | 10 | $k \geq 2$ |
| $d$ | dPeriod | 3 | $d \geq 1$ |
| $f$ | fastLength | 23 | $f \geq 1$ |
| $s$ | slowLength | 50 | $s > f$ |
| — | smoothing | EMA | None / EMA / Sigmoid / Digital |
| Library | Status | Notes |
|---------|--------|-------|
| Manual Calculation | ✅ Match | Step-by-step pipeline verified |
| TradingView | ✅ Match | Cross-validated against TV implementation |
| Quantower | ✅ Match | `Stc.Quantower.Tests.cs` adapter tests |
### Pseudo-code
## Usage & Pitfalls
```
Initialize:
ema_fast = ema_slow = first price
α_f = 2 / (fastLength + 1)
α_s = 2 / (slowLength + 1)
α_d = 2 / (dPeriod + 1)
macd_buf = RingBuffer(kPeriod)
d1_buf = RingBuffer(kPeriod)
%D₁ = 0
bar_count = 0
- **Flatlining Expected:** STC stays at 0 or 100 during strong trends—this is trend continuation, not broken data
- **Cycle Length:** kPeriod ≈ fastLength/2 targets the cycle within the MACD trend
- **Threshold Zones:** Below 25 = oversold, above 75 = overbought
- **Smoothing Modes:** EMA (default), Sigmoid (S-curve), Digital (square wave), None
- **Recursive Dependencies:** Cannot be vectorized with SIMD due to sequential state
- **Square Wave Convergence:** In steady trends, output approaches binary 0/100 behavior
On each bar (price, isNew):
if !isNew: restore previous state
## API
// Step 1: MACD
ema_fast = FMA(ema_fast, 1 - α_f, α_f × price)
ema_slow = FMA(ema_slow, 1 - α_s, α_s × price)
macd = ema_fast - ema_slow
```mermaid
classDiagram
class AbstractBase {
<<abstract>>
+Name string
+WarmupPeriod int
+IsHot bool
+Last TValue
+Update(TValue input, bool isNew) TValue
+Reset() void
}
class Stc {
+IsNew bool
+Stc(int kPeriod, int dPeriod, int fastLength, int slowLength, StcSmoothing smoothing)
+Stc(ITValuePublisher source, int kPeriod, int dPeriod, int fastLength, int slowLength, StcSmoothing smoothing)
+Update(TValue input, bool isNew) TValue
+Update(TSeries source) TSeries
+Prime(ReadOnlySpan~double~ source, TimeSpan? step) void
+Reset() void
+Calculate(TSeries source, int kPeriod, int dPeriod, int fastLength, int slowLength, StcSmoothing smoothing)$ TSeries
+Calculate(ReadOnlySpan~double~ source, Span~double~ output, ...)$ void
}
class StcSmoothing {
<<enumeration>>
None
Ema
Sigmoid
Digital
}
AbstractBase <|-- Stc
Stc ..> StcSmoothing
// Step 2: First Stochastic
macd_buf.Add(macd)
macd_max = Max(macd_buf)
macd_min = Min(macd_buf)
range1 = macd_max - macd_min
%K₁ = range1 > 0 ? 100 × (macd - macd_min) / range1 : prev_%K₁
// Step 3: First Smoothing
%D₁ = FMA(%D₁, 1 - α_d, α_d × %K₁)
// Step 4: Second Stochastic
d1_buf.Add(%D₁)
d1_max = Max(d1_buf)
d1_min = Min(d1_buf)
range2 = d1_max - d1_min
%K₂ = range2 > 0 ? 100 × (%D₁ - d1_min) / range2 : prev_%K₂
// Step 5: Final Smoothing
switch smoothing:
None: STC = %K₂
EMA: STC = FMA(prev_STC, 1 - α_d, α_d × %K₂)
Sigmoid: STC = 100 / (1 + exp(-0.1 × (%K₂ - 50)))
Digital: STC = %K₂ ≥ 50 ? 100 : 0
output = Clamp(STC, 0, 100)
```
### Class: `Stc`
### Signal Characteristics
Schaff Trend Cycle oscillator with configurable smoothing.
| Condition | Output Behavior |
|-----------|----------------|
| Strong uptrend | Flatlines at 100 (square wave high) |
| Strong downtrend | Flatlines at 0 (square wave low) |
| Trend transition | Rapid swing between extremes |
| Ranging market | Oscillates mid-range (2575) |
| Above 75 | Overbought zone |
| Below 25 | Oversold zone |
### Properties
### Cycle Length Heuristic
| Name | Type | Description |
|------|------|-------------|
| `IsHot` | `bool` | True after warmup complete |
| `IsNew` | `bool` | Whether last update was a new bar |
| `Last` | `TValue` | Most recent STC output (0-100) |
Setting $k \approx f/2$ targets the half-cycle of the MACD's dominant frequency, aligning the Stochastic window with the trend's internal oscillation period.
### Methods
### SIMD Applicability
| Name | Returns | Description |
|------|---------|-------------|
| `Update(TValue, bool)` | `TValue` | Updates state with new price value |
| `Calculate(TSeries, ...)` | `TSeries` | Static factory with all parameters |
| `Calculate(span, span, ...)` | `void` | Zero-allocation span-based calculation |
| `Reset()` | `void` | Clears all internal state |
The recursive EMA dependencies and sequential min/max ring buffer updates prevent SIMD vectorization of the streaming path. The `Calculate(Span)` path can parallelize independent MACD computations but must serialize the double-Stochastic pipeline.
## C# Example
## Resources
```csharp
using QuanTAlib;
// Create STC with standard parameters
var stc = new Stc(
kPeriod: 10, // Stochastic lookback
dPeriod: 3, // Smoothing period
fastLength: 23, // Fast EMA for MACD
slowLength: 50, // Slow EMA for MACD
smoothing: StcSmoothing.Ema
);
// Process price data
foreach (var bar in bars)
{
var result = stc.Update(new TValue(bar.Time, bar.Close));
if (stc.IsHot)
{
double value = result.Value;
// Signal interpretation
if (value > 75)
Console.WriteLine("Overbought zone");
else if (value < 25)
Console.WriteLine("Oversold zone");
// Note: Flatlining at 0 or 100 indicates strong trend
if (value == 100)
Console.WriteLine("Strong uptrend continuation");
else if (value == 0)
Console.WriteLine("Strong downtrend continuation");
}
}
// Static calculation with different smoothing
var results = Stc.Calculate(
prices,
kPeriod: 10,
dPeriod: 3,
fastLength: 23,
slowLength: 50,
smoothing: StcSmoothing.Digital // Square wave output
);
```
- Schaff, D. — "Schaff Trend Cycle" (currency trading methodology, 1990s)
- PineScript reference: `stc.pine` in indicator directory
- Ehlers, J.F. — *Cybernetic Analysis for Stocks and Futures* (cycle extraction theory)