True Strength Index (TSI)
long_period = 25 | short_period = 13 Overview
The True Strength Index (TSI) normalizes momentum by comparing smoothed price changes to smoothed absolute price changes, producing a bounded oscillator that reveals both trend direction and strength. The calculation begins with period-to-period price momentum, which undergoes double exponential smoothing using first a long period and then a short period. Simultaneously, the absolute value of momentum receives identical double smoothing. Dividing the smoothed momentum by smoothed absolute momentum and multiplying by 100 yields TSI values typically ranging from -100 to +100. This normalization technique makes TSI less susceptible to extreme price movements compared to raw momentum oscillators. Traders interpret positive TSI values as bullish momentum and negative values as bearish momentum, with the magnitude indicating strength. Crossovers of the zero centerline signal momentum regime changes, while divergences between TSI and price often foreshadow reversals. The default parameters of 25 and 13 periods provide smooth signals suitable for swing trading, though day traders often shorten both periods for increased responsiveness to intraday momentum shifts.
Formula: TSIt = 100 × EMAshort(EMAlong(m)) / EMAshort(EMAlong(|m|)),
where m = pricet − pricet−1 and α = 2/(n+1) for each EMA.
Defaults (VectorTA): long=25, short=13.
See also
- Relative Strength Index (RSI) — momentum oscillator (single-EMA based).
- Rate of Change (ROC) — raw momentum without smoothing.
Interpretation & Use
- Zero-line: TSI above 0 suggests bullish momentum; below 0 suggests bearish momentum.
- Normalization: Output is approximately bounded to
[-100, 100]in batch/slice APIs. - Smoothing effect: Double-EMA reduces noise while retaining directional changes in momentum.
- Constant prices: When momentum and its absolute value are both 0, the ratio is undefined and yields
NaN.
Implementation Examples
Get started with TSI in just a few lines:
use vector_ta::indicators::tsi::{tsi, TsiInput, TsiParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = TsiParams { long_period: Some(25), short_period: Some(13) };
let input = TsiInput::from_slice(&prices, params);
let result = tsi(&input)?;
// Using with Candles data structure
// Quick and simple with default parameters (long=25, short=13; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = TsiInput::with_default_candles(&candles);
let result = tsi(&input)?;
// Access the TSI values
for value in result.values {
println!("TSI: {}", value);
} API Reference
Input Methods ▼
// From price slice
TsiInput::from_slice(&[f64], TsiParams) -> TsiInput
// From candles with custom source
TsiInput::from_candles(&Candles, &str, TsiParams) -> TsiInput
// From candles with default params (close prices, long=25/short=13)
TsiInput::with_default_candles(&Candles) -> TsiInput Parameters Structure ▼
pub struct TsiParams {
pub long_period: Option<usize>, // Default: 25
pub short_period: Option<usize>, // Default: 13
} Output Structure ▼
pub struct TsiOutput {
pub values: Vec<f64>, // TSI values; batch/slice kernels clamp to [-100, 100]
} Validation, Warmup & NaNs ▼
long_period > 0,short_period > 0, each ≤ data length; otherwiseTsiError::InvalidPeriod.- Needs at least
1 + long + shortvalid points after the first finite value; otherwiseTsiError::NotEnoughValidData. - Indices before
first + long + shortareNaN(warmup). Gaps in input propagateNaNwithout poisoning EMA state. - If the denominator path equals 0.0, output is
NaN; constant prices yieldNaNthroughout. - Whole/slice and batch implementations clamp outputs to
[-100, 100]; the streamingTsiStream::updatereturns the raw ratio×100. - All-NaN inputs:
TsiError::AllValuesNaN.
Error Handling ▼
use vector_ta::indicators::tsi::{tsi, TsiInput, TsiParams, TsiError};
let params = TsiParams { long_period: Some(25), short_period: Some(13) };
let input = TsiInput::from_slice(&prices, params);
match tsi(&input) {
Ok(output) => process(output.values),
Err(TsiError::AllValuesNaN) => eprintln!("input was all NaNs"),
Err(TsiError::InvalidPeriod { long_period, short_period, data_len }) => {
eprintln!("invalid periods: long={}, short={}, len={}", long_period, short_period, data_len);
}
Err(TsiError::NotEnoughValidData { needed, valid }) => {
eprintln!("need {} valid points after first finite; have {}", needed, valid);
}
Err(TsiError::EmaSubError(e)) => eprintln!("ema error: {}", e),
} Python Bindings
Basic Usage ▼
Calculate TSI in Python (NumPy array in, NumPy array out):
import numpy as np
from vector_ta import tsi
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=np.float64)
values = tsi(prices, long_period=25, short_period=13, kernel=None)
print(values) Streaming ▼
from vector_ta import TsiStream
stream = TsiStream(25, 13)
for p in price_stream:
v = stream.update(p)
if v is not None:
handle(v) Batch Parameter Sweep ▼
import numpy as np
from vector_ta import tsi_batch
prices = np.asarray([...], dtype=np.float64)
out = tsi_batch(
prices,
long_period_range=(20, 30, 5),
short_period_range=(10, 16, 3),
kernel=None,
)
# out is a dict with 'values' shaped [rows, cols] and parameter arrays
print(out['values'].shape, out['long_periods'], out['short_periods']) CUDA Acceleration ▼
CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).
import numpy as np
from vector_ta import tsi_cuda_batch_dev, tsi_cuda_many_series_one_param_dev
# One series (float32)
data_f32 = np.asarray(load_data(), dtype=np.float32)
dev = tsi_cuda_batch_dev(
data_f32=data_f32,
long_period_range=(5, 30, 5),
short_period_range=(5, 30, 5),
device_id=0,
)
# Many series (time-major)
data_tm_f32 = np.asarray(load_data_time_major_matrix(), dtype=np.float32)
rows, cols = data_tm_f32.shape
data_tm_f32 = data_tm_f32.ravel()
dev_tm = tsi_cuda_many_series_one_param_dev(
data_tm_f32=data_tm_f32,
cols=cols,
rows=rows,
long_period=14,
short_period=14,
device_id=0,
) JavaScript/WASM Bindings
Basic Usage ▼
Calculate TSI in JavaScript/TypeScript:
import { tsi_js } from 'vectorta-wasm';
// Price data as Float64Array or regular array
const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
// Calculate TSI with specified parameters
const values = tsi_js(prices, 25, 13); // long=25, short=13
console.log('TSI values:', values); Memory-Efficient Operations ▼
Use zero-copy operations for better performance with large datasets:
import { tsi_alloc, tsi_free, tsi_into, memory } from 'vectorta-wasm';
// Prepare your price data
const prices = new Float64Array([/* your data */]);
const length = prices.length;
// Allocate WASM memory for input and output
const inPtr = tsi_alloc(length);
const outPtr = tsi_alloc(length);
// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);
// Calculate TSI directly into allocated memory
// Args: in_ptr, out_ptr, len, long_period, short_period
tsi_into(inPtr, outPtr, length, 25, 13);
// Read results from WASM memory (slice() to copy out)
const tsiValues = new Float64Array(memory.buffer, outPtr, length).slice();
// Free allocated memory when done
tsi_free(inPtr, length);
tsi_free(outPtr, length);
console.log('TSI values:', tsiValues); Batch Processing ▼
Test multiple parameter combinations efficiently:
import { tsi_batch_js } from 'vectorta-wasm';
// Your price data
const prices = new Float64Array([/* historical prices */]);
// Define parameter sweep ranges (start, end, step)
const config = {
long_period_range: [20, 30, 5], // 20, 25, 30
short_period_range: [10, 16, 3], // 10, 13, 16
};
// Calculate all combinations
const result = tsi_batch_js(prices, config);
// Result fields
// result.values: flat array of size rows*len
// result.combos: array of { long_period: Some(n), short_period: Some(n) }
// result.rows, result.cols: matrix shape
console.log(result.rows, result.cols, result.combos); CUDA Bindings (Rust)
use vector_ta::cuda::CudaTsi;
use vector_ta::indicators::tsi::TsiBatchRange;
let cuda = CudaTsi::new(0)?;
let prices_f32: [f32] = /* ... */;
let sweep = TsiBatchRange::default();
let out = cuda.tsi_batch_dev(&prices_f32, &sweep)?;
let _ = out; Export Code
use vector_ta::indicators::tsi;
// Calculate TSI with custom parameters
let result = tsi(&data, 25, 13);
// Or using the builder pattern
let result = indicators::tsi::new()
.long_period(25)
.short_period(13)
.calculate(&data);This code snippet shows how to use the TSI indicator with your current parameter settings.
Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28
CUDA note
In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.