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 vectorta::indicators::tsi::{tsi, TsiInput, TsiParams};
use vectorta::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 vectorta::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 vectorta 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 vectorta 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 vectorta 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 support for TSI is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.
# Coming soon: CUDA-accelerated TSI calculations 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); Export Code
use vectorta::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-01-05
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.