Adaptive Schaff Trend Cycle
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
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)