Time Series Forecast (TSF)

Parameters: period = 14

Overview

Time Series Forecast applies least squares linear regression over a rolling window to project the expected price one bar into the future, providing a forward looking trend estimate rather than a lagging average. TSF fits a straight line through the most recent period bars using regression analysis, then extends that line one step ahead to forecast where price should move next if the current trend continues. This projection makes TSF inherently more responsive than traditional moving averages, as it anticipates the next value based on recent trajectory rather than simply averaging past data. The indicator updates with each new bar by recalculating the regression and generating a new forecast, creating a smooth line that adjusts quickly to trend changes while filtering out random noise. Traders value TSF for its ability to stay ahead of price action during trends, positioning the forecast line where support or resistance should appear rather than where it has been, making it particularly useful for setting trailing stops or identifying early trend reversals.

Implementation Examples

Compute TSF over a price slice or candles:

use vectorta::indicators::tsf::{tsf, TsfInput, TsfParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with price data slice
let prices = vec![100.0, 101.0, 102.5, 101.0, 103.0, 105.0];
let params = TsfParams { period: Some(14) };
let input = TsfInput::from_slice(&prices, params);
let result = tsf(&input)?;

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

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

API Reference

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

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

// From candles with default params (close prices, period=14)
TsfInput::with_default_candles(&Candles) -> TsfInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct TsfParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
#[derive(Debug, Clone)]
pub struct TsfOutput {
    pub values: Vec<f64>, // one-step-ahead forecast for each index
}
Validation, Warmup & NaNs
  • period ≥ 2; otherwise TsfError::PeriodTooSmall.
  • period ≤ len(data); else TsfError::InvalidPeriod { period, data_len }.
  • After the first finite input at index first, there must be at least period valid points; else TsfError::NotEnoughValidData { needed, valid }.
  • Slice paths: indices [0 .. first + period − 1) are filled with NaN (warmup prefix).
  • Windows containing any NaN yield NaN outputs for those indices.
  • Streaming: returns None until the buffer fills to period points, then returns Some(f64) each update (may be NaN if the current ring includes a NaN).
Error Handling
use vectorta::indicators::tsf::{tsf, TsfError, TsfInput, TsfParams};

match tsf(&input) {
    Ok(output) => process(output.values),
    Err(TsfError::EmptyInputData) => eprintln!("no data provided"),
    Err(TsfError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(TsfError::InvalidPeriod { period, data_len }) => {
        eprintln!("invalid period: {} (data_len={})", period, data_len)
    }
    Err(TsfError::NotEnoughValidData { needed, valid }) => {
        eprintln!("need {} valid points, only {} available after first finite", needed, valid)
    }
    Err(TsfError::MismatchedOutputLen { expected, actual }) => {
        eprintln!("output len {} does not match input len {}", actual, expected)
    }
    Err(TsfError::PeriodTooSmall { period }) => eprintln!("period must be ≥ 2 (got {})", period),
}

Python Bindings

Basic Usage

Calculate TSF using NumPy arrays (required argument: period):

import numpy as np
from vectorta import tsf

prices = np.array([100.0, 101.0, 102.5, 101.0, 103.0, 105.0])

# period is required for TSF Python API
result = tsf(prices, period=14)

# Select SIMD kernel if desired: "auto" | "scalar" | "avx2" | "avx512"
result = tsf(prices, period=14, kernel="auto")

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

Use the streaming class to process tick-by-tick data:

from vectorta import TsfStream

stream = TsfStream(period=14)
for price in price_feed:
    value = stream.update(price)
    if value is not None:
        # value may be NaN if the window contains NaN
        handle(value)
Batch Parameter Sweep

Test many period values efficiently:

import numpy as np
from vectorta import tsf_batch

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

# (start, end, step)
results = tsf_batch(prices, period_range=(5, 30, 5), kernel="auto")

# results['values'] shape: (num_periods, len(prices))
print(results['values'].shape)

# List of tested periods
print(results['periods'])
CUDA Acceleration

CUDA support for TSF is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated TSF calculations
# The planned APIs will mirror other indicators' CUDA functions.

JavaScript/WASM Bindings

Basic Usage

Calculate TSF in JavaScript/TypeScript:

import { tsf_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 101.0, 102.5, 101.0, 103.0, 105.0]);

// Calculate TSF with specified period
const result = tsf_js(prices, 14);
console.log('TSF values:', result);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { tsf_alloc, tsf_free, tsf_into, memory } from 'vectorta-wasm';

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

const inPtr = tsf_alloc(length);
const outPtr = tsf_alloc(length);

new Float64Array(memory.buffer, inPtr, length).set(prices);

// Args: in_ptr, out_ptr, len, period
tsf_into(inPtr, outPtr, length, 14);

const tsfValues = new Float64Array(memory.buffer, outPtr, length).slice();

tsf_free(inPtr, length);
tsf_free(outPtr, length);

console.log('TSF values:', tsfValues);
Batch Processing

Test multiple period values:

import { tsf_batch } from 'vectorta-wasm';

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

// Provide configuration as an object; tuple is deserialized from the array
const res = tsf_batch(prices, { period_range: [5, 30, 5] });

// res = { values: Float64Array, combos: [{ period: 5 }, ...], rows, cols }
const periods = res.combos.map(c => c.period);

// Extract first combo's row
const valuesFirst = res.values.slice(0, res.cols);
console.log('Periods tested:', periods);
console.log('First row length:', valuesFirst.length);

Performance Analysis

Comparison:
View:

Across sizes, Rust CPU runs about 1.04× faster than Tulip C in this benchmark.

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