Adaptrade Software Newsletter Article

True/False Indicators as Entry and Exit Conditions for Trading

Most systematic trading methods include logical conditions that determine when to enter and exit trades. These conditions typically consist of indicators, price patterns, and other functions, such as "the moving average of the last 10 bars is greater than the moving average of the last 30 bars". If the condition is true, you might place a stop order to enter long on the next bar, for example. But what if you already have a strategy that works well and you want to build on it? Is there a way to use that existing strategy as an entry or, possibly, exit condition in a new strategy in combination with other strategy elements?

Adaptrade Builder includes a custom indicator feature that can process user-provided bar-by-bar indicator values and include them in the build process as an indicator function. If the indicator function returns a true/false value, it can be used as a condition for trade entry or exit. In this article, I'll demonstrate different ways to define and use these true/false indicator functions as entry and exit conditions for trading, including two different ways to use the results of an existing trading strategy as a custom true/false indicator.

Custom Indicators in Adaptrade Builder

The custom indicator feature in Adaptrade Builder allows you to include indicators in the build process that are not part of the built-in set of indicators. To include a custom indicator for a symbol, you would add an extra column to the file of price data for the indicator values that result from applying that indicator to the symbol. Some trading platforms, such as TradeStation and MultiCharts, make this easy by allowing you to save both the price data and the indicator values at the same time for indicators that are plotted on a price chart. The indicator values are saved in separate columns in the same file as the price data. If your platform doesn't allow this, you can use a spreadsheet to add an extra column to the file of price data for each custom indicator.

When you select the resulting file of price data in Builder, you can identify the extra column as containing the custom indicator values. The program can then include that data in the build process. Builder also allows you to assign a code function name to the indicator. This name is substituted into the code that Builder generates whenever the custom indicator values are used. The code function names are intended to represent actual functions in the trading platform that return the same indicator values that were provided in the price file in the custom indicator column. However, there's nothing to prevent these function names from representing something more complex, such as a discretionary trading idea or even the results of a completely separate trading strategy.

True/False Custom Indicators

Custom indicators in Builder can return a variety of types, including price values, price differences, oscillator values, and, to the point of this article, true/false (also known as Boolean) values. In Builder, a true/false indicator represents "true" by returning the integer value 1 and "false" by returning any other integer value; typically, the value 0 is used for "false". An example of what a true/false custom indicator looks like in a price data file is shown below in Fig. 1.

Figure 1. Three true/false custom indicators defined on the Price File Format window in Adaptrade Builder.

In the figure, three custom Boolean indicators have been included with the price data. The true/false values are represented by 1 and 0, respectively, in the columns labeled "Indicator 1", "Indicator 2", and "Indicator 3". The return type has been set to "True/false" for each indicator. If, for example, the first indicator is selected by the build algorithm to be included in a trading strategy, the strategy code will include the code statement "EntryFilter1" at the point in the code where the indicator values are used.

Before considering what this type of indicator might represent or how it might be defined, let's examine several examples that show different ways in which such indicators can be used in the strategy logic generated by Builder. The following examples were copied from EasyLanguage code generated by Builder:

{ Entry and exit conditions }
VarL1 = EntryFilter1();
VarL2 = EntryFilter1();
VarL3 = EntryFilter2();
EntCondL = (VarL1 = 1);
ExCondL = (VarL2 = 1) or (VarL3 = 1);

In the example above, the entry condition (variable EntCondL) illustrates how the return value of 1, which represents "true", is used. If the function EntryFilter1() is equal to 1, EntCondL will be true, which will indicate that a long entry condition is present. This example also shows how the program can use the custom Boolean functions in unexpected ways. Even though the custom indicator functions were intended as entry filters, the program has used them in an exit condition, given by variable ExCondL. This condition effectively says that a long trade will be exited if either EntryFilter1() or EntryFilter2() is true. This might seem contradictory at first since a long trade is entered if EntryFilter1() is true, only to be exited by ExCondL. However, the exit condition is not applied until the trade is already long, which starts one bar after entry. On that bar, EntryFilter1() may no longer be true, so this logic does not imply an immediate exit. It's basically saying "if the entry conditions are still true after you're already in the trade, get out".

{ Entry and exit conditions }
VarL1 = EntryFilter1()[Shift1];
EntCondL = (VarL1 = 1);

In this example (above), the entry condition looks at the value of the Boolean indicator three bars back (i.e., Shift1 is equal to 3 in this case). Since all inputs in the program are subject to the mutation operator, the build algorithm can consider different values of Shift1 to try different look-back values. Perhaps in combination with a different exit type, it might be better to enter based on the value of the entry filter function a few bars prior to the current bar.

{ Entry and exit conditions }
VarL1 = EntryFilter1();
VarL2 = EntryFilter1();
VarL3 = EntryFilter3()[Shift1];
EntCondL = (VarL1 = 1);
ExCondL = (VarL2 = 1) = (VarL3 = 1);

The code shown above is a variation of the first example, probably obtained by mutating the "or" function to "=" and mutating VarL3. The long exit condition (ExCondL) illustrates how the equality operator can be used with Boolean variables. This condition will be true if both sides of the equation are true or if both sides are false. This implies that a long trade will be exited on the next open if either EntryFilter1() and EntryFilter3() (shifted by Shift1) are true on this bar or both functions are false on this bar. Whether or not this somewhat non-intuitive result is better than some other condition will depend on the evolutionary build process, but it illustrates the type of logical variety the program considers.

{ Entry and exit conditions }
VarL1 = EntryFilter2();
VarL2 = EntryFilter1();
EntCondL = (VarL1 = 1) and (VarL2 = 1);
ExCondL = true;

The example above represents a more conventional condition for entry (EntCondL). This entry condition allows a long entry order on the next bar if both EntryFilter1() and EntryFilter2() are true on this bar. This essentially makes the conditions for entry more selective than if either Boolean function was used by itself.

{ Entry and exit conditions }
VarL1 = EntryFilter2();
VarL2 = AdaptiveICyc(C, N1, N2, X1);
VarL3 = InvFisherCycle(O, N3)[Shift1];
CondL1 = VarL2 ≤ VarL3;
EntCondL = (VarL1 = 1) or CondL1;

The preceding examples involved the Boolean indicators by themselves. However, it's probably more interesting to combine them with other logic. This example (above) involves the EntryFilter2 Boolean custom indicator combined with the inverse Fisher cycle and adaptive inverse Fisher cycle indicators. A long entry condition is true if either the Boolean indicator is true or the inverse Fisher cycle condition is true. Nearly any combination of indicators is possible.

Converting a Trading Strategy to a Boolean Indicator

Now that I've presented some examples to show how custom indicators returning true/false values might be used in the strategy logic generated by Adaptrade Builder, let's see how you can create such an indicator from an existing trading strategy. As mentioned above, you might want to do this if you believe an existing trading strategy might provide a good entry filter for other strategies.

There are at least two approaches you might consider. First though, since a Boolean function only returns true and false, it's necessary to separate the functions into long-only and short-only versions. If your strategies will include both long and short trades, separate Boolean functions could be included for each case. As an aside, the build algorithm will decide which functions to use for each side of the market, so you might find that your long-entry custom Boolean indicators are used for short trades and vice-versa. As with the examples above, the logic might be non-intuitive at first but will usually make sense if examined more closely.

The first approach to consider is to set the Boolean function for a long trade to true if the strategy generates a buy signal on the next bar and to false otherwise. For a short trade, you would do likewise: set the function to true if the strategy generates a sell-short signal on the next bar and to false otherwise. As shown above, "true" and "false" are represented by 1 and 0, respectively. The code to do this has been added to a long-only strategy in EasyLanguage, shown below in Fig. 2.

{ Strategy inputs }
Inputs: N1 (13), { Indicator look-back length (bars) }
N2 (37), { Indicator look-back length (bars) }
N3 (100), { Max look-back length for adaptive indicator (bars) }
X1 (-2.0385), { Number of standard deviations }
X2 (-2.1406), { TrendParam value for adaptive indicator (-5 to 5) }
NBarEn1 (81), { Indicator look-back length (bars) }
EntFr (0.8429), { Multiple of price difference (e.g., ATR); entry }
TargPct (1.409), { Value of percentage exit target }
NBarEx1 (5), { Number of bars from entry for market exit if profitable }
NBarEx2 (5), { Number of bars from entry for market exit }
PSParam (1.00), { Position sizing parameter value }
RoundPS (true), { Round-to-nearest (true/false) }
RoundTo (1), { Round-to position size value }
MinSize (1), { Minimum allowable position size }
SizeLimit (100), { Maximum allowable position size }
FName ("c:\bcm\EntryFilter3.csv"); { file name for results }

{ Variables for entry and exit prices }
Var: EntPrL (0),
TargPrL (0);

{ Variables for entry and exit conditions }
Var: VarL1 (0),
VarL2 (0),
EntCondL (false);

{ Variables for position sizing }
Var: NShares (0);

if BarNumber = 1 then
  FileDelete(FName);


{ Entry price }
EntPrL = Average(O, NBarEn1) + EntFr * TrueRange;

{ Entry and exit conditions }
VarL1 = KeltnerChannel(L, N1, X1);
VarL2 = AdaptiveVMA(H, N2, N3, X2);
EntCondL = VarL1 <= VarL2;

{ Position sizing calculations }
NShares = PSParam;

If RoundPS and RoundTo > 0 then
  NShares = IntPortion(NShares/RoundTo) * RoundTo;

NShares = MaxList(NShares, MinSize);
NShares = MinList(NShares, SizeLimit);

Var: GoLong (0), { 1/0 for go long next bar or not }
     BarDate (""), { formatted bar date }
     BarTime (""), { formatted bar time }
     StrOut (""); { text string for file output }

BarDate = FormatDate("M/d/yyyy", DateTime);
BarTime = FormatTime("H:mm:ss", DateTime);


{ Entry orders }
GoLong = 0;

If MarketPosition = 0 and EntCondL then begin
   Buy("EnStop-L") NShares shares next bar at EntPrL stop;
   GoLong = 1;
end

StrOut = BarDate + "," + BarTime + "," + NumtoStr(GoLong, 1) + Newline;
FileAppend(FName, StrOut);

Figure 2. Partial EasyLanguage strategy code listing for writing out Boolean indicator values based on the strategy's long entry orders. The code necessary to write out the indicator values is shown in blue in a larger font. The exit conditions are not shown.

The first piece of added code, shown in a larger, blue font, is an additional input (FName) for the name of the file to write the indicator values to. If the file given by FName is present, it's deleted on the first bar, which makes sure the first added line is added to an empty file. Additional variables are added to represent the indicator value (GoLong) and the bar date and time. The entry order statements have been modified to set GoLong to 1 (true) if a long entry order will be placed on the next bar. If not, GoLong will be equal to zero. Finally, a text string is constructed that consists of the date, time, and the value of GoLong. This string is written out to the specified file in comma-delimited format.

After running the strategy, there will be one line in the file for each bar of data with three columns per line: date, time, and the indicator value (GoLong). Since the file will be in comma-delimited format, it can be opened in a spreadsheet. At that point, the column for GoLong can be copied to the file of price data to add the custom indicator column. Alternatively, the code above could be modified to include extra columns for the open, high, low, close, and volume. If that is done, the data saved to the file given by FName will be the complete file of price data with the custom indicator included, and no further modifications will be necessary.

To create the analogous indicator for short trade signals, a GoShort variable could be added. It would look exactly the same as above except that the statements for GoShort (GoShort = 0; and GoShort = 1;) would be placed in the code block for the short entry orders.

One potential drawback of defining the custom indicator as true when a signal is generated and false otherwise is that most bars will have a 0 since relatively few bars typically generate an entry signal. This may restrict the bars for which the indicator can be used, making it less useful.

An alternative approach that provides less restrictive filtering is to set the indicator values to the market position. For a long indicator, this means you would write out the value 1 if the strategy position was long and 0 otherwise (i.e., write 0 if the position was flat or short). Likewise, for a short indicator, you would write out the value 1 if the strategy position was short and 0 otherwise (i.e., write 0 if the position was flat or long).

The code above only requires a few changes for this approach. In particular, the GoLong variable would now be defined as follows:

GoLong = IFF(MarketPosition(0) > 0, 1, 0);

Likewise, the analogous GoShort variable would be written as:

GoShort = IFF(MarketPosition(0) < 0, 1, 0);

Indicators based on this approach will signal "true" on any bar for which the trade has a long (short) trade (for a long (short) indicator). This approach may not be ideal either, however. For one thing, it means that a signal will be generated on the last bar before the trade is exited. For example, if a trading strategy consisted of trades that entered on one bar and exited on the next bar, an indicator based on this approach would return "true" only on the bar prior to the trade exit. In this case, the entry indicator would actually be a valid exit indicator. Although the build algorithm would most likely recognize that fact and use it as an exit signal, that would be contrary to the intended purpose.

Trading a Strategy that Includes an Indicator Derived from Another Strategy

If you've generated a good trading strategy based on the ideas presented above, there is still the problem of how to trade it. A custom Boolean indicator based on the signals from another strategy is not something that can be easily automated. If the indicator signals are obtained from running a separate strategy, that strategy will have to be executed along with the strategy that utilizes its signals.

However, the real problem is that the Boolean functions referenced in the strategy are just placeholders as presented above. For example, the function EntryFilter1(), shown in the code examples above, just represents the column of indicator values in the price file.

If you wanted to automate such a strategy, one approach would be to implement EntryFilter1() so that it reads the text file generated by the strategy that generates the indicator values. In other words, the first strategy would write out the indicator values to a text file, as the code in Fig. 2 does. The main strategy would call EntryFilter1(), which would be written to read the values from the file generated by the first strategy. However, the code to accomplish that is beyond the scope of this article.

If it's not necessary to automate the strategy execution -- for example, for trading an end-of-day strategy -- then it should be possible to manually generate the trading signals in the Builder software. To do this, it would only be necessary to update the column(s) of Boolean custom indicator values stored in the price file each day. For example, you could run the strategy that generates the custom indicator values, which could include code such as shown in Fig. 2. The resulting signal (1 or 0) could then be added to the indicator column in the price file as part of the new day's data. Then the strategy orders for the next day could be obtained in Builder by reloading the chart data and opening the Trading Orders window.

Using a Prediction Indicator as a True/False Custom Indicator

While either approach described above may transform a trading strategy into a useful Boolean indicator, the idea of using a trading strategy as an entry or exit condition may have limitations beyond those mentioned above. For example, if the strategy used for the indicator has a low percentage of winning trades (say, less than 50%), using its trade signals as an entry condition may not be helpful. While the original strategy may perform well with, say, 40% winning trades due to a high win/loss ratio, other strategies may not benefit by using those trades as entry points.

So what might work better? Imagine if you had an indicator that could predict whether the market would be higher or lower five bars ahead with a high accuracy. You could use such an indicator to generate a "go long" signal if it predicted the market would be higher five bars from now.

To verify that Builder could figure out how to properly use such an indicator, I created an ideal version of such a prediction indicator. By "ideal", I mean it utilized perfect foresight by looking ahead five bars to see whether the market would be higher or lower. Obviously, this is not a realistic or useable indicator; its purpose is merely to confirm that a useful predictive indicator would be utilized correctly by the program as part of the build process. If the ideal indicator is utilized correctly, we would expect the resulting equity curve to be almost perfect with nearly 100% profitable trades.

The ideal prediction indicator writes out 1 for a bar if the closing price 5 bars from this bar is higher than this bar's close and writes out 0 otherwise. The code to do this is similar to the code shown above and is not included here. I then added that result as a true/false custom indicator to a file of daily E-mini S&P 500 price data going back to 2005.

I restricted the indicator set to only the ideal prediction indicator and restricted the order set to a market entry order and an exit after N bars. The parameter range for exiting after N bars was restricted to 5 to 5; i.e., it was limited to a value of N equal to 5. I ran a build with a population size of 100 and 10 generations. Even though the number of possible logical combinations is small, I nonetheless found it helpful to use a population size of 100 to make sure it found the obvious solution.

The expected strategy code is shown below in Fig. 3.

Inputs: N1 (1), { True/false value (0 for false; 1 for true) }
NBarEx1 (5), { Number of bars from entry for market exit }

{ Entry and exit conditions }
VarL1 = IdealPredict();
EntCondL = VarL1 = N1;

{ Position sizing code not shown }

{ Entry orders }
If MarketPosition = 0 and EntCondL then begin
    Buy("EnMark-L") NShares shares next bar at market;
end;

{ Exit orders, long trades }
If MarketPosition = 1 then begin
    If BarsSinceEntry >= NBarEx1 then
        Sell("ExMark-L") next bar at market;
end;

Figure 3. Partial EasyLanguage strategy code listing for a strategy that utilizes an ideal Boolean prediction indicator. The strategy enters at market if the prediction is "true" and exits after 5 bars.

As hoped, the build algorithm correctly utilized the ideal prediction indicator. The strategy essentially reads "if the IdealPredict function returns true (which means it knows the market will be higher 5 bars from this bar), then go long next bar at market and exit in 5 bars".

The equity curve generated by this strategy over the ES symbol used in the build is shown below in Fig. 4. The fact that not all trades are winners is due to the fact that the trading costs were set to $25 per trade and not all five-bar price changes exceeded this cost.

Figure 4. Equity curve for the strategy shown in Fig. 3 based on the ideal prediction indicator.

The resulting code and corresponding equity curve demonstrate that it's possible to include a Boolean prediction indicator in the program and expect it to be utilized correctly by the build process. Obviously, the ideal indicator used in the example is not realistic, but it does suggest that if a valid prediction indicator were available, including it as a Boolean custom indicator might be a viable approach to incorporating it into the build process. An example of a valid prediction indicator might be one generated using a machine learning approach, such as a neural network.

Conclusions

This article discussed several different ways in which custom indicators that return a true/false value can be incorporated into the build process in Adaptrade Builder. The motivation for this article arose from the interest of several of my customers in using their existing trading strategies as entry conditions or filters for other trading strategies. They basically wanted to have Builder construct a trading strategy using their existing trading strategy as a base. As shown above, this is a feasible idea, but it does have potential drawbacks and may be difficult to implement for real-time trading other than for strategies that can be manually executed through Builder.

As an alternative to using one trading strategy as an indicator in another strategy, I demonstrated that a predictive indicator could potentially be a useful Boolean custom indicator. Whether or not such an indicator would be easier to automate in real-time trading depends on how it's implemented. However, it may be easier for the build process to incorporate such an indicator effectively into trading logic.

Good luck with your trading.

Mike Bryant
Adaptrade Software

The features described in this article are available in Adaptrade Builder version 2.2.1 and later.

This article appeared in the May 2017 issue of the Adaptrade Software newsletter.

HYPOTHETICAL OR SIMULATED PERFORMANCE RESULTS HAVE CERTAIN INHERENT LIMITATIONS. UNLIKE AN ACTUAL PERFORMANCE RECORD, SIMULATED RESULTS DO NOT REPRESENT ACTUAL TRADING. ALSO, SINCE THE TRADES HAVE NOT ACTUALLY BEEN EXECUTED, THE RESULTS MAY HAVE UNDER- OR OVER-COMPENSATED FOR THE IMPACT, IF ANY, OF CERTAIN MARKET FACTORS, SUCH AS LACK OF LIQUIDITY. SIMULATED TRADING PROGRAMS IN GENERAL ARE ALSO SUBJECT TO THE FACT THAT THEY ARE DESIGNED WITH THE BENEFIT OF HINDSIGHT. NO REPRESENTATION IS BEING MADE THAT ANY ACCOUNT WILL OR IS LIKELY TO ACHIEVE PROFITS OR LOSSES SIMILAR TO THOSE SHOWN.