EasyLanguage
Techniques
Many of you are
TradeStation users and have no doubt used (or tried to use) EasyLanguage to
program your own trading system. The name notwithstanding, EasyLanguage can
be tricky, even for experienced programmers. So, this month, I'm going to
cover a few EasyLanguage techniques that may be nonobvious to the
uninitiated. I'll first discuss a few basic concepts that are necessary to
understand in order to successfully program a system in EasyLanguage. Then
I'll address a couple of commonly encountered problems that arise when
programming a system. Finally, I'll show you some EasyLangauge code that
accumulates an equity curve using fixed fractional position sizing. This
will illustrate the use of functions.
Some Basics
I don't have the time or
space in this format to cover the basics of EasyLanguage programming or
language syntax in any comprehensive way, but I would like to discuss the
concept of program flow and execution. If you've programmed before in other
languages, EasyLanguage probably looks similar to other procedural
languages, such as BASIC, C, Pascal, or Fortran. With respect to syntax,
EasyLanguage is similar in many ways to these other languages. However, it
differs in terms of how the program is executed. Traditional programming
languages, such as the ones mentioned, execute from top to bottom, then stop
at the end. EasyLanguage code, on the other hands, is executed on each bar
of the chart to which it's applied. Confusion can sometimes arise because
variables retain their values from bar to bar. This is true even within
functions. However, the declarations at the top of an EasyLanguage program
are executed only once, on the first bar.
Consider the following code for a make-believe system (or "strategy" in the
latest jargon of TradeStation/EasyLanguage):
Input: Len1 (10),
Len2 (5);
Var: A1 (0),
B1 (0),
C1 (0);
A1 = C1 + 1;
B1 = A1 + 1;
C1 = B1 + 1;
{
Comments are written between braces: the following 2 lines generate the
entry orders }
If C1 > 10 then Begin
Buy next bar at C + Len1 stop;
Sell Short next bar at C - Len2 stop;
End;
The
inputs to the system, Len1 and Len2, are initialized to values of 10 and 5,
respectively. You cannot change the values of inputs within the system code,
so these inputs will have the values given unless the user enters different
values from the chart.
The
variables, A1, B1, and C1 are declared next. Each variable is initialized to
zero in the declaration by putting 0 in parentheses after the variable name.
This initialization happens once, on the first bar. The next three lines
increment the variables. These lines, as well as the remaining lines in the
code, are executed in order from top to bottom on each bar of the chart, and
the values of A1, B1, and C1 are "remembered" by the system from one bar to
the next. On the first bar, for example, A1 is given the value C1 + 1, which
is equal to 1 because C1 is initialized to zero on the first bar. Then, B1
gets the value A1 + 1, which is equal to 2. Finally, C1 gets the value B1 +
1, which is equal to 3. On the second bar, the execution begins at the line
"A1 = C1 + 1." On this bar, C1 has the value of 3 from the prior bar, so A1
now gets the value C1 + 1, which is 4. B1 then becomes 5 and C1 becomes 6.
On the third bar, C1 will end up with the value 9. On the fourth bar, C1
will become 12. On this bar, the entry orders will finally get executed
because C1 is greater than 10. (The entry condition is nonsensical, of
course, and is just to illustrate the idea of program execution.) As we'll
see in the sample function presented later, variables declared within
functions also retain their values from bar to bar.
Using conditions
on one bar for entry on a later bar
Now that a few basics
are out of the way, let's cover a couple of common situations that come up
in programming a system. Because the EasyLanguage code executes on every
bar, it's straightforward to define entry conditions for a trading system
that apply to the next bar. For example, if you want to buy the next bar if
today's close is less than the close of two day's ago, you would write:
If C < C[2] then
Buy next bar at market;
Buying the next bar "at market," by the way, will generate an order to buy
at the open of the next bar.
Let's make it a little more interesting by buying not at the next open but
at the highest high of the last five bars on a stop. The code for this could
be written as follows:
If C < C[2] then
Buy next bar at Highest(H, 5) stop;
However, what do you do if you want to place this order not just for the
next bar but for all subsequent bars once this condition has been met? In
other words, the setup condition is that the close is less than the close
two day's ago. Once the setup condition is met, we want to continue to look
for the trade until we either get filled or other conditions nullify the
setup.
The
easiest way to handle this is to define a logical variable "Setup" that is
set to "true" once the setup condition is met. Until the variable is set to
false again, the entry order is executed. For example,
Var: Setup (false);
If C < C[2] then
Setup = true;
If Setup = true then
Buy next bar at Highest(H, 5) stop;
{
Here's one way to reset the Setup variable for the next trade }
If Setup = true and MarketPosition = 1 then
Setup = false;
In this code, the variable Setup is set to "true" once
the condition C < C[2] is found. Because variables retain their values from
one bar to the next, Setup will be "true" on subsequent bars as well, so the
buy order will be generated on subsequent bars until Setup is reset to
false. You always have to be careful when using this technique to reset the
Setup variables so it can be used on the next trade. There are a variety of
ways to do this, depending on the rest of your system code, but one way is
shown above. I've used the built-in function MarketPosition to detect that
the long trade has been entered. Once the trade has been entered,
MarketPosition returns 1 (MarketPosition returns 0 for flat, 1 for long, and
-1 for short), indicating a long position is open. This condition is
detected by the last "if" statement and used to set Setup to false.
Otherwise, Setup would remain true on all subsequent bars.
Speaking of MarketPosition...
The function MarketPosition, used
above, is a very useful function. One problem, however, is that it only
references the current bar. It's sometimes useful to be able to compare the
current market position with the market position on the prior bar. For
example, this can be used to detect that a trade has exited. The common way
to reference the value of a variable or function on prior bars is to use the
[] notation. For example, C[2] is the value of the close two bars ago. If
you try to write MarketPosition[2], however, you'll get an error message.
The way around this it to define your own market position variable:
Var: MarkPos (0); { variable for market
position }
MarkPos = MarketPosition;
If BarNumber > 1 and MarkPos[1] = 1 and MarkPos <> MarkPos[1] then
TrailOnL = 0; { long trade exited on this bar }
If BarNumber > 1 and MarkPos[1] = -1 and MarkPos <> MarkPos[1] then
TrailOnS = 0; { short trade exited on this bar }
Variables in EasyLanguage store the values on the prior
bars (as far back as the MaxBarsBack setting), so by setting our MarkPos
variable equal to MarketPosition on each bar, we are effectively storing the
past values of MarketPosition. We can then reference them using the []
notation.
Also note how the MarkPos variable is used to determine
that long and short trades have exited. To detect that a long trade has
exited, we check that we were long on the prior bar (MarkPos[1] = 1) and
that our position on the current bar has changed (MarkPos <> MarkPos[1]).
The reverse logic is used to detect that a short trade has exited. In the
example above, taken from an intraday system I was testing, this logic is
used to reset flags that tell the system a trailing stop is active.
A
function for fixed fractional position sizing
Of the many ways to size a trade,
my favorite is fixed fractional position sizing, where you risk a certain
percentage of your account equity on each trade. For example, you might risk
2% of your equity on each trade. The risk is usually determined from the
size of the money management stop (e.g., a 12 point stop in the E-mini S&P
represents a risk of $600) or from the largest historical loss for the
system. TradeStation includes a built-in indicator called Strategy Equity
that plots the equity curve for the system currently applied to a chart.
However, if you want the equity curve to represent fixed fractional position
sizing, you're on your own in programming your system to adjust the number
of contracts for each trade according to the fixed fractional equation. The
function shown below will do this for you. It returns the number of
contracts for the next trade based on the account equity, trade risk, and
fixed fraction. It accumulates the equity from trade to trade.
Here's the code, preceded by a lengthy comment:
{ User function: NConFF
Calculate the number of contracts for the current trade assuming a fixed
percentage
of account is risked on each trade.
This function implements a fixed
fractional approach to position sizing; see R. Vince, Portfolio
Management Formulas, 1990 for a complete discussion of fixed fractional
trading. The function is
intended to be called by a trading system prior to each trade to determine
the number of contracts
to trade based on the accumulated trading equity, the risk of the current
trade, and the amount to
risk on each trade.
INPUTS:
StEqty: initial account size (starting equity) in dollars.
RiskPer: percentage risk per trade. This is the so-called "fixed fraction"
of fixed
fractional trading.
TrRisk: risk for current trade in dollars; should be positive. This number
can be different
for each trade if desired.
MinNEq1: Set MinNEq1 to either 1 or 0. If equal to 1, the number of
contracts is always
at least equal to 1. In other words, if the number of contracts would
otherwise be equal
to 0 because of a small account size or high trade risk, this sets the
number of contracts
equal to 1.
OUTPUT:
The function returns the number of contracts for the current trade. If the
account equity falls below
zero, the number zero ("0") will be returned. This is true even if
MinNEq1=1.
You can use the "Expert Commentary" tool in charting to see the account
equity, current profits,
trade risk, risk percentage, and number of contracts for the next trade on
each bar.
NOTES:
1. This version writes error messasges to the TradeStation MessageLog, so
it will only work in
TradeStation versions 2000i and TS 6. For TradeStation 4.0, change "MessageLog" to
"Print."
Michael R. Bryant
Breakout Futures
www.BreakoutFutures.com
mrb@BreakoutFutures.com
11/28/00
Copyright 2000 Breakout Futures
}
Input: StEqty (NumericSimple), { starting account size, $ }
RiskPer (NumericSimple), { risk
percentage }
TrRisk (NumericSeries), { risk
for current trade }
MinNEq1 (NumericSimple); { =1 -->
# contracts at least 1 }
Var: NCon (0), { number of contracts }
Equity (0); { account equity }
Equity = StEqty + NetProfit +
OpenPositionProfit;
If ABSVALUE(TrRisk) > 0 then
NCon = IntPortion(RiskPer/100 *
Equity/ABSVALUE(TrRisk))
else Begin
MessageLog("**Error: Trade risk <= 0.
Assign positive value to TrRisk.");
NCon = 0;
end;
If MinNEq1 > 0 and NCon < 1 then
NCon = 1;
If Equity <= 0 then Begin
MessageLog("**Warning: Account equity
<= 0.");
NCon = 0;
end;
#BeginCmtry
If CheckCommentary then Begin
Commentary("Starting Equity: $",
StEqty:0:2, NewLine);
Commentary("Net Profit (closed +
open): $", (NetProfit + OpenPositionProfit):0:2, NewLine);
Commentary("Current Equity: $", Equity:0:2, NewLine);
Commentary("Trade Risk: $", TrRisk:0:2, NewLine);
Commentary("Risk Percentage: ", RiskPer:0:2,"%", NewLine);
Commentary("Number of Contracts for Next Trade: ", NCon:0:0);
end;
#End;
NConFF = NCon;
This example provides a good opportunity to explain
EasyLanguage functions. First, why would you choose to use a function? A
function is generally used to contain code that will be used repeatedly.
Instead of rewriting the code every time you need it, you write it into a
function and call the function whenever you need that code. The NConFF
function above, for example, can be called from any trading system. If the
code were written into the system, it would not only result in more
complicated-looking system code, but we'd need to duplicate the effort the
next time we wanted that functionality in another system.
Notice how the inputs to the function are declared by
"type." In this case, they're declared "NumericSimple"
to indicate that they represent simple numbers (as opposed to arrays of
numbers or logical variables, for instance). This is different from in a
strategy, where the inputs are initialized in the declaration. Another
important feature of functions is that they return a value. The NConFF
function returns the number of contracts, given by variable NCon. The
function must contain a line in which the function name is assigned the
return value. In this example, the last line in the function -- NConFF
= NCon -- serves this purpose.
The NConFF function is basically a two-line function.
Everything else is basically bells and whistles. The first key line is the
first statement of the function, where it calculates the total account
equity as the sum of the starting account equity, current open profit, and
total closed profit. The second key line is two lines below, where it
calculates the number of contracts, NCon, using the fixed fractional
formula. This line divides the account equity by the trade risk and
multiplies by the fixed fraction. Notice that the function first checks to
make sure the risk is not zero. This is done to insure that the function
doesn't accidently divide by zero, which would generate an error. If the
trade risk, TrRisk, is zero, a message is written to the Message Log, and
the number of contracts is set to zero.
The next "if" statement checks the input
MinNEq1, which enables the user to force the
number of contracts to 1 in cases where it would otherwise be equal to zero
due to insufficient equity. Following that, the function checks to see if
the account equity is still positive. If not, a message is output to the
Message Log, and the number of contracts is set to zero so that the next
trade will be skipped. Lastly, I've used the Commentary feature of
EasyLanguage to write out some information. Commentary statements write out
information to the Analysis Commentary window ("Expert" Commentary in TS
2000i), which is accessed through the Analysis Commentary tool in the
Drawing menu in a chart window. When you click the Analysis Commentary tool
on a bar on the chart, the Analysis Commentary window pops up, revealing any
commentary that's available on that bar. The NConFF function makes all the
calculated results available on each bar in this manner. For example, if the
system running on your chart calls this function, you can see the net
profit, current equity, and the number of contracts for the next trade by
clicking the Analysis Commentary tool on any bar.
To illustrate how you would use this function in a
system, consider the following simple system:
Input: StEqty (30000), { starting account size, $
}
StopSz (15), { money management stop
size, points }
RiskPer (3.0), { risk percentage }
MinNEq1 (1); { =1 --> # contracts at
least 1 }
Var: TrRisk (0), { risk for current trade }
NCon (0); { number of contracts }
TrRisk = StopSz * BigPointValue;
NCon = NConFF(StEqty, RiskPer, TrRisk,
MinNEq1); { Here's where you call the function }
If C < C[2] then
Buy NCon contracts next bar at Highest(H, 5)
stop;
If MarketPosition = 1 then
Sell next bar at EntryPrice - StopSz stop;
A couple of notes:
-
The trade risk is calculated from the size in
points of the money management stop using the built-in function
BigPointValue, which gives the dollar value of a full point move in the
contract. For example, for the e-mini S&P, BigPointValue = 50.
-
The call to the function is the second statement,
NCon = NConFF(...). The function returns the number of contracts, which
is assigned to the variable NCon. We then use NCon in the buy order: Buy
NCon contracts ...
-
The sell statement implements the money management
stop, which is a stop order StopSz points below the entry, given by the
built-in function EntryPrice. Since we haven't specified the number of
contracts for the sell order, it will exit the entire position.
-
This code is for TS 6. For earlier versions of
TradeStation, the sell statement should be "ExitLong."
Hopefully, this function example, and the other examples, help address a few
of the more common problems traders have in writing trading systems in
EasyLanguage. Of course, there are a myriad of issues in using EasyLanguage
to develop trading systems, but I think that's enough for now.