Momentum Ghost Machine
Updated
March 19, 2024
TradingView

For free use on the TradingView platform

Momentum Ghost Machine (ChartPrime) is designed to be the next generation in momentum/rate of change analysis. This indicator utilizes the properties of one of our favorite filters to create a more accurate and stable momentum oscillator by using a high quality filtered delayed signal to do the momentum comparison.

Traditional momentum/roc uses the raw price data to compare current price to previous price to generate a directional oscillator. This leaves the oscillator prone to false readings and noisy outputs that leave traders unsure of the real likelihood of a future movement. One way to mitigate this issue would be to use some sort of moving average. Unfortunately, this can only go so far because simple moving average algorithms result in a poor reconstruction of the actual shape of the underlying signal.

The windowed sinc low pass filter is a linear phase filter, meaning that it doesn't change the shape or size of the original signal when applied. This results in a faithful reconstruction of the original signal, but without the "high frequency noise". Just like any filter, the process of applying it requires that we have "future" samples resulting in a time delay for real time applications. Fortunately this is a great thing in the context of a momentum oscillator because we need some representation of past price data to compare the current price data to. By using an ideal low pass filter to generate this delayed signal we can super charge the momentum oscillator and fix the majority of issues its predecessors had.

This indicator has a few extra features that other momentum/roc indicators dont have. One major yet simple improvement is the inclusion of a moving average to help gauge the rate of change of this indicator. Since we included a moving average, we thought it would only be appropriate to add a histogram to help visualize the relationship between the signal and its average. To go further with this we have also included linear extrapolation to further help you predict the momentum and direction of this oscillator. Included with this extrapolation we have also added the histogram in the extrapolation to further enhance its visual interpretation. Finally, the inclusion of a candle coloring feature really drives how the utility of the Momentum Machine.

There are three distinct options when using the candle coloring feature: Direct, MA, and Both. With direct the candles will be colored based on the indicators direction and polarity. When it is above zero and moving up, it displays a green color. When it is above zero and moving down it will display a light green color. Conversely, when the indicator is below zero and moving down it displays a red color, and when it it moving up and below zero it will display a light red color. MA coloring will color the candles just like a MACD. If the signal is above its MA and moving up it will display a green color, and when it is above its MA and moving down it will display a light green color.

When the signal is below its MA and moving down it will display a red color, and when its below its ma and moving up it will display a light red color. Both combines the two into a single color scheme providing you with the best of both worlds. If the indicator is above zero it will display the MA colors with a slight twist. When the indicator is moving down and is below its MA it will display a lighter color than before, and when it is below zero and is above its MA it will display a darker color color.

Length of 50 with a smoothing of 100

Length of 50 with a smoothing of 25

By default, the indicator is set to a momentum length of 50, with a post smoothing of 2. We have chosen the longer period for the momentum length to highlight the performance of this indicator compared to its ancestors. A major point to consider with this indicator is that you can only achieve so much smoothing for a chosen delay. This is because more data is required to produce a smoother signal at a specified length. Once you have selected your desired momentum length you can then select your desired momentum smoothing. This is made possible by the use of the windowed sinc low pass algorithm because it includes a frequency cutoff argument. This means that you can have as little or as much smoothing as you please without impacting the period of the indicator. In the provided examples above this paragraph is a visual representation of what is going on under the hood of this indicator. The blue line is the filtered signal being compared to the current closing price. As you can see, the filtered signal is very smooth and accurately represents the underlying price action without noise.

We hope that users can find the same utility as we did in this indicator and that it levels up your analysis utilizing the momentum oscillator or rate of change.

Enjoy


// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ChartPrime

//@version=5
indicator("Momentum Ghost Machine [ChartPrime]", overlay = false, explicit_plot_zorder = true)

method is_even(simple int n)=> n % 2 == 0
sinc(float x)=> x == 0 ? 1 : math.sin(math.pi * x) / (math.pi * x)
blackman(float n, int length)=> 0.42 - 0.5 * math.cos((2 * math.pi * n) / (length - 1)) + 0.08 * math.cos((4 * math.pi * n) / (length - 1))
mid_point(int x)=> (x - 1) / 2
offset(simple int length)=> (length - 1) / 2

get_data(float source, simple int length)=>
    var float[] data = array.new()
    if data.size() < length
        data.unshift(source)
        data

    else
        data.unshift(source)
        data.pop()
        data

sinc_coefficients(simple int length, float fc)=>
    float[] coefficients = array.new()
    int mid = mid_point(length)
    float cutoff = 1 / fc
    for i = 0 to length - 1
        int n = i - mid
        int k = i
        int M = length
        if length.is_even()
            float coefficient = sinc(2 * cutoff * n) * blackman(k + 0.5, M)
            coefficients.push(coefficient)
        else
            float coefficient = sinc(2 * cutoff * n) * blackman(k, M)
            coefficients.push(coefficient)
    coefficients

toeplitz_matrix_valid(float[] coefficients, int M)=>
    int N = coefficients.size()
    int rows = M - N + 1
    matrix toeplitz = matrix.new(rows, M, 0)

    for r = 0 to rows - 1
        for c = 0 to N - 1
            if (c + r) < M
                toeplitz.set(r, c + r, coefficients.get(c))
            else
                break

    toeplitz

convolution(float[] data, float[] coefficients)=>
    var float normalize = coefficients.sum()
    var matrix toeplitz_matrix = toeplitz_matrix_valid(coefficients, data.size())
    float convolved = matrix.mult(toeplitz_matrix, data).first()
    convolved / normalize

lti_sinc(series float source, simple int length, simple float fc = 100)=>
    float[] data = get_data(source, length)
    var float[] coefficients = sinc_coefficients(length, fc)

    if bar_index > length
        convolved = convolution(data, coefficients)
    else
        float(na)

ema(series float source, simple float length)=>
    float alpha = 2.0 / (length + 1)
    var float smoothed = na
    smoothed := alpha * source + (1.0 - alpha) * nz(smoothed[1], source)

dema(series float source, simple float length)=>
    float ema1 = ema(source, length)
    float ema2 = ema(ema1, length)
    2.0 * ema1 - ema2

tema(series float source, simple float length)=>
    float ema1 = ema(source, length)
    float ema2 = ema(ema1, length)
    float ema3 = ema(ema2, length)
    (ema1 - ema2) * 3.0 + ema3

wma(series float source, simple int length)=>
    var float weight_sum = length * 0.5 * (length + 1)
    float sum = 0.0
    for int i=0 to length - 1
        sum += source[i] * (length - i)
    sum / weight_sum

sma(series float source, simple int length)=>
    float sum = ta.cum(source)
    if bar_index < length - 1
        sum / (bar_index + 1)
    else
        (sum - sum[length]) / length

filter(series float source, simple int length, simple string style)=>
    if length > 1
        switch style
            "EMA"  =>  ema(source, length)
            "DEMA" => dema(source, length)
            "TEMA" => tema(source, length)
            "WMA"  =>  wma(source, length)
            =>         sma(source, length)
    else
        source

color_changer(color source, bool direction)=>
    float r = color.r(source)
    float g = color.g(source)
    float b = color.b(source)
    if direction
        color.rgb((255 - r) / 3 + r, (255 - g) / 3 + g, (255 - b) / 3 + b)
    else
        color.rgb(r / 1.5, g / 1.5, b / 1.5)

const string settings = "Settings"
float source = input.source(close, "Source", group = settings)
int momentum_length = input.int(50, "Momentum Length", minval = 1, group = settings)
float momentum_smoothing = input.float(50, "Momentum Smoothing", tooltip = "Adjust the level of smoothing for the momentum length. The longer the momentum length the more you can smooth it.", minval = 1, group = settings)
int post_smoothing = input.int(4, "Post Smoothing", minval = 1, tooltip = "Adjust the level of smoothing for the indicator.", group = settings)
string post_smoothing_style = input.string("WMA", "Post Smoothing Style", ["EMA", "DEMA", "TEMA", "WMA", "SMA"], group = settings)
int ma_length = input.int(24, "MA Length", minval = 2, group = settings)
string ma_style = input.string("EMA", "MA Style", ["EMA", "DEMA", "TEMA", "WMA", "SMA"], group = settings)
const string style = "Style"
bool enable_projection = input.bool(true, "Ghost Mode", group = style)
int cd_alpha = input.int(25, "MA Difference Alpha", minval = 0, maxval = 100, group = style)
string candle_color_style = input.string("Both", "Candle Color Style", ["MA", "Direct", "Both", "None"]
 , tooltip = "MA: Use the moving average convergance divergance to color the candles."
 + "\n\n" + "Direct: Use the momentum as a source of color. When the momentum is positive it will be the bullish color and when its negative it will be the bearish color."
 + "\n\n" + "Both: Color the candles bassed on the ma and the polarity of the momentum. The bullish color will be darker if the momentum is negative and the bearish color will be lighter if the momentum is positive."
 + "\n\n" + "None: Dont color the candles.", group = style)

color momentum_color = input.color(#ffffff, "Momentum Color", group = style)
color ma_color = input.color(#f7a000, "MA Color", group = style)
color cd_up_over = input.color(#2ac075, "Bullish Up", group = style)
color cd_up_under = input.color(#d5fce9, "Bullish Down", group = style)
color cd_down_under = input.color(#f82934, "Bearish Down", group = style)
color cd_down_over = input.color(#ffc8cb, "Bearish Up", group = style)
bool glow = input.bool(true, "Glow", group = style)


int length_correction = momentum_length * 2
float sinc = lti_sinc(source, length_correction, momentum_smoothing)
int offset = offset(length_correction)
float delta = filter((source - sinc) / offset, post_smoothing, post_smoothing_style)
float projection = math.avg(delta - delta[1], (delta - delta[2]) / 2)
float ma = filter(filter(delta, 2, ma_style), ma_length, ma_style)
float ma_project = math.avg(ma - ma[1], (ma - ma[2]) / 2)

float momo = delta - ma
var color momo_color = na
if momo > 0
    if momo > momo[1]
        momo_color := cd_up_over
    else
        momo_color := cd_up_under
else
    if momo < momo[1]
        momo_color := cd_down_under
    else
        momo_color := cd_down_over

var color direct_color = na

if candle_color_style == "Both"
    if delta > 0
        if momo < 0
            direct_color := color_changer(momo_color, true)
        else
            direct_color := momo_color
    else
        if momo > 0
            direct_color := color_changer(momo_color, false)
        else
            direct_color := momo_color
else
    if delta > 0
        if delta > delta[1]
            direct_color := cd_up_over
        else
            direct_color := cd_up_under

    else
        if delta < delta[1]
            direct_color := cd_down_under
        else
            direct_color := cd_down_over

hline(0)
plot(momo, "Convergance Divergence", color.new(momo_color, cd_alpha), 5, glow ? plot.style_histogram : plot.style_columns)
plot(glow ? momo : na, "Convergance Divergence Glow", color.new(momo_color, cd_alpha + (100 - cd_alpha) / 2), 1, plot.style_columns, display = display.pane)
plot(ma, "MA", ma_color)
a = plot(glow ? ma : na, "MA Glow", color.new(ma_color, 80), 4, display = display.pane)
plot(delta, "Momentum", momentum_color)
b = plot(glow ? delta : na, "Momentum Glow", color.new(momentum_color, 80), 4, display = display.pane)
fill(a, b, color.new(delta > ma ? momentum_color : ma_color, 90), "Momentum Fill", editable = true)

var float p_ma1 = na
var float p_ma2 = na
var float p_m1 = na
var float p_m2 = na

if enable_projection
    p_ma1 := ma + ma_project
    p_m1 := delta + projection
    p_ma2 := ma + ma_project * 2
    p_m2 := delta + projection * 2
    var line project_delta = line.new(na, na, na, na, color = momentum_color, style = line.style_dotted)
    var line project_ma = line.new(na, na, na, na, color = ma_color, style = line.style_dotted)
    project_ma.set_xy1(bar_index, ma)
    project_ma.set_xy2(bar_index + 2, p_ma2)
    project_delta.set_xy1(bar_index, delta)
    project_delta.set_xy2(bar_index + 2, p_m2)
    linefill.new(project_delta, project_ma, color.new(p_ma2 > p_m2 ? ma_color : momentum_color, 90))

float d1 = p_m1 - p_ma1
float d2 = p_m2 - p_ma2

color dc1 = na
color dc2 = na

if d1 > 0
    if d1 > momo
        dc1 := cd_up_over
    else
        dc1 := cd_up_under
else
    if d1 < momo
        dc1 := cd_down_under
    else
        dc1 := cd_down_over

if d2 > 0
    if d2 > d1
        dc2 := cd_up_over
    else
        dc2 := cd_up_under
else
    if d2 < d1
        dc2 := cd_down_under
    else
        dc2 := cd_down_over

plot(d1, "Projected Delta", color.new(dc1, 60), 1, plot.style_columns, offset = 1, show_last = 1)
plot(d2, "Projected Delta", color.new(dc2, 60), 1, plot.style_columns, offset = 2, show_last = 1)

bar_color = switch candle_color_style
    "MA" => momo_color
    "Both" => direct_color
    "Direct" => direct_color
    "None" => na

barcolor(bar_color)

Get access to our Exclusive
premium indicators