Professional Documents
Culture Documents
Summary: This tutorial will explore a detailed back testing example that manages positions with
multiple closing trades. This formula example accomplishes this type of strategy logic by utilizing
the lot size parameter of the Strategy methods. This formula example also combines the trailing
stop and profit target logic explained in Back Testing Tutorial 2 to illustrate a back testing formula
that contains some commonly used elements of a trading system. The preMain() code will not
be covered in detail in this tutorial. However, the completed formula will include the preMain()
code for review. There is a link for downloading the completed formula at the end of the tutorial.
All of the formula examples in the tutorial series are for educational purposes only. They are not
designed for profitability and are not intended for trading. The strategy rules used for these
examples have been simplified for educational purposes.
Topic List:
1. Position Management Formula Example:
BtDonchian_PositionManagement.efs
A condition at the top of main() is added to prevent the formula code from executing in
real time as this formula is for back testing only. This is done (line 48) by checking for a
bar index of 0 with getCurrentBarIndex(). The global variables for the indicator series
are initialized in the bInit initialization routine followed by the final return statement. This
initialization routine is used because the global series variables only need to be initialized
one time. Setting bInit to true at the end of this routine prevents this code block from
executing again due to the bInit conditional if() statement (line 50). The return statement
up to this point is only returning the current value of the Upper and Lower Donchian
indicators to be plotted on the chart. The ATR and Middle Donchian indicators will not be
plotted on the chart, but will be used for the Strategy conditions that follow.
As discussed in Back Test Tutorial 2, most back testing formulas will require some
specific code logic to prevent multiple trades from occurring on the same bar. For this
strategy, since the exit rules do not allow for positions to be closed with reversing entry
signals, the entry conditions can be placed in an else statement associated with the initial
if() statement for the exit conditions. The initial exit condition checks to see if the strategy
is currently holding a position before evaluating the exit strategy. If that condition is false
then the else portion of this condition will be executed, which contains the following entry
conditions. This if() else structure ensures that a new position will not be entered on
the same bar that closes a position.
The two entry conditions on lines 87 and 91 simply compare the high and low of the
current bar to the previous bars Upper and Lower Donchian channels. If one of these
conditions evaluates to true, a long or short position will be reported to the Strategy
Analyzer, respectively. Make note of the nEntry variable on lines 88 and 92. This
variable stores the entry price that will be used for the entry trade, which must be
validated to ensure that a realistic entry price is recorded. In the case of the long entry
condition, it would be possible for the open of the bar to be higher than the previous bars
Upper Donchian value, nU. Therefore, the open of the current bar would be the most
realistic entry price as that would have been the trade that triggered the entry signal. If
the current bar opened below the previous bars Upper Donchian channel, then it can be
assumed that at some point during the current bar the price moved higher, which then
triggered the long entry signal. In this case, nU would be the realistic entry price for the
entry trade. Passing nU and the open price to the Math.max() function will set nEntry to
the maximum value between those two values. Therefore, the open will only become the
recorded entry price if the open is greater than nU. The short condition would evaluate
the reverse logic of the long condition and use Math.min() to record the proper entry
price, which would be the lower value between nL and the open price.
3.1.3 Add Exit Conditions
Before diving into the specific code for the exit conditions, there are a few required
elements that need to be added, which will be used within these conditions. First, the
logic for the trailing stop will be added. A global variable is needed to store the stop price,
which will be named, nStop.
When a new position is taken, the initial stop is set to the previous bars Middle Donchian
indicator. This series was already declared in the fist section. All that needs to be added
now is the local variable, nM, which will be assigned to the previous bars value for this
series. The nM variable will also be added to the null check routine for validation.
Now the initial stop value can be assigned to nStop, which occurs inside the entry
conditions (lines 129 and 134 below).
The next step for the stop logic is to add the code that increments this price, which creates
the trailing stop. For each bar where an active position does not get stopped out, the stop
will be incremented by 25% of the previous bars ATR. Another local variable, nInc, will
be added to store this value, which is also added to the null check routine.
Just after the null check routine and above the exit strategy is where this logic needs to be
placed. This trailing stop adjustment needs to be done before evaluating the stop
conditions for the current bar to simulate the real time adjustment after the completion of a
bar, which did not close the current position. Likewise, if the position was closed, the
nStop variable needs to be reset to null at this instance as well.
As the formula process the new bar, the strategy first checks to see if there is an active
position (line 68). If line 68 is true, nStop is reset to null. If line 68 is false, then the else
block is executed and nStop gets incremented accordingly.
The last modification to make for the stop logic is to add the nStop variable to the return
statement in main() so that it will be plotted on the chart for visual reference. When the
value is null, there will not be a line plotted on the chart. This appearance on the chart is
controlled by the plot type assigned to this returned series in preMain(), which is
PLOTTYPE_FLATLINES.
With the stop logic in place, now the logic for the profit targets will be added. The profit
target will be a fixed target. However, when the price breaches this target only half of the
position will be closed. The remaining half will persist until it is taken out by the trailing
stop. To process this logic, there will be two new global variables needed. nTarget will
be used to store the fixed profit target level and bTargetBreach will be used to keep track
of when, or if, the target is hit.
bTargetBreach is a Boolean variable that is initially set to false. A false value will indicate
that the profit target has not been breached for the current position. When the target is hit
and half of the position is closed, bTargetBreach will be set to true. This variable will be
used in the profit target conditions to ensure that this exit trade will only occur once and
allow the other half to be closed by the trailing stop.
Following the same logic used for nStop, these two profit target variables will also need to
be reset after a position is closed, which can be done within the same condition that
resets nStop (line 72 and 73 below).
Also, in similar fashion to the stop logic, the profit target will be plotted on the chart for
visual reference, which is why it also is reset to null when there is no active position. It will
be added to the return statement and will also be formatted with the plot type of
PLOTTYPE_FLATLINES in preMain().
At this point, all of the variables needed for coding the exit conditions are in place. As
mentioned in the previous section, the initial condition for the exit strategy will check for an
active position. If that condition is true, then the exit rules will be evaluated. The next
layer of conditions that will be evaluated will simply check to see if the position is long or
short. Both conditions cant be true at the same time because the Strategy object does
not allow this by default. Therefore, they can be evaluated in succession without an else
if() statement.
Heres the complete code for the long exit strategy, which includes the logic for both profit
target and trailing stop conditions. The conditions for the short exit strategy will
immediately follow starting at line 105.
If the strategy is long at line 85, then the long exit rules will be evaluated. First, make note
that each of the possible exit rules are evaluated separately and with an if() else if()
structure right down to the last possible exit condition. This is done to prevent multiple
long exit conditions from being executed on the same bar. If each exit condition were
placed in their own if() statement, each condition would be evaluated in succession, which
would allow for the possibility of multiple .doSell() executions. This would lead to
improper back test results based on the intended rules for this strategy. Remember that it
is the specific code logic of the formula that determines what trades, fill price and number
of shares are reported to the Strategy Analyzer. The Strategy Analyzer and the EFS
Strategy object do not have the ability to make any logic decisions.
In reviewing the specific code logic in the image above, a breach of the trailing stop,
nStop, is evaluated first starting at line 86. If the bar opened below the stop level, the
position will be closed at the open, which is the result of the fill type and fill bar constants
used in the .doSell() method on line 87. If the open did not breach the stop level, then the
low of the bar is compared to the stop level, as the price could have moved lower during
the interval and triggered the stop trade. This condition is on line 89, which records the
trade price at the stop level, nStop.
One observation that should be made at this point is the choice of code logic that was
used to execute the stop trades. It could have been done in a similar manner to the entry
logic where a local variable, such as nExit, could have been assigned to the minimum
value between nStop and the open of the bar. Then there would have been only one
conditional statement needed that would compare the low of the bar to nStop and then
pass nExit as the stop price to .doSell() with a fill type of Strategy.STOP. This would
have eliminated the need for two different .doSell() calls where one uses
Strategy.MARKET and the other uses Strategy.STOP. This would actually be a slightly
better way to code it. There are two reasons why the current code logic was used. First,
to illustrate that there can be more than one way to properly interpret the intended rules of
a strategy. Most importantly, this logic illustrates an example of when a strategy method,
such as .doSell(), can be used with a fill type of Strategy.MARKET and a fill bar of
Strategy.THISBAR. Remember from Back Test Tutorial 1 that this combination of
constants records the trade price at the open of the current bar that is being processed.
This combination of constants often creates confusion and misunderstanding for new
users. Many new users first back testing formulas are coded to evaluate bar 0, or the
current bar, to trigger trade signals. This type of logic often compares the close of bar 0 to
an indicator to trigger a trade. In this case, using the MARKET/THISBAR combination of
constants can falsely inflate the back test results because the open of the bar is usually a
better price than the close of the bar that was used to evaluate the trade signal. The rule
of thumb to remember is that if the strategy logic evaluates trade signals based on bar -1,
like the example in this tutorial, then recording a trade at the open of bar 0 with
MARKET/THISBAR is acceptable logic. If the strategy logic only evaluates bar 0 for trade
signals, the MARKET/THISBAR combination of constants is not acceptable logic for the
reasons just explained. An alternative solution to avoid any confusion related to the
Strategy.MARKET fill type is to simply not use it. Instead, use Strategy.LIMIT or
Strategy.STOP for all of the strategy trades and pass in the specific trade price that is
validated by the code logic. This concept also applies to the code logic used for the profit
target, which is discussed next.
When neither of the trailing stop conditions is true, then the profit target conditions will be
evaluated. The first profit target condition on line 92 checks to see if the profit target had
previously been executed for the current position. If not, then the current bar is evaluated
for a breach of the profit target with the open and high of the bar. If the profit target has
been breached, then half of the position is closed at the appropriate price level. When this
event occurs, the bTargetBreach variable is set to true so that the profit target conditions
will not be evaluated again for the current position. Determining the number of shares (or
contracts) to close is done on line 93. The local variable, nLot, is declared and assigned
to half of the current position by retrieving this number using the
8
However, there is one minor difference on line 113 that sets the value for the nLot
variable. The .getPositionSize() method returns a negative number if the position is short
and positive if long. The lot size parameter of the strategy methods also requires positive
numbers. Therefore, the rounded result also needs to be passed to the Math.abs()
function, which converts the number to an absolute value.
On the chart, the first bar that is colored dark green (or maroon) will indicate the bar where
the entry trade was executed. The date and time of these bars will also be found in the
list of trades on the Trades tab in the Strategy Analyzer.
There are many different options available to highlight the trading activity or position
status on the chart. For example, an image, shape or text could be drawn above or below
10
The trade was recorded at the stop price of 80.26 for 50 shares. The default number of
shares used for this back test was 100. The first 50 shares for this long position were sold
at the profit target level of 79.43, which occurred 13 bars before the stop on 8/17. This
can easily be identified on the chart by looking for the khaki horizontal line (the profit
target) that starts where this long position was first taken.
This back test didnt include any slippage or commission. However, once the logic for a
back testing formula has been verified to be accurate and realistic, it is recommended to
add some slippage and commission to the back tests to add an additional layer of realism
to the final results.
Whats Next?
This concludes the Back Testing tutorials for beginners. Now its time to start coding
some of your trading system ideas and back test them with the Strategy Analyzer. Any
11
questions or challenges that arise should be posted to the EFS Back Testing forum at
www.esignalcentral.com. Also remember to post your formula code along with your
detailed questions.
12