Divergence Filter for Moving Average Crossing

ArniJoe

New member
VIP
Hey everyone! I'm venturing back into the world of coding after a hiatus of about 15 years. Although not completely new to coding, my recent endeavors have been more about tweaking others' snippets rather than crafting my own from scratch. Diving into ThinkScript and the ThinkOrSwim platform has been an enlightening journey, filled with both rewarding and challenging moments. Yes, there have been plenty of all-nighters!

This is my debut post in this incredible useThinkScript community. What a fantastic resource it has turned out to be!

Lately, I've been toying with the concept of filtering simple moving average crossings and have concocted a piece of code that's left me scratching my head. I'm reaching out to you, pros, hoping to get fresh eyes on my project. Frankly, I'm a bit sheepish about the current state of my code—it feels overly brute-forced and cumbersome. I have this nagging feeling that I'm missing something obvious and could really use your insights to help streamline and enhance it.

My ThinkScript code uses two moving averages to generate trading signals, with extra filters for one-bar reversals and divergence over a specified look-back period. This divergence filter is the real head-scratcher (check out lines 36 through 120). If / when we hit the divergence threshold, I wanted signals to pop up at the crossover. But if that moment never comes before the averages cross back, this signal should NOT show, NOR the cross back signal after that. This is where I'd typically lean on a do-while loop in other languages. My attempt to mimic this in ThinkScript led me down a path of trial and error and a bit of brain overload last night. (I struggled to wrap my head around the fold operator.) I resorted to a brute-force approach, limiting my focus to just two bars ahead with a series of comparisons that felt clunky. To keep a lid on subsequent signals if the initial one is suppressed, I introduced a "carryover variable" (trackSignal), though its elegance is debatable.

Eager for your insights or a nudge in the right direction. I'm sure I'm missing something obvious. Thanks a ton in advance for diving into this with me!

Code:
# Define input parameters for customization
input price = close; # Primary price to be used for MA calculations
input fastLength = 3; # Length for the fast moving average
input fastAverageType = AverageType.EXPONENTIAL; # Defines fast MA as exponential
input slowLength = 5; # Length for the slow moving average
input slowAverageType = AverageType.EXPONENTIAL; # Defines slow MA as exponential
input oneBarReversalFilter = no; # Enables/disables one bar reversal filter
input divergenceFilter = no; # Enables/disables divergence filter for signal refinement
input lookBackPeriod = 126; # Defines the historical period for divergence calculation
input percentageOfMaxDivergence = 5; # Threshold for divergence-based signal filtering

# Calculate moving averages based on user inputs
def fastAvg = MovingAverage(fastAverageType, price, fastLength);
def slowAvg = MovingAverage(slowAverageType, price, slowLength);

# Detect basic MA crossovers
def aboveCross = Crosses(fastAvg, slowAvg, CrossingDirection.ABOVE);
def belowCross = Crosses(fastAvg, slowAvg, CrossingDirection.BELOW);

# Apply one bar reversal filter to crossovers, if enabled
def filtered1AboveCross = if oneBarReversalFilter then !belowCross[1] and aboveCross and !belowCross[-1] else aboveCross;
def filtered1BelowCross = if oneBarReversalFilter then !aboveCross[1] and belowCross and !aboveCross[-1] else belowCross;

# Calculate divergence and apply divergence filter, if enabled
def filterDivergence = AbsValue(fastAvg - slowAvg);
def maxFilterDivergence = Highest(filterDivergence[1], lookBackPeriod);
def filterTest = filterDivergence > maxFilterDivergence * percentageOfMaxDivergence / 100;

# Initialize variables for filtered crossing signals
def filtered2AboveCross;
def filtered2BelowCross;

# Track signals to manage filtering logic
def trackSignal;

# Complex filtering logic incorporating divergence filter and tracking previous signals
# The following blocks implement the logic to filter signals based on divergence criteria
# and the historical signal tracking to manage the application of filters over time.

if divergenceFilter
then {
    if filtered1AboveCross and filterTest and trackSignal[1] == 0
    then {
        filtered2AboveCross = 1;
    } else {
        if filtered1BelowCross[-1] or trackSignal[1] != 0
        then {
            filtered2AboveCross = 0;

        } else {
            if filtered1AboveCross and filterTest[-1]
            then {
                filtered2AboveCross = 1;
            } else {
                if filtered1BelowCross[-2]
                then {
                    filtered2AboveCross = 0;
                } else {
                    if filtered1AboveCross and filterTest[-2]
                    then {
                        filtered2AboveCross = 1;
                    } else {
                        filtered2AboveCross = 0;
                    }
                }
            }
        }
    }
} else {
    filtered2AboveCross = filtered1AboveCross;
}

if divergenceFilter
then {
    if filtered1BelowCross and filterTest and trackSignal[1] == 0
    then {
        filtered2BelowCross = 1;
    } else {
        if filtered1AboveCross[-1] or trackSignal[1] != 0
        then {
            filtered2BelowCross = 0;
        } else {
            if filtered1BelowCross and filterTest[-1]
            then {
                filtered2BelowCross = 1;
            } else {
                if filtered1AboveCross[-2]
                then {
                    filtered2BelowCross = 0;
                } else {
                    if filtered1BelowCross and filterTest[-2]
                    then {
                        filtered2BelowCross = 1;
                    } else {
                        filtered2BelowCross = 0;
                    }
                }
            }
        }
    }
} else {
    filtered2BelowCross = filtered1BelowCross;
}

if filtered2AboveCross or filtered2BelowCross
then {
    trackSignal = 0;
} else {
    if filtered1AboveCross and !filterTest
    then {
        trackSignal = trackSignal[1] + 1;
    } else {
        if filtered1BelowCross and !filterTest
        then {
            trackSignal = trackSignal[1] - 1;
        } else {
            trackSignal = trackSignal[1];
        }
    }
}


# The AddChartBubble() function is commented out, but it can display debug information on the chart
# AddChartBubble(yes, close - 1000, filterDivergence / maxFilterDivergence * 100 + "\n" + percentageOfMaxDivergence + "\n" + filterTest + "\n" + filtered2BelowCross + "\n" + filterTest[-1] + "\n" + !filtered1AboveCross[-1]  + "\n" + tracksignal[1], Color.WHITE);

# Final signal plotting based on the filtering and criteria checks
plot aboveSignal = filtered2AboveCross;
plot belowSignal = filtered2BelowCross;

# Customize the appearance of signals on the chart
aboveSignal.DefineColor("Above", Color.LIGHT_GREEN);
aboveSignal.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
aboveSignal.AssignValueColor(aboveSignal.Color("Above"));
aboveSignal.SetLineWeight(5);

belowSignal.DefineColor("Below", Color.PINK);
belowSignal.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
belowSignal.AssignValueColor(belowSignal.Color("Below"));
belowSignal.SetLineWeight(5);
 
Last edited:
Hey everyone! I'm venturing back into the world of coding after a hiatus of about 15 years. Although not completely new to coding, my recent endeavors have been more about tweaking others' snippets rather than crafting my own from scratch. Diving into ThinkScript and the ThinkOrSwim platform has been an enlightening journey, filled with both rewarding and challenging moments. Yes, there have been plenty of all-nighters!

This is my debut post in this incredible useThinkScript community. What a fantastic resource it has turned out to be!

Lately, I've been toying with the concept of filtering simple moving average crossings and have concocted a piece of code that's left me scratching my head. I'm reaching out to you, pros, hoping to get fresh eyes on my project. Frankly, I'm a bit sheepish about the current state of my code—it feels overly brute-forced and cumbersome. I have this nagging feeling that I'm missing something obvious and could really use your insights to help streamline and enhance it.

My ThinkScript code uses two moving averages to generate trading signals, with extra filters for one-bar reversals and divergence over a specified look-back period. This divergence filter is the real head-scratcher (check out lines 36 through 120). If / when we hit the divergence threshold, I wanted signals to pop up at the crossover. But if that moment never comes before the averages cross back, this signal should NOT show, NOR the cross back signal after that. This is where I'd typically lean on a do-while loop in other languages. My attempt to mimic this in ThinkScript led me down a path of trial and error and a bit of brain overload last night. (I struggled to wrap my head around the fold operator.) I resorted to a brute-force approach, limiting my focus to just two bars ahead with a series of comparisons that felt clunky. To keep a lid on subsequent signals if the initial one is suppressed, I introduced a "carryover variable" (trackSignal), though its elegance is debatable.

Eager for your insights or a nudge in the right direction. I'm sure I'm missing something obvious. Thanks a ton in advance for diving into this with me!

Code:
# Define input parameters for customization
input price = close; # Primary price to be used for MA calculations
input fastLength = 3; # Length for the fast moving average
input fastAverageType = AverageType.EXPONENTIAL; # Defines fast MA as exponential
input slowLength = 5; # Length for the slow moving average
input slowAverageType = AverageType.EXPONENTIAL; # Defines slow MA as exponential
input oneBarReversalFilter = no; # Enables/disables one bar reversal filter
input divergenceFilter = no; # Enables/disables divergence filter for signal refinement
input lookBackPeriod = 126; # Defines the historical period for divergence calculation
input percentageOfMaxDivergence = 5; # Threshold for divergence-based signal filtering

# Calculate moving averages based on user inputs
def fastAvg = MovingAverage(fastAverageType, price, fastLength);
def slowAvg = MovingAverage(slowAverageType, price, slowLength);

# Detect basic MA crossovers
def aboveCross = Crosses(fastAvg, slowAvg, CrossingDirection.ABOVE);
def belowCross = Crosses(fastAvg, slowAvg, CrossingDirection.BELOW);

# Apply one bar reversal filter to crossovers, if enabled
def filtered1AboveCross = if oneBarReversalFilter then !belowCross[1] and aboveCross and !belowCross[-1] else aboveCross;
def filtered1BelowCross = if oneBarReversalFilter then !aboveCross[1] and belowCross and !aboveCross[-1] else belowCross;

# Calculate divergence and apply divergence filter, if enabled
def filterDivergence = AbsValue(fastAvg - slowAvg);
def maxFilterDivergence = Highest(filterDivergence[1], lookBackPeriod);
def filterTest = filterDivergence > maxFilterDivergence * percentageOfMaxDivergence / 100;

# Initialize variables for filtered crossing signals
def filtered2AboveCross;
def filtered2BelowCross;

# Track signals to manage filtering logic
def trackSignal;

# Complex filtering logic incorporating divergence filter and tracking previous signals
# The following blocks implement the logic to filter signals based on divergence criteria
# and the historical signal tracking to manage the application of filters over time.

if divergenceFilter
then {
    if filtered1AboveCross and filterTest and trackSignal[1] == 0
    then {
        filtered2AboveCross = 1;
    } else {
        if filtered1BelowCross[-1] or trackSignal[1] != 0
        then {
            filtered2AboveCross = 0;

        } else {
            if filtered1AboveCross and filterTest[-1]
            then {
                filtered2AboveCross = 1;
            } else {
                if filtered1BelowCross[-2]
                then {
                    filtered2AboveCross = 0;
                } else {
                    if filtered1AboveCross and filterTest[-2]
                    then {
                        filtered2AboveCross = 1;
                    } else {
                        filtered2AboveCross = 0;
                    }
                }
            }
        }
    }
} else {
    filtered2AboveCross = filtered1AboveCross;
}

if divergenceFilter
then {
    if filtered1BelowCross and filterTest and trackSignal[1] == 0
    then {
        filtered2BelowCross = 1;
    } else {
        if filtered1AboveCross[-1] or trackSignal[1] != 0
        then {
            filtered2BelowCross = 0;
        } else {
            if filtered1BelowCross and filterTest[-1]
            then {
                filtered2BelowCross = 1;
            } else {
                if filtered1AboveCross[-2]
                then {
                    filtered2BelowCross = 0;
                } else {
                    if filtered1BelowCross and filterTest[-2]
                    then {
                        filtered2BelowCross = 1;
                    } else {
                        filtered2BelowCross = 0;
                    }
                }
            }
        }
    }
} else {
    filtered2BelowCross = filtered1BelowCross;
}

if filtered2AboveCross or filtered2BelowCross
then {
    trackSignal = 0;
} else {
    if filtered1AboveCross and !filterTest
    then {
        trackSignal = trackSignal[1] + 1;
    } else {
        if filtered1BelowCross and !filterTest
        then {
            trackSignal = trackSignal[1] - 1;
        } else {
            trackSignal = trackSignal[1];
        }
    }
}


# The AddChartBubble() function is commented out, but it can display debug information on the chart
# AddChartBubble(yes, close - 1000, filterDivergence / maxFilterDivergence * 100 + "\n" + percentageOfMaxDivergence + "\n" + filterTest + "\n" + filtered2BelowCross + "\n" + filterTest[-1] + "\n" + !filtered1AboveCross[-1]  + "\n" + tracksignal[1], Color.WHITE);

# Final signal plotting based on the filtering and criteria checks
plot aboveSignal = filtered2AboveCross;
plot belowSignal = filtered2BelowCross;

# Customize the appearance of signals on the chart
aboveSignal.DefineColor("Above", Color.LIGHT_GREEN);
aboveSignal.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
aboveSignal.AssignValueColor(aboveSignal.Color("Above"));
aboveSignal.SetLineWeight(5);

belowSignal.DefineColor("Below", Color.PINK);
belowSignal.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
belowSignal.AssignValueColor(belowSignal.Color("Below"));
belowSignal.SetLineWeight(5);

sorry, it's not clear to me what your rules are and what you want changed.

do you want to look at more than 2 bars into the future to determine something?


'But if that moment never comes before the averages cross back, this signal should NOT show, NOR the cross back signal after that.'

can you clarify this?
what is 'that moment' ?
can you write the rules with positive logic, what is the desired output, without the nots and nors

are you saying, you want to find a

aboveCross then a belowCross
or
belowCross then a aboveCross

--------------

you are using an offset of -2, to look 2 bars into the future, to determine a signal. since this signal depends on future signals existing, drawing arrows on previous bars doesn't help. they are in the past and can't be acted on.
might as well change the offsets, to look at past bars, so the current bar gets the arrow.
in this formula, and the below one
def filtered2AboveCross;


here is the same code, but with some shorter variable names , and some code reorganized, with the hope of making it easier to understand.

Code:
#diverge_filter_ma
def na = double.nan;
def bn = barnumber();

# Define input parameters for customization
input price = close; # Primary price to be used for MA calculations
input fastLength = 3; # Length for the fast moving average
input fastAverageType = AverageType.EXPONENTIAL; # Defines fast MA as exponential
input slowLength = 5; # Length for the slow moving average
input slowAverageType = AverageType.EXPONENTIAL; # Defines slow MA as exponential

input oneBarReversalFilter = no; # Enables/disables one bar reversal filter
#input divergenceFilter = no; # Enables/disables divergence filter for signal refinement
input diffFilter = no; # Enables/disables divergence filter for signal refinement
input lookBackPeriod = 126; # Defines the historical period for divergence calculation
#input percentageOfMaxDivergence = 5; # Threshold for divergence-based signal filtering
input max_diff_per = 5; # Threshold for divergence-based signal filtering

# Calculate moving averages based on user inputs
def fastAvg = MovingAverage(fastAverageType, price, fastLength);
def slowAvg = MovingAverage(slowAverageType, price, slowLength);

input show_avg_lines = yes;
plot za1 = if show_avg_lines then fastavg else na;
plot za2 = if show_avg_lines then slowavg else na;

# Detect basic MA crossovers
#def aboveCross = Crosses(fastAvg, slowAvg, CrossingDirection.ABOVE);
#def belowCross = Crosses(fastAvg, slowAvg, CrossingDirection.BELOW);
def xup = fastAvg crosses above slowAvg;
def xdwn = fastAvg crosses below slowAvg;


# Apply one bar reversal filter to crossovers, if enabled
#def filtered1AboveCross = if oneBarReversalFilter then !belowCross[1] and aboveCross and !belowCross[-1] else aboveCross;
#def filtered1BelowCross = if oneBarReversalFilter then !aboveCross[1] and belowCross and !aboveCross[-1] else belowCross;
def filtered1xup = if oneBarReversalFilter then !xdwn[1] and xup and !xdwn[-1] else xup;
def filtered1xdwn = if oneBarReversalFilter then !xup[1] and xdwn and !xup[-1] else xdwn;


# Calculate divergence and apply divergence filter, if enabled
#def filterDivergence = AbsValue(fastAvg - slowAvg);
#def maxFilterDivergence = Highest(filterDivergence[1], lookBackPeriod);
#def filterTest = filterDivergence > maxFilterDivergence * percentageOfMaxDivergence / 100;
def diff = AbsValue(fastAvg - slowAvg);
def maxdiff = Highest(diff[1], lookBackPeriod);
def isdiffhi = diff > maxdiff * max_diff_per / 100;


# Initialize variables for filtered crossing signals
def filtered2xup;
def filtered2xdwn;

# Track signals to manage filtering logic
def trackSignal;
if filtered2xup or filtered2xdwn then {
   trackSignal = 0;
} else if filtered1xup and !isdiffhi then {
   trackSignal = trackSignal[1] + 1;
} else if filtered1xdwn and !isdiffhi then {
   trackSignal = trackSignal[1] - 1;
} else {
   trackSignal = trackSignal[1];
}


# Complex filtering logic incorporating divergence filter and tracking previous signals
# The following blocks implement the logic to filter signals based on divergence criteria
# and the historical signal tracking to manage the application of filters over time.

if !diffFilter then {
    filtered2xup = filtered1xup;
} else if filtered1xup and isdiffhi and trackSignal[1] == 0 then {
    filtered2xup = 1;
} else if filtered1xdwn[-1] or trackSignal[1] != 0 then {
    filtered2xup = 0;
} else if filtered1xup and isdiffhi[-1] then {
    filtered2xup = 1;
} else if filtered1xdwn[-2] then {
    filtered2xup = 0;
} else if filtered1xup and isdiffhi[-2] then {
    filtered2xup = 1;
} else {
    filtered2xup = 0;
}


if !diffFilter then {
    filtered2xdwn = filtered1xdwn;
} else if filtered1xdwn and isdiffhi and trackSignal[1] == 0 then {
    filtered2xdwn = 1;
} else if filtered1xup[-1] or trackSignal[1] != 0 then {
    filtered2xdwn = 0;
} else if filtered1xdwn and isdiffhi[-1] then {
    filtered2xdwn = 1;
} else if filtered1xup[-2] then {
    filtered2xdwn = 0;
} else if filtered1xdwn and isdiffhi[-2] then {
    filtered2xdwn = 1;
} else {
    filtered2xdwn = 0;
}


# The AddChartBubble() function is commented out, but it can display debug information on the chart
# AddChartBubble(yes, close - 1000, filterDivergence / maxFilterDivergence * 100 + "\n" + percentageOfMaxDivergence + "\n" + filterTest + "\n" + filtered2BelowCross + "\n" + filterTest[-1] + "\n" + !filtered1AboveCross[-1]  + "\n" + tracksignal[1], Color.WHITE);

# Final signal plotting based on the filtering and criteria checks
plot aboveSignal = filtered2xup;
plot belowSignal = filtered2xdwn;

# Customize the appearance of signals on the chart
aboveSignal.DefineColor("Above", Color.LIGHT_GREEN);
aboveSignal.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
aboveSignal.AssignValueColor(aboveSignal.Color("Above"));
aboveSignal.SetLineWeight(5);

belowSignal.DefineColor("Below", Color.PINK);
belowSignal.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
belowSignal.AssignValueColor(belowSignal.Color("Below"));
belowSignal.SetLineWeight(5);
#
 
Last edited:
Hi Halcyonguy,

Thank you for refining my code and making it more understandable. I appreciate your effort to simplify things.

can you write the rules with positive logic, what is the desired output, without the nots and nors

Regarding your request for a more positively framed logic, here's a clearer breakdown of what I'm aiming for:

I want to display a signal on the day of a moving average crossing under these conditions:
  1. The last signal given was of the opposite type (e.g., if we're looking at an "above" crossing now, the last signaled crossing should have been a "below" crossing, and vice versa).
  2. The difference between the moving averages is greater than the user-defined threshold parameters.
  3. We do not encounter an opposite-direction crossing before the moving average difference meets the threshold mentioned in point 2 (for instance, we have an "above" crossing, but the moving average difference doesn't hit the threshold on the crossing day or the days following, and then, before the threshold is met, there's a "below" crossing).
My intention with this code is to trigger the signal right at the crossing if the specified conditions are met, even if this determination happens days later. I understand your point about the impracticality of signals based on future bars since they can't be acted upon in real time. My initial logic aimed to place the signal at the crossing moment once the conditions align, though I see the complexity this introduces. If aligning the signal with my criteria complicates coding unnecessarily, I'm open to adjusting my approach based on your insights.

Thank you again for your help and patience. I look forward to making this script as efficient and practical as possible.
 
Last edited:
sorry, it's not clear to me what your rules are and what you want changed.

do you want to look at more than 2 bars into the future to determine something?


'But if that moment never comes before the averages cross back, this signal should NOT show, NOR the cross back signal after that.'

can you clarify this?
what is 'that moment' ?
can you write the rules with positive logic, what is the desired output, without the nots and nors

are you saying, you want to find a

aboveCross then a belowCross
or
belowCross then a aboveCross

--------------

you are using an offset of -2, to look 2 bars into the future, to determine a signal. since this signal depends on future signals existing, drawing arrows on previous bars doesn't help. they are in the past and can't be acted on.
might as well change the offsets, to look at past bars, so the current bar gets the arrow.
in this formula, and the below one
def filtered2AboveCross;


Hi Halcyonguy,

I'm curious if you've had a chance to noodle on my response from last week? Any thoughts?
 

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
341 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top