Indicator · Pine Script v6 · Own pane

Christiano-Fitzgerald Band-Pass Filter (CFITZ)

by mihakralj

This indicator plots the Christiano-Fitzgerald asymmetric band-pass filter in a separate pane.

View on GitHub

GitHub stars99on the repository
Pine versionv6current
Lines of code106
Settings3inputs
LicenceApache-2.0
Last update4 weeks ago27 Sep 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

106 lines of Pine Script v6 from lib/filters/cfitz/cfitz.pine, published under Apache-2.0. Keep the author's credit and licence lines when you reuse it.

cfitz.pine
// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Christiano-Fitzgerald Band-Pass Filter (CFITZ)", "CFITZ", overlay=false, max_bars_back=5000)

//@function Christiano-Fitzgerald asymmetric band-pass filter
//     filter that is optimal under a random-walk assumption. Unlike the
//     symmetric Baxter-King filter, CF uses ALL available data and produces
//     output for every bar — no data loss at endpoints.
//
//     The filter minimizes the mean squared error between the filtered series
//     and the series filtered by an ideal band-pass filter. Weights vary by
//     position in the sample: interior bars use more surrounding data, while
//     endpoint bars receive corrected weights that force the weight sum to zero.
//
//     Ideal band-pass weights (same as Baxter-King):
//       B_0 = (wh - wl) / pi
//       B_j = (sin(j*wh) - sin(j*wl)) / (pi*j)  for j >= 1
//     where wl = 2*pi/pHigh, wh = 2*pi/pLow
//
//     For the current (last) bar t = T in the sample:
//       c_T = 0.5*B_0*y_T + sum(j=1..T-2, B_j*y_{T-j}) + btilde*y_1
//     where btilde = -0.5*B_0 - sum(j=1..T-2, B_j)
//     This guarantees weight sum = 0 (DC rejection / trend removal).
//
//     When maxLookback caps the filter length, the endpoint correction is
//     recomputed over the truncated window so the zero-sum property is preserved.
//
//     Reference: Christiano & Fitzgerald (2003), "The Band Pass Filter,"
//     International Economic Review, 44(2), 435-465.
//
//@param src Input series
//@param pLow Minimum period of the passband (bars). Must be >= 2.
//@param pHigh Maximum period of the passband (bars). Must be > pLow.
//@param maxLookback Maximum number of past bars to use. Caps the filter length
//                    for performance. The endpoint correction adapts to maintain
//                    zero-sum weights regardless of cap.
//@returns Cyclical component (oscillates around zero).
cfitz(series float src, simple int pLow, simple int pHigh, simple int maxLookback) =>
    if pLow < 2
        runtime.error("pLow must be >= 2")
    if pHigh <= pLow
        runtime.error("pHigh must be > pLow")

    // Angular frequencies for the passband edges
    float wl = 2.0 * math.pi / pHigh  // low cutoff (long period = low freq)
    float wh = 2.0 * math.pi / pLow   // high cutoff (short period = high freq)

    // Central ideal weight
    float b0 = (wh - wl) / math.pi

    float result = 0.0

    if bar_index < 1
        // Only one bar: no filtering possible, output zero
        result := 0.0
    else if bar_index < 2
        // Two bars: weights are 0.5*B_0 on y_T and btilde on y_1
        // btilde = -0.5*B_0 (no interior terms), so c = 0.5*B_0*(y_T - y_1)
        result := 0.5 * b0 * (nz(src) - nz(src[1]))
    else
        // General case: current bar is t = T (last observation)
        // Effective sample depth = min(bar_index, maxLookback)
        // Number of interior terms: depth - 1 (j = 1..depth-1)
        // Endpoint index: depth (the oldest bar in our window)

        // c_T = 0.5*B_0*y_T + sum(j=1..depth-1, B_j*y_{T-j}) + btilde*y_{T-depth}
        // btilde = -0.5*B_0 - sum(j=1..depth-1, B_j)  [ALWAYS recomputed]

        float weightedSum = 0.5 * b0 * nz(src)
        float sumBj = 0.0

        // Loop with simple int bound; break when exceeding available bars
        int loopBound = maxLookback - 1
        for j = 1 to loopBound
            if j > bar_index - 1
                break
            float bj = (math.sin(j * wh) - math.sin(j * wl)) / (math.pi * j)
            weightedSum += bj * nz(src[j])
            sumBj += bj

        // Endpoint correction: force total weights to zero
        // btilde = -(0.5*B_0 + sumBj) so that 0.5*B_0 + sumBj + btilde = 0
        float btilde = -0.5 * b0 - sumBj

        // Endpoint = oldest bar in our effective window
        int depth = math.min(bar_index, maxLookback)
        weightedSum += btilde * nz(src[depth])

        result := weightedSum

    result


// ─── Inputs ───
pLow  = input.int(6,  "Min Period (pLow)",  minval=2,  tooltip="Shortest cycle to pass through (bars)")
pHigh = input.int(32, "Max Period (pHigh)", minval=3,  tooltip="Longest cycle to pass through (bars)")
maxLB = input.int(500, "Max Lookback", minval=10, maxval=5000,
     tooltip="Maximum bars of history to use. Larger = better frequency resolution but slower. Zero-sum property is always maintained.")

// ─── Calculation ───
float cycle = cfitz(close, pLow, pHigh, maxLB)

// ─── Visualization ───
hline(0, "Zero", color.gray, hline.style_dotted)
plot(cycle, "CF Cycle", color.new(color.teal, 0), 2)

Overview

Christiano-Fitzgerald Band-Pass Filter (CFITZ) estimates the cyclical component of a series under a random-walk assumption, using ideal band-pass weights between a minimum and maximum period and forcing the weights to sum to zero so the trend is removed. Output is produced on every bar, including endpoints. The minimum period, maximum period, and maximum lookback settings change the passband and how far back the filter reads. The result oscillates around zero.

Details

Published by

mihakralj on GitHub

Type

Indicator, Pine Script v6

Draws

Separate pane below the chart

Category

Trend

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

algorithmic-tradingcsharpdotnetpinescriptpythonquantitative-financesimdta-libtechnical-analysistechnical-indicators

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Min Period (pLow)int6pLow
Max Period (pHigh)int32pHigh
Max Lookbackint500maxLB
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Schaff Trend Cycle

by everget
948 stars

This indicator plots the Schaff Trend Cycle in a separate pane. It subtracts a slow exponential moving average from a fast one, applies a stochastic cycle twice, and smooths each result to form a 0–100 oscillator. Line color follows the slope, or the upper and

Indicator v4 Oscillators & momentumTrend
Updated 10 days ago · GPL-3.0 GitHub repository of Schaff Trend Cycle

This indicator plots a Supertrend-based trend-following setup in a separate pane and skips conditions it treats as a sideways market. A volatility range is built from an ATR value, with RMA, SMA, EMA, or WMA smoothing, or from a fixed point distance. Optional

Indicator v5 TrendVolatility & bands
Updated 1 year ago · MPL-2.0 GitHub repository of Trend Following Setup - Sideways Market Skipper

Beep Boop

by magic8bot
409 stars

This indicator calculates a modified MACD oscillator and signal line alongside a trend EMA filter, plotting the output as a stylized column histogram in a separate pane. Instead of plotting true MACD distance, it quantifies positive and negative histogram mome

Indicator v4 Oscillators & momentumTrend
Updated 3 years ago · MIT GitHub repository of Beep Boop

[RS]Standardized Trend Map

by ricardosantos79
261 stars

Standardized Trend Map calculates series momentum relative to active price range across 40 distinct lookback periods ranging from 2 to 80 bars on an exponential moving average source. Each standardized calculation rounds to an integer values of -1, 0, or 1 to

Indicator v4 TrendOscillators & momentum
Updated 4 years ago · None stated GitHub repository of [RS]Standardized Trend Map

ADX Histogram

by harryguiacorn
219 stars

This indicator calculates and displays the Average Directional Index (ADX) as a column histogram in a separate pane. It evaluates market trend strength by calculating directional movement metrics based on specified smoothing and DI length inputs. When the ADX

Indicator v5 TrendOscillators & momentum
Updated 1 year ago · None stated GitHub repository of ADX Histogram

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers