Category Archives: EasyLanguage Tutorial

How To Program A Ratcheting Stop in EasyLanguage

30 Minute Break Out utilizing a Ratchet Stop [7 point profit with 6 point retention]
I have always been a big fan of trailing stops.  They serve two purposes – lock in some profit and give the market room to vacillate.  A pure trailing stop will move up as the market makes new highs, but a ratcheting stop (my version) only moves up when a certain increment or multiple of profit has been achieved.  Here is a chart of a simple 30 minute break out on the ES day session.  I plot the buy and short levels and the stop level based on whichever level is hit first.

When you program something like this you never know what is the best profit trigger or the best profit retention value.  So, you should program this as a function of these two values.  Here is the code.

inputs: ratchetAmt(6),trailAmt(6);
vars:longMult(0),shortMult(0),myBarCount(0);
vars:stb(0),sts(0),buysToday(0),shortsToday(0),mp(0);
vars:lep(0),sep(0);

If d <> d[1] then
Begin
longMult = 0;
shortMult = 0;
myBarCount = 0;
mp = 0;
lep = 0;
sep = 0;
buysToday = 0;
shortsToday = 0;
end;

myBarCount = myBarCount + 1;

If myBarCount = 6 then // six 5 min bars = 30 minutes
Begin
stb = highD(0); //get the high of the day
sts = lowD(0); //get low of the day
end;

If myBarCount >= 6 and buysToday + shortsToday = 0 and high >= stb then
begin
mp = 1; //got long - illustrative purposes only
lep = stb;

end;
If myBarCount >=6 and buysToday + shortsToday = 0 and low <= sts then begin
mp = -1; //got short
sep = sts;
end;

If myBarCount >=6 then
Begin
plot3(stb,"buyLevel");
plot4(sts,"shortLevel");
end;
If mp = 1 then buysToday = 1;
If mp =-1 then shortsToday = 1;


// Okay initially you want a X point stop and then pull the stop up
// or down once price exceeds a multiple of Y points
// longMult keeps track of the number of Y point multipes of profit
// always key off of lep(LONG ENTRY POINT)
// notice how I used + 1 to determine profit
// and - 1 to determine stop level
If mp = 1 then
Begin
If h >= lep + (longMult + 1) * ratchetAmt then longMult = longMult + 1;
plot1(lep + (longMult - 1) * trailAmt,"LE-Ratchet");
end;

If mp = -1 then
Begin
If l <= sep - (shortMult + 1) * ratchetAmt then shortMult = shortMult + 1;
plot2(sep - (shortMult - 1) * trailAmt,"SE-Ratchet");
end;
Ratcheting Stop Code

So, basically I set my multiples to zero on the first bar of the trading session.  If the multiple = 0 and you get into a long position, then your initial stop will be entryPrice + (0 – 1) * trailAmt.  In other words your stop will be trailAmt (6 in this case) below entryPrce.  Once price exceeds or meets 7 points above entry price, you increment the multiple (now 1.)  So, you stop becomes entryPrice + (1-1) * trailAmt – which equals a break even stop.  This logic will always move the first stop to break even.  Assume the market moves 2 multiples into profit (14 points), what would your stop be then?

stop = entryPrice + (2 – 1) * 6 or entryPrice + 6 points.

See how it ratchets.  Now you can optimized the profit trigger and profit retention values.  Since I am keying of entryPrice your first trailing stop move will be a break-even stop.

This isn’t a strategy but it could very easily be turned into one.

Passing a Two Dimensional Array to a Function- EasyLanguage

In this post, I want to share some code that I was surprised wasn’t really all that accessible.  I was in need of passing a 2-D array to a function and couldn’t remember the exact syntax.  The semantics of the problem is pretty straightforward.

  • Build Array
  • Pass Array as Reference
  • Unpack Array
  • Do Calculation

A 2D Array Is Just Like a Table

Any readers please correct me if I am wrong here, but you can’t use dynamic arrays on any dimension greater than 1.  A dynamic array is one where you don’t initially know the size of the array so you leave it blank and TradeStation allocates memory dynamically.  Also, there are a plethora of built-in functions that only work with dynamic arrays.  Once we step up in dimension then that all goes out the window.  I know what you are thinking, just use multiple dynamic arrays.  Sometimes you want to keep the accounting down to a minimum and a matrix or table fits this bill.  So if you do use multi-dimension arrays just remember you will need to know the total number of rows and columns in your table.  Table?  What do you mean table?  I thought we were talking about arrays.  Well, we are and a two-dimensional array can look like a table with rows as your first index and columns as your second.  Just like an Excel spreadsheet.  First, let’s create a very small and simple table in EasyLangauge:

array: testArray[2,2](0);

Once
begin
testArray[0,0] = 100;
testArray[0,1] = 5;

testArray[1,0] = 200;
testArray[1,1] = 10;

testArray[2,0] = 300;
testArray[2,1] = 7.5;

Value2 = getArrayHH(testArray,2,2);
Print (value2);

end;
Create a Very Small Table in Our Sandbox

 

My EasyLanguage Sandbox

You will notice I used the keyword Once.  I use this whenever I want to play around with some code in my EasyLanguage Sandbox.  Huh?  In programmer-ese a Sandbox is a quick and dirty environment that runs very quickly and requires nearly zero overhead.  So here I apply the code to print out just one line of output when applied to any chart.   Notice how I declare the 2-D array – use the keyword Array: and the name of the array or table and allocate the total number of rows as the first argument and the total number of columns as the second argument.  Also notice the arguments are separated by a comma and enclosed in square brackets.  The following value enclosed in parentheses is the default value of every element in the array.    Remember arrays are zero-based in EasyLanguage.  So if you dimension an array with the number 2 you access the rows with 0 and 1 and 2.  Same goes for columns as well.  Did you catch that.  If you dimension an array with the number 2 shouldn’t there be just 2 rows?  Well in EasyLanguage when you dimension an array you get a free element at row 0 and column 0.  In EasyLanguage you can just ignore row 0 and column 0 if you like.  Here is the code if you ignore row 0 and column 0.

Should I Use Row 0 and Column Zero – It’s A Preference

array: testArray[2,2](0);

Once
begin
testArray[1,1] = 100;
testArray[1,2] = 5;

testArray[2,1] = 200;
testArray[2,2] = 10;

Value2 = getArrayHH(testArray,2,2);
Print (value2);

end;
Ignore the Elements at Row 0 and Column 0

Out Of Bounds

Even though you get one free row you still cannot go beyond the boundaries of the array size.  If I were to say something like:

testArray[3,1] = 300;

I would get an error message.  If you want to work with a zero element then all of your code must coincide with that.  If not, then your code shouldn’t try to access row 0 or column 0.    Okay here is the function that I programmed for this little test:

inputs: tempArray[x,y](numericArray),numOfRows(numericSimple),whichColumn(numericSimple);

vars: j(0),tempHi(0),cnt(0);

For j= 1 to numOfRows
Begin
print(j," ",tempArray[j,whichColumn]);
If tempArray[j,whichColumn] > tempHi then tempHi = tempArray[j,whichColumn];
end;

GetArrayHH = tempHi;
Function That Utilizes a 2D Array

Notice in the inputs how I declare the tempArray with the values x and y.  You could have used a and b if you like or any other letters.  This informs the compiler to expect a 2D array.  It doesn’t know the size and that’s not important as long as you control the boundaries from the calling routine.  The second parameter is the number of rows in the table and the third parameter is the column I am interested in.  In this example, I am interested in column 2.

The Caller Function is the QuarterBack – Make Sure You Don’t Throw it Out of Bounds

Again this function assumes the caller will prevent stepping out of bounds.  I loop the number of rows in the table and examine the second column and keep track of the highest value.  I then return the highest column value.

This was a simple post, but remembering the syntax can be tough and know that EasyLangauge is zero-based when it comes to arrays is nice to know.   You can also use this format for your own Sandbox.

 

 

MULTI-TIME FRAME – KEEPING TRACK OF DISCRETE TIME FRAMES

Just a quick post here.  I was asked how to keep track of the opening price for each time frame from our original Multi-Time Frame indicator and I was answering the question when I thought about modifying the indicator.  This version keeps track of each discrete time frame.  The original simply looked back a multiple of the base chart to gather the highest highs and lowest lows and then would do a simple calculation to determine the trend.  So let’s say its 1430 on a five-minute bar and you are looking back at time frame 2.  All I did was get the highest high and lowest low two bars back and stored that information as the high and low of time frame 2.  Time frame 3 simply looked back three bars to gather that information.  However if you tried to compare these values to a 10-minute or 15-minute chart they would not match.

In this version, I use the modulus function to determine the demarcation of each time frame.  If I hit the border of the time frame I reset the open, high, low and carry that value over until I hit the next demarcation.  All the while collecting the highest highs and lowest lows.  In this model, I am working my way from left to right instead of right to left.  And in doing so each time frame is discrete.

Let me know which version you like best.

 

Inputs:tf1Mult(2),tf2Mult(3),tf3Mult(4),tf4Mult(5);



vars: mtf1h(0),mtf1l(0),mtf1o(0),mtf1c(0),mtf1pvt(0),diff1(0),
mtf2h(0),mtf2l(0),mtf2o(0),mtf2c(0),mtf2pvt(0),diff2(0),
mtf3h(0),mtf3l(0),mtf3o(0),mtf3c(0),mtf3pvt(0),diff3(0),
mtf4h(0),mtf4l(0),mtf4o(0),mtf4c(0),mtf4pvt(0),diff4(0),
mtf0pvt(0),diff0(0);

If barNumber = 1 then
Begin
mtf1o = o;
mtf2o = o;
mtf3o = o;
mtf4o = o;
end;


If barNumber > 1 then
Begin

Condition1 = mod((barNumber+1),tf1Mult) = 0;
Condition2 = mod((barNumber+1),tf2Mult) = 0;
Condition3 = mod((barNumber+1),tf3Mult) = 0;
Condition4 = mod((barNumber+1),tf4Mult) = 0;

mtf1h = iff(not(condition1[1]),maxList(high,mtf1h[1]),high);
mtf1l = iff(not(condition1[1]),minList(low,mtf1l[1]),low);
mtf1o = iff(condition1[1],open,mtf1o[1]);
mtf1c = close;


mtf0pvt = (close + high + low) / 3;
diff0 = close - mtf0pvt;

mtf2h = iff(not(condition2[1]),maxList(high,mtf2h[1]),high);
mtf2l = iff(not(condition2[1]),minList(low,mtf2l[1]),low);
mtf2o = iff(condition2[1],open,mtf2o[1]);
mtf2c = close;


mtf1pvt = (mtf1h+mtf1l+mtf1c) / 3;
diff1 = mtf1c - mtf1pvt;

mtf2pvt = (mtf2h+mtf2l+mtf2c) / 3;
diff2 = mtf2c - mtf2pvt;

mtf3h = iff(not(condition3[1]),maxList(high,mtf3h[1]),high);
mtf3l = iff(not(condition3[1]),minList(low,mtf3l[1]),low);
mtf3o = iff(condition3[1],open,mtf3o[1]);
mtf3c = close;

mtf3pvt = (mtf3h+mtf3l+mtf3c) / 3;
diff3 = mtf3c - mtf3pvt;

mtf4h = iff(not(condition4[1]),maxList(high,mtf4h[1]),high);
mtf4l = iff(not(condition4[1]),minList(low,mtf4l[1]),low);
mtf4o = iff(condition4[1],open,mtf4o[1]);
mtf4c = close;

mtf4pvt = (mtf4h+mtf4l+mtf4c) / 3;
diff4 = mtf4c - mtf4pvt;


Condition10 = diff0 > 0;
Condition11 = diff1 > 0;
Condition12 = diff2 > 0;
Condition13 = diff3 > 0;
Condition14 = diff4 > 0;

If condition10 then setPlotColor(1,Green) else SetPlotColor(1,Red);
If condition11 then setPlotColor(2,Green) else SetPlotColor(2,Red);
If condition12 then setPlotColor(3,Green) else SetPlotColor(3,Red);
If condition13 then setPlotColor(4,Green) else SetPlotColor(4,Red);
If condition14 then setPlotColor(5,Green) else SetPlotColor(5,Red);

condition6 = condition10 and condition11 and condition12 and condition13 and condition14;
Condition7 = not(condition10) and not(condition11) and not(condition12) and not(condition13) and not(condition14);

If condition6 then setPlotColor(7,Green);
If condition7 then setPlotColor(7,Red);

If condition6 or condition7 then plot7(7,"trend");

Plot6(5,"line");
Plot1(4,"t1");
Plot2(3,"t2");
Plot3(2,"t3");
Plot4(1,"t4");
Plot5(0,"t5");

end;
Multi-Time Frame with Discrete Time Frames

Using a Dictionary to Create a Trading System

Dictionary Recap

Last month’s post on using the elcollections dictionary was a little thin so I wanted to elaborate on it and also develop a trading system around the best patterns that are stored in the dictionary.  The concept of the dictionary exists in most programming languages and almost all the time uses the (key)–>value model.  Just like a regular dictionary a word or a key has a unique definition or value.  In the last post, we stored the cumulative 3-day rate of return in keys that looked like “+ + – –” or “+ – – +“.  We will build off this and create a trading system that finds the best pattern historically based on average return.  Since its rather difficult to store serial data in a Dictionary I chose to use Wilder’s smoothing average function.

Ideally, I Would Have Liked to Use a Nested Dictionary

Initially, I played around with the idea of the pattern key pointing to another dictionary that contained not only the cumulative return but also the frequency that each pattern hit up.  A dictionary is designed to have  unique key–> to one value paradigm.  Remember the keys are strings.  I wanted to have unique key–> to multiple values. And you can do this but it’s rather complicated.  If someone wants to do this and share, that would be great.  AndroidMarvin has written an excellent manual on OOEL and it can be found on the TradeStation forums.  

Ended Up Using A Dictionary With 2*Keys Plus an Array

So I didn’t want to take the time to figure out the nested dictionary approach or a vector of dictionaries – it gets deep quick.  So following the dictionary paradigm I came up with the idea that words have synonyms and those definitions are related to the original word.  So in addition to having keys like “+ + – -” or “- – + -” I added keys like “0”, “1” or “15”.  For every  + or – key string there exists a parallel key like “0” or “15”.  Here is what it looks like:

–  –  –  –  = “0”
– – – + = “1”
– – + – = “2”

You can probably see the pattern here.  Every “+” represents a 1 and every “0” represent 0 in a binary-based numbering system.  In the + or – key I store the last value of Wilders average and in the numeric string equivalent, I store the frequency of the pattern.

Converting String Keys to Numbers [Back and Forth]

To use this pattern mapping I had to be able to convert the “++–” to a number and then to a string.  I used the numeric string representation as a dictionary key and the number as an index into an array that store the pattern frequency.  Here is the method I used for this conversion.  Remember a method is just a function local to the analysis technique it is written.

//Lets convert the string to unique number
method int convertPatternString2Num(string pattString)
Vars: int pattLen, int idx, int pattNumber;
begin
pattLen = strLen(pattString);
pattNumber = 0;
For idx = pattLen-1 downto 0
Begin
If MidStr(pattString,pattLen-idx,1) = "+" then pattNumber = pattNumber + power(2,idx);
end;
Return (pattNumber);
end;
String Pattern to Number

This is a simple method that parses the string from left to right and if there is a “+” it is raised to the power(2,idx) where idx is the location of “+” in the string.  So “+  +  –  –  ” turns out to be 8 + 4 + 0 + 0 or 12.

Once I retrieve the number I used it to index into my array and increment the frequency count by one.  And then store the frequency count in the correct slot in the dictionary.

patternNumber = convertPatternString2Num(patternString); 
//Keep track of pattern hits
patternCountArray[patternNumber] = patternCountArray[patternNumber] + 1;
//Convert pattern number to a string do use as a Dictionary Key
patternStringNum = numToStr(patternNumber,2);
//Populate the pattern number string key with the number of hits
patternDict[patternStringNum] = patternCountArray[patternNumber] astype double;
Store Value In Array and Dictionary

Calculating Wilder’s Average Return and Storing in Dictionary

Once I have stored an instance of each pattern [16] and the frequency of each pattern[16] I calculate the average return of each pattern and store that in the dictionary as well.

//Calculate the percentage change after the displaced pattern hits
Value1 = (c - c[2])/c[2]*100;
//Populate the dictionary with 4 ("++--") day pattern and the percent change
if patternDict.Contains(patternString) then
Begin
patternDict[patternString] = (patternDict[patternString] astype double *
(patternDict[patternStringNum] astype double - 1.00) + Value1) / patternDict[patternStringNum] astype double;
end
Else
begin
patternDict[patternString] = value1;
// print("Initiating: ",patternDict[patternString] astype double);
end;
(pAvg * (N-1) + return) / N

When you extract a value from a collection you must us an identifier to expresses its data type or you will get an error message : patternDict[patternString] holds a double value {a real number}  as well as patternDict[patternStringNum] – so I have to use the keyword asType.  Once I do my calculation I ram the new value right back into the dictionary in the exact same slot.  If the pattern string is not in the dictionary (first time), then the Else statement inserts the initial three-day rate of return.

Sort Through All of The Patterns and Find the Best!

The values in a dictionary are stored in alphabetic order and the string patterns are arranged in the first 16 keys.  So I loop through those first sixteen keys and extract the highest return value as the “best pattern.”

//  get the best pattern that produces the best average 3 bar return
vars: hiPattRet(0),bestPattString("");
If patternDict.Count > 29 then
Begin
index = patternDict.Keys;
values = patternDict.Values;
hiPattRet = 0;
For iCnt = 0 to 15
Begin
If values[iCnt] astype double > hiPattRet then
Begin
hiPattRet = values[iCnt] astype double ;
bestPattString = index[iCnt] astype string;
end;
end;
// print(Date," BestPattString ",bestPattString," ",hiPattRet:8:4," CurrPattString ",currPattString);
end;
Extract Best Pattern From All History

If Today’s Pattern Matches the Best Then Take the Trade

// if the current pattern matches the best pattern then bar next bar at open
If currPattString = BestPattString then buy next bar at open;
// cover in three days
If barsSinceEntry > 2 then sell next bar at open;
Does Today Match the Best Pattern?

If today matches the best pattern then buy and cover after the second day.

Conclusion

I didn’t know if this code was worth proffering up but I decided to posit it because it contained a plethora of programming concepts: dictionary, method, string manipulation, and array.  I am sure there is a much better way to write this code but at least this gets the point across.

Contents of Dictionary at End of Run

++++    0.06
+++- -0.08
++-+ 0.12
++-- -0.18
+-++ 0.08
+-+- 0.40
+--+ -0.46
+--- 0.34
-+++ 0.20
-++- 0.10
-+-+ 0.23
-+-- 0.31
--++ 0.02
--+- 0.07
---+ 0.22
---- 0.46
0.00 103.00
1.00 128.00
10.00 167.00
11.00 182.00
12.00 146.00
13.00 168.00
14.00 163.00
15.00 212.00
2.00 157.00
3.00 133.00
4.00 143.00
5.00 181.00
6.00 151.00
7.00 163.00
8.00 128.00
9.00 161.00
Contents of Dictionary

Example of Trades

Pattern Dictionary System

 

Code in Universum

//Dictionary based trading sytem
//Store pattern return
//Store pattern frequency
// by George Pruitt
Using elsystem.collections;

vars: string keystring("");
vars: dictionary patternDict(NULL),vector index(null), vector values(null);
array: patternCountArray[100](0);

input: patternTests(8);

var: patternTest(""),tempString(""),patternString(""),patternStringNum("");
var: patternNumber(0);
var: iCnt(0),jCnt(0);
//Lets convert the string to unique number
method int convertPatternString2Num(string pattString)
Vars: int pattLen, int idx, int pattNumber;
begin
pattLen = strLen(pattString);
pattNumber = 0;
For idx = pattLen-1 downto 0
Begin
If MidStr(pattString,pattLen-idx,1) = "+" then pattNumber = pattNumber + power(2,idx);
end;
Return (pattNumber);
end;


once begin
clearprintlog;
patternDict = new dictionary;
index = new vector;
values = new vector;
end;

//Convert 4 day pattern displaced by 2 days
patternString = "";
for iCnt = 5 downto 2
begin
if(close[iCnt]> close[iCnt+1]) then
begin
patternString = patternString + "+";
end
else
begin
patternString = patternString + "-";
end;
end;

//What is the current 4 day pattern
vars: currPattString("");
currPattString = "";

for iCnt = 3 downto 0
begin
if(close[iCnt]> close[iCnt+1]) then
begin
currPattString = currPattString + "+";
end
else
begin
currPattString = currPattString + "-";
end;
end;

//Get displaced pattern number
patternNumber = convertPatternString2Num(patternString);
//Keep track of pattern hits
patternCountArray[patternNumber] = patternCountArray[patternNumber] + 1;
//Convert pattern number to a string do use as a Dictionary Key
patternStringNum = numToStr(patternNumber,2);
//Populate the pattern number string key with the number of hits
patternDict[patternStringNum] = patternCountArray[patternNumber] astype double;
//Calculate the percentage change after the displaced pattern hits
Value1 = (c - c[2])/c[2]*100;
//Populate the dictionary with 4 ("++--") day pattern and the percent change
if patternDict.Contains(patternString) then
Begin
patternDict[patternString] = (patternDict[patternString] astype double *
(patternDict[patternStringNum] astype double - 1.00) + Value1) / patternDict[patternStringNum] astype double;
end
Else
begin
patternDict[patternString] = value1;
// print("Initiating: ",patternDict[patternString] astype double);
end;
// get the best pattern that produces the best average 3 bar return
vars: hiPattRet(0),bestPattString("");
If patternDict.Count > 29 then
Begin
index = patternDict.Keys;
values = patternDict.Values;
hiPattRet = 0;
For iCnt = 0 to 15
Begin
If values[iCnt] astype double > hiPattRet then
Begin
hiPattRet = values[iCnt] astype double ;
bestPattString = index[iCnt] astype string;
end;
end;
// print(Date," BestPattString ",bestPattString," ",hiPattRet:8:4," CurrPattString ",currPattString);
end;
// if the current pattern matches the best pattern then bar next bar at open
If currPattString = BestPattString then buy next bar at open;
// cover in three days
If barsSinceEntry > 2 then sell next bar at open;
Pattern Dictionary Part II

 

 

 

Using A Dictionary to Store Chart Patterns in EasyLanguage

Dictionary – Another Cool Collection Object

The dictionary object in EasyLanguage works just like a real dictionary.  It stores values that referenced by a key.  In a real-life dictionary, the keys would be words and the values would be the definitions of those words.

An Introduction

This little bit of code just barely skims the surface of the dictionary object, but it gives enough to get a nice introduction to such a powerful tool.  I am piggybacking off of my Pattern Smasher code here, so you might recognize some of it.

Object Delcaration

Like any of the objects in EasyLanguage a dictionary must be declared initially.

Using elsystem.collections; 
vars: dictionary patternDict(NULL),vector index(null), vector values(null);
input: patternTests(8);
var: patternTest(""),tempString(""),patternString("");
var: iCnt(0),jCnt(0);

once begin
clearprintlog;
patternDict = new dictionary;
index = new vector;
values = new vector;
end;
Declaring Objects

Here I tell the editor that I am going to be using the elsystem.collections and then a declare/define a dictionary named patterDict and two vectors:  index and values.  In the Once block, I create instances of the three objects.  This is boilerplate stuff for object instantiation.

 

for iCnt = 5 downto 2
begin
if(close[iCnt]> close[iCnt+1]) then
begin
patternString = patternString + "+";
end
else
begin
patternString = patternString + "-";
end;
end;

If patternString = "+++-" then Value99 = value99 + (c - c[2])/c[2];

if patternDict.Contains(patternString) then
Begin
// print("Found pattern: ",patternString," 3-day return is: ", (c - c[2])/c[2]);
patternDict[patternString] = patternDict[patternString] astype double + (c - c[2])/c[2];
end
Else
patternDict[patternString] = (c - c[2])/c[2];
Build the Pattern String and Then Store It

 

The keys that index into the dictionary are strings.  In this very simple example, I want to examine all of the different combinations of the last four-bar closing prices.   Once the pattern hits up I want to accumulate the percentage change over the past three days and store that value in the location pointed to by the patternString key.

Notice how I displace the loop by three days (5-2 insteat of 3-0)?  I do this so I can compare the close at the end of the pattern with today’s close, hence gathering the percentage change.  Also, notice that I test to make sure there is an entry in the dictionary with the specific key string.  If there wasn’t already an entry with the key and I tried to reference the value I would get an error message – “unable to cast null object.”

Once I store the keys and values I can regurgitate the entire dictionary very simply.  The keys and values are stored as vectors.  I can simply assign these components of the dictionary to the two vectors I instantiated earlier.

If lastBarOnChart and patternDict.Count > 0 then
Begin
index = patternDict.Keys;
values = patternDict.Values;
For iCnt = 0 to patternDict.Count-1
Begin
print(index[iCnt] astype string," ",values[iCnt] astype double);
end;
print("Value99 : ",value99:8:4);
end;
Printing Out the Dictionary

And then I can simply index into the vectors to print out their contents.  I will add some more commentary on this post a little later this week.  I hope you find this useful.  And remember this will not work with MultiCharts.

Programming a Multi-Time Frame Indicator in EasyLanguage

Take a look at this indictor.

MTF indicator EasyLanguage

This indicator plots five different time frames as a stacked chart. The circles or dots at the bottom represent the difference between the closing price of each time frame and its associated pivot price  [(high + low + close)/3].  The value plotted at 4, in this case, represents the 5 minute time frame.  The 10-minute time frame is represented by the plot at 3 and so on.  The value plotted at 7 represents the composite of all the time frames.  It is only turned on if all times are either red or green.  If there is a disagreement then nothing is plotted.

This indicator is relatively simple even though the plot looks complicated.  You have to make sure the indicator is plotted in a separate pane.  The y – axis has 0 and 8 as its boundaries.  All you have to do is keep track of the highest highs/lowest lows for each time frame.  I use a multiplier of the base time frame to create different time frames.  TimeFrame1Mult = 2 represents 10 minutes and TimeFrame2Mult = 3 and that represents 15 minutes.  The indicator shows how strong the current swing is across five different time frames.  When you start getting a mix of green and red dots this could indicate a short term trend change.  You can use the EasyLanguage to plug in any indicator over the different time frames.  Here’s the code.  Just email me with questions or if you see a mistake in the coding.

{EasyLanguage MultiTime Frame Indicator)
written by George Pruitt - copyright 2019 by George Pruitt
}


Inputs:tf1Mult(2),tf2Mult(3),tf3Mult(4),tf4Mult(5);

vars: mtf1h(0),mtf1l(0),mtf1o(0),mtf1c(0),mtf1pvt(0),diff1(0),
mtf2h(0),mtf2l(0),mtf2o(0),mtf2c(0),mtf2pvt(0),diff2(0),
mtf3h(0),mtf3l(0),mtf3o(0),mtf3c(0),mtf3pvt(0),diff3(0),
mtf4h(0),mtf4l(0),mtf4o(0),mtf4c(0),mtf4pvt(0),diff4(0),
mtf0pvt(0),diff0(0);


If barNumber > 1 then
Begin

mtf0pvt = (close + high + low) / 3;
diff0 = close - mtf0pvt;

mtf1h = highest(h,tf1Mult);
mtf1l = lowest(l,tf1Mult);
mtf1c = close;

mtf1pvt = (mtf1h+mtf1l+mtf1c) / 3;
diff1 = mtf1c - mtf1pvt;

mtf2h = highest(h,tf2Mult);
mtf2l = lowest(l,tf2Mult);
mtf2c = close;

mtf2pvt = (mtf2h+mtf2l+mtf2c) / 3;
diff2 = mtf2c - mtf2pvt;

mtf3h = highest(h,tf3Mult);
mtf3l = lowest(l,tf3Mult);
mtf3c = close;

mtf3pvt = (mtf3h+mtf3l+mtf3c) / 3;
diff3 = mtf3c - mtf3pvt;

mtf4h = highest(h,tf4Mult);
mtf4l = lowest(l,tf4Mult);
mtf4c = close;

mtf4pvt = (mtf4h+mtf4l+mtf4c) / 3;
diff4 = mtf4c - mtf4pvt;

Condition10 = diff0 > 0;
Condition11 = diff1 > 0;
Condition12 = diff2 > 0;
Condition13 = diff3 > 0;
Condition14 = diff4 > 0;

If condition10 then setPlotColor(1,Green) else SetPlotColor(1,Red);
If condition11 then setPlotColor(2,Green) else SetPlotColor(2,Red);
If condition12 then setPlotColor(3,Green) else SetPlotColor(3,Red);
If condition13 then setPlotColor(4,Green) else SetPlotColor(4,Red);
If condition14 then setPlotColor(5,Green) else SetPlotColor(5,Red);

condition6 = condition10 and condition11 and condition12 and condition13 and condition14;
Condition7 = not(condition10) and not(condition11) and not(condition12) and not(condition13) and not(condition14);

If condition6 then setPlotColor(7,Green);
If condition7 then setPlotColor(7,Red);

If condition6 or condition7 then plot7(7,"trend");

Plot6(5,"line");
Plot1(4,"t1");
Plot2(3,"t2");
Plot3(2,"t3");
Plot4(1,"t4");
Plot5(0,"t5");

end;
MTF in EasyLanguage

 

Working Around 0:00 Time in EasyLanguage

Let’s say you want to carve out a special session of data from the 24-hour data session – maybe keep track of the highest high and lowest low from 9:00 p.m. to 4:00 p.m. the next day.  How would you do it?

To start with you would need to reset the highest high and lowest low values each day.  So you could say if the current bars time > StartTime and the prior bars time <= StartTime then you know the first bar of your specialized session has started.  So far so good.  If the time falls outside the boundaries of your special session then you want to ignore that data -right?  What about this:

If t >StartTime and t <= EndTime then…

{Remember EasyLanguage uses the end time stamp for its intraday bars}

Sounds good.  But what happens when time equals 2300 or 11:00 p.m.?  You want to include this time in your session but the if-then construct doesn’t work.    2300 is greater than 2100 but it’s not less than 1600 so it doesn’t pass the test.  The problems arise when the EndTime < StartTime.  It really isn’t since the EndTime is for the next day, but the computer doesn’t know that.  What to do?  Here is a quick little trick to help you solve this problem:  use a special offset if the time falls in a certain range.

EndTimeOffset = 0 ;

If t >=StartTime and t <= 2359 then EndTimeOffset= 2400 – EndTime;

Going back to our example of the current time of 2300 and applying this little bit of code our EndTimeOffset would be equal to 2400 – 1600 or 800.  So if t = 2300, you subtract 800 and get 1500 and that works.

2300 – 800 = 1500 which is less than 1600 –> works

What if t = 300 or 3:00 a.m.  Then EndTimeOffset = 0; 300 – 0 is definitely less than 1600.

That solves the problem with the EndTime.  Or does it?  What if EndTime is like 1503?  So you have 2400 – 1503 which is something like 897.  What if time is 2354 and you subtract 897 you get 1457 and that still works since its less than 1503.  Ok, what about if EndTime = 1859 then you get 2400 – 1859 which equals 541.  If time  = 2354 and you subtract 541 you get 1843 and that still works.

Is there a similar problem with the StartTime?  If t = 3:00 a.m. then it is not greater than our StartTime of 2100, but we want it in our window.  We need another offset.  This time we want to make a StartTime offset equal to 2400 when we cross the 0:00 timeline.  And then reset it to zero when we cross the StartTime timeline.  Let’s see if it works:

t = 2200 : is t > StartTime?  Yes

t=0002 : is t > StartTime?  No, but should be.  We crossed the 0000 timeline so we need to add 2400 to t and then compare to StartTime:

t + 2400 = 2402 and it is greater than StartTime.  Make sense?

Probably not but look at the code:

inputs: StartTime(numericSimple),EndTime(numericSimple),StartTimeOffSet(numericRef),EndTimeOffSet(numericRef);

If t >= StartTime and t[1] < StartTime then StartTimeOffSet = 0;
EndTimeOffSet = 0;
If t >= StartTime and t <= 2359 then EndTimeOffSet = 2400 - EndTime;
If t < t[1] then StartTimeOffSet = 2400;

TimeOffsets = 1;
Function To Calculate Start and End Time Offsets

Here is an the indicator code that calls the function:

vars: startTimeWindow(2100),endTimeWindow(1600);
vars: startOffSet(0),endOffSet(0);
Value1 = timeOffSets(startTimeWindow,endTimeWindow,startOffSet,endOffSet);

If t+startOffset > startTimeWindow and t-endOffSet <=endTimeWindow then
Begin

end
Else
Begin
print(d," ",t," outside time window ");
end;
Calling TimeOffsets Function

Hope this helps you out.  I am posting this for two reasons: 1) to help out and 2) prevent me from reinventing the wheel every time I have to use time constraints on a larger time frame of data.

StartTimeWindow = 2300

EndTimeWindow = 1400

Time = 2200, FALSE

Time = 2315, TRUE [2315 > 2300 and 2315 – (2400 -1400) <1400)]

This code should work with all times.  Shoot me an email if you find it doesn’t.

 

Calculating Position Size with Optimal F

I had a reader of the blog ask how to use Optimal F.  That was really a great question.  A few posts back I provided the OptimalFGeo function but didn’t demonstrate on how to use it for allocation purposes.  In this post, I will do just that.

I Have Optimal F – Now What?

From Ralph Vince’s book, “Portfolio Management Formulas”, he states: “Once the highest f is found, it can readily be turned into a dollar amount by dividing the biggest loss by the negative optimal f.  For example, if our biggest loss is $100 and our optimal f is 0.25, then -$100/ 0.25 = $400.  In other words, we should bet 1 unit for every $400 we have in our stake.”

Convert Optimal F to dollars and then to number of shares

In my example strategy, I start out with an initial capital of $50,000 and allow reinvestment of profit or loss.  The protective stop is set as 3 X ATR(10).  A fixed $2000 profit objective is also utilized.  The conversion form Optimal F to position size is illustrated by the following lines of code:

//keep track of biggest loss
biggestLoss = minList(positionProfit(1),biggestLoss);
//calculate the Optimal F with last 10 trades.
OptF = OptimalFGeo(10);
//reinvest profit or loss
risk$ = initCapital$ + netProfit;
//convert Optimal F to $$$
if OptF <> 0 then numShares = risk$ / (biggestLoss / (-1*OptF));
Code snippet - Optimal F to Position Size
  1. Keep track of biggest loss
  2. Calculate optimal F with OptimalFGeo function – minimum 10 trades
  3. Calculate Risk$ by adding InitCapital to current NetProfit (Easylanguage keyword)
  4. Calculate position size by dividing Risk$  by the quotient of biggest loss and (-1) Optimal F

I applied the Optimal F position sizing to a simple mean reversion algorithm where you buy on a break out in the direction of the 50-day moving average after a lower low occurs.

Code listing:

vars: numShares(0),initCapital$(50000),biggestLoss(0),OptF(0),risk$(0);


//keep track of biggest loss
biggestLoss = minList(positionProfit(1),biggestLoss);
//calculate the Optimal F with last 10 trades.
OptF = OptimalFGeo(10);
//reinvest profit or loss
risk$ = initCapital$ + netProfit;
//convert Optimal F to $$$
if OptF <> 0 then numShares = risk$ / (biggestLoss / (-1*OptF));
numShares = maxList(1,numShares);
//if Optf <> 0 then print(d," ",t," ",risk$ / (biggestLoss / (-1*OptF))," ",biggestLoss," ",optF);

if c > average(c,50) and low < low[1] then Buy numShares shares next bar at open + .25* range stop;

setStopPosition;
setProfitTarget(2000);

setStopLoss(3*avgTrueRange(10)*bigPointValue);
Strategy Using Optimal F

I have included the results below.  At one time during the testing the number of contracts jumped up to 23.  That is 23 mini Nasdaq futures ($20 * 7,300) * 23.  That’s a lot of leverage and risk.  Optimal doesn’t  always mean the best risk mitigation.  Please let me know if you find any errors in the code or in the logic.

 

Here is the ELD that incorporates the Strategy and the Function.USINGOPTIMALF

 

A Bar Scoring System – inspired by Keith Fitschen

In Keith’s wonderful book, “Building Reliable Trading Sytems”, he reveals several algorithms that classify an instruments’ movement potential.  In the part of the book that is titled Scoring by a Bar Type Criterion, he describes eight different two-day patterns that involve 3 different criteriaEight different Bar-Types

He looks at the relationship between today’s open and today’s close, today’s close and yesterday’s close, and today’s close in terms of the day’s range.  Bar-Types 1 to 4 all have the close of today >= close of yesterday.  Bar-Types 5 to 8 have close of today < close of yesterday.

I wanted to program this into my TradeStation and do some research to see if the concept is valid.  In his book, Keith tested a lot of different stocks and commodities.  In this post, I just test the ES, US, and Beans.  This form of research can be used to enhance an existing entry technique.

Here is how I defined the eight different bar types:

array : barTypeArray[8](false); 

midRange = (h + l)/2;

barTypeArray[0] = c >= c[1] and c > o and c >= midRange;
barTypeArray[1] = c >= c[1] and c > o and c < midRange;
barTypeArray[2] = c >= c[1] and c < o and c >= midRange;
barTypeArray[3] = c >= c[1] and c < o and c < midRange;
barTypeArray[4] = c < c[1] and c > o and c >= midRange;
barTypeArray[5] = c < c[1] and c > o and c < midRange;
barTypeArray[6] = c < c[1] and c < o and c >= midRange;
barTypeArray[7] = c < c[1] and c < o and c <= midRange;
Defining Eight Different Bar Types

I used a brute force approach by creating an 8-element array of boolean values.  Remember EasyLanguage uses a 0 index.  If the two -day pattern matches one of the eight criteria I assign the element a true value.  If it doesn’t match then a false value is assigned.  I use an input value to tell the computer which pattern I am looking for.  If I choose Bar-Type[0] and there is a true value in that array element then I take a trade.   By providing this input I can optimize over all the different Bar-Types.

Input : 
BarTypeNumber(0), // which bar type
buyOrSell(1), //1 to buy 2 to sell
numDaysToHold(2); //how many days to hold position




For cnt = 0 to 7 //remember to start at 0
Begin
If barTypeArray[cnt] = true then whichBarType = cnt;
end;

If whichBarType = BarTypeNumber then
begin
if buyOrSell = 1 then buy this bar on close;
if buyOrSell = 2 then sellshort this bar on close;
end;
Loop Thru Array to find Bar Type

Here are some results of looping through all eight Bar-Types, Buy and Sell, and holding from 1 to 5 days.

ES – ten – year results – remember these are hypothetical results with no commission or slippage.

Here’s what the equity curve looks like.   Wild swings lately!!

Beans:

Bonds

Keith was right – look at the Bar Category that bubbled to the top every time – the most counter-trend pattern.  My Bar-Type Number 7  is the same as Keith’s 8.  Here is the code in its entirety.

{Bar Scoring by Keith Fitschen
from his book "Building Reliable Trading Systems" 2013 Wiley}

Input : BarTypeNumber(0),
buyOrSell(1),
numDaysToHold(2);

vars: midRange(0);
array : barTypeArray[8](false);

midRange = (h + l)/2;

barTypeArray[0] = c >= c[1] and c > o and c >= midRange;
barTypeArray[1] = c >= c[1] and c > o and c < midRange;
barTypeArray[2] = c >= c[1] and c < o and c >= midRange;
barTypeArray[3] = c >= c[1] and c < o and c < midRange;
barTypeArray[4] = c < c[1] and c > o and c >= midRange;
barTypeArray[5] = c < c[1] and c > o and c < midRange;
barTypeArray[6] = c < c[1] and c < o and c >= midRange;
barTypeArray[7] = c < c[1] and c < o and c <= midRange;

vars: whichBarType(0),cnt(0);

For cnt = 0 to 7
Begin
If barTypeArray[cnt] = true then whichBarType = cnt;
end;

If whichBarType = BarTypeNumber then
begin
if buyOrSell = 1 then buy this bar on close;
if buyOrSell = 2 then sellshort this bar on close;
end;

If barsSinceEntry = numDaysToHold then
begin
If marketPosition = 1 then sell this bar on close;
If marketPosition =-1 then buytocover this bar on close;
end;
Bar Scoring Example

Keith’s book is very well researched and written.  Pick one up if you can find one under $500.  I am not kidding.  Check out Amazon.

 

 

What’s Our Vector Victor – Tiptoeing in the EL Collections

Tired of Manipulating Arrays – Try a Vector and a Queue

Vectors:

An array like structure but are dynamic and have a plethora of tools at your disposal.  Arrays are cool and can be multi-dimensional and can be easily manipulated.  But they require a lot of forethought as to how much size to reserve for their implementation.  Now don’t think this is going to be an advanced EasyLanguage tutorial, because it’s really not.  Most of us TRS-80, Ti-99/4A, Vic-20 and Commodore 64 trained programmers of the early ’80s have not welcomed objects with open arms and that is really a mistake.  In this sense we are like cavemen – we have all of the rudimentary tools at our disposal and can create some really cool stuff and we can really understand what we are doing.  With time and effort, we can get to the same place as object-oriented programmers.  We just don’t like the concept of using other’s tools as much as we like using ours.  So if you aren’t classically trained in programming you may have an advantage when tieing into the objects of a programming language.  This little tutorial is a very brief glimpse into a whole different world of programming.  The beauty is you can combine “old school” programming with objects – even if you don’t understand how the objects are truly constructed.    I want to introduce the concept of the Vector and the Queue-  truly cool Swiss Army knives.  First the vector.  Let’s just jump into some of the code – it really is simple.

Object Instantiation – a long word for declaring variable:
Using elsystem.collections;

Vars: Vector opVector(NULL),
Vector hiVector(Null),
Vector loVector(Null),
Vector clVector(Null),
Vector barVector(Null),
Queue timeStampQue(Null);
Once
Begin
barVector = new Vector;
opVector = new Vector;
hiVector = new Vector;
loVector = new Vector;
clVector = new Vector;
timeStampQue = new Queue;
end;
Instantiating and Declaring Vectors and Queue

You have to tell EasyLanguage you want to use some of the tools in the elsystem.collections.  You do this by simply tell it you are Using elsystem.collections.  The word collections is a catch-all for a bunch of different types of data structures.  Remember data structures are just programming constructs used to hold data – like an array.  All the variables that you declare in EasyLanguage are arrays – you just aren’t really aware of it.   When you index into them to get prior values then you become slightly aware of it.  In this portion of code, I create five vectors and one queue and assign them the Null or an empty value.  I just finished a programming gig where I had to build dynamically sized bars from the base data.  Kind of like creating 15, 30, 60-minute bars from a 5-minute bar chart or stream.   I did this using arrays because I wanted to be able to index into them to go back in time and I didn’t how far I wanted to go back.  So I declared some arrays with large dimensions to be safe.  This really takes a bite out of your resources which costs space and time.  I had played with Vector like objects in Python, so I thought I would post about them here and show how cool they are.  Remember this is a rudimentary program and could be streamlined and cleaned up.  Each vector will store their respective time, open, high, low and close values of the combined bar.  In a later post, I would like to do this with a Dictionary.  So the opVector will hold the open price, the hiVector will hold the high price and so on.

Build a Queue – why the extra ue?

I want to build 15-minute bars from 5-minute bars so I need to know when to sample the data to properly collect the corresponding data.  If I start at 9:30 then I want to sample the data at 9:45 and look back three bars to get the open and the highest high and the lowest low.  The close will simply be the close of the 9:45 bar.  I want to do this at 9:45, 10:00. 10:15 and so on.  I could manipulate the time and use the modulus function to see if the minutes are multiples of 15 and I tried this but it didn’t work too well.  So I thought since I was already in the collections why not build a list or a queue with all the timestamps I would need.  This is how I did it.

vars: hrs(0),mins(0),barMult(3),combBarTimeInterval(0),totBarsInHour(0),startTime(930),endTime(1615),cnt(0);

Once
Begin
mins = fracPortion(t/100);
combBarTimeInterval = barInterval*barMult;
While value1 < endTime
Begin
cnt = cnt + 1;
Value1 = calcTime(startTime,cnt*combBarTimeInterval);
// print("Inside queue : ",Value1," ",cnt*combBarTimeInterval);
timeStampQue.Enqueue(Value1);
end;
end;
Populating A Queue With Time Stamps

I simply use the CalcTime function to add 15-minute intervals to the start time and then I add them to the queue:  timeStampQue.Enqueue(Value1);  You access the methods or tools to a class by using the dot (” . “) notation.  Once I instantiated or created the timeStampQue I gained access to all the tools that belong to that object.  The Enqueue method simply appends the list the value that you pass it.  I would have preferred the method to be labeled simply add.  How did I figure out the right method name you ask?  I accessed the Dictionary from the View menu in the TDE.  Here is a picture to help:

Dictionary:

I use the keyword Once to just execute the code one time.  You could have said if BarNumber = 1, but why not use the tools at your disposal,   I figured out the combBarTimeInterval by using the 5-minute bar multiplier (3).  I then looped from startTime to endTime in 15-minute intervals and stored the timeStamps in the queue.  So every time stamp I need is in the timeStampQue.  All I need now is to compare the time of the 5-minute bar to the time stamps inside the queue.  This is where using object really come in handy.

Queue Methods:

Old school would have looped through all of the elements in the list and compared them to the value I was seeking and if found it would return true.  In the object world, I can simply ask the object itself to see if the value is in it:

condition1 = timeStampQue.Contains(t);

Cool!  If condition1 is true then I know I am sitting on the 5-minute bar that shares the same timestamp as a 15-minute bar.  If the time stamps are the same then I can start building the large timeframe from the lower timeframe.  You add elements to a vector by using the insert method.  I simply looked it up in the dictionary.   I had to specify where to insert the value in the vector.  I simply inserted each value into the [0] location.  Remember we are inserting so everything else in the vector is moved down.

Vector Methods:

 

If condition1 then
Begin
barVector.insert(0,t);
opVector.insert(0,open[2]);
hiVector.insert(0,highest(h[0],3));
loVector.insert(0,lowest(l[0],3));
clVector.insert(0,close[0]);
end;
Inserting Values at Vector Location 0

I only need to keep track of the last 10 15-minute bars, so once the vector count exceeded 10, I simply popped off the value at the back end – pop_back().  I figured this out by looking at Martin Whittaker’s awesome website – www.markplex.com. 

 

If opVector.Count > 10 then 
begin
barVector.pop_back();
opVector.pop_back();
hiVector.pop_back();
loVector.pop_back();
clVector.pop_back();
end;
Popping the Back-End

To check my work I printed the 15-minute bars on each 5-minute bar to make sure the bars were being built properly.  These data structures expect an object to be inserted, added, popped so when you print out one of their values you have to tell the print statement what the object should be translated as.  Here the keyword asType comes into play.  Take a look at my code, and you will see what I mean.  I hope this gets you excited about objects because the collections class can save you a ton of time and is really cool.  Use it and you can brag that you are an OOP programmer at your next cocktail party.

Code Listing:
Using elsystem.collections;

Vars: Vector opVector(NULL),
Vector hiVector(Null),
Vector loVector(Null),
Vector clVector(Null),
Vector barVector(Null),
Queue timeStampQue(Null);
Once
Begin
barVector = new Vector;
opVector = new Vector;
hiVector = new Vector;
loVector = new Vector;
clVector = new Vector;
timeStampQue = new Queue;
end;

vars: hrs(0),mins(0),barMult(3),combBarTimeInterval(0),totBarsInHour(0),startTime(930),endTime(1615),cnt(0);

Once
Begin
mins = fracPortion(t/100);
combBarTimeInterval = barInterval*barMult;
While value1 < endTime
Begin
cnt = cnt + 1;
Value1 = calcTime(startTime,cnt*combBarTimeInterval);
// print("Inside queue : ",Value1," ",cnt*combBarTimeInterval);
timeStampQue.Enqueue(Value1);
end;
end;



condition1 = timeStampQue.Contains(t);

Print(d," ",t," ",condition1);

If condition1 then
Begin
barVector.insert(0,t);
opVector.insert(0,open[2]);
hiVector.insert(0,highest(h[0],3));
loVector.insert(0,lowest(l[0],3));
clVector.insert(0,close[0]);
end;

If opVector.Count > 10 then
begin
barVector.pop_back();
opVector.pop_back();
hiVector.pop_back();
loVector.pop_back();
clVector.pop_back();
end;

vars:vectCnt(0);
print(d," ",t);
If opVector.Count > 9 then
Begin
For vectCnt = 0 to 9
begin
print(vectCnt," ",barVector.at(vectCnt) astype int," ",opVector.at(vectCnt) astype double," ",hiVector.at(vectCnt) astype double," ",loVector.at(vectCnt) astype double," ",clVector.at(vectCnt) astype double);
end;
end;
Program in its Entirety