Adaptive Schaff Trend Cycle

Parameters: adaptive_length = 55 | stc_length = 12 | smoothing_factor = 0.45 | fast_length = 26 | slow_length = 50

Overview

Adaptive Schaff Trend Cycle takes the usual Schaff Trend Cycle idea of smoothing a MACD-derived oscillator through repeated normalization, but it makes the front end more responsive by adapting to the current structure of the market. In the VectorTA implementation, high, low, and close are validated together, a rolling correlation drives an adaptive MACD-style core, the result is normalized twice through rolling min/max windows, and the final cycle is centered around zero by subtracting fifty from the smoothed stochastic output.

The stc line is the main centered trend-cycle readout, while the histogram measures how far the normalized MACD deviates from its own EMA baseline. In practice, traders can use the stc line for trend-state and cycle-turn analysis, then use the histogram as a faster confirmation of momentum expansion or contraction. Because the indicator requires valid high, low, and close bars and several rolling windows to be populated, the early bars should be treated as warmup rather than actionable output.

Defaults: Adaptive Schaff Trend Cycle uses `adaptive_length = 55`, `stc_length = 12`, `smoothing_factor = 0.45`, `fast_length = 26`, and `slow_length = 50`.

Implementation Examples

Compute Adaptive Schaff Trend Cycle from high/low/close slices or from a candle container.

use vector_ta::indicators::adaptive_schaff_trend_cycle::{
    adaptive_schaff_trend_cycle,
    AdaptiveSchaffTrendCycleInput,
    AdaptiveSchaffTrendCycleParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let high = vec![101.0, 102.5, 103.0, 102.2, 104.1, 105.0];
let low = vec![99.4, 100.1, 101.2, 100.8, 102.5, 103.3];
let close = vec![100.6, 101.8, 102.4, 101.3, 103.7, 104.4];

let params = AdaptiveSchaffTrendCycleParams {
    adaptive_length: Some(55),
    stc_length: Some(12),
    smoothing_factor: Some(0.45),
    fast_length: Some(26),
    slow_length: Some(50),
};

let input = AdaptiveSchaffTrendCycleInput::from_slices(&high, &low, &close, params);
let output = adaptive_schaff_trend_cycle(&input)?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = adaptive_schaff_trend_cycle(
    &AdaptiveSchaffTrendCycleInput::with_default_candles(&candles)
)?;

println!("Latest STC: {:?}", output.stc.last());
println!("Latest histogram: {:?}", output.histogram.last());
println!("Candle output length: {}", candle_output.stc.len());

API Reference

Input Methods
// From candles
AdaptiveSchaffTrendCycleInput::from_candles(&Candles, AdaptiveSchaffTrendCycleParams) -> AdaptiveSchaffTrendCycleInput

// From high/low/close slices
AdaptiveSchaffTrendCycleInput::from_slices(&[f64], &[f64], &[f64], AdaptiveSchaffTrendCycleParams) -> AdaptiveSchaffTrendCycleInput

// From candles with default parameters
AdaptiveSchaffTrendCycleInput::with_default_candles(&Candles) -> AdaptiveSchaffTrendCycleInput
Parameters Structure
pub struct AdaptiveSchaffTrendCycleParams {
    pub adaptive_length: Option<usize>, // default: 55
    pub stc_length: Option<usize>,      // default: 12
    pub smoothing_factor: Option<f64>,  // default: 0.45
    pub fast_length: Option<usize>,     // default: 26
    pub slow_length: Option<usize>,     // default: 50
}
Output Structure
pub struct AdaptiveSchaffTrendCycleOutput {
    pub stc: Vec<f64>,        // centered trend-cycle output
    pub histogram: Vec<f64>,  // normalized MACD deviation from its EMA baseline
}
Validation, Warmup & NaNs
  • High, low, and close must all be non-empty and have matching lengths.
  • Bars are only valid when high, low, and close are all finite and high is at least low.
  • The indicator returns errors for invalid adaptive length, STC length, smoothing factor, fast length, and slow length.
  • It requires enough valid bars after the first valid bar to satisfy the adaptive and stochastic windows; otherwise it returns `NotEnoughValidData`.
  • Single-run outputs use NaN-prefixed buffers, so early values remain non-finite until the rolling correlation and normalization windows are populated.
  • If streaming receives an invalid bar, the internal state resets and no output is returned for that bar.
  • Batch mode rejects non-batch kernels through `AdaptiveSchaffTrendCycleError::InvalidKernelForBatch`.
Error Handling
use vector_ta::indicators::adaptive_schaff_trend_cycle::AdaptiveSchaffTrendCycleError;

match adaptive_schaff_trend_cycle(&input) {
    Ok(output) => {
        println!("latest STC = {:?}", output.stc.last());
    }
    Err(AdaptiveSchaffTrendCycleError::EmptyInputData) =>
        eprintln!("Adaptive Schaff Trend Cycle needs high, low, and close data."),
    Err(AdaptiveSchaffTrendCycleError::DataLengthMismatch { high, low, close }) =>
        eprintln!("Length mismatch: high={high}, low={low}, close={close}"),
    Err(AdaptiveSchaffTrendCycleError::AllValuesNaN) =>
        eprintln!("No valid bars were found."),
    Err(AdaptiveSchaffTrendCycleError::InvalidAdaptiveLength { adaptive_length, data_len }) =>
        eprintln!("adaptive_length={adaptive_length} is invalid for data_len={data_len}"),
    Err(AdaptiveSchaffTrendCycleError::InvalidStcLength { stc_length, data_len }) =>
        eprintln!("stc_length={stc_length} is invalid for data_len={data_len}"),
    Err(AdaptiveSchaffTrendCycleError::InvalidSmoothingFactor { smoothing_factor }) =>
        eprintln!("smoothing_factor={smoothing_factor} is invalid"),
    Err(AdaptiveSchaffTrendCycleError::InvalidFastLength { fast_length }) =>
        eprintln!("fast_length={fast_length} is invalid"),
    Err(AdaptiveSchaffTrendCycleError::InvalidSlowLength { slow_length }) =>
        eprintln!("slow_length={slow_length} is invalid"),
    Err(AdaptiveSchaffTrendCycleError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {needed} valid bars, got {valid}."),
    Err(e) => eprintln!("Adaptive Schaff Trend Cycle error: {e}"),
}

Python Bindings

Basic Usage

The Python helper accepts high, low, and close arrays and returns two NumPy arrays: STC and histogram.

import numpy as np
from vector_ta import adaptive_schaff_trend_cycle

high = np.asarray(load_high(), dtype=np.float64)
low = np.asarray(load_low(), dtype=np.float64)
close = np.asarray(load_close(), dtype=np.float64)

stc, histogram = adaptive_schaff_trend_cycle(
    high,
    low,
    close,
    adaptive_length=55,
    stc_length=12,
    smoothing_factor=0.45,
    fast_length=26,
    slow_length=50,
    kernel="auto",
)

print(stc[-5:])
print(histogram[-5:])
Streaming Real-time Updates

The Python stream mirrors the Rust stream and consumes one high/low/close bar at a time.

from vector_ta import AdaptiveSchaffTrendCycleStream

stream = AdaptiveSchaffTrendCycleStream(
    adaptive_length=55,
    stc_length=12,
    smoothing_factor=0.45,
    fast_length=26,
    slow_length=50,
)

for high, low, close in live_bars:
    value = stream.update(high, low, close)
    if value is not None:
        stc, histogram = value
        print(stc, histogram)
Batch Processing

Batch mode returns flattened STC and histogram matrices plus the parameter axes used for each row.

import numpy as np
from vector_ta import adaptive_schaff_trend_cycle_batch

high = np.asarray(load_high(), dtype=np.float64)
low = np.asarray(load_low(), dtype=np.float64)
close = np.asarray(load_close(), dtype=np.float64)

result = adaptive_schaff_trend_cycle_batch(
    high,
    low,
    close,
    adaptive_length_range=(40, 55, 5),
    stc_length_range=(10, 14, 2),
    smoothing_factor_range=(0.35, 0.55, 0.10),
    fast_length_range=(20, 26, 3),
    slow_length_range=(40, 50, 5),
    kernel="auto",
)

print(result["rows"], result["cols"])
print(result["adaptive_lengths"])
print(result["stc_lengths"])
print(result["smoothing_factors"])

JavaScript/WASM Bindings

Basic Usage

The main WASM helper accepts high, low, and close arrays and returns a serialized object with STC and histogram arrays.

import { adaptive_schaff_trend_cycle } from 'vectorta-wasm';

const high = new Float64Array([/* highs */]);
const low = new Float64Array([/* lows */]);
const close = new Float64Array([/* closes */]);

const result = adaptive_schaff_trend_cycle(high, low, close, 55, 12, 0.45, 26, 50) as {
  stc: number[];
  histogram: number[];
};

console.log(result.stc.slice(-5));
console.log(result.histogram.slice(-5));
Memory-Efficient Operations

Use the shared output buffer helpers when you want the STC and histogram arrays written into one contiguous allocation.

import {
  adaptive_schaff_trend_cycle_alloc,
  adaptive_schaff_trend_cycle_free,
  adaptive_schaff_trend_cycle_into_host,
  memory,
} from 'vectorta-wasm';

const high = new Float64Array([/* highs */]);
const low = new Float64Array([/* lows */]);
const close = new Float64Array([/* closes */]);
const len = close.length;

const outPtr = adaptive_schaff_trend_cycle_alloc(len);
adaptive_schaff_trend_cycle_into_host(
  high,
  low,
  close,
  outPtr,
  55,
  12,
  0.45,
  26,
  50
);

const out = new Float64Array(memory.buffer, outPtr, len * 2).slice();
const stc = out.slice(0, len);
const histogram = out.slice(len);

adaptive_schaff_trend_cycle_free(outPtr, len);
Batch Processing

The unified batch helper accepts all five ranges and returns flattened STC and histogram grids plus combo metadata.

import { adaptive_schaff_trend_cycle_batch } from 'vectorta-wasm';

const batch = adaptive_schaff_trend_cycle_batch(high, low, close, {
  adaptive_length_range: [40, 55, 5],
  stc_length_range: [10, 14, 2],
  smoothing_factor_range: [0.35, 0.55, 0.10],
  fast_length_range: [20, 26, 3],
  slow_length_range: [40, 50, 5],
}) as {
  stc: number[];
  histogram: number[];
  combos: Array<Record<string, number>>;
  rows: number;
  cols: number;
};

console.log(batch.rows, batch.cols);
console.log(batch.combos[0]);
console.log(batch.stc.slice(0, batch.cols));

CUDA Bindings (Rust)

Additional details for the CUDA bindings can be found inside the VectorTA repository.

Performance Analysis

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators