Schaff Trend Cycle (STC)
fast_period = 23 | slow_period = 50 | k_period = 10 | d_period = 3 | fast_ma_type = ema | slow_ma_type = ema Overview
The Schaff Trend Cycle combines the trend detection capabilities of MACD with the responsiveness of stochastic oscillators, creating a fast moving indicator that captures both momentum and cyclical turns within a bounded 0 to 100 range. STC first calculates the difference between fast and slow moving averages like MACD, then applies a double stochastic transformation with exponential smoothing to normalize this difference into an oscillator that reacts more quickly to trend changes than traditional MACD. Values above 75 suggest overbought conditions within an uptrend, while readings below 25 indicate oversold conditions in a downtrend, with the fastest signals occurring when STC crosses these extreme levels. Traders appreciate STC for its ability to identify trend changes earlier than MACD while avoiding the whipsaws common in pure stochastic indicators. The indicator excels in trending markets where its dual nature helps confirm trend direction while timing entries at cycle lows and exits at cycle highs.
Implementation Examples
Compute STC from slices or candles:
use vectorta::indicators::stc::{stc, StcInput, StcParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using a price slice
let price = vec![100.0, 101.0, 100.5, 102.0, 103.5];
let params = StcParams { // defaults: fast=23, slow=50, k=10, d=3, types="ema"
fast_period: Some(23),
slow_period: Some(50),
k_period: Some(10),
d_period: Some(3),
fast_ma_type: Some("ema".into()),
slow_ma_type: Some("ema".into()),
};
let input = StcInput::from_slice(&price, params);
let out = stc(&input)?;
// Using Candles (source defaults to "close") with default parameters
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = StcInput::with_default_candles(&candles);
let out = stc(&input)?;
// Access values
for v in out.values { println!("STC: {}", v); } API Reference
Input Methods ▼
// From price slice
StcInput::from_slice(&[f64], StcParams) -> StcInput
// From candles with explicit source (e.g., "close")
StcInput::from_candles(&Candles, &str, StcParams) -> StcInput
// From candles with defaults (source="close", fast=23, slow=50, k=10, d=3)
StcInput::with_default_candles(&Candles) -> StcInput Parameters Structure ▼
#[derive(Debug, Clone)]
pub struct StcParams {
pub fast_period: Option<usize>, // Default: 23
pub slow_period: Option<usize>, // Default: 50
pub k_period: Option<usize>, // Default: 10
pub d_period: Option<usize>, // Default: 3
pub fast_ma_type: Option<String>, // Default: "ema"
pub slow_ma_type: Option<String>, // Default: "ema"
} Output Structure ▼
#[derive(Debug, Clone)]
pub struct StcOutput {
pub values: Vec<f64>, // STC values in [0, 100] with NaN warmup
} Validation, Warmup & NaNs ▼
- Empty input yields
StcError::EmptyData. - If all values are
NaN:StcError::AllValuesNaN. - Requires at least
max(fast, slow, k, d)valid points after the first finite value; elseStcError::NotEnoughValidData { needed, valid }. - Warmup: indices before
first + max(fast, slow, k, d) − 1are set toNaN(viaalloc_with_nan_prefix). - Flat ranges in stochastic windows seed to
50.0when inputs are finite. - Streaming:
StcStream::try_newerrors if any period is zero;updatereturnsNoneuntil enough data accumulates.
Error Handling ▼
use vectorta::indicators::stc::{stc, StcError};
match stc(&input) {
Ok(output) => handle(output.values),
Err(StcError::EmptyData) => eprintln!("Empty data"),
Err(StcError::AllValuesNaN) => eprintln!("All values are NaN"),
Err(StcError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {} valid points, have {}", needed, valid),
Err(StcError::Internal(msg)) => eprintln!("Internal: {}", msg),
} Python Bindings
Basic Usage ▼
Calculate STC using NumPy arrays (defaults: fast=23, slow=50, k=10, d=3, types="ema"):
import numpy as np
from vectorta import stc
data = np.array([100.0, 101.0, 100.5, 102.0, 103.5], dtype=float)
# Specify kernel (optional): "auto", "scalar", "avx2", "avx512"
values = stc(data, fast_period=23, slow_period=50, k_period=10, d_period=3,
fast_ma_type="ema", slow_ma_type="ema", kernel="auto")
print(values) Streaming Real-time Updates ▼
from vectorta import StcStream
stream = StcStream(fast_period=23, slow_period=50, k_period=10, d_period=3)
for x in feed:
value = stream.update(x)
if value is not None:
use(value) Batch Parameter Optimization ▼
Test multiple parameter combinations efficiently:
import numpy as np
from vectorta import stc_batch
data = np.array([...], dtype=float)
results = stc_batch(
data,
fast_period_range=(20, 30, 5),
slow_period_range=(40, 60, 10),
k_period_range=(5, 15, 5),
d_period_range=(3, 5, 1),
kernel="auto",
)
print(results['values'].shape) # (rows, len(data))
print(results['fast_periods'])
print(results['slow_periods'])
print(results['k_periods'])
print(results['d_periods']) CUDA Acceleration ▼
CUDA support for STC is currently under development.
JavaScript/WASM Bindings
Basic Usage ▼
Calculate STC in JavaScript/TypeScript:
import { stc_js } from 'vectorta-wasm';
const data = new Float64Array([100, 101, 100.5, 102, 103.5]);
const values = stc_js(data, 23, 50, 10, 3, 'ema', 'ema');
console.log('STC values:', values); Memory-Efficient Operations ▼
Use zero-copy into/alloc/free for large datasets:
import { stc_alloc, stc_free, stc_into, memory } from 'vectorta-wasm';
const data = new Float64Array([...]);
const len = data.length;
const inPtr = stc_alloc(len);
const outPtr = stc_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(data);
// Args: in_ptr, out_ptr, len, fast, slow, k, d, fast_ma_type, slow_ma_type
stc_into(inPtr, outPtr, len, 23, 50, 10, 3, 'ema', 'ema');
const result = new Float64Array(memory.buffer, outPtr, len).slice();
stc_free(inPtr, len);
stc_free(outPtr, len);
console.log('STC values:', result); Batch Processing ▼
Test multiple parameter combinations and get a result matrix:
import { stc_batch } from 'vectorta-wasm';
const data = new Float64Array([...]);
// Config uses tuples for ranges: (start, end, step)
const output = stc_batch(data, {
fast_period_range: [20, 30, 5],
slow_period_range: [40, 60, 10],
k_period_range: [5, 15, 5],
d_period_range: [3, 5, 1],
});
// output = { values: Float64Array(flat), combos: [{ fast_period, slow_period, k_period, d_period }], rows, cols }
const { values, combos, rows, cols } = output;
// Reshape flat values into [rows x cols]
const matrix: Float64Array[] = [];
for (let r = 0; r < rows; r++) {
const start = r * cols;
matrix.push(values.slice(start, start + cols));
}
console.log('Fast periods tested:', combos.map(c => c.fast_period)); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Average Directional Index
Technical analysis indicator
Average Directional Movement Index Rating
Technical analysis indicator
Alligator
Technical analysis indicator
Aroon
Technical analysis indicator
Aroon Oscillator
Technical analysis indicator
Chande Momentum Oscillator
Technical analysis indicator