Pyramiding with Python

A reader of the “UATSTB” asked for an example of how to pyramid in the Python System BackTester (PSB).  I had original bug where you couldn’t peel off all of the positions at once.  If you tried then the PSB would crash.  I have now fixed that problem and here are the necessary lines to add on another position after the initial positions is put on.   I am using a simple moving average crossover to initiate the first position and then if the longer term moving average is positive for 4 days in a row I add on another position.  That’s it.  A max of two position only.  The system either gets reversed to the other side, stopped out or takes profits.  Attached in this post is the module that fixes the pyramiding problem and the code for this system in its entirety.   I will also put the fix in the version 2.0 download.

        upCnt = 0
        dnCnt = 0
        for j in range(4):
            if sAverage(myClose,39,i,j) > sAverage(myClose,39,i,j+1):
                upCnt += 1
            if sAverage(myClose,39,i,j) < sAverage(myClose,39,i,j+1):
                dnCnt += 1


#Long Entry Logic
        if (mp != 1) and avg1 > avg2 and prevAvg1 < prevAvg2:
            profit = 0
            price = myClose[i]
            tradeName = "DMA Buy"
			
#Long Pyramid Logic
        if (mp == 1) and upCnt == 4:
            profit = 0
            price = myClose[i]
            tradeName = "LongPyra"
Pyramiding the Long Side of a Dual Moving Average

I only put in the summary of code that will create the initial position and then add 1 more.  Notice the highlighted code from line 3 to 7.  Here I am using a simple loop to determine if the the 39-day moving average is increasing/decreasing consecutively over the past four days.  If the upCnt == 4 then I add a long position.  Notice how I test in the Long Pyramid Logic the values of mp and upCnt.  I only go long another position if I am already long 1 and upCnt == 4.  

Here is a print out of some of the trades:

20040923      DMA Buy  1 127.79000       0.00       0.00
20040923     LongPyra  1 127.79000       0.00       0.00
20041006       L-Prof  2 130.79000    5800.00   14560.00
20041116    DMA Short  1 126.14000       0.00       0.00
20041119    ShortPyra  1 128.64000       0.00       0.00
20041201       S-Prof  2 125.64000    3300.00   17860.00
20050121      DMA Buy  1 127.85000       0.00       0.00
20050202     LongPyra  1 126.01000       0.00       0.00
20050222       L-Prof  2 129.01000    3960.00   21820.00
20050418    DMA Short  1 128.18000       0.00       0.00
20050419     S-MMLoss  1 130.28000   -2200.00   19620.00
20050616      DMA Buy  1 131.41000       0.00       0.00
20050621     LongPyra  1 133.02000       0.00       0.00
20050630     L-MMLoss  2 130.48000   -3670.00   15950.00
20050809    DMA Short  1 135.95000       0.00       0.00
20050810   RevShrtLiq  1 137.78000   -1930.00   14020.00
20050810      DMA Buy  1 137.78000       0.00       0.00
20050810     LongPyra  1 137.78000       0.00       0.00
20050829       L-Prof  2 140.98000    6200.00   20220.00
Trade Listing Of DMA with Pyramiding

While I was posting the trades I found something that struck my funny – look at line 5 (highlighted).  The short pyramid trade occurs at a higher price than the initial short – at first I thought I had made a programming error.  So I thought I would double check the code and then do some debugging.  Instead of invoking the debugger which is really cool and easy to use I decided to just print out the results to the console.

        for j in range(4):
            if sAverage(myClose,39,i,j) > sAverage(myClose,39,i,j+1):
                upCnt += 1
            if sAverage(myClose,39,i,j) < sAverage(myClose,39,i,j+1):
                dnCnt += 1
            if tempDate == 20041119:
                print(myDate[i]," ",j," ",sAverage(myClose,39,i,j))
																	 
'''Output of debugging using a print statement
20041119   0   130.68692307692308
20041119   1   130.69538461538463
20041119   2   130.74871794871794
20041119   3   130.7723076923077'''
Debugging using Print Statements

As you can see the longer term moving average is moving down event though price has increased.  Take a look at this chart and you can see multiple occurrences of this.

Examples of Divergence of Moving Average and Price

Remember to copy and replace the tradeClass.py in you PSB2.0 directory – this fixes the pyramid bug.  Also copy the DMAPyra.py to the same directory.  If you haven’t as of yet download the PSB from this website and buy the book for a very good description.

PostPythonCode

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

A Slightly More Eloquent Approach to Programming Our Pyramiding E-Mini DayTrading Algorithm.

Okay let’s see how I was able to add some eloquence to the brute force approach to this pyramiding algorithm.  The original code included multiple entry directives and a ton of hard coded numerical values.   So let me show you how I was able to refine the logic/code and in doing so make it much more flexible.  We might lose a little bit of the readability, but we can compensate by using extra commentary.

First off, let’s add flexibility by employing input variables.  In this case, we need to inform the algorithm the distance from the open to add additional positions and the max number of entries allowed for the day.

inputs : pyramidDistance(5),maxDailyEntries(3);

Now we need to set somethings up for the first bar of the day.  Comparing the date of today with the date of yesterday is a good way to do this.

if d<>d[1] then 
begin
	canSell = true;
	sellMult = 1;
	sellStop = -999999;
	entries = 0;
end;
First bar of the day housekeeping.

Here is a neat way to keep track of the number of entries as they occur throughout the trading day.  Remember the function EntriesToday(date) will not provide the information we need.

mp = marketPosition * currentShares;

if mp[1] <> mp and mp <> 0 then entries = entries + 1;
How to track the number of entries for today.

If the last bar’s mp[1] is not equal to the current bar’s mp then and mp is not equal to zero then we know we have added on another entry.  Okay now let’s think about eliminating the “brute force” approach.

Instead of placing multiple order entry directives I  only want to use one with a variable stop level.  This stop level will be guided by the variable SellMult.  We start the day with a wacky sell stop level and then calculate it based on the SellMult variable and PyramidDistance input.

if low <= sellStop  then
begin
	sellMult = sellMult + 1;
end;

sellStop = openD(0) - sellMult * pyramidDistance;
Calculate and adapt sell stop level as we go along.

So on the first bar of the day the sellStop = openD(0) – sellMult * pyramidDistance or sellStop = openD(0) – 1 * 5.  Or 5 handles below the open.  Note you an change the pyramidDistance input and make it three to match the previous examples.

if entries = maxDailyEntries then canSell = false;
if time < sess1EndTime and canSell then sellShort 1 contract next bar at sellStop stop;
if mp <=-1 {and barsSinceEntry > 0} then buyToCover next bar at sellStop + 2* pyramidDistance stop;

setexitonclose;
That's it! Pretty simple isn't it?

Ok, we need to tell the computer to turn off the ability to place orders if one of two things happens:  1) we have reached the maxDailyEntries or 2) time >= sess1EndTime.    You could make the time to stop entering trades an input as well.  If neither criteria applies then place an order to sellShort at our sellStop level.   If price goes below our sell stop level then we know we have been filled and the new sellStop level needs to be recalculated.  See how we use a calculation to adapt the stop level with a single order placement directive?  This is where the eloquence comes into play.  QED.

Now you code the opposite side and then see if you can make money  (hypothetically speaking of course) with it.  If you think about it, why does this not work.  And the not so obvious reason is that it trades too much.  Other than trading too much it makes perfect sense – buy or sell by taking a nibbles at the market.  If the market takes off then take a big bite.  The execution costs of the nibbles are just way too great.  So we need to think of a filtering process to determine when it is either better to buy or sell or when to trade at all.  Good Luck with this ES [emini S&P ]day trading algorithm!

inputs : pyramidDistance(5),maxDailyEntries(3);
vars: mp(0),icnt(0),sellStop(0),sellMult(0),canSell(true),entries(0);

if d<>d[1] then 
begin
	canSell = true;
	sellMult = 1;
	sellStop = -999999;
	entries = 0;
end;

mp = marketPosition * currentShares;

if mp[1] <> mp and mp <> 0 then entries = entries + 1;
if mp[1] = -1 and mp[0] = 0 then canSell = false;
if time > 1430 then canSell = false;

if low <= sellStop  then
begin
	sellMult = sellMult + 1;
end;

sellStop = openD(0) - sellMult * pyramidDistance;
if entries = maxDailyEntries then canSell = false;
if time < sess1EndTime and canSell then sellShort 1 contract next bar at sellStop stop;
if mp <=-1 {and barsSinceEntry > 0} then buyToCover next bar at sellStop + 2* pyramidDistance stop;

setexitonclose;
Much More Flexible Code

Long Entry Logic and EasyLanguage Code for Pyramiding Algorithm

Sorry for the delay in getting this up on the web.  Here is the flip side of the pyramiding day trade scheme from the buy side perspective.  I simply flipped the rules.  In some cases, to keep the programming a little cleaner I like to keep the buy and sellShort logic in two separate strategies.  So with this chart make sure you insert both strategies.

And here is the code:

vars: mp(0),lastTradePrice(0),canBuy(true);

mp = marketPosition * currentContracts;

if date[0] <> date[1] then
begin
	canBuy = true;
end;

if mp = 1 then canBuy = false; 
if time > 1430 then canBuy = false;

if mp = 0 and canBuy = true then buy next bar at OpenD(0) + 3 stop;
if mp = 1 then buy next bar at OpenD(0) + 6 stop;
if mp = 2 then buy next bar at OpenD(0) + 9 stop;

if mp = 1 then lastTradePrice = OpenD(0) + 3;
if mp = 2 then lastTradePrice = OpenD(0) + 6;
if mp = 3 then lastTradePrice = OpenD(0) + 9;


if mp <> 0 then sell next bar at lastTradePrice - 3 stop;

if mp = 3 and barsSinceEntry > 0 and highD(0) > lastTradePrice + 3 then sell next bar at highD(0) - 3 stop;

setExitOnClose;
Pyramiding 3 Handles Up and Trailing Stop

EasyLanguage Code for Pyramiding a Day-Trading System w/video [PART-2]

 

Check out the latest video on Pyramiding.

Here is the finalized tutorial on building the pyramiding ES-day-trade system that was presented in the last post.

I will admit this video should be half as long as the end result.  I get a bit long-winded.  However, I think there are some good pointers that should save you some time when programming a similar system.

EasyLanguage Source:

Here is the final code from the video:

vars: mp(0),lastTradePrice(0),canSell(true);

mp = marketPosition * currentContracts;

if date[0] <> date[1] then
begin
	canSell = true;  // canSell on every day
end;

if mp = -1 then canSell = false; // one trade on - no more
if time > 1430 then canSell = false; //no entries afte 230 central

if mp = 0 and canSell = true then sellShort next bar at OpenD(0) - 3 stop;

if mp = -1 then sellShort next bar at OpenD(0) - 6 stop; //add 1
if mp = -2 then sellShort next bar at OpenD(0) - 9 stop; //add 2

if mp = -1 then lastTradePrice = OpenD(0) - 3; //keep track of entryPrice
if mp = -2 then lastTradePrice = OpenD(0) - 6;
if mp = -3 then lastTradePrice = OpenD(0) - 9;


if mp <> 0 then buyToCover next bar at lastTradePrice + 3 stop; // 3 handle risk on last trade

// next line provides a threshold prior to engaging trailing stop
if mp = -3 and barsSinceEntry > 0 and lowD(0) < lastTradePrice - 3 then buyToCover next bar at lowD(0) + 3 stop;

setExitOnClose;
EasyLanguage for Pyramiding and Day-Trading ES

What we learned here:

  • can’t use entriesToday(date) to determine last entry price
  • must use logic to not issue an order to execute on the first bar of the next day
  • mp = marketPosition * currentContracts is powerful stuff!

In the next few days, I will publish the long side version of this code and also a more eloquent approach to the programming that will allow for future modifications and flexibility.

Let me know how it works out for you.

Take this code and add some filters to prevent trading every day or a filter to only allow long entries!

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.

Utilizing Indicator Functions with Multi-Data on MultiCharts

A good portion of my readers use MultiCharts and the similarities between their PowerLanguage and EasyLanguage is almost indistinguishable.  However, I came across a situation where one my clients was getting different values between an indicator function call and the actual plotted indicator when using Multi-Data.

Here is the code that didn’t seem to work, even though it was programmed correctly in TradeStation.

vars:
oDMIPlus1(0),oDMIMinus1(0),oDMI1(0),oADX1(0),oADXR1(0),oVolty1(0),
oDMIPlus2(0,data2),oDMIMinus2(0,data2),oDMI2(0,data2),oADX2(0,data2),oADXR2(0,data2),oVolty2(0,data2),
ema1(0),ema2(0,data2),trendUp(false);



Value1 = DirMovement( H, L, C , Data1ADXLen, oDMIPlus1, oDMIMinus1, oDMI1, oADX1, oADXR1, oVolty1 ) ;
Value2 = DirMovement( H of data2, L of data2, C of data2, Data2ADXLen, oDMIPlus2, oDMIMinus2, oDMI2, oADX2, oADXR2, oVolty2 );

 

Pretty simple – so what is the problem.  Data aliasing was utilized in the Vars: section – this keeps the indicator from being calculated on the time frame of data1.  Its only calculated on the data2 time frame – think of data1 being a 5 min. chart and data2 a 30 min. chart.  I discovered that you have to also add data aliasing to not just the variables used in the indicator function but also to the function call itself.  This line of code fixed the problem:


DirMovement( H of data2, L of data2, C of data2, Value2, oDMIPlus2, oDMIMinus2, oDMI2, oADX2, oADXR2, oVolty2 )data2;
Add data2 after the function call to tie it to data2.

 

See that!  Just add Data2 to the end of the function call.  This verifies in TradeStation and compiles in MC with no problems.

 

Dynamic Moving Average Cross Over EasyLanguage

I had a request recently to publish the EasyLanguage code for my Dynamic Moving Average system.  This system tries to solve the problem of using an appropriate length moving average that will keep you out of the chop.  The adaptive engine utilizes market volatility and increases moving average lengths as volatility increases and decreases moving average lengths as volatility decreases.  Its had some success, but the adaptive engine has not truly solved the problem.  The logic is pretty straightforward and can be modified to use different types of adaptive engines.

{Dynamic Moving AVerage System by George Pruitt}
{Use volatility to adapt moving average lenghts}
{in hopes to outperform a static parameter set!}

vars: avg1Ceil(23),avg1Floor(9),avg2Ceil(100),avg2Floor(25);

vars: shortMALen(19),longMALen(29);
vars: x1(0),y1(0),x2(0),y2(0),deltaVol1(0),deltaVol2(0);


If barNumber = 1 then
Begin
	shortMALen = 19;
	longMALen = 29;
end;

x1 = Stddev(close,shortMALen);
y1 = Stddev(close[1],shortMALen);

x2 = Stddev(close,longMALen);
y2 = Stddev(close[1],longMALen);



deltaVol1 = (x1-y1)/y1;
deltaVol2 = (x2-y2)/y2;


shortMALen = (1+deltaVol1)*shortMALen;
shortMALen = MaxList(shortMALen,avg1Floor);
shortMALen = MinList(shortMALen,avg1Ceil);
shortMALen = round(shortMALen,0);

longMALen = (1+deltaVol2)*longMALen;
longMALen = MaxList(longMALen,avg2Floor);
longMALen = MinList(longMALen,avg2Ceil);
longMALen = round(longMALen,0);

If average(c,shortMALen) crosses above average(c,longMALen) then buy next bar at open;
If average(c,shortMALen) crosses below average(c,longMALen) then sellshort next bar at open;

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.

Hash Table in EasyLanguage [Part 1]

 

This concept may be considered advanced and only used by pure programmers, but that is not the case at all.  A Hash Table is simply a table that is indexed by a function.  The function acts like the post office – it sends the data to the correct slot in the table.  I utilized this data structure because  I wanted to know the closing prices for the past fifteen years for the “1stThuJan” (first Thursday of January.)  This, of course, would require some programming and I could simply store the values in an array.  However, what if I wanted to know the closing prices for the “3rdFriMar?”  I would have to spend more time and re-code, right?   What if I changed my mind again.  Instead, as we programmers often do, I wanted to be able to pull the data for any instance of “Week, Day Of Week, Month.”  This is where a table structure comes in handy.  With this table, I can query it and find out the average yearly closing prices for the “1stMonSep” or the “4thFriJuly” or the “3rdWedApr ” on a rolling year by year basis.  Why would you want this you might ask?  Would it be helpful to know the price  change from the “2ndMonMar” to the subsequent “2ndMonMar” on a rolling basis?  What if the average price change is 10%.  You could use this information to make sure you always buy on this particular day.  That is if you believe in this form of analysis.

Here’s how I created a table that stores the closing prices for the past 15 years for each entry in the table.  Remember each row value only comes up once a year.  You only have one “1stMonJan” in a calendar year.  So the first part of the problem was simple, create a table that can store the closing prices with all the different combinations like the “1stTueJan” for the past fifteen years.  The second part of creating the post office like function that places the correct closing price in the right row was a little more difficult, but not much.  Here’s how I did it.

As I said earlier, a Hash Table is a very simple concept and very useful as well!  For some of you out there, I just want to make sure that you know that I am not talking about a device to keep your cannabis off of the floor;-) All kidding aside, go ahead and take a look at the table below.  Notice how it stores the closing prices of all the possible occurrences of Week, Day Of Week, Month.   Column 1 is the key or Hash Index value.  You will need this key to unlock the data for that particular row.  Column 2 shows the number of years that the data was collected.  Column 3 and on are the closing prices for that particular day across the years.  Once you have the data collected you can do anything your heart desires with it.

Table Index Num. Years Close 1 Close 2 Close 3 Close 4 Close 5 Close 6
1stWedJan 6 603 496 450.25 589 612.75 684.5
1stThuJan 6 606.5 486.25 446 571.75 597.75 683
1stFriJan 6 607.25 491.75 451.5 564.75 597.75 674
1stMonJan 6 606.75 490.75 447 590.25 606.25 679.25
1stTueJan 6 619.25 507 447.25 578.25 612.75 682.5
2ndWedJan 6 617.75 446 412.5 600.75 605.75 688
2ndThuJan 6 615.5 444.75 409.5 612.25 565.75 692.5
2ndFriJan 6 635.5 490.25 400 618.5 553.75 702.5
2ndMonJan 6 652.5 460.25 451 576.75 574.25 717.75

Sounds cool – so let’s do it!

Step 1:  Calculate the size of the table.

Each month consists of 4.25 weeks (52/12).  Because of this, you can have up to five occurrences of any given day of the week inside of a month – five Mondays, Tuesdays, etc.,  So we must build the table big enough to handle five complete weeks for each month.  Since there are 5 days in a week and 5 weeks in a month (not really but plan on it)  and 12 months in a year, the table must contain at least 300 rows ( 5 X 5 X 12.)  Since we don’t know how many years of data that we might want to collect we could make the arrays dynamic, but I want to keep things simple so I will reserve space for 100 years.  Overkill?

Step 2:  Use measurements from Step 1 to construct the container and create an addressing function.

The container is easy just dimension a 2-d array.  A 2-d array is a table whereas a 1-d array is a list.  A spreadsheet is an example of a 2-d array.  Just make the table big enough to hold the data.  Remember the key component to the Hash Table is not what it can hold, but the ability to quickly reference the data.  Just like your home, we need to create a unique address for each of the three hundred rows so the right mail, er data can be delivered or stored.  This is really quite simple –  we know we need a distinctive address and we know we need 300 of them.  Like the table above we can create a unique address in the form of “1stMonJan.”  This is a nine character string.  This  string can easily represent the 300 different addresses.  We start with “1stMonJan” and end with “5thFriDec.”  These values most consist of only nine characters.  I could have done the same thing using an integer value to represent each address.  “1stMonJan” could also be represented with 10101.  The “3rdFriDec” would be 30512.  I liked the string approach because the addresses are instantly recognizable with little or no translation.  So we need to get to typing, right?  Always remember if you are doing something redundant a computer can do the chore and do it quicker.  Just a quick note here.  I  designed the table ahead of time with the values in column 1 already filled in.  I could have done it more dynamically, but creating a data structure and filling in as much information before can save time on the programming side.

Instead of typing each unique address into the table, let’s let the computer do it for us.  Remember, Easylanguage has some cool string manipulation tools and with a little bit of cleverness, you can create the 300 unique addresses in one fell swoop.  The following code creates an array (list) of all of the possible combinations of “Week, Day Of Week, Month.”  There are 100 lines of code here, don’t freak out!  It’s mostly redundant.  I used a Finite State Machine and Easylanguage’s Switch – Case programming structure.  So you are learning about Hash Tables, Hash Indices, Finite State Machines, and Switch-Case programming in one post.  And here, all you want is a winning trading system.  Well, they are hard to come by and you need as many tools at your disposal to unlock the Holy Grail.  This is just one way to come up with the address values.

{Developed and programmed by George Pruitt-copyright 2017 www.georgepruitt.com}
{Just provide credit if you reuse! Or buy my book ;-)}

Input: hashIndex[n](stringArrayRef);
Vars: done(false);
Vars: firstCount(0),secondCount(0),thirdCount(0),fourthCount(0),fifthCount(0);
Vars: state(1),arrCnt(0),tempStr(""),monthCnt(0),returnValString(""),iCnt(0),jCnt(0),numBucket(0);
array: dayString[5](""),monString[12]("");


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

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


arrCnt = 0;
monthCnt = 1;
While not(done) and arrCnt<300
begin
	if state < 6 then arrCnt = arrCnt + 1;
	switch (state)
	Begin		   
		case 1:
			firstCount = firstCount + 1;
			tempStr = "1st";
			tempStr = tempStr + dayString[firstCount] + monString[monthCnt];
			hashIndex[arrCnt] = tempStr;
			If firstCount = 5 then 
			begin
				state = 2;
				firstCount = 0;
			end;
		case 2:
			secondCount = secondCount + 1;
			tempStr = "2nd";
			tempStr = tempStr + dayString[secondCount] + monString[monthCnt];
			hashIndex[arrCnt] = tempStr;	
			If secondCount = 5 then 
			begin
				state = 3;
				secondCount = 0;
			end;
		case 3:
			thirdCount = thirdCount + 1;
			tempStr = "3rd";
			tempStr = tempStr + dayString[thirdCount] + monString[monthCnt];
			hashIndex[arrCnt] = tempStr;
			If thirdCount = 5 then 
			begin
				state = 4;
				thirdCount = 0;
			end;
		case 4:
			fourthCount = fourthCount + 1;
			tempStr = "4th";
			tempStr = tempStr + dayString[fourthCount] + monString[monthCnt];
			hashIndex[arrCnt] = tempStr;
			If fourthCount = 5 then 
			begin
				state = 5;
				fourthCount = 0;
			end;
		case 5:
			fifthCount = fifthCount + 1;
			tempStr = "5th";
			tempStr = tempStr + dayString[fifthCount] + monString[monthCnt];
			hashIndex[arrCnt] = tempStr;	
			If fifthCount = 5 then 
			begin
				state = 6;	
				fifthCount = 0;
			end;
		case 6:
			If monthCnt < 12 then
			Begin
				state = 1;
				monthCnt = monthCnt + 1;
			end
			else
			begin
				done = true;
			end;
		end;				
end;
HashIndexCreator = 1;
Hash Index Creator

Here is a brief overview of this code.  The switch statement requires matching case statements.  In this machine, there are 6 different states.  Based on whatever the current state happens to be, the computer executes that block of code.  If the state is 1, then the block of code encapsulated with case(1) is executed.  All other code is ignored.  I start building the array by executing all of the “1st”‘s in January – “1stMonJan, 1stTueJan, 1stWedJan, 1stThuJan, and 1stFriJan.”   The nine character strings are built using concatenation.  In Easylanguage and most other languages you can add strings together:  “Cat” + “Dog” = “CatDog.”  So I take the string “1st” + “Mon” +  “Jan” to form the string “1stMonJan.”  I store the three characters for the day of the week and the three characters for the month in simple arrays.  After the fifth “1st”, I transition to state 2 and start working on all the “2nd”‘s.  Eventually the machine switches into 6th gear, er uh I mean state.  If month count is less than twelve, we gear down all the way back down to state 1 and start the process again for the month of February.  The machine finally turns off after month counter exceeds 12.  The Hash Index is completed; we have a unique address for the 300 rows.  In Part 2 I will show how to map the Hash Index onto the Hash Table and how to store the necessary information.  Finally, we will create an indicator using the data pulled from the table.

Backtesting with [Trade Station,Python,AmiBroker, Excel]

Follow

Get every new post delivered to your Inbox

Join other followers: