Category Archives: EasyLanguage Function

EasyLanguage Code for Optimal F (Multi-Charts and VBA too!)

Optimal F in EasyLanguage for TradeStation and MultiCharts

Here is the code for the Optimal F calculation.  For a really good explanation of Optimal F I refer you to Ralph Vince’s Book Portfolio Management FORMULAS.  We had programmed this years ago for our Excalibur software and I was surprised the EasyLanguage code was really all that accessible on the internet.  Finding the optimal f is found through an iterative process or in programmers terms a loop.  The code is really quite simple and I put it into a Function.  I decided to create this function because I wanted to demonstrate the ideas from my last post on how a function can store variable and array data.  Plus this code should be readily available somewhere out there.

//OptimalFGeo by George Pruitt
//My interpretation Sept. 2018
//www.georgepruitt.com
//georgeppruitt@gmail.com

input: minNumTrades(numericSimple);
vars: totalTradesCount(0),tradeCnt(0);
array: tradesArray[500](0);

vars: iCnt(00),jCnt(00),grandTot(0),highI(0);
vars: optF(0.0),gMean(0.0),fVal(0.0),HPR(0.0),TWR(0.0),hiTWR(0.0);
vars: biggestLoser(0.0),gat(0.0);

totalTradesCount = totalTrades;
If totalTradesCount > totalTradesCount[1] then
begin
	tradeCnt = tradeCnt + 1; 
	tradesArray[tradeCnt] = positionProfit(1);
end;

// Taken from my Fortran library - GPP and Vince Book PMF

optF = 0.0;
gMean = 1.00;
gat   = 0.00;
//Only calculate if new trade
IF(tradeCnt>minNumTrades and totalTradesCount > totalTradesCount[1]) then 
Begin
	biggestLoser = 0;
	grandTot = 0;
	For iCnt = 1 to tradeCnt //get the biggest loser
	begin
   		grandTot = grandTot + tradesArray[iCnt];
   		IF(tradesArray[iCnt]<biggestLoser) then biggestLoser = tradesArray[iCnt];
	end;
//	print(grandTot," ",biggestLoser);
	IF({grandTot > 0 and} biggestLoser <0) then 
	begin
//		print("Inside TWR Calculations");
		highI = 0;
		hiTWR = 0.0;
		for iCnt = 1 to 100
		begin
			fVal = .01 * iCnt;
			TWR = 1.0;
			for jCnt = 1 to tradeCnt // calculate the Terminal Wealth Relative
			begin
    			HPR = 1. + (fVal * (-1*tradesArray[jCnt]) / biggestLoser);
    			TWR = TWR * HPR;
 //   			print(fVal," ",iCnt," " ,jCnt," Trades ",tradesArray[jCnt]," HPR ",HPR:6:4," TWR : ",TWR:6:4," hiTWR",hiTWR:6:4," bl ",biggestLoser);
			end;
//			print(iCnt," ",TWR," ",hiTWR);
			IF(TWR>hiTWR) THEN
			begin
    			hiTWR = TWR;
    			optF = fVal;    	// assign optF to fVal in case its the correct one		
			end
			else
    			break;                     //highest f found - stop looping
		end;		
		If (TWR <= hiTWR or optF >= 0.999999) then
		begin
			TWR  = hiTWR;
			OptimalFGeo = optF;  //assign optF to the name of the function
		end;	
		gmean = power(TWR,(1.0 / tradeCnt));
		
		if(optF<>0) then GAT   = (gMean - 1.0) * (biggestLoser / -(optF));		
		print(d," gmean ",gmean:6:4," ",GAT:6:4);  // I calculate the GMEAN and GeoAvgTrade
	end;
end;
Optimal F Calculation by Ralph Vince code by George Pruitt

VBA version of Optimal F

For those of you who have a list of trades and want to see how this works in Excel here is the VBA code:

Sub OptimalF()

    Dim tradesArray(1000) As Double
    i = 0
    biggestLoser = 0#
    Do While (Cells(3 + i, 1) <> "")
        tradesArray(i) = Cells(3 + i, 1)
        If tradesArray(i) < bigLoser Then biggestLoser = tradesArray(i)
        i = i + 1
    Loop
    tradeCnt = i - 1
    highI = 0
    hiTWR = 0#
    rc = 3
    For fVal = 0.01 To 1 Step 0.01
        TWR = 1#
        For jCnt = 0 To tradeCnt
            HPR = 1# + (fVal * (-1 * tradesArray(jCnt)) / biggestLoser)
            TWR = TWR * HPR
            Cells(rc, 5) = jCnt
            Cells(rc, 6) = tradesArray(jCnt)
            Cells(rc, 7) = HPR
            Cells(rc, 8) = TWR
            rc = rc + 1
        Next jCnt
        Cells(rc, 9) = fVal
        Cells(rc, 10) = TWR
        rc = rc + 1

        If (TWR > hiTWR) Then
            hiTWR = TWR
            optF = fVal
        Else
            Exit For
        End If

    Next fVal
    If (TWR <= hiTWR Or optF >= 0.999999) Then
        TWR = hiTWR
        OptimalFGeo = optF
    End If
    Cells(rc, 8) = "Opt f"
    Cells(rc, 9) = optF
    rc = rc + 1
    gMean = TWR ^ (1# / (tradeCnt + 1))
    If (optF <> 0) Then GAT = (gMean - 1#) * (biggestLoser / -(optF))
    Cells(rc, 8) = "Geo Mean"
    Cells(rc, 9) = gMean
    rc = rc + 1
    Cells(rc, 8) = "Geo Avg Trade"
    Cells(rc, 9) = GAT

End Sub
VBA code for Optimal F

I will attach the eld and .xlsm file a little later.

 

 

 

Multiple Ouput function in EasyLanguage

In the Pascal programming language you have Procedures and Functions.  Procedures are used when you want to modify multiple variables within a sub-program.  A function is a sub-program that returns a single value after it has been modified by say a formula.  EasyLanguage combines procedures and functions into one sub-program called a function.  Functions and procedures both have a formal parameter definition –  a list that describes the type of parameters that are being received by the calling program.  In Pascal procedures, you pass the address of the value that you want changed.  By modifying the contents of the address you can pass the value back and forth or in and out of the procedure.  In functions you pass by value.   Remember the parameter in a normal function call is used to instruct something within the body of the function and is not altered (e.g. the number 19 in value1 = average(c,19)).  This value doesn’t need to be modified it’s just used.  Look at the following code:

Here I am modifying mav1, mav2 and mav3 within the function and then passing the values back to the calling strategy/indicator/paintbar.  All functions must return a value so I simply assign the value 1 to the function name.  The key here is the keyword numericRef, once I change the values located in the addresses of mav1, mav2 and mav3 (address are provided by the keyword numericRef), they will be made available to the calling program.  This code allows the function to return more than just one value.

Re-Entry After Taking A Profit

Here is some code I have been working on.  I will go into detail on the code a little later.  But this is how you monitor re-entering at a better price after taking a profit.  The problem with taking profits on longer term trend following systems is that the logic that got you into the position is probably still true and you will notice your algorithm will re-enter in the same direction.  So you need to inform your algorithm not to re-enter until a certain condition is met.  In this example, I only re-enter at a better price if the condition that got me into the trade is still valid.

Inputs: swingHiStrength(2),swingLowStrength(2),numDaysToLookBack(30),stopAmt$(500),profitAmt$(1000),getBackInAfterProfAmt$(250);
vars: mp(0),longProfTaken(false),shortProfTaken(false);

mp = marketPosition;

if not(longProfTaken) and mp <> 1 then Buy("BreakOut-B") next bar at highest(h[1],20) on a stop;
if not(shortProfTaken) and mp <>-1 then SellShort("BreakOut-S") next bar at lowest(l[1],20)  on a stop;

//If mp[0] = 0 and mp[1] = 1 then print(date," ",exitPrice(1)," ",entryPrice(1));

If longProfTaken then 
Begin
	If c < exitPrice(1) - getBackInAfterProfAmt$/bigPointValue then
	begin 
		longProfTaken = false;
		If mp <> -1 then buy("longRe-Entry") next bar at open;
	end;
end;

If shortProfTaken then 
Begin
	If c > exitPrice(1) + getBackInAfterProfAmt$/bigPointValue then
	begin 
		shortProfTaken = false;
		If mp <>-1 then sellShort("shortRe-Entry") next bar at open;
	end;
end;


If mp = 1 and c > entryPrice + profitAmt$/bigPointValue then 
begin
	sell this bar on close;
	longProfTaken = true;
end;

If mp =-1 and c < entryPrice - profitAmt$/bigPointValue then 
begin
	buyToCover this bar on close;
	shortProfTaken = true;
end;

If mp = -1 then longProfTaken = false;
If mp = 1 then shortProfTaken = false;

//if mp = 1 then setStopLoss(stopAmt$);
//if mp = 1 then setProfitTarget(profitAmt$);
Re-Entering At Better Price After Profit

Learn to Program Pyramiding Algorithm

Would you like to learn how to do this?  Check back over the next few days and I will show you to do it.  Warning:  its not straightforward as it seems – some tricks are involved.  Remember to sign up for email notifications of new posts.

UPDATE[1]:  I have recorded an introductory webcast on how to program this pyramiding scheme.  This webcast is Part 1 and illustrates how to brainstorm and start thinking/programming about a problem.  Part 1 introduces some concepts that show how you can use and adapt some of EasyLanguage built-in reserved words and functions.  I start from the perspective of a somewhat beginning EasyLanguage programmer  – one that knows enough to maybe not get the problem solved, but at least get the ball rolling.  The final code may not look anything like the code I present in Part 1.  However it is sometimes important to go down the wrong trail so that you can learn the limitations of a programming language.  Once you know the limitations, you can go about programming workarounds and fixes.  I hope you enjoy Part 1  I should have Part 2 up soon.  Don’t be too critical, this is really the first webcast I have recorded.  You’ll notice I repeat myself and I refer  to one function input as a subscript.  Check it out:  https://youtu.be/ip-DyyKpOTo

Adding positions at fixed intervals.

Hash Table In EasyLanguage [Part 2]

Using The Hash Table

Now that we have created an empty Hash Table and the Hash Index it is now time to start filling the table up with the appropriate information.  As I pointed out in my last post, every day of any given year can be represented by a nine character string. If January 1st lands on a Tuesday, you can express this day with the following string, “1stTueJan.” That is if you want to ignore the year and in this case, we do.

Mapping Into the Hash Table

The table has already been prepared as well as the index.  All we have to do is map the current day into the index.  The location of the index value in the Hash Index array will then be used to locate the day’s location in the Hash Table.  We will use a function to convert the current day of the year into a value our Hash Index can interpret.

Here is the code to the function.  Don’t fret too much at the number of lines of code!

inputs: testDate(numericSeries);

vars: testMonth(0),tempStr("");
Array : prefixStrArr[6](""),dayofweekStr[5](""),monthName[12]("");
vars: monCnt(0),tueCnt(0),wedCnt(0),thuCnt(0),friCnt(0),tempDate1(0),tempDate2(0);
vars: freshStart(false),occurString(""),dayString(""),monthString("");
vars: whatOccurOfMonthStr(""),cnt(0),td(0),myCnt(0),daysBack(0);

preFixStrArr[1] = "1st";
preFixStrArr[2] = "2nd";
preFixStrArr[3] = "3rd";
preFixStrArr[4] = "4th";
preFixStrArr[5] = "5th";
preFixStrArr[6] = "6th";

dayOfWeekStr[1] = "Mon";
dayOfWeekStr[2] = "Tue";
dayOfWeekStr[3] = "Wed";
dayofWeekStr[4] = "Thu";
dayOfWeekStr[5] = "Fri";

monthName[1] = "Jan";
monthName[2] = "Feb";
monthName[3] = "Mar";
monthName[4] = "Apr";
monthName[5] = "May";
monthName[6] = "Jun";
monthName[7] = "Jul";
monthName[8] = "Aug";
monthName[9] = "Sep";
monthName[10] = "Oct";
monthName[11] = "Nov";
monthName[12] = "Dec";

tempDate1 = month(testDate[0]);
tempDate2 = month(testDate[1]);
cnt = 0;monCnt = 0;tueCnt=0;wedCnt=0;thuCnt=0;friCnt=0;
While (month(date) = month(date[cnt])) and cnt < 30
Begin
//	print(date," ",date[cnt]," ",cnt);
	cnt = cnt + 1;
end;
daysBack = cnt -1;

If daysBack < 0 then daysBack = 0;

For cnt = daysBack downto 0
begin
	If dayOfWeek(date[cnt]) = 1 then monCnt = monCnt + 1;
	If dayOfWeek(date[cnt]) = 2 then tueCnt = tueCnt + 1;	
	If dayOfWeek(date[cnt]) = 3 then wedCnt = wedCnt + 1;
	If dayOfWeek(date[cnt]) = 4 then thuCnt = thuCnt + 1;
	If dayOfWeek(date[cnt]) = 5 then friCnt = friCnt + 1;
end;
//print("counts: ",monCnt," ",tueCnt," ",wedCnt," ",thuCnt," ",friCnt);

If dayOfWeek(date) = Monday then tempStr = preFixStrArr[monCnt];
If dayOfWeek(date) = Tuesday then tempStr = preFixStrArr[tueCnt];
If dayOfWeek(date) = Wednesday then tempStr = preFixStrArr[wedCnt];
If dayOfWeek(date) = Thursday then tempStr = preFixStrArr[thuCnt];
If dayOfWeek(date) = Friday then tempStr = preFixStrArr[friCnt];

tempStr = tempStr + dayOfWeekStr[dayOfWeek(date)];
tempStr = tempStr + monthName[month(date)];
GetWhichWeekMonth = tempStr;
GetWhichWeekMonth Function

Here is where using an integer representation of the date would reduce the number of lines of code tremendously.  Well, I made my bed I might as well sleep in it.  You will see some duplication between this code and the Hash Table creator function.  I have to store names for the week rank, day of the week, and month in arrays.  There isn’t a simple function that will pull the week rank from any given date.  So I simply take the date and work my way back to the beginning of the month counting each weekday as I go along.

For cnt = daysBack downto 0
begin
	If dayOfWeek(date[cnt]) = 1 then monCnt = monCnt + 1;
	If dayOfWeek(date[cnt]) = 2 then tueCnt = tueCnt + 1;	
	If dayOfWeek(date[cnt]) = 3 then wedCnt = wedCnt + 1;
	If dayOfWeek(date[cnt]) = 4 then thuCnt = thuCnt + 1;
	If dayOfWeek(date[cnt]) = 5 then friCnt = friCnt + 1;
end;

Getting The Hash Index

The number that is stored in the individual counters (monCnt, tueCnt, etc.) determines which week of the month the current day is located.  I build the string through concatenation.  First I get the week rank (“1st”, “2nd”, “3rd”, “4th”, “5th”), add the name of the day and then add the month.  The end result looks like “1stMonJan”.  From here I cross-reference the Hash Index and pull out the location of the of the string (aka index.)  Here is the function GetHashIndex.

input: hashIndex[n](stringArrayRef),hashTableRows(numericSimple),searchString(string);
vars: iCnt(0),done(false);

GetHashIndex = 0;
done = false;

For iCnt = 1 to hashTableRows
Begin
//	print("Looking for: ",searchString," ",hashIndex[iCnt]," ",iCnt);
	If searchString = hashIndex[iCnt] then 
	begin
		done = true;
		GetHashIndex = iCnt;
	end;
	If done then break;
end;
GetHashIndex

As you can see it is a linear search that returns the Hash Index’s Index.  Check out how I prematurely exit the loop by using the keyword Break.  This keyword knocks you out of any loop where it is located.  If you have a nested loop, the break only gets you out of that current loop where it is located.

Hast Table Indicator

Now how can we pull all this together to create a useful trading tool.  I used these tools to create an indicator that plots the average daily change from one day to the next.  So, if today is the “3rdMonJune” and the indicator reads 0.52, this represents that over the last X years the average percentage change is a plus .5%.  Would it make sense to buy the “2ndFriJun” and exit on the close of the “3rdMonJune?”  Maybe.

Here is the code for the Hash Table indicator.

vars: returnValString(""),iCnt(0),jCnt(0); 
array: weekDayMonthIndex[300]("");
array: HashTable[300,100](0);
array: timeLine[300](0);
vars: searchString(""),numYearsCollected(0),hashIndex(0);
vars: yCnt(0),numYears(0);
vars: hashRows(300);
vars: myBarCount(0),maxNumYearsInHash(0),avgDailyChange(0),dailyChangeSum(0);

If barNumber = 1 then  //build the hash index - index form "1stMonJul" "2ndFriDec"
begin
	Value1 = HashIndexCreator(weekDayMonthIndex);
end;

numYearsCollected = HashTableCreator(HashTable,weekDayMonthIndex);  {Build hash table as we go along}

If year(date) <> year(date[1]) then numYears = numYears + 1; 

If numYearsCollected > 3 then  // only pull information if there is at least three years of data
Begin
	searchString = GetWhichWeekMonth(date);	// convert today's date into a compatible Hash Index value
	hashIndex = GetHashIndex(weekDayMonthIndex,hashRows,searchString);  // find the location of today's value in the Hash Index
	dailyChangeSum = 0;;
//	print(d," ",searchString," ",hashIndex);
	For yCnt = 2 to numYearsCollected
	Begin
		dailyChangeSum = dailyChangeSum + HashTable[hashIndex,yCnt];
	end;
	avgDailyChange = dailyChangeSum/numYearsCollected;
	if year(date) = 116 then print(d," ",searchString," ",numYearsCollected," ",avgDailyChange);
	if numYearsCollected > numYears-1  then plot1(avgDailyChange,"AvgChgFromYesterday");
End;
HashTableIndicator

Results of Using the Hash Table

Here is a simple output of the results from the indicator for the year of 2016.  I sorted the data based on highest average daily change and number of years collected.

1160729 5thFriJul 7 0.95
1161031 5thMonOct 5 0.62
1160115 2ndFriJan 16 0.56
1160830 5thTueAug 7 0.55
1160713 2ndWedJul 17 0.52
1160812 2ndFriAug 17 0.52
1160519 3rdThuMay 16 0.43
1161003 1stMonOct 17 0.38
1160112 2ndTueJan 16 0.38
1160223 4thTueFeb 16 0.38
1161122 4thTueNov 16 0.37
1160804 1stThuAug 17 0.35
1160316 3rdWedMar 16 0.35
1160711 1stMonJul 17 0.34
1161121 3rdMonNov 17 0.34
1160225 4thThuFeb 16 0.34
1160517 3rdTueMay 16 0.34
1160610 2ndFriJun 16 0.34
1161215 3rdThuDec 17 0.33

It looks like the buying the close “4thThuJul” is the way to go!  But since there are only seven observations I think would think twice.  But, buying the close on the day prior to “2ndFriJan” might offer that technical advantage you’re looking for.