This system has been around for several years. Its based on the belief that fund managers start pouring money into the market near the end of the month and this creates momentum that lasts for just a few days. The original system states to enter the market on the close of the last bar of the day if the its above a certain moving average value. In the Jaekle and Tomasini book, the authors describe such a trading system. Its quite simple, enter on the close of the month if its greater than X-Day moving average and exit either 4 days later or if during the trade the closing price drops below the X-Day moving average.
EasyLanguage or Multi-Charts Version
Determining the end of the month should be quite easy -right? Well if you want to use EasyLanguage on TradeStation and I think on Multi-Charts you can’t sneak a peek at the next bar’s open to determine if the current bar is the last bar of the month. You can try, but you will receive an error message that you can’t mix this bar on close with next bar. In other words you can’t take action on today’s close if tomorrow’s bar is the first day of the month. This is designed, I think, to prevent from future leak or cheating. In TradeStation the shift from backtesting to trading is designed to be a no brainer, but this does provide some obstacles when you only want to do a backtest.
LDOM function – last day of month for past 15 years or so
So I had to create a LastDayOfMonth function. At first I thought if the day of the month is the 31st then it is definitely the last bar of the month. And this is the case no matter what. And if its the 30th then its the last day of the month too if the month is April, June, Sept, and November. But what happens if the last day of the month falls on a weekend. Then if its the 28th and its a Friday and the month is blah, blah, blah. What about February? To save time here is the code:
// 29th of the month and a Friday if theDayOfMonth = 29 and theDayOfWeek = 5 then endOfMonth = True; // 30th of the month and a Friday if theDayOfMonth = 30 and theDayOfWeek = 5 then endOfMonth = True; // 31st of the month if theDayOfMonth = 31 then endOfMonth = True; // 30th of the month and April, June, Sept, or Nov if theDayOfMonth = 30 and (theMonth=4 or theMonth=6 or theMonth=9 or theMonth=11) then endOfMonth = True; // 28th of the month and February and not leap year if theDayOfMonth = 28 and theMonth = 2 and not(isLeapYear) then endOfMonth = True; // 29th of the month and February and a leap year or 28th, 27th and a Friday if theMonth = 2 and isLeapYear then Begin If theDayOfMonth = 29 or ((theDayOfMonth = 28 or theDayOfMonth = 27) and theDayOfWeek = 5) then endOfMonth = True; end; // 28th of the month and Friday and April, June, Sept, or Nov if theDayOfMonth = 28 and (theMonth = 4 or theMonth = 6 or theMonth = 9 or theMonth =11) and theDayOfWeek = 5 then endOfMonth = True; // 27th, 28th of Feb and Friday if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 27 then endOfMonth = True; // 26th of Feb and Friday and not LeapYear if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 26 and not(isLeapYear) then endOfMonth = True; // Memorial day adjustment If theMonth = 5 and theDayOfWeek = 5 and theDayOfMonth = 28 then endOfMonth = True; //Easter 2013 adjustment If theMonth = 3 and year(d) = 113 and theDayOfMonth = 28 then endOfMonth = True; //Easter 2018 adjustment If theMonth = 3 and year(d) = 118 and theDayOfMonth = 29 then endOfMonth = True;
if endOfMonth and c > average(c,movAvgPeriods) then Buy("BuyDay") this bar on close;
If C <average(c,movAvgPeriods) then Sell("MovAvgExit") this bar on close; If BarsSinceEntry=4 then Sell("4days") this bar on close;
Last Day Of Month Function and Strategy
All the code is generic except for the hard code for days that are a consequence of Good Friday.
All this code because I couldn’t sneak a peek at the date of tomorrow. Here are the results of trading the ES futures sans execution costs for the past 15 years.
What if it did the easy way and executed the open of the first bar of the month.
If c > average(c,50) and month(d) <> month(d of tomorrow) then buy next bar at open;
If barsSinceEntry >=3 then sell next bar at open;
If marketPosition = 1 and c < average(c,50) then sell next bar at open;
Buy First Day Of Month
The results aren’t as good but it sure was easier to program.
Since you can use daily bars we can test this with my TradingSimula-18 Python platform. And we will execute on the close of the month. Here is the snippet of code that you have to concern yourself with. Here I am using Sublime Text and utilizing their text collapsing tool to hide non-user code:
This was easy to program in TS-18 because I do allow Future Leak – in other words I will let you sneak a peek at tomorrow’s values and make a decision today. Now many people might say this is a huge boo-boo, but with great power comes great responsibility. If you go in with eyes wide open, then you will only use the data to make things easier or even doable, but without cheating. Because you are only going to cheat yourself. Its in your best interest do follow the rules. Here is the line that let’s you leak into the future.
The curBar is today and curBar+1 is tomorrow. So I am saying if tomorrow is the first day of the month then buy today’s close. Here you are leaking into the future but not taking advantage of it. We all know if today is the last day of the month, but try explaining that to a computer. You saw the EasyLanguage code. So things are made easier with future leak, but not taking advantage of .
Here is a quick video of running the TS-18 Module of 4 different markets.
Many Times It Takes Multiple Tools to Get the Job Done
Just like a mechanic, a Quant needs tools to accomplish many programming tasks. In this post, I use a toolbox to construct an EasyLanguage function that will test a date and determine if it is considered a Holiday in the eyes of the NYSE.
Why a Holiday Function?
TradeStation will pump holiday data into a chart and then later go back and take it out of the database. Many times the data will only be removed from the daily database, but still persist in the intraday database. Many mechanical day traders don’t want to trade on a shortened holiday session or use the data for indicator/signal calculations. Here is an example of a gold chart reflecting President’s Day data in the intra-day data and not in the daily.
This affects many stock index day traders. Especially if automation is turned on. At the end of this post I provide a link to my youTube channel for a complete tutorial on the use of these tools to accomplish this task. It goes along with this post.
First Get The Data
I searched the web for a list of historical holiday dates and came across this:
You might be able to find this in a easier to use format, but this was perfect for this post.
Extract Data with Beautiful Soup
Here is where Python and the plethora of its libraries come in handy. I used pip to install the requests and the bs4 libraries. If this sounds like Latin to you drop me an email and I will shoot you some instructions on how to install these libraries. If you have Python, then you have the download/install tool known as pip.
Here is the Python code. Don’t worry it is quite short.
url = 'http://www.market-holidays.com/' page = requests.get(url) soup = BeautifulSoup(page.text,'html.parser') print(soup.title.text) all_tables = soup.findAll('table') #print (all_tables) print (len(all_tables)) #print (all_tables) print("***") a = list() b = list() c = list() #print(all_tables.find_all('tr').text) for numTables in range(len(all_tables)-1): for rows in all_tables[numTables].find_all('tr'): a.append(rows.find_all('td').text) b.append(rows.find_all('td').text)
for j in range(len(a)-1): print(a[j],"-",b[j])
Using Beautiful Soup to Extract Table Data
As you can see this is very simple code. First I set the variable url to the website where the holidays are located. I Googled on how to do this – another cool thing about Python – tons of users. I pulled the data from the website and stuffed it into the page object. The page object has several attributes (properties) and one of them is a text representation of the entire page. I pass this text to the BeautifulSoup library and inform it to parse it with the html.parser. In other words, prepare to extract certain values based on html tags. All_tables contains all of the tables that were parsed from the text file using Soup. Don’t worry how this works, as its not important, just use it as a tool. In my younger days as a programmer I would have delved into how this works, but it wouldn’t be worth the time because I just need the data to carry out my objective; this is one of the reasons classically trained programmers never pick up the object concept. Now that I have all the tables in a list I can loop through each row in each table. It looked liker there were 9 rows and 2 columns in the different sections of the website, but I didn’t know for sure so I just let the library figure this out for me. So I played around with the code and found out that the first two columns of the table contained the name of the holiday and the date of the holiday. So, I simply stuffed the text values of these columns in two lists: a and b. Finally I print out the contents of the two lists, separated by a hyphen, into the Interpreter window. At this point I could simply carry on with Python and create the EasyLanguage statements and fill in the data I need. But I wanted to play around with Excel in case readers didn’t want to go the Python route. I could have used a powerful editor such as NotePad++ to extract the data from the website in place of Python. GREP could have done this. GREP is an editor tool to find and replace expressions in a text file.
Use Excel to Create Actual EasyLanguage – Really!
I created a new spreadsheet. I used Excel, but you could use any spreadsheet software. I first created a prototype of the code I would need to encapsulate the data into array structures. Here is what I want the code to look like:
holidayName="New Year's Day "; holidayDate=19900101;
This is just the first few lines of the function prototype. But you can notice a repetitive pattern. The array names stay the same – the only values that change are the array elements and the array indices. Computers love repetitiveness. I can use this information a build a spreadsheet – take a look.
I haven’t copied the data that I got out of Python just yet. That will be step 2. Column A has the first array name holidayName (notice I put the left square [ bracket in the column as well). Column B will contain the array index and this is a formula. Column C contains ]=”. Column D will contain the actual holiday name and Column E contains the ; These columns will build the holidayName array. Columns G throuh K will build the holidayDates array. Notice column H equals column B. So whatever we do to column B (Index) will be reflected in Column H (Index). So we have basically put all the parts of the EasyLanguage into Columns A thru K.
Excel provides tools for manipulating strings and text. I will use the Concat function to build my EasyLanguage. But before I can use Concat all the stuff I want to string together must be in a string or text format. The only column in the first five that is not a string is Column B. So the first thing I have to do is convert it to text. First copy the column and paste special as values. Then go to your Data Tab and select Text To Columns.
It will ask if fixed width or delimited – I don’t think it matters which you pick. On step 3 select text.
The Text To Columns button will solve 90% of your formatting issues in Excel. Once you do this you will notice the numbers will be left justified – this signifies a text format. Now lets select another sheet in the workbook and past the holiday data.
Copy Holiday Data Into Another Spreadsheet
New Year's Day - January 1, 2021 Martin Luther King, Jr. Day - January 18, 2021 Washington's Birthday (Presidents' Day) - February 15, 2021 Good Friday - April 2, 2021 Memorial Day - May 31, 2021 Independence Day - July 5, 2021 Labor Day - September 6, 2021 Thanksgiving - November 25, 2021 Christmas - December 24, 2021 New Year's Day - January 1, 2020 Martin Luther King, Jr. Day - January 20, 2020 Washington's Birthday (Presidents' Day) - February 17, 2020 Good Friday - April 10, 2020 Memorial Day - May 25, 2020
Text To Columns to the rescue. Here I will separate the data with the “-” as delimiter and tell Excel to import the second column in Date format as MDY.
Now once the data is split accordingly into two columns with the correct format – we need to convert the date column into a string.
Now the last couple of steps are really easy. Once you have converted the date to a string, copy Column A and past into Column D from the first spreadsheet. Since this is text, you can simply copy and then paste. Now go back to Sheet 2 and copy Column C and paste special [values] in Column J on Sheet 1. All we need to do now is concatenate the strings in Columns A thru E for the EasyLanguage for the holidayName array. Columns G thru K will be concatenated for the holidayDate array. Take a look.
Now create a function in the EasyLanguage editor and name it IsHoliday and have it return a boolean value. Then all you need to do is copy/paste Columns F and L and the data from the website will now be available for you use. Here is a portion of the function code. Notice I declare the holidayNameStr as a stringRef? I did this so I could change the variable in the function and pass it back to the calling routine.
holidayName="New Year's Day "; holidayDate=19900101; holidayName="Martin Luther King, Jr. Day "; holidayDate=19900115; holidayName="Washington's Birthday (Presidents' Day) "; holidayDate=19900219; holidayName="Good Friday "; holidayDate=19900413; holidayName="Memorial Day "; holidayDate=19900528; holidayName="Independence Day "; holidayDate=19900704; holidayName="Labor Day "; holidayDate=19900903; holidayName="Thanksgiving "; holidayDate=19901122; holidayName="New Year's Day "; holidayDate=19910101; holidayName="Martin Luther King, Jr. Day "; holidayDate=19910121; holidayName="Washington's Birthday (Presidents' Day) "; holidayDate=19910218;
// There are 287 holiays in the database. // Here is the looping mechanism to compare the data that is passed // to the database
vars: j(0); IsHoliday = False; For j=1 to 287 Begin If testDate = holidayDate[j] - 19000000 then Begin holidayNameStr = holidayName[j] + " " + numToStr(holidayDate[j],0); IsHoliday = True; end; end;
A Snippet Of The Function - Including Header and Looping Mechanism
This was a pretty long tutorial and might be difficult to follow along. If you want to watch my video, then go to this link.
I created this post to demonstrate the need to have several tools at your disposal if you really want to become a Quant programmer. How you use those tools is up to you. Also you will be able to take bits and pieces out of this post and use in other ways to get the data you really need. I could have skipped the entire Excel portion of the post and just did everything in Python. But I know a lot of Quants that just love spreadsheets. You have to continually hone your craft in this business. And you can’t let one software application limit your creativity. If you have a problem always be on the lookout for alternative platforms and/or languages to help you solve it.
Its a new decade! Time to see what’s up with Trend Following.
I am a huge fan of Andreas Clenow’s books, and how he demonstrated that a typical trader could replicate the performance of most large Trend Following CTAs and not pay the 2% / 20% management/incentive combo fees. So. I felt the system that he described in his book would be a great representation of TheState of Trend Following. At the same time I am going to demonstrate TradingSimula18 (the software included in my latest book).
Take a look at my last post. I provide the EasyLanguage and a pretty good description of Clenow’s strategy.
TradingSimula18 Code [Python]
#--------------------------------------------------------------------------------------------------- # Start programming your great trading ideas below here - don't touch stuff above #--------------------------------------------------------------------------------------------------- # Define Long, Short, ExitLong and ExitShort Levels - mind your indentations ATR = sAverage(myTrueRange,30,curBar,1) posSize = 2000/(ATR*myBPV) posSize = max(int(posSize),1) posSize = min(posSize,20) avg1 = xAverage(myClose,marketVal5[curMarket],50,curBar,1) avg2 = xAverage(myClose,marketVal6[curMarket],100,curBar,1) marketVal5[curMarket] = avg1 marketVal6[curMarket] = avg2 donchHi = highest(myHigh,50,curBar,1) donchLo = lowest(myLow,50,curBar,1)
if mp == 1 : marketVal1[curMarket] = max(marketVal1[curMarket],myHigh[curBar-1]- 3 * ATR) if mp ==-1 : marketVal2[curMarket] = min(marketVal2[curMarket],myLow[curBar-1]+ 3 * ATR) # Long Entry if avg1 > avg2 and myHigh[curBar-1] == donchHi and mp !=1: price = myOpen[curBar] tradeName = "TFClenowB";numShares = posSize marketVal1[curMarket] = price - 3 * ATR if mp <= -1: profit,curShares,trades = bookTrade(entry,buy,price,myDate[curBar],tradeName,numShares) barsSinceEntry = 1 marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades) # Long Exit if mp == 1 and myClose[curBar-1] <= marketVal1[curMarket] and barsSinceEntry > 1: price = myOpen[curBar] tradeName = "Lxit";numShares = curShares profit,curShares,trades = bookTrade(exit,ignore,price,myDate[curBar],tradeName,numShares) todaysCTE = profit;barsSinceEntry = 0 marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades) # Short Entry if avg1 < avg2 and myLow[curBar-1] == donchLo and mp !=-1: price = myOpen[curBar];numShares = posSize marketVal2[curMarket] = price + 3 * ATR if mp >= 1: tradeName = "TFClenowS" profit,curShares,trades = bookTrade(entry,sell,price,myDate[curBar],tradeName,numShares) barsSinceEntry = 1 marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades) # Short Exit if mp == -1 and myClose[curBar-1] >= marketVal2[curMarket] and barsSinceEntry > 1: price = myOpen[curBar] tradeName = "Sxit"; numShares = curShares profit,curShares,trades = bookTrade(exit,ignore,price,myDate[curBar],tradeName,numShares) todaysCTE = profit;barsSinceEntry = 0 marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades) #---------------------------------------------------------------------------------------------------------------------------- # - Do not change code below - trade, portfolio accounting - our great idea should stop here #----------------------------------------------------------------------------------------------------------------------------
TradingSimula18 Python System Testing Environment
I am going to go over this very briefly. I know that many of the readers of my blog have attempted to use Python and the various packages out there and have given up on it. Quantopia and QuantConnect are great websites, but I feel they approach back-testing with a programmer in mind. This was the main reason I created TS-18 – don’t get me wrong its not a walk in the park either, but it doesn’t rely on external libraries to get the job done. All the reports I show here are generated from the data created solely by TS-18. Plus it is very modular – Step 1 leads to Step2 and on and on. Referring to the code I calculate the ATR (average true range) by calling the simple average function sAverage. I pass it myTrueRanges, 30, curBar and 1. I am looking for the average true range over the last 30 days. I then move onto my position sizing – posSize = $2,000 / ATR in $s. PosSize must fit between 1 and 20 contracts. The ATR calculation can get rather small for some markets and the posSize can get rather large. Avg1 and Avg2 are exponential moving averages of length 50 and 100. DonchHi and donchLo are the highest high and lowest low of the past 50 days. If mp == 1 (long position) then a trailing stop (marketVal1) is set to whichever is higher – the current marketVal1 or the yesterday’s High – 3 X ATR; the trailing stop tracks new intra-trade highs. The trailing stop for the short side, marketVal2 is calculated in a similar manner, but low prices are used as well as a positive offset of 3 X ATR.
Now the next section of code is quite a bit different than say EasyLanguage, but parallels some of the online Python paradigms. Here you must test the current bar’s extremes against the donchHi if you are flat and marketVal1 (the trailing stop variable) if you are long. If flat you also test the low of the bar against donchLo. The relationship between avg1 and avg2 are also examined. If the testing criteria is true, then its up to you to assign the correct price, posSize and tradeName. So you have four independent if-then constructs:
Long Entry – if flat test to see if a long position should be initiated
Long Exit – if Long then test to see if a liquidation should be initiated
Short Entry – if flat test to see if a short position should be initiated
Short Exit – if Short then test to see if a liquidation should be initiated
That’s it – all of the other things are handled by TS-18. Now that I have completely bored you out of your mind, let’s move onto some results.
Results from 2000 – risking $2,000 per trade:
Sector Performance from 2000
From this chart it doesn’t make much sense to trade MEATS, SOFTS or GRAINS with a Trend Following approach or does it?
In the next post, I will go over the results with more in depth and possibly propose some ideas that might or might not help. Stay Tuned!
The Turtle N or Volatility is basically a 20-day Average True Range in terms of Dollars. This amount is considered the market volatility. In other words the market, based on the average, can either move up or down by this amount. It can move much less or much further; this is just an estimate. If the market moves 2 N against a position, then as a Turtle you were to liquidate your stake in that commodity. The logic indicates that if the market has a break out and then moves 2 N in the opposite direction, then the break out has failed. First the code must be defined to represent the market volatility. This is simple enough by using the sAverage function call and passing it the trueRanges and 20 days. There’s no use in converting this to dollars because what we want is a price offset. Once a position is entered the turtleN is either added to the price [short position] or subtracted from the price [long position] to determine the respective stop levels. Look at lines 2, 8 and 17 to see how this is handled. An additional trade code block must be added to facilitate this stop. Lines 17 to 28 takes care of exiting a long position when the market moves 2 N in the opposite direction. This new stop is in addition to the highest/lowest high/low stops for the past 10 -20 days.
I include the Python Backtester in my latest book “The Ultimate Algorithmic Trading System Toolbox” book. A good tutorial on how to use it would be to program the Turtle Algorithm in three different parts. Here is part 1:
Entry Description: Buy on stop at highest high of last twenty days. Short on lowest low of last twenty days.
Exit Description: Exit long on stop at lowest low of last ten days. Exit short on highest high of past ten days.
Position Sizing: Risk 2% of simulated 100K account on each trade. Calculate market risk by utilizing the ten day ATR. Size(shares or contracts) = $2,000/ATR in dollars.
#Long Entry Logic
if (mp==0 or mp==-1) and barsSinceEntry>1 and myHigh[i]>=hh20:
profit = 0
price = max(myOpen[i],hh20)
numShares = max(1,int(dollarRiskPerTrade/(atrVal*myBPV)))
tradeName = "Turt20Buy"
if (mp==0 or mp==1) and barsSinceEntry>1 and myLow[i] <= ll20:
profit = 0
price = min(myOpen[i],ll20)
numShares = max(1,int(dollarRiskPerTrade/(atrVal*myBPV)))
tradeName = "Turt20Shrt"
#Long Exit Loss
if mp >= 1 and myLow[i] <= ll10 and barsSinceEntry > 1:
price = min(myOpen[i],ll10)
tradeName = "Long10-Liq"
#Short Exit Loss
if mp <= -1 and myHigh[i] >= hh10 and barsSinceEntry > 1:
price = max(myOpen[i],hh10)
tradeName = "Shrt10-Liq"
Turtle Part 1
This snippet only contains the necessary code to use in the Python Backtester – it is not in its entirety.
This algorithm utilizes a fixed fractional approach to position sizing. Two percent or $2000 is allocated on each trade and perceived market risk is calculated by the ten-day average true range (ATR.) So if we risk $2000 and market risk is $1000 then 2 contracts are traded. In Part 2, I will introduce the N risk stop and the LAST TRADE LOSER Filter.
Backtesting with [Trade Station,Python,AmiBroker, Excel]. Intended for informational and educational purposes only!