Trend Flex Filter (TrendFlex)

Parameters: period = 20

Overview

TrendFlex measures the cyclical deviation of price from its underlying trend using sophisticated digital filtering techniques developed by John Ehlers. The indicator isolates transient price movements by first applying a low pass filter to establish the dominant trend, then calculating how current prices oscillate around this smoothed baseline. This approach effectively separates trending behavior from mean reverting fluctuations, helping traders identify when markets are likely to continue their current direction versus when they might snap back toward equilibrium. The oscillator highlights short to medium term cycles that traditional trend following indicators miss, making it valuable for timing entries and exits within established trends. Positive values indicate price is flexing above the filtered trend, while negative readings suggest downward deviation, with extreme readings often preceding reversals back toward the trend line. Default period is 20, calibrated to capture meaningful cyclical behavior while filtering noise in most market timeframes.

Implementation Examples

Get started with TrendFlex in a few lines:

use vectorta::indicators::moving_averages::trendflex::{trendflex, TrendFlexInput, TrendFlexParams};
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 = TrendFlexParams { period: Some(20) }; // default is 20
let input = TrendFlexInput::from_slice(&prices, params);
let result = trendflex(&input)?; // TrendFlexOutput

// Using with Candles (defaults: period=20, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = TrendFlexInput::with_default_candles(&candles);
let result = trendflex(&input)?;

// Access values
for value in result.values {
    println!("TrendFlex: {}", value);
}

API Reference

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

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

// From candles with default params (close prices, period=20)
TrendFlexInput::with_default_candles(&Candles) -> TrendFlexInput
Parameters Structure
pub struct TrendFlexParams {
    pub period: Option<usize>, // Default: 20
}
Output Structure
pub struct TrendFlexOutput {
    pub values: Vec<f64>, // Normalized momentum values; NaN until warmup
}
Validation, Warmup & NaNs
  • Input must be non-empty; otherwise TrendFlexError::NoDataProvided.
  • period > 0 and period < len; otherwise TrendFlexError::ZeroTrendFlexPeriod or TrendFlexError::TrendFlexPeriodExceedsData.
  • Uses the first finite value index; if all NaN, returns TrendFlexError::AllValuesNaN.
  • Super smoother period ss = round(period/2); if ss exceeds available data, TrendFlexError::SmootherPeriodExceedsData.
  • Warmup: indices before first_valid + period are NaN. After warmup, outputs are finite.
Error Handling
use vectorta::indicators::moving_averages::trendflex::{trendflex, TrendFlexError};

match trendflex(&input) {
    Ok(output) => process(output.values),
    Err(TrendFlexError::NoDataProvided) =>
        eprintln!("No data provided"),
    Err(TrendFlexError::AllValuesNaN) =>
        eprintln!("All values are NaN"),
    Err(TrendFlexError::ZeroTrendFlexPeriod { period }) =>
        eprintln!("Period must be > 0 (got {})", period),
    Err(TrendFlexError::TrendFlexPeriodExceedsData { period, data_len }) =>
        eprintln!("Period {} exceeds available data after first valid ({}).", period, data_len),
    Err(TrendFlexError::SmootherPeriodExceedsData { ss_period, data_len }) =>
        eprintln!("Smoother period {} exceeds data length {}.", ss_period, data_len),
}

Python Bindings

Basic Usage

Calculate TrendFlex using NumPy arrays (default period=20):

import numpy as np
from vectorta import trendflex

# Prepare price data as NumPy array
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])

# Calculate TrendFlex with defaults (period=20)
result = trendflex(prices)

# Or specify a custom period and kernel
result = trendflex(prices, period=20, kernel="avx2")

# Result is a NumPy array matching input length
print(f"TrendFlex values: {result}")
Streaming Real-time Updates

Process real-time price updates efficiently:

from vectorta import TrendFlexStream

# Initialize streaming TrendFlex calculator
stream = TrendFlexStream(period=20)

# Process real-time price updates
for price in price_feed:
    tf_value = stream.update(price)
    if tf_value is not None:
        print(f"Current TrendFlex: {tf_value}")
Batch Parameter Sweep

Test multiple period values in a single pass:

import numpy as np
from vectorta import trendflex_batch

prices = np.array([...])

results = trendflex_batch(
    prices,
    period_range=(5, 50, 5),
    kernel="auto"
)

print(f"Values shape: {results['values'].shape}")  # (rows, len(prices))
print(f"Periods tested: {results['periods']}")
CUDA Acceleration

Developer APIs available when built with CUDA and Python features.

# Requires vectorta built with CUDA support
from vectorta import trendflex_cuda_batch_dev, trendflex_cuda_many_series_one_param_dev
import numpy as np

# One series, many periods (GPU)
prices_f32 = np.asarray(prices, dtype=np.float32)
out_dev, meta = trendflex_cuda_batch_dev(
    data_f32=prices_f32,
    period_range=(5, 50, 1),
    device_id=0
)
print(meta["periods"])  # Tested periods

# Many series (time-major), one period (GPU)
tm_f32 = np.asarray(time_major_matrix, dtype=np.float32)  # shape [T, N]
dev_arr = trendflex_cuda_many_series_one_param_dev(
    data_tm_f32=tm_f32,
    period=20,
    device_id=0
)

JavaScript/WASM Bindings

Basic Usage

Calculate TrendFlex in JavaScript/TypeScript:

import { trendflex_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 TrendFlex with specified period
const result = trendflex_js(prices, 20);

// Result is a Float64Array
console.log('TrendFlex values:', result);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { trendflex_alloc, trendflex_free, trendflex_into, memory } from 'vectorta-wasm';

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

// Allocate WASM memory
const inPtr = trendflex_alloc(length);
const outPtr = trendflex_alloc(length);

// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);

// Compute TrendFlex directly into allocated memory
trendflex_into(inPtr, outPtr, length, 20);

// Read results (slice to copy out)
const tfValues = new Float64Array(memory.buffer, outPtr, length).slice();

// Free allocated memory
trendflex_free(inPtr, length);
trendflex_free(outPtr, length);

console.log('TrendFlex values:', tfValues);
Batch Processing

Test multiple period values efficiently:

import { trendflex_batch_js, trendflex_batch_metadata_js } from 'vectorta-wasm';

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

// Define parameter sweep
const pStart = 5, pEnd = 50, pStep = 5;  // 5, 10, 15, ...

// Get metadata about combinations (periods)
const metadata = trendflex_batch_metadata_js(pStart, pEnd, pStep);
const numCombos = metadata.length;

// Calculate all combinations
const flat = trendflex_batch_js(prices, pStart, pEnd, pStep);

// Reshape to matrix [rows x len]
const rows = numCombos;
const cols = prices.length;
const matrix = [] as Float64Array[];
for (let i = 0; i < rows; i++) {
  const start = i * cols;
  const end = start + cols;
  matrix.push(flat.slice(start, end));
}

console.log('Periods:', metadata);
console.log('Matrix rows:', matrix.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