Fisher Transform

Haiyue
10min

I. What is the Fisher Transform Indicator

The Fisher Transform is a mathematical transformation indicator that converts price distributions into an approximately normal (Gaussian) distribution. Through a nonlinear transformation, it maps prices into an unbounded space, greatly stretching the distribution tails and producing sharper turning signals. This makes price reversal points much easier to identify.

Historical Background

The Fisher Transform was invented by John Ehlers and published in his 2002 book Cybernetic Analysis for Stocks and Futures as well as in Technical Analysis of Stocks and Commodities magazine. Ehlers is a pioneer in applying signal processing techniques to financial analysis, bringing Digital Signal Processing (DSP) concepts into technical analysis. The indicator is named after statistician Ronald Fisher, who developed the Fisher z-transformation.

Indicator Classification

  • Type: Oscillator, displayed in a separate panel
  • Category: Momentum / Statistical Transformation
  • Default Parameter: Period n=9n = 9
  • Value Range: Unbounded, but typically stays between -3 and +3
Core Advantage of Fisher Transform

Traditional oscillators (such as RSI, Stochastic) have skewed distributions between 0–100 — values cluster in the middle region most of the time, with extreme values appearing less frequently. Fisher Transform stretches this skewed distribution into a normal distribution, making extreme values easier to identify and turning points sharper.


II. Mathematical Principles and Calculation

Core Formula

Step 1: Normalize price to [-1, 1] range

xt=2×CtLLnHHnLLn1x_t = 2 \times \frac{C_t - LL_n}{HH_n - LL_n} - 1

Where:

  • CtC_t = current closing price (or median price (H+L)/2(H+L)/2)
  • LLnLL_n = lowest low over the past nn periods
  • HHnHH_n = highest high over the past nn periods

Step 2: Clamp and smooth

Clamp xtx_t to [0.999,+0.999][-0.999, +0.999] to prevent logarithm overflow, and apply EMA smoothing:

Valuet=0.5×(xt+Valuet1)Value_t = 0.5 \times (x_t + Value_{t-1})

Take the clamped value: Vt=min(max(Valuet,0.999),+0.999)V_t = \min(\max(Value_t, -0.999), +0.999)

Step 3: Apply Fisher Transform

Fishert=0.5×ln(1+Vt1Vt)Fisher_t = 0.5 \times \ln\left(\frac{1 + V_t}{1 - V_t}\right)

This formula is the inverse hyperbolic tangent function: Fisher=atanh(V)Fisher = \text{atanh}(V).

Step 4: Signal line

Signalt=Fishert1Signal_t = Fisher_{t-1}

The previous period’s Fisher value serves as the signal line.

Mathematical Meaning

The Fisher Transform f(x)=0.5ln1+x1xf(x) = 0.5 \cdot \ln\frac{1+x}{1-x} has these properties:

  • When xx is near 0: f(x)xf(x) \approx x (linear)
  • When xx approaches ±1: f(x)±f(x) \to \pm\infty (extreme amplification)

This means when price is in the middle of the range, the Fisher value changes gradually; but when price approaches extreme positions, the Fisher value changes dramatically, producing sharp peaks and troughs.

Step-by-Step Calculation Logic

  1. Find range extremes: Highest high and lowest low over the past nn periods
  2. Normalize: Map price to [-1, 1]
  3. Smooth: Use simple recursive smoothing (α=0.5\alpha = 0.5)
  4. Clamp: Restrict values to ±0.999
  5. Fisher Transform: Apply 0.5ln1+x1x0.5 \ln\frac{1+x}{1-x}
  6. Signal line: 1-period lag of Fisher
About the Smoothing Coefficient

Ehlers uses α=0.5\alpha = 0.5 smoothing (equivalent to Valuet=0.5xt+0.5Valuet1Value_t = 0.5x_t + 0.5Value_{t-1}), which is simpler than standard EMA. Some implementations skip this step and apply the Fisher Transform directly to the normalized value.


III. Python Implementation

import numpy as np
import pandas as pd


def fisher_transform(high: pd.Series,
                     low: pd.Series,
                     period: int = 9) -> pd.DataFrame:
    """
    Calculate Fisher Transform

    Parameters
    ----------
    high : pd.Series
        High price series
    low : pd.Series
        Low price series
    period : int
        Lookback period, default 9

    Returns
    -------
    pd.DataFrame
        DataFrame with Fisher and Signal columns
    """
    # Use median price
    median_price = (high + low) / 2

    highest = high.rolling(window=period, min_periods=period).max()
    lowest = low.rolling(window=period, min_periods=period).min()

    # Normalize to [-1, 1]
    raw = 2 * (median_price - lowest) / (highest - lowest) - 1

    # Smooth and clamp
    value = pd.Series(np.nan, index=high.index)
    fisher = pd.Series(np.nan, index=high.index)

    first_valid = raw.first_valid_index()
    if first_valid is None:
        return pd.DataFrame({"Fisher": fisher, "Signal": fisher}, index=high.index)

    start_idx = high.index.get_loc(first_valid)
    value.iloc[start_idx] = 0.0
    fisher.iloc[start_idx] = 0.0

    for i in range(start_idx, len(high)):
        if np.isnan(raw.iloc[i]):
            continue

        # Smooth
        prev_val = value.iloc[i - 1] if i > start_idx and not np.isnan(value.iloc[i - 1]) else 0.0
        v = 0.5 * raw.iloc[i] + 0.5 * prev_val

        # Clamp
        v = max(min(v, 0.999), -0.999)
        value.iloc[i] = v

        # Fisher Transform
        fisher.iloc[i] = 0.5 * np.log((1 + v) / (1 - v))

    signal = fisher.shift(1)

    return pd.DataFrame({
        "Fisher": fisher,
        "Signal": signal
    }, index=high.index)


# ========== Usage Example ==========
if __name__ == "__main__":
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=120, freq="D")
    base = 100 + np.cumsum(np.random.randn(120) * 1.0)

    df = pd.DataFrame({
        "date": dates,
        "open":  base + np.random.randn(120) * 0.3,
        "high":  base + np.abs(np.random.randn(120) * 0.8),
        "low":   base - np.abs(np.random.randn(120) * 0.8),
        "close": base,
        "volume": np.random.randint(1000, 10000, size=120),
    })
    df.set_index("date", inplace=True)

    # Calculate Fisher Transform
    ft = fisher_transform(df["high"], df["low"], period=9)
    df = pd.concat([df, ft], axis=1)

    print("=== Fisher Transform Results (Last 15 Rows) ===")
    print(df[["close", "Fisher", "Signal"]].tail(15).to_string())

    # Crossover signals
    df["buy"] = (df["Fisher"] > df["Signal"]) & (df["Fisher"].shift(1) <= df["Signal"].shift(1))
    df["sell"] = (df["Fisher"] < df["Signal"]) & (df["Fisher"].shift(1) >= df["Signal"].shift(1))

    print("\n=== Buy Signals (Fisher Crosses Above Signal) ===")
    print(df[df["buy"]][["close", "Fisher", "Signal"]].head(10).to_string())
    print("\n=== Sell Signals (Fisher Crosses Below Signal) ===")
    print(df[df["sell"]][["close", "Fisher", "Signal"]].head(10).to_string())

IV. Problems the Indicator Solves

1. Producing Sharp Turning Signals

Fisher Transform’s greatest advantage is making price reversal points very sharp and obvious. Traditional oscillators show relatively smooth changes in extreme areas, while Fisher Transform produces very sharp peaks and troughs that are easy to identify.

2. Crossover Signals

SignalConditionMeaning
BuyFisher crosses above SignalMomentum shifts from bearish to bullish
SellFisher crosses below SignalMomentum shifts from bullish to bearish

3. Extreme Value Reversals

  • Fisher > 1.5 ~ 2.0 -> Overbought zone; watch for Signal cross-under as sell signal
  • Fisher < -1.5 ~ -2.0 -> Oversold zone; watch for Signal cross-over as buy signal

4. Approximately Normal Distribution

Since Fisher-transformed values are approximately normally distributed, standard deviation (e.g., ±2σ) can be used to define statistically-based overbought/oversold thresholds — more theoretically grounded than RSI’s empirical thresholds.

Note

Fisher Transform signals are very sensitive with high crossover frequency. In ranging markets, it may produce many short-term signals, requiring trend filtering to control the number of trades.


V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
Sharp signalsReversal points are very clear and hard to miss
Statistical foundationBased on probability distribution transformation with mathematical theory support
Unbounded distributionNot limited to a fixed range; extreme values are fully expressed
Fast responseResponds quickly to price changes

Disadvantages

DisadvantageDescription
Noise sensitiveHigh sensitivity brings more false signals
Few parametersOnly one period parameter, limited tuning space
Abstract conceptFisher Transform’s mathematical meaning is less intuitive than RSI or MACD
Tail riskExtreme values can be very large, affecting chart readability

Use Cases

  • Short-term trading: Fisher’s sensitivity is well-suited for short-term and intraday trading
  • Reversal trading: Capturing reversals within well-defined support/resistance ranges
  • Pairing with other Ehlers indicators: Such as MAMA, Instantaneous Trendline, etc.
ComparisonFisherRSIStochastic
DistributionApprox. normalSkewedSkewed
Value rangeUnbounded0–1000–100
Signal sharpnessVery highMediumMedium
Response speedFastMediumFast
False signal rateHighMediumHigh
Practical Advice
  1. Focus on large crossovers: When the difference between Fisher and Signal is significant (e.g., > 0.5), the crossover signal is more reliable than a tiny crossover.
  2. Combine with trend filtering: Use EMA(50) or ADX to filter trend direction and reduce counter-trend false signals.
  3. Watch peak levels: Record historical Fisher peak levels; when the current peak fails to exceed the previous high, it may indicate weakening momentum.
  4. Moderate smoothing: If signals are too frequent, increase the period parameter (e.g., to 13 or 21).