Files
QuanTAlib/lib/cycles/stc/stc.md
T
Miha Kralj 26280ce80b Add Choppiness Index (CHOP) implementation and tests
- Implemented ChopIndicator for Quantower with configurable period and cold value display.
- Created Chop class for calculating the Choppiness Index with detailed documentation.
- Added comprehensive unit tests for Chop functionality, covering various market conditions and edge cases.
- Developed markdown documentation for CHOP, detailing its historical context, mathematical foundation, and usage examples.
- Established a remediation plan for channel indicators documentation, identifying gaps and prioritizing updates.
2026-02-05 19:42:49 -08:00

6.8 KiB
Raw Blame History

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.

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.

Architecture & Physics

The algorithm implements a deep signal processing pipeline with recursive Stochastic normalization.

Step 1: MACD Construction

Fast and slow EMAs generate the 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{MACD}_t = \text{EMA}_f - \text{EMA}_s

Step 2: First Stochastic (%K₁)

Normalize MACD within its recent range:

\%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})}

Step 3: First Smoothing (%D₁)

EMA smooth the first Stochastic:

\%D_1 = \alpha_d \cdot \%K_1 + (1 - \alpha_d) \cdot \%D_{1,t-1}

Step 4: Second Stochastic (%K₂)

Apply Stochastic normalization again to %D₁:

\%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})}

Step 5: Final Output

Apply selected smoothing method to %K₂:

\text{STC}_t = \text{Smooth}(\%K_2)

Smoothing options: None, EMA, Sigmoid, Digital (threshold-based)

Performance Profile

Operation Count (Streaming Mode, per Bar)

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

Complexity Analysis

  • 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

Validation

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

Usage & Pitfalls

  • 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

API

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

Class: Stc

Schaff Trend Cycle oscillator with configurable smoothing.

Properties

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)

Methods

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

C# Example

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
);