Tilson T3

Parameters: period = 5 | volume_factor = 0

Overview

The Tilson T3 Moving Average employs a sophisticated multi-stage exponential smoothing algorithm designed to minimize lag while maintaining smooth output. Developed by Tim Tilson, this indicator applies six cascaded exponential moving averages with a volume factor coefficient that controls the balance between responsiveness and smoothness. The volume factor typically ranges from 0 to 1, where values near zero produce behavior similar to a double exponential moving average, while higher values increase smoothing at the cost of slightly more lag. The T3 excels in trending markets by hugging price action more closely than traditional moving averages while filtering out minor fluctuations that generate false signals. Traders favor it for its ability to identify trend changes earlier than simple or exponential averages without the excessive whipsaws of more reactive indicators. Default parameters are a 5 period lookback with a volume factor of 0.7, providing an effective balance for most trading timeframes.

Implementation Examples

Get started with Tilson T3 in Rust:

use vectorta::indicators::moving_averages::tilson::{tilson, TilsonInput, TilsonParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = TilsonParams { period: Some(5), volume_factor: Some(0.0) }; // defaults
let input = TilsonInput::from_slice(&prices, params);
let result = tilson(&input)?; // result.values: Vec<f64>

// Using Candles with defaults (source="close", period=5, volume_factor=0.0)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = TilsonInput::with_default_candles(&candles);
let result = tilson(&input)?;

for v in result.values { println!("T3: {}", v); }

API Reference

Input Methods
// From price slice
TilsonInput::from_slice(&[f64], TilsonParams) -> TilsonInput

// From candles with custom source
TilsonInput::from_candles(&Candles, &str, TilsonParams) -> TilsonInput

// From candles with defaults (close, period=5, volume_factor=0.0)
TilsonInput::with_default_candles(&Candles) -> TilsonInput
Parameters Structure
pub struct TilsonParams {
    pub period: Option<usize>,        // Default: 5
    pub volume_factor: Option<f64>,   // Default: 0.0 (must be finite)
}
Output Structure
pub struct TilsonOutput {
    pub values: Vec<f64>, // T3 values (row length = input length)
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise TilsonError::InvalidPeriod.
  • After the first finite input, need at least 6×(period−1)+1 valid points; otherwise TilsonError::NotEnoughValidData.
  • volume_factor must be finite (not NaN or infinite); otherwise TilsonError::InvalidVolumeFactor.
  • Indices before first_valid + 6×(period−1) are NaN; subsequent outputs follow the cascade.
  • Streaming returns None until warmup is satisfied; then yields a value per update.
Error Handling
use vectorta::indicators::moving_averages::tilson::{tilson, TilsonError};

match tilson(&input) {
    Ok(output) => use_values(output.values),
    Err(TilsonError::EmptyInputData) => eprintln!("Input data is empty"),
    Err(TilsonError::AllValuesNaN) => eprintln!("All input values are NaN"),
    Err(TilsonError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for data length {}", period, data_len),
    Err(TilsonError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, only {} available", needed, valid),
    Err(TilsonError::InvalidVolumeFactor { v_factor }) =>
        eprintln!("Volume factor must be finite, got {}", v_factor),
}

Python Bindings

Basic Usage

Calculate T3 using NumPy arrays (defaults: period=5, volume_factor=0.0):

import numpy as np
from vectorta import tilson

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=np.float64)

# Defaults
result = tilson(prices, period=5)

# Custom parameters and kernel
result = tilson(prices, period=7, volume_factor=0.5, kernel="avx2")

print("T3 values:", result)
Streaming Real-time Updates

Stream values via TilsonStream:

from vectorta import TilsonStream

stream = TilsonStream(period=5, volume_factor=0.0)
for price in price_feed:
    t3 = stream.update(price)
    if t3 is not None:
        handle(t3)
Batch Parameter Optimization

Test multiple parameter combinations efficiently:

import numpy as np
from vectorta import tilson_batch

prices = np.array([...], dtype=np.float64)

results = tilson_batch(
    prices,
    period_range=(3, 10, 1),
    volume_factor_range=(0.0, 1.0, 0.2),
    kernel="auto",
)

print(results['values'].shape)   # (num_combinations, len(prices))
print(results['periods'])        # periods per row
print(results['volume_factors']) # volume factors per row
CUDA Acceleration

CUDA APIs are available for batch and many‑series cases:

# Requires CUDA-enabled build of vectorta
from vectorta import tilson_cuda_batch_dev, tilson_cuda_many_series_one_param_dev
import numpy as np

# One series, many parameter combinations (F32 input recommended)
prices_f32 = np.array([...], dtype=np.float32)
out_dev = tilson_cuda_batch_dev(
    prices_f32,
    period_range=(3, 10, 1),
    volume_factor_range=(0.0, 1.0, 0.2),
    device_id=0,
)

# Many series (time-major), one parameter set
tm_f32 = np.array([...], dtype=np.float32)  # shape [T, N]
out_dev = tilson_cuda_many_series_one_param_dev(
    tm_f32,
    period=5,
    volume_factor=0.0,
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Calculate T3 in JavaScript/TypeScript:

import { tilson_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
const t3 = tilson_js(prices, 5, 0.0);
console.log('T3:', t3);
Memory-Efficient Operations

Use zero-copy operations for larger datasets:

import { tilson_alloc, tilson_free, tilson_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* your data */]);
const len = prices.length;

const inPtr = tilson_alloc(len);
const outPtr = tilson_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(prices);

// Args: in_ptr, out_ptr, len, period, volume_factor
tilson_into(inPtr, outPtr, len, 5, 0.0);

const out = new Float64Array(memory.buffer, outPtr, len).slice();
tilson_free(inPtr, len);
tilson_free(outPtr, len);
Batch Processing

Test multiple parameter combinations efficiently:

import { tilson_batch_js, tilson_batch_metadata_js } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

// Define parameter ranges
const pStart = 3, pEnd = 10, pStep = 1;
const vStart = 0.0, vEnd = 1.0, vStep = 0.2;

// Metadata: [periods..., volume_factors...]
const meta = tilson_batch_metadata_js(pStart, pEnd, pStep, vStart, vEnd, vStep);
const numCombos = meta.length / 2;

// Compute all combinations
const flat = tilson_batch_js(prices, pStart, pEnd, pStep, vStart, vEnd, vStep);

// Reshape into rows (one row per (period, v_factor))
const rows: Float64Array[] = [];
for (let i = 0; i < numCombos; i++) {
  const start = i * prices.length;
  rows.push(flat.slice(start, start + prices.length));
}

Performance Analysis

Comparison:
View:
Loading chart...

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.

Related Indicators