We can use code to generate MultiCharts alerts, but also cancel any alert generated during the current script calculation. Later on in the script, we can then ’re-activate’ the alert again. Are these the kind of programming features that we should use in our scripts?

In this article:

Complicating MultiCharts code by cancelling alerts programmatically

We generate alerts in PowerLanguage with the Alert() keyword (PowerLanguage Keyword Reference, 2016), although we’ll need to manually enable the script’s alerts setting for that. Then depending on the alert configuration options, alerts can appear as a notification window, email alert, and/or audio alert (MultiCharts Wiki, 2013).

After triggering an alert with Alert(), we can programmatically cancel that alert before it fires. There are two ways for that. The first approach is the Cancel Alert keyword combination, which deactivates any generated during the current script calculation (PowerLanguage Keyword Reference, 2016). Another way is with the SetAlertState() keyword to set the script’s either on or off.

So when we create an alert with Alert(), then as long as the current script calculation didn’t finish (at which point the alert fires), we can still cancel that alert with either Cancel Alert or SetAlertState(false). If MultiCharts executed all of the script’s code for the current price update, then the alert fires and those two keywords cannot cancel the alert anymore.

The Cancel Alert and SetAlertState() keywords are probably useful in specific situations. But they also make reading, understanding, and troubleshooting code unnecessarily complicated. And while a script that uses these keywords looks understandable now, in three months time you may be wondering which alert conditions are cancelled with Cancel Alert, which conditions disable alerts with SetAlertState(false), and which previously cancelled alerts are reactivated again with SetAlertState(true).

To sum up, the disadvantages of Cancel Alert and SetAlertState() are:

  • They make the code harder to understand and read, especially when the code is long and there are several places in the code where alerts may be cancelled.
  • They make it more difficult to troubleshoot code since when an alert fires and when not becomes less clear. And an overlooked SetAlertState(false) keyword can accidentally cancel any alert our script generates.
  • They cancel any alert our script generates. With that, Cancel Alert and SetAlertState() don’t give us the flexibility to specify which alert gets disabled.
  • And after we cancel an alert and then re-enable it with SetAlertState(true), we lose the alert message of the original alert. And so when a script can fire different alerts, we have no way of telling which alert fired.

But most importantly, we can simply replace Cancel Alert and SetAlertState(false) with an if statement condition that filters when Alert() should fire. For instance, say we ant to cancel alerts when the bar’s high is the 20-bar highest high. We can use Cancel Alert to programmatically cancel the script’s alerts like this:


Alert("Example alert");

// ...

if (High = Highest(High, 20)) then
    Cancel Alert;

But we can also use an if statement to only generate an alert programmatically with Alert() when the bar’s high is not the 20-bar highest high:


if (High <> Highest(High, 20)) then
    Alert("Example alert");

In the remainder of this example, we’ll look at an indicator that uses Cancel Alert and SetAlertState() to cancel and re-enable alerts. After discussing that code, we’re going to rewrite it to see how the indicator can behave the same but without those keywords.

Example: unclear code due to Cancel Alert and SetAlertState()

The indicator below generates alerts based on a 10-bar and 20-bar EMA (Exponential Moving Average) cross. But we’ll cancel some alerts with Cancel Alert based on how the 10-bar moving average relates to a 35-bar EMA. In the morning session we’ll re-enable cancelled alerts, but on Friday we’ll cancel any alert with the SetAlertState() keyword.

This description already shows that the indicator’s logic isn’t that straightforward. And as we’ll see, neither is its code. The script’s goal is to show how Cancel Alert and SetAlertState() make code harder to understand. Later we’ll rewrite the programming code to make those keywords unnecessary, and that easier-to-understand code will also be a better example of how to program the indicator’s features.

The image below displays how alerts generated by the indicator look like. After discussing the indicator’s code, we’ll take a closer look at its behaviour.

Example alerts generated by the MultiCharts indicator

Variables:
    firstMA(0),
    secondMA(0),
    thirdMA(0);

// Compute and plot values
firstMA  = XAverage(Close, 10);
secondMA = XAverage(Close, 20);
thirdMA  = XAverage(Close, 35);

Plot1(firstMA);
Plot2(secondMA);
Plot3(thirdMA);

// Generate an alert when the first MA crosses the second
if (firstMA cross over secondMA) then
    Alert("The first moving average crossed above the second.")
else if (firstMA cross under secondMA) then
    Alert("The first MA dropped below the second MA.");
    
// Cancel the alert when the upward cross happens below the third MA
// or when the downward cross happens above the third MA
if (firstMA cross over secondMA and firstMA < thirdMA) or
    (firstMA cross under secondMA and firstMA > thirdMA) then begin
  
        Cancel Alert;
    
        // But turn the alert back on if there's a morning session
        if (Time >= 800 and Time <= 1200) then
            SetAlertState(true);
      
end;

// But we don't open positions on Fridays, so turn off alerts on that day
if (DayOfWeek(Date) = Friday) then
    SetAlertState(false);

We begin by creating three variables: firstMA, secondMA, and thirdMA. We’ll store the EMA values in these variables later on.

Then we compute the moving averages and plot them on the chart:


firstMA  = XAverage(Close, 10);
secondMA = XAverage(Close, 20);
thirdMA  = XAverage(Close, 35);

Plot1(firstMA);
Plot2(secondMA);
Plot3(thirdMA);

The XAverage() function calculates moving averages and requires two parameters: a series of data to process and the number of bars to calculate the moving average on. Here each computes on the bar’s closing prices (Close) and the length of the moving averages is set to 10, 20, and 35 bars. We store those EMA values in the firstMA, secondMA, and thirdMA variables to use later on.

Then we plot those variables on the chart with Plot1(), Plot2(), and Plot3(). Each of those keywords only needs one parameter: the numerical value to plot (PowerLanguage Keyword Reference, 2016). Since we don’t set more parameters, other plot features (like the colour and width) will display with their default value.

Next we generate an alert when the first two moving averages cross:


if (firstMA cross over secondMA) then
    Alert("The first moving average crossed above the second.")
else if (firstMA cross under secondMA) then
    Alert("The first MA dropped below the second MA.");

The if part of this if/else statement checks whether the 10-bar EMA crossed above the 20-bar EMA. To translate that into PowerLanguage code, we use cross over. That keyword combination returns true when, on the current bar, the value on its left (firstMA) is greater than the value on its right (secondMA), while on the previous bar the left value was less than or equal to the right one (PowerLanguage Keyword Reference, 2016). When such a crossover happens, cross over returns true; otherwise, that keyword returns false.

When the 10-bar average indeed rose above the 20-bar average, we generate an alert with Alert(). Inside the parentheses of that keyword we specify an alert message that confirms the crossover.

Similarly, the if/else statement’s else if portion checks whether the 10-bar EMA dropped below the 20-bar EMA. We evaluate that with cross under, a keyword combination that returns true when on the current bar its left value is less than the value on its right, while on the previous bar that left value was still greater than or equal to the right value (PowerLanguage Keyword Reference, 2016). Without such a crossunder happening, cross under returns false.

If the 10-bar EMA indeed fell below the 20-bar EMA, we use Alert() to generate an alert with a message informing us of the crossunder.

After this code portion that generates alerts, we get to the part where we cancel alerts with Cancel Alert, re-enable them with SetAlertState(true), and possibly cancel alerts again with SetAlertState(false). First, we use Cancel Alert to filter out alerts generated when there’s a crossover below the 35-bar EMA or a crossunder above that 35-bar average:


if (firstMA cross over secondMA and firstMA < thirdMA) or
    (firstMA cross under secondMA and firstMA > thirdMA) then begin
  
        Cancel Alert;
    
        // ..
      
end;

Just as before, this if statement also uses the cross over and cross under keywords to see whether the 10-bar EMA (firstMA) crossed the 20-bar EMA (secondMA). Each keyword is used in one part of the if statement’s condition, which we combine with the or logical keyword.

That keyword returns true when the value on its left is true, the value on its right is true, or when both values are true. Only when the left and right value are false will or return false too (PowerLanguage Keyword Reference, 2016). For our if statement’s condition, that means there are two situations in which the if statement’s code executes.

The first situation is when there’s a crossover (firstMA cross over secondMA) while the firstMA variable’s value is less than (<) the 35-bar EMA (thirdMA). Since we join those two expressions together with the and logical keyword, both need to be true before the combined result is true too (PowerLanguage Keyword Reference, 2016).

The second situation is a crossunder (firstMA cross under secondMA) while the 10-bar EMA (firstMA) is above (>) the 35-bar EMA (thirdMA). By also using and here, these two things also need to happen before this part of the if statement’s condition is true.

Regardless of whether the first or second situation happened, the same code executes: Cancel Alert. With that keyword combination we programmatically cancel alerts. That means any alert triggered with Alert() during the current script calculation will not materialise as a notification window, audio alert, and/or email alert. With that some of the EMA crosses alerts are filtered out here.

Inside the if statement that cancels alerts with the 35-bar EMA filter, there’s an if statement that turns alerts back on – but only when the bar’s time is within the morning session (8:00 till 12:00):


if (Time >= 800 and Time <= 1200) then
    SetAlertState(true);

This if statement evaluates Time, a keyword that returns the bar’s closing time in 24-hour HHmm format (PowerLanguage Keyword Reference, 2016). The first expression of this if statement’s condition checks whether the bar’s time (Time) is greater than or equal to (>=) 800 (that is, 8:00). With the second expression we evaluate if the bar’s time is less than or equal to (<=) 1200 (meaning, 12:00).

Combing those two expressions with and means the if statement’s condition is true for bars whose closing time is between 8:00 and 12:00, while the condition is false for bars outside that range. Now when the current bar is inside that morning session, we use SetAlertState(true) to programmatically enable a previously cancelled alert. With that the alert previously cancelled with Cancel Alert is turned back on again here.

The last if statement of the example provides another opportunity to turn off alerts:


if (DayOfWeek(Date) = Friday) then
    SetAlertState(false);

The DayOfWeek() keyword, with the date of the current bar (Date) between its parentheses, returns the day of week for the current bar (PowerLanguage Keyword Reference, 2016). Here we evaluate whether that keyword returns a value equal to (=) Friday. When it does, we cancel any alert generated during the current script calculation with SetAlertState(false). This way no alert generates on Friday. We can for instance use that to prevent opening a position just before the weekend begins.

Example: generating, cancelling, and activating MultiCharts alerts

Now let’s look at the behaviour of the above example indicator. When we add it to a chart, we use the following manual alert settings:

Configuring the alert settings of the MultiCharts example indicator

On the chart itself, the three EMAs appear like:

Example chart of the MultiCharts indicator

And the alerts that the script makes appear as follows:

Examples of the reactivated alerts generated by the MultiCharts indicator

While our indicator generates and cancels alerts like it should, its code is not clear and can be improved in several ways. Let’s see how we can rewrite the indicator to make its code easier to follow.

Example indicator: filtering MultiCharts alerts with clear code

Below we rewrite the previous example to make it clearer and easier to understand. For that we’ll strip out the Cancel Alert and SetAlertState() keywords, and replace their behaviour with true/false variables. The indicator’s logic remains the same. And so we still generate alerts based on the 10-bar EMA crossing the 20-bar EMA, with the 35-bar EMA, time, and day of week acting as filters.

The new code of the indicator becomes:


Variables:
    firstMA(0),
    secondMA(0),
    thirdMA(0),
    timeFilter(false),
    dayFilter(false),
    upMACross(false),
    downMACross(false);

// Compute and plot values
firstMA  = XAverage(Close, 10);
secondMA = XAverage(Close, 20);
thirdMA  = XAverage(Close, 35);

Plot1(firstMA);
Plot2(secondMA);
Plot3(thirdMA);

// Set the filters
timeFilter = (Time >= 800 and Time <= 1200);
dayFilter  = not (DayOfWeek(Date) = Friday);

// Determine if the alerts are generated
upMACross = (firstMA cross over secondMA) and ((firstMA >= thirdMA) or
    timeFilter) and dayFilter;
    
downMACross = (firstMA cross under secondMA) and ((firstMA <= thirdMA) or
    timeFilter) and dayFilter;
    
// Generate alerts
if (upMACross) then
    Alert("The first moving average crossed above the second.")
else if (downMACross) then
    Alert("The first MA dropped below the second MA.");

Because some things are unchanged (like the plotting and alert messages), let’s discuss what did change. First, we added four true/false variables to the existing three numerical variables:


Variables:
    firstMA(0),
    secondMA(0),
    thirdMA(0),
    timeFilter(false),
    dayFilter(false),
    upMACross(false),
    downMACross(false);

The first new variable is timeFilter. We use that true/false variable to specify later on whether the current bar falls inside the morning session (8:00 till 12:00) or not. With the dayFilter true/false variable we’re going to filter on Friday so that we won’t generate alerts on that day. The other two true/false variables (upMACross and downMACross) are given a value later based on whether the first EMA crosses above the second EMA, with taking into account the third moving average for filtering (like we did in the previous version of the script).

Giving each filter its own true/false variable makes the code easier to read. And troubleshooting goes quicker, since we can now check the value of each individual filter separately. And when we do combine these smaller individual conditions into one large combined condition, the code becomes easier to read with the descriptive variable names.

The next new part of the indicator sets the timeFilter and dayFilter variables:


timeFilter = (Time >= 800 and Time <= 1200);
dayFilter  = not (DayOfWeek(Date) = Friday);

We set the timeFilter variable its value a condition that uses and to combine two true/false expressions. With that logical keyword, both expressions need to be true before the combined condition is also true (PowerLanguage Keyword Reference, 2016). These expressions are the same as in the original script, so once again we require that the bar’s closing time (Time) is greater than or equal to 8:00 and less than or equal to 12:00. When that’s the case, timeFilter is assigned true; otherwise, the variable’s value becomes false.

We update the dayFilter variable with an expression that checks whether the bar’s day of week is Friday (DayOfWeek(Date) = Friday), just like in the original script. But now we also put the not logical keyword before that expression. That keyword returns the logical opposite of the expression it’s placed for. So with not before something that’s true, not true returns false. Likewise, not in front of something that’s false (not false) returns true (MultiCharts Wiki, 2012).

For our dayFilter variable, this use of not means the variable is true when the day is not Friday, and holds false when the day is Friday. This way we still use the day of week as a filter, but opposite to the previous script version: in the previous script we cancelled any alert that fired on Friday; now we require that the day is not Friday before generating the alert.

Next up we determine the alert conditions for the EMA crosses. Instead cancelling a triggered alert later on with Cancel Alert or SetAlertState(false), we now include the different filters in the value of the true/false variable that determines whether a particular alert condition happened during the current bar:


upMACross = (firstMA cross over secondMA) and ((firstMA >= thirdMA) or
    timeFilter) and dayFilter;
    
downMACross = (firstMA cross under secondMA) and ((firstMA <= thirdMA) or
    timeFilter) and dayFilter;

Both the upMACross and downMACross variables are set to a value based on the combination of an EMA cross with different filters. While the descriptive variable names already make much of the statements readable, let’s walk through them.

For the upMACross variable, we first check whether the 10-bar EMA (firstEMA) crossed above (cross over) the 20-bar EMA (secondMA). This is the same as in the previous script version. But now instead of cancelling the alert later on, we now require that two additional true/false expressions are true too before upMACross becomes true.

Those two expressions (between parentheses acting as a sub-condition) are combined with the or logical keyword. That keyword returns true when the value on its left, the value on its right, or both are true too. Only when the left and right value are false, will or return false too (PowerLanguage Keyword Reference, 2016). This means one or both of these two expressions have to be true.

The first expression checks whether the 10-bar EMA is greater than or equal to the 35-bar EMA (firstMA >= thirdMA). This is in contrast with how we calculated the script previously, where we cancelled alerts whenever the EMA crossover happened while the 10-bar EMA was below the 35-bar EMA. But by including the thirdMA variable here already, we don’t need to cancel the alerts later on. The second expression of this sub-condition is whether the timeFilter variable returns true, which it does when the bar’s time is between 8:00 and 12:00.

The last true/false expression that sets the value of the upMACross variable is the dayFilter variable, which we assigned a value of true earlier when the bar’s day of week is not Friday and false when it was that day of week. This also prevents us from having to cancel the alert later on in the script, like we did with the previous script version where we cancelled any alert triggered on Friday.

Combined, this code means that the upMACross variable is only given a value of true when the 10-bar EMA crossed above the 20-bar EMA, with either the 10-bar moving average above or equal to the 35-bar EMA or the bar’s time being between 8:00 till 12:00 o’clock, and with the day not being Friday. Even when just one of these expressions is false, upMACross becomes false too and we won’t generate an alert later on.

We set the value of the downMACross variable in the same manner. Here we require that the 10-bar EMA crossed below the 20-bar EMA (firstMA cross under secondMA), that either the 10-bar moving average is below or equal to the 35-bar EMA (firstMA <= thirdMA) or the bar’s time happening in the morning session (timeFilter), and that the day of week is not Friday (dayFilter). With all those situations true, downMACross is set to true too (and false otherwise).

In the last part of the code we generate alerts programmatically:


if (upMACross) then
    Alert("The first moving average crossed above the second.")
else if (downMACross) then
    Alert("The first MA dropped below the second MA.");

Here we use an if/else statement to generate the script’s alerts. While the alert messages are the same as in the previous version of the script, the code is much clearer. That’s because we now check the value of the upMACross and downMACross true/false variables, instead of also having conditions later on in the script that can cancel the already-generated alert.

This rewritten code of our earlier example shows that, when we create alert conditions with some planning, there’s no need for the Cancel Alert and SetAlertState() keywords. As a reward our code becomes cleaner and easier to read.

Summary

Generating alerts is done with Alert(). After executing that keyword but before the alert fires, we can cancel the alert with the Cancel Alert and SetAlertState(false) keywords. Once cancelled, we can use SetAlertState(true) to fire another, new alert. While these keywords might be helpful in specific cases, they also make code harder to understand since the flow of when an alert fires and when not becomes less clear. Luckily, both Cancel Alert and SetAlertState(false) can be easily replaced by a true/false variable coupled with an if statement. Even when it takes more lines of code, writing clear code is always preferable to writing clever code that becomes hard to understand when we haven’t looked at our script in a while.

Learn more:


References

MultiCharts Wiki (2012, February 19). Not. Retrieved on June 2, 2016, from http://www.multicharts.com/trading-software/index.php/Not

MultiCharts Wiki (2013, May 10). Using Alerts. Retrieved on March 7, 2016, from https://www.multicharts.com/trading-software/index.php/Using_Alerts

PowerLanguage Keyword Reference (2016). Retrieved on May 25, 2016, from http://www.multicharts.com/trading-software/images/c/c6/PowerLanguage_Keyword_Reference.pdf