Fisher Transform
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 vectorta::indicators::fisher::{fisher, FisherInput, FisherParams};
use vectorta::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 > 0andperiod ≤ data_len; otherwiseFisherError::InvalidPeriod.- High/low must be same length; else
FisherError::MismatchedDataLength. Empty inputs yieldFisherError::EmptyData. - First valid index is the first
(high, low)where both are finite. If none,FisherError::AllValuesNaN. - Requires at least
periodvalid points after the first valid; elseFisherError::NotEnoughValidData. - Warmup: indices
< first + period − 1are filled withNaNfor 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_slicevalidates output lengths; elseFisherError::InvalidLength.
Error Handling ▼
use vectorta::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 vectorta 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 vectorta 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 vectorta 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 support for Fisher is coming soon.
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),
}); Performance Analysis
Across sizes, Rust CPU runs about 1.04× slower than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05