Fisher Transform

Parameters: period = 9

Overview

The Fisher Transform converts price distributions into a near Gaussian probability curve, amplifying turning points by transforming price extremes through an inverse hyperbolic tangent function that creates sharp, clear reversal signals. The calculation normalizes price within its recent range, then applies the Fisher equation to generate values that swing dramatically at market turns, with crossovers of the Fisher line and its signal line marking precise entry and exit points. Positive Fisher values above the signal indicate upward momentum gaining strength, while negative values below the signal suggest accelerating bearish pressure. Traders prize the Fisher Transform for catching tops and bottoms more reliably than traditional oscillators because the mathematical transformation exaggerates price movements near range boundaries where reversals typically occur. The indicator generates its clearest signals when extreme Fisher readings (beyond +/- 2) reverse sharply, indicating exhaustion points where the market has stretched too far from equilibrium. Additionally, divergences between Fisher peaks and price highs provide early warning of trend weakness before momentum oscillators detect the shift.

Implementation Examples

Compute Fisher from high/low slices or candles:

use vector_ta::indicators::fisher::{fisher, FisherInput, FisherParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using high/low slices
let high = vec![10.0, 11.0, 12.0, 12.5, 13.0];
let low  = vec![ 9.0, 10.2, 11.1, 11.7, 12.0];
let params = FisherParams { period: Some(9) }; // default = 9
let input = FisherInput::from_slices(&high, &low, params);
let out = fisher(&input)?; // FisherOutput { fisher: Vec<f64>, signal: Vec<f64> }

// Using Candles with defaults (period=9; HL2 from candles)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = FisherInput::with_default_candles(&candles);
let out = fisher(&input)?;

// Access values
for (f, s) in out.fisher.iter().zip(out.signal.iter()) {
    println!("fisher={f}, signal={s}");
}

API Reference

Input Methods
// From candles (HL2 from candles)
FisherInput::from_candles(&Candles, FisherParams) -> FisherInput

// From high/low slices
FisherInput::from_slices(&[f64], &[f64], FisherParams) -> FisherInput

// With default params (period=9)
FisherInput::with_default_candles(&Candles) -> FisherInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct FisherParams {
    pub period: Option<usize>, // Default: 9
}
Output Structure
#[derive(Debug, Clone)]
pub struct FisherOutput {
    pub fisher: Vec<f64>, // main Fisher Transform
    pub signal: Vec<f64>, // lagged Fisher (prev value)
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ data_len; otherwise FisherError::InvalidPeriod.
  • High/low must be same length; else FisherError::MismatchedDataLength. Empty inputs yield FisherError::EmptyData.
  • First valid index is the first (high, low) where both are finite. If none, FisherError::AllValuesNaN.
  • Requires at least period valid points after the first valid; else FisherError::NotEnoughValidData.
  • Warmup: indices < first + period − 1 are filled with NaN for both outputs.
  • Clamps the normalized value to ±0.999 to avoid log singularities; uses HL2 window min/max and a 0.001 floor on the range.
  • Slice writer fisher_into_slice validates output lengths; else FisherError::InvalidLength.
Error Handling
use vector_ta::indicators::fisher::{fisher, FisherError};

match fisher(&input) {
    Ok(out) => handle(out.fisher, out.signal),
    Err(FisherError::EmptyData) => println!("Empty data"),
    Err(FisherError::InvalidPeriod { period, data_len }) =>
        println!("Invalid period {} for length {}", period, data_len),
    Err(FisherError::NotEnoughValidData { needed, valid }) =>
        println!("Need {} valid points, have {}", needed, valid),
    Err(FisherError::AllValuesNaN) => println!("All values are NaN"),
    Err(FisherError::InvalidLength { expected, actual }) =>
        println!("Output len {} != expected {}", actual, expected),
    Err(FisherError::MismatchedDataLength { high, low }) =>
        println!("High/low length mismatch: {} vs {}", high, low),
}

Python Bindings

Basic Usage

Calculate Fisher and its lagged signal from NumPy arrays:

import numpy as np
from vector_ta import fisher

high = np.array([10.0, 11.0, 12.0, 12.5, 13.0])
low  = np.array([ 9.0, 10.2, 11.1, 11.7, 12.0])

# period is required; kernel is optional (e.g., "auto", "avx2")
fisher_vals, signal_vals = fisher(high, low, period=9, kernel="auto")
print(fisher_vals[-3:], signal_vals[-3:])
Streaming
from vector_ta import FisherStream

stream = FisherStream(period=9)
for h, l in zip(high_stream, low_stream):
    out = stream.update(h, l)
    if out is not None:
        fisher_val, signal_val = out
        use(fisher_val, signal_val)
Batch Parameter Sweep
import numpy as np
from vector_ta import fisher_batch

high = np.array([...], dtype=float)
low  = np.array([...], dtype=float)

cfg = (5, 20, 5)  # (start, end, step) for period
res = fisher_batch(high, low, cfg, kernel="auto")

# res['fisher'] and res['signal'] are 2D arrays with shape [rows, len]
# res['periods'] lists the period for each row
print(res['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 fisher_cuda_batch_dev, fisher_cuda_many_series_one_param_dev

# One series (float32)
high_f32 = np.asarray(load_high(), dtype=np.float32)
low_f32 = np.asarray(load_low(), dtype=np.float32)

dev = fisher_cuda_batch_dev(
    high_f32=high_f32,
    low_f32=low_f32,
    period_range=(5, 30, 5),
    device_id=0,
)

# Many series (time-major)
high_tm_f32 = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
rows, cols = high_tm_f32.shape
high_tm_f32 = high_tm_f32.ravel()
low_tm_f32 = np.asarray(load_low_time_major_matrix(), dtype=np.float32)
low_tm_f32 = low_tm_f32.ravel()

dev_tm = fisher_cuda_many_series_one_param_dev(
    high_tm_f32=high_tm_f32,
    low_tm_f32=low_tm_f32,
    cols=cols,
    rows=rows,
    period=14,
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Calculate Fisher and its signal from high/low arrays:

import { fisher_js } from 'vectorta-wasm';

const high = new Float64Array([10.0, 11.0, 12.0, 12.5, 13.0]);
const low  = new Float64Array([ 9.0, 10.2, 11.1, 11.7, 12.0]);

const res = fisher_js(high, low, 9); // { values, rows: 2, cols: len }
const fisher = res.values.slice(0, res.cols);
const signal = res.values.slice(res.cols, res.cols * 2);
Memory-Efficient Operations

Use zero-copy into variants for large datasets:

import { fisher_alloc, fisher_free, fisher_into, memory } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low  = new Float64Array([...]);
const len = high.length;

// Allocate output buffer of length 2*len (fisher then signal)
const outPtr = fisher_alloc(2 * len);

// Call into WASM
fisher_into(high, low, outPtr, len, 9);

// Read results
const view = new Float64Array(memory.buffer, outPtr, 2 * len);
const fisher = view.slice(0, len);
const signal = view.slice(len, 2 * len);

// Free
fisher_free(outPtr, 2 * len);
Batch Processing

Sweep period values and decode interleaved rows:

import { fisher_batch } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low  = new Float64Array([...]);
const cfg = { period_range: [5, 20, 5] };

const res = fisher_batch(high, low, cfg);
// res.values is [fisher_row, signal_row] per combo; res.rows = 2 * combos, res.cols = len

const combos = res.rows / 2;
const perCombo = (i: number) => ({
  fisher: res.values.slice(i * 2 * res.cols + 0 * res.cols, i * 2 * res.cols + 1 * res.cols),
  signal: res.values.slice(i * 2 * res.cols + 1 * res.cols, i * 2 * res.cols + 2 * res.cols),
});

CUDA Bindings (Rust)

use vector_ta::cuda::CudaFisher;
use vector_ta::indicators::fisher::FisherBatchRange;

let cuda = CudaFisher::new(0)?;

let high_f32: [f32] = /* ... */;
let low_f32: [f32] = /* ... */;
let sweep = FisherBatchRange::default();

let out = cuda.fisher_batch_dev(&high_f32, &low_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:

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

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28

Related Indicators