A Quant’s ToolBox: Beautiful Soup, Python, Excel and EasyLanguage

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.

Holiday Data Throws A Monkey Wrench Into the Works

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:

Historic List of Holidays and Their Dates

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.

# Created:     24/02/2020
# Copyright: (c) George 2020
# Licence: <your licence>
#-------------------------------------------------------------------------------

import requests
from bs4 import BeautifulSoup

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[0])
print("***")
a = list()
b = list()
c = list()
#print(all_tables[0].find_all('tr')[0].text)
for numTables in range(len(all_tables)-1):
for rows in all_tables[numTables].find_all('tr'):
a.append(rows.find_all('td')[0].text)
b.append(rows.find_all('td')[1].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:

Arrays: holidayName[300](""),holidayDate[300](0);

holidayName[1]="New Year's Day "; holidayDate[1]=19900101;
Code Prototype

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.

Type EasyLanguage Into the Columns and Fill Down!

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 theThese 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 EasyLanguageBut 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. 

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.

Text To Columns – A Powerful Tool

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
Holiday Output

 

Data Is In Column A

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.  

Text To Columns with “-” as the delimiter and MDY as Column B Format

Now once the data is split accordingly into two columns with the correct format – we need to convert the date column into a string.

Convert Date to 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.

Concatenate all the strings to create the EasyLanguage

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.

Inputs : testDate(numericSeries),holidayNameStr(stringRef);

Arrays: holidayName[300](""),holidayDate[300](0);

holidayNameStr = "";

holidayName[1]="New Year's Day "; holidayDate[1]=19900101;
holidayName[2]="Martin Luther King, Jr. Day "; holidayDate[2]=19900115;
holidayName[3]="Washington's Birthday (Presidents' Day) "; holidayDate[3]=19900219;
holidayName[4]="Good Friday "; holidayDate[4]=19900413;
holidayName[5]="Memorial Day "; holidayDate[5]=19900528;
holidayName[6]="Independence Day "; holidayDate[6]=19900704;
holidayName[7]="Labor Day "; holidayDate[7]=19900903;
holidayName[8]="Thanksgiving "; holidayDate[8]=19901122;
holidayName[9]="New Year's Day "; holidayDate[9]=19910101;
holidayName[10]="Martin Luther King, Jr. Day "; holidayDate[10]=19910121;
holidayName[11]="Washington's Birthday (Presidents' Day) "; holidayDate[11]=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.

 

 

The Cure for the Common Trend Follower – SOTF Part 2

Clenow’s algorithm is definitely an indicator for the current State of Trend Following (SOTF).  However, the 3 X ATR trailing stop mechanism actually dampens the profit/draw down ratio.  Take a look at this chart.

Battle of Titans

All Trend Following mechanisms have a very common thread in their entry mechanisms.   The thing that separates them is the preemptive exit.  Do you allow the the algorithm to exit on a purely market defined method or do you overlay trade management?  Here the best approach was to let the Bollinger Band system run unfettered; even though it seems somewhat illogical.  Many times  trade management actually increases draw down.   Is there a solution?  What about this – keep risk down by trading a small, yet diverse portfolio of high volume markets and overlay it with a stock index mean reversion algo.  Take a look.

Bollinger Marries ES Reversion

 

Should’ve, Would’ve , Could’ve.

This could be scaled up.  The mean reversion helped lift  the chart out of the flat and draw down periods of late.  However, the smaller portfolio did OK during this time period too!  Can four or five high volume markets replicate a much larger portfolio?  All tests were carried out with TradingSimula18 – the software that comes with my latest book.

State of Trend Following – Part 1

Clenow’s Trend Following System

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 The State of Trend Following.  At the same time I am going to demonstrate TradingSimula18 (the software included in my latest book).

System Description

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 100DonchHi 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:

Roller Coaster Ride for most CTAs, Last one out turn off the lights!

Sector Performance from 2000

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!

 

Free Trend Following System with Indicator Tracker

Free Trend Following System

Here is a free Trend Following System that I read about on Andreas Clenow’s www.followthetrend.com website and from his book.  This is my interpretation of the rules as they were explained.  However the main impetus behind this post wasn’t to provide a free trading system, but to show how you can program a simple system with a complete input interface and program a tracking indicator.   You might be asking what is a “tracking indicator?”  We use a tracking indicator to help provide insight to what the strategy is doing and what it might do in the near future.  The indicator can let you know that a new signal is imminent and also what the risk is in a graphical form.  The indicator can also plot the indicators that are used in the strategy itself.

Step 1:  Program the Strategy

This system is very simple.  Trade on a 50 day Donchian in the direction of the trend and use a 3 X ATR trailing stop.  So the trend is defined as bullish when the 50-day exponential moving average is greater than the 100-day exponential moving average.  A bearish trend is defined when the 50-day is below the 100-day.  Long positions are initiated on the following day when a new 50 day high has been established and the trend is bullish.  Selling short occurs when the trend is bearish and a new 50 day low is establish.  The initial stop  is set to 3 X ATR below the high of the day of entry.  I tested using a 3 X ATR stop initially from the entryPrice for protection on the day of entry, but it made very little difference.  As the trade moves more into your favor, the trailing stop ratchets up and tracks the higher intra-trade extremes.  Eventually once the market reverses you get stopped out of a long position 3 X ATR from the highest high since you entered the long trade.  Hopefully, with a big winner.   The Clenow model also uses a position sizing equation that uses ATR to determine market risk and $2000 for the allocated amount to risk.  Size= 2000 / ATR – this equation will normalize size across a portfolio of markets.

Here is the code.

//Based on Andreas Clenow's description from www.followingthetrend.com
//This is my interpretation and may or may not be what Andreas intended
//Check his books out at amazon.com
//
inputs: xAvgShortLen(50),xAvgLongLen(100),hhllLen(50),buyTrigPrice(h),shortTrigPrice(l),risk$Alloc(2000);
inputs: atrLen(30),trailATRMult(3);
vars: avg1(0),avg2(0),lXit(0),sXit(0),posSize(0),atr(0);

avg1 = xaverage(c,xAvgShortLen);
avg2 = xaverage(c,xAvgLongLen);

atr = avgTrueRange(atrLen);
posSize = maxList(1,intPortion(risk$Alloc/(atr*bigPointValue)));

If marketPosition <> 1 and avg1 > avg2 and buyTrigPrice = highest(buyTrigPrice,hhllLen) then buy posSize contracts next bar at open;
If marketPosition <> -1 and avg1 < avg2 and shortTrigPrice = lowest(shortTrigPrice,hhllLen) then sellshort posSize contracts next bar at open;

If marketPosition = 0 then
Begin
lXit = o - trailATRMult * atr ;
sXit = o + trailATRMult * atr;
// if c < lXit then Sell currentcontracts contracts next bar at open;
// If c > sXit then buyToCover currentcontracts contracts next bar at open;
end;

If marketPosition = 1 then
begin
lXit = maxList(lXit,h - trailATRMult * atr);
If c < lXit then sell currentContracts contracts next bar at open;
end;

If marketPosition = -1 then
begin
sXit = minList(sXit,l + trailATRMult * atr);
If c > sXit then buyToCover currentContracts contracts next bar at open;
end;
Cleanow Simple Trend Following System

What I like about this code is how you can use it as a template for any trend following approach.  All the variables that could be optimized are included as inputs.  Many may not know that you can actually change the data series that you want to use as your signal generator right in the input.  Here I have provided two inputs : buyTrigPrice(H), shortTrigPrice(L).  If you want to use the closing price, then all you need to do is change the H and L to C.  The next lines of code performs the calculations needed to calculate the trend.  PosSize is then calculated next.  Here I am dividing the variable risk$Alloc by atr*bigPointValue.  Basically I am taking $2000 and dividing the average true range over the past 30 days multiplied by the point value of the market being tested.  Always remember when doing calculations with $s you have to convert whatever else you are using into dollars as well.  The ATR is expressed in the form of a price difference.  You can’t divide dollars by a price component, hence the multiplication by bigPointValue.  So now we have the trend calcuation and the position sizing taken care of and all we need now is the trend direction and the entry levels.  If avg1 > avg2 then the market is in a bullish posture, and if today’s High = highest(High,50) days back then initiate a long position with posSize contracts at the next bar’s openNotice how I used the keyword contracts after posSize.  This let’s TS know that I want to trade more than one contract.  If the current position is flat I set the lXit and sXit price levels to the open -/+ 3 X ATR.  Once a position (long or short) is initiated then I start ratcheting the trailing stop up or down.  Assuming a long position, I compare the current lXit and the current bar’s HIGH- 3 X ATR and take the larger of the two valuesSo lXit always moves up and never down.  Notice if the close is less than lXit I used the keyword currentContracts and contracts in the directive to exit a long trade.  CurrentContracts contains the current number of contracts currently long and contracts informs TS that more than one contract is being liquidated.  Getting out of a short position is exactly the same but in a different direction.

Step 2: Program the System Tracking Indicator

Now you can take the exact code and eliminate all the order directives and use it to create a tracking indicator.  Take a look at this code:

//Based on Andreas Clenow's description from www.followingthetrend.com
//This is my interpretation and may or may not be what Andreas intended
//Check his books out at amazon.com
//
inputs: xAvgShortLen(50),xAvgLongLen(100),hhllLen(50),buyTrigPrice(h),shortTrigPrice(l);
inputs: atrLen(30),trailATRMult(3);
vars: avg1(0),avg2(0),lXit(0),sXit(0),posSize(0),atr(0),mp(0);

avg1 = xaverage(c,xAvgShortLen);
avg2 = xaverage(c,xAvgLongLen);

atr = avgTrueRange(atrLen);

plot1(avg1,"stXavg");
plot2(avg2,"ltXavg");

If avg1[1] > avg2[1] and buyTrigPrice[1] = highest(buyTrigPrice[1],hhllLen) then mp = 1;
If avg1[1] < avg2[1] and shortTrigPrice[1] = lowest(shortTrigPrice[1],hhllLen) then mp = -1;

If mp = 0 then
Begin
lXit = o - trailATRMult * atr ;
sXit = o + trailATRMult * atr;
end;

If mp = 1 then
begin
lXit = maxList(lXit,h - trailATRMult * atr);
plot3(lXit,"LongTrail");
If c < lXit then mp = 0;
end;

If mp = -1 then
begin
sXit = minList(sXit,l + trailATRMult * atr);
plot4(sXit,"ShortTrail");
If c > sXit then mp = 0;
end;

However, you do need to keep track if the underlying strategy is long or short and you can do this by pretending you are the computer and using the mp variable.  You know if yesterdays avg1 > avg2 and HIGH[1] = highestHigh(HIGH[1],50), then a long position should have been initiated.  If this happens just set mp to 1You set mp to -1 by checking the trend and lowestLow(LOW[1],50).  Once you know the mp or implied market position then you can calculate the lXit and sXit.  You will always plot the moving averages to help determine trend direction, but you only plot the lXit and sXit when a position is on.  So plot3 and plot4 should only be plotted when a position is long or short.

Here is a screenshot of the strategy and tracking indicator.

Notice how the Yellow and Cyan plots follow the correct market position.  You will need to tell TS not to connect these plot lines when they are not designed to be plotted.

Turn-Off Auto Plot Line Connection

Do this for Plot3 and Plot4 and you will be good to go.

I hope you found this post useful.  Also don’t forget to check out my new book at Amazon.com.  If you really want to learn programming that will help across different platforms I think it would be a great learning experience.

 

A Christmas Project for TradeStation Day-Traders

Here is a neat little day trader system that takes advantage of what some technicians call a “CLEAR OUT” trade.  Basically traders push the market through yesterday’s high and then when everybody jumps on board they pull the rug out from beneath you.  This strategy tries to take advantage of this.  As is its OK, but it could be made into a complete system with some filtering.  Its a neat base to start your day-trading schemes from.

But first have you ever encountered this one when you only want to go long once during the day.

I have logic that examines marketPosition, and if it changes from a non 1 value to 1 then I increment buysToday.  Since there isn’t an intervening bar to establish a change in marketPosition, then buysToday does not get incremented and another buy order is issued.  I don’t want this.  Remember to plot on the @ES.D.

Here’s how I fixed it and also the source of the CLEAR-OUT day-trade in its entirety.  I have a $500 stop and a $350 take profit, but it simply trades way too often.  Have fun with this one – let me now if you come up with something.

inputs: clearOutAmtPer(0.1),prot$Stop(325),prof$Obj(500),lastTradeTime(1530);

vars: coBuy(false),coSell(false),buysToday(0),sellsToday(0),mp(0),totNumTrades(0);

If d <> d[1] then
Begin
coBuy = false;
coSell = false;
buysToday = 0;
sellsToday = 0;
totNumTrades = totalTrades;
end;


mp = marketPosition;
If mp[1] <> mp and mp = 1 then buysToday = buysToday + 1;
If mp[1] <> mp and mp = -1 then sellsToday = sellsToday + 1;

If h > highD(1) + clearOutAmtPer * (highD(1) - lowD(1)) then coSell = true;
If l < lowD(1) - clearOutAmtPer * (highD(1) - lowD(1)) then coBuy = true;

If totNumTrades <> totalTrades and mp = 0 and mp[1] = 0 and positionProfit(1) < 0 and entryPrice(1) > exitPrice(1) then buysToday = buysToday + 1;
If totNumTrades <> totalTrades and mp = 0 and mp[1] = 0 and positionProfit(1) < 0 and entryPrice(1) < exitPrice(1) then sellsToday =sellsToday + 1;

totNumTrades = totalTrades;

If buysToday = 0 and t < lastTradeTime and coBuy = true then buy ("COBuy") next bar at lowD(1) + minMove/priceScale stop;
If sellsToday = 0 and t < lastTradeTime and coSell = true then sellShort ("COSell") next bar at highD(1) - minMove/priceScale stop;

setStopLoss(prot$stop);
Setprofittarget(prof$Obj);
setExitOnClose;
Look at lines 22 and 23 - the entry/exit same bar fix

Get My Latest Book-TrendFollowing Systems: A DIY Project – Batteries Included

Just wanted to let you know that my latest book has just been published.

Trend Following Systems: A DIY Project – Batteries Included: Can You Reboot and Fix Yesterday’s Algorithms to Work with Today’s Markets? 

Trend Following Systems: A DIY Project – Batteries Included

This book introduces my new Python back-tester, TradingSimula-18.  It is completely and I mean completely self contained.  All you need is the latest version of Python and you will be up and running trading systems in less than 5 minutes.  Fifteen years of data on 30 futures is included (data from Quandl).  I have included more than 20 scripts that you can test and build on.   This back-tester is different than the one I published in the Ultimate Algorithmic Trading System Toolbox.  It utilizes what I call the horizontal portfolio spanning paradigm.  Instead of sequentially testing different markets in a portfoio:

It process data in the following manner:

This form of testing allows for decisions to be made on a portfolio basis at the end of any historic bar.   Things like inputting portfolio performance into an allocation formula is super simple.  However, this paradigm opens up a lot of different “what-if” scenarios.

  1. What If I Limit 2 Markets Per Sector
  2. What If I Turn Off A Certain Sector
  3. What If I Liquidate The Largest OTE loser
  4. What If I Liquidate The Largest OTE winner
  5. What If I Only Trade The Ten Markets With The Highest ADX Values

All the data and market performance and portfolio performance is right at your fingertips.  Your testing is only limited by your creativity.

The best part is you get to learn raw Python without having to install complicated libraries like SciKit, Numpy or Pandas.  You don’t even need to install distributions of commercial products – like Anaconda.  Don’t get me wrong I think Anaconda is awesome but many times it is overkill.  If you want to do machine learning then that is the way to go.  If you want to test simple Trend Following algorithms and make portfolio level decisions you don’t need a data science application.

There isn’t a complicated interface to learn.  Its all command line driven from Python’s IDLE.  90% of the source code is revealed for the back-testing software.  Its like one of those see-thru calculators.  You see all the circuits and semiconductors, but in Python.  So you will need to flow through the code to get to the sections that pertain to your test.  Here is a small sample of how you set up the testing parameters for a Donchian Script.

 

#--------------------------------------------------------------------------------
# If you want to ignore a bunch of non-eseential stuff then
# S C R O L L A L M O S T H A L F W A Y D O W N
#--------------------------------------------------------------------------------
#TradingSimula18.py - programmed by George Pruitt
#Built on the code and ideas from "The Ultimate Algorithmic Tradins System T-Box"
#Code is broken into sections
#Most sections can and should be ignored
#Each trading algorithm must be programmed with this template
#This is the main entry into the platform
#--------------------------------------------------------------------------------
#Import Section - inlcude functions, classes, variables from external modules
#--------------------------------------------------------------------------------
# --- Do not change below here
from getData import getData
from equityDataClass import equityClass
from tradeClass import tradeInfo
from systemMarket import systemMarketClass
from indicators import highest,lowest,rsiClass,stochClass,sAverage,bollingerBands
from indicators import highest,lowest,rsiClass,stochClass,sAverage,bollingerBands,\
adxClass,sAverage2
from portfolio import portfolioClass
from systemAnalytics import calcSystemResults
from utilityFunctions import getDataAtribs,getDataLists,roundToNearestTick,calcTodaysOTE
from utilityFunctions import setDataLists,removeDuplicates
from portManager import portManagerClass,systemMarkTrackerClass
from positionMatrixClass import positionMatrixClass
from barCountCalc import barCountCalc

from sectorClass import sectorClass, parseSectors, numPosCurrentSector,getCurrentSector
#-------------------------------------------------------------------------------------------------
# Pay no attention to these two functions - unless you want to
#-------------------------------------------------------------------------------------------------
def exitPos(myExitPrice,myExitDate,tempName,myCurShares):
global tradeName,entryPrice,entryQuant,exitPrice,numShares,myBPV,cumuProfit
if mp < 0:
trades = tradeInfo('liqShort',myExitDate,tempName,myExitPrice,myCurShares,0)
profit = trades.calcTradeProfit('liqShort',mp,entryPrice,myExitPrice,entryQuant,myCurShares) * myBPV
profit = profit - myCurShares *commission;trades.tradeProfit = profit;cumuProfit += profit
trades.cumuProfit = cumuProfit
if mp > 0:
trades = tradeInfo('liqLong',myExitDate,tempName,myExitPrice,myCurShares,0)
profit = trades.calcTradeProfit('liqLong',mp,entryPrice,myExitPrice,entryQuant,myCurShares) * myBPV
profit = profit - myCurShares * commission;trades.tradeProfit = profit;cumuProfit += profit
trades.cumuProfit = cumuProfit
curShares = 0
for remShares in range(0,len(entryQuant)):curShares += entryQuant[remShares]
return (profit,trades,curShares)

def bookTrade(entryOrExit,lOrS,price,date,tradeName,shares):
global mp,commission,totProfit,curShares,barsSinceEntry,listOfTrades
global entryPrice,entryQuant,exitPrice,numShares,myBPV,cumuProfit
if entryOrExit == -1:
profit,trades,curShares = exitPos(price,date,tradeName,shares);mp = 0
else:
profit = 0;curShares = curShares + shares;barsSinceEntry = 1;entryPrice.append(price);entryQuant.append(shares)
if lOrS == 1:mp += 1;trades = tradeInfo('buy',date,tradeName,entryPrice[-1],shares,1)
if lOrS ==-1:mp -= 1;trades = tradeInfo('sell',date,tradeName,entryPrice[-1],shares,1)
return(profit,curShares,trades)

dataClassList = list()

marketMonitorList,masterDateList,masterDateGlob,entryPrice = ([] for i in range(4))
buy = entry = 1; sell = exit = -1; ignore = 0;
entryQuant,exitQuant,trueRanges,myBPVList = ([] for i in range(4))
myComNameList,myMinMoveList,systemMarketList = ([] for i in range(3))
cond1,cond2,cond3,cond4 = ([] for i in range(4))
marketVal1,marketVal2,marketVal3,marketVal4 = ([] for i in range(4))
portManager = portManagerClass();marketList = getData();portfolio = portfolioClass()
numMarkets = len(marketList);positionMatrix = positionMatrixClass();positionMatrix.numMarkets = numMarkets
firstMarketLoop = True

#----------------------------------------------------------------------------------
# Set up algo parameters here
#----------------------------------------------------------------------------------
startTestDate = 20100101 #must be in yyyymmdd
stopTestDate = 20190228 #must be in yyyymmdd
rampUp = 100 # need this minimum of bars to calculate indicators
sysName = 'Donch-MAX2NSect' #System Name here
initCapital = 500000
commission = 100
Ignore Most Of This Code

Everything is batched processed: set up, pick market or portfolio, run.  Then examine all of the reports.  Here is an example of the sector analysis report.

          Total Profit  Max DrawDown
Currency -------------------------------
BP -14800 19062
SF -8600 53575
AD 4670 11480
DX 10180 10279
EC -9000 16775
JY 10025 18913
CD -19720 21830
-------------------------------------------
Totals: -27245 69223
-------------------------------------------
Energies -------------------------------
CL -40400 55830
HO 80197 23382
NG -14870 28920
RB -45429 61419
-------------------------------------------
Totals: -20502 75957
-------------------------------------------
Metals -------------------------------
GC 27210 36610
SI -1848 2389
HG -2402 2438
PL -16750 25030
PA 27230 38615
-------------------------------------------
Totals: 33440 61472
-------------------------------------------
Grains -------------------------------
S_ 27312 9088
W_ -25538 32600
C_ -1838 12212
BO -8460 9544
SM 390 11250
RR -1390 12060
-------------------------------------------
Totals: -9523 34135
-------------------------------------------
Financials -------------------------------
US 29488 18375
TY 969 12678
TU -2020 3397
FV -2616 4531
ED -4519 4869
-------------------------------------------
Totals: 21302 30178
-------------------------------------------
Softs -------------------------------
SB -1716 19717
KC 15475 44413
CC 540 8090
CT -8705 35660
LB 22269 16586
OJ 4720 8262
-------------------------------------------
Totals: 32583 57976
-------------------------------------------
Meats -------------------------------
LC -18910 24020
LH -31270 35640
FC 14600 25737
-------------------------------------------
Totals: -35580 59550
-------------------------------------------
Sector Analysis

Plus I include EasyLanguage for the majority of the scripts.  Of course without the portfolio level management.  I am working on a new website that will support the new book at TrendFollowingSystems.com.

Please take a look at my latest book – it would make an awesome Christmas present.

 

Testing Keith Fitschen’s Bar Scoring with Pattern Smasher

Keith’s Book

Thanks to MJ for planting the seed for this post.  If you were one of the lucky ones to get Keith’s “Building Reliable Trading SystemsTradable Strategies that Perform as They Backtest and Meet Your Risk-Reward Goals”  book by John Wiley 2013 at the list price of $75 count yourself lucky.  The book sells for a multiple of that on Amazon.com.  Is there anything earth shattering in the book you might ask?  I wouldn’t necessarily say that, but there are some very well thought out and researched topics that most traders would find of interest.

Bar Scoring

In his book Keith discusses the concept of bar-scoring.  In Keith’s words, “Bar-scoring is an objective way to classify an instrument’s movement potential every bar.  The two parts of the bar-scoring are the criterion and the resultant profit X days hence.”  Keith provides several bar scoring techniques, but I highlight just one.

Keith broke these patterns down into the relationship of the close to the open, and close in the upper half of the range; close greater than the open and close in the lower half of the range.  He extended the total number of types to 8 by adding the relationship of the close of the bar to yesterdays bar.

The PatternSmasher code can run through a binary representation

for each pattern and test holding the position for an optimizable number of days.  It can also check for long and short positions.  The original Pattern Smasher code used a for-loop to create patterns that were then compared to the real life facsimile.  In this code it was easier to just manually define the patterns and assign them the binary string.

if c[0]> c[1] and c[0] > o[0] and c[0] > (h[0] + l[0])/2  then patternString = "----";
if c[0]> c[1] and c[0] > o[0] and c[0] < (h[0] + l[0])/2 then patternString = "---+";
if c[0]> c[1] and c[0] < o[0] and c[0] > (h[0] + l[0])/2 then patternString = "--+-";
if c[0]> c[1] and c[0] < o[0] and c[0] < (h[0] + l[0])/2 then patternString = "--++";
if c[0]< c[1] and c[0] > o[0] and c[0] > (h[0] + l[0])/2 then patternString = "-+--";
if c[0]< c[1] and c[0] > o[0] and c[0] < (h[0] + l[0])/2 then patternString = "-+-+";
if c[0]< c[1] and c[0] < o[0] and c[0] > (h[0] + l[0])/2 then patternString = "-++-";
if c[0]< c[1] and c[0] < o[0] and c[0] < (h[0] + l[0])/2 then patternString = "-+++";
Manual Pattern Designations

Please check my code for any errors.  Here I go through the 8 different relationships and assign them to a Patter String.  “-+++”  represents pattern number (7 ) or type (7 + 1 = 8 – my strings start out at 0).  You can then optimize the test pattern and if the test pattern matches the actual pattern, then the Pattern Smasher takes the trade  on the opening of the next bar and holds it for the number of days you specify.  You an also designate long and short positions in the code.  Here I optimized the 8 patterns going long and short and holding from 1-4 days.

Here is the equity curve!  Remember these are Hypothetical Results with $0 commission/slippage and historic performance is not necessarily indicative of future results.  Educational purposes only!  This is tested on ES.D

Play around with the code and let me know if you find any errors or any improvements.

input: patternTests(8),orbAmount(0.20),LorS(1),holdDays(0),atrAvgLen(10),enterNextBarAtOpen(true);

var: patternTest(""),patternString(""),tempString("");
var: iCnt(0),jCnt(0);
array: patternBitChanger[4](0);

{written by George Pruitt -- copyright 2019 by George Pruitt
This will test a 4 day pattern based on the open to close
relationship. A plus represents a close greater than its
open, whereas a minus represents a close less than its open.
The default pattern is set to pattern 14 +++- (1110 binary).
You can optimize the different patterns by optimizing the
patternTests input from 1 to 16 and the orbAmount from .01 to
whatever you like. Same goes for the hold days, but in this
case you optimize start at zero. The LorS input can be
optimized from 1 to 2 with 1 being buy and 2 being sellshort.}

patternString = "";
patternTest = "";

patternBitChanger[0] = 0;
patternBitChanger[1] = 0;
patternBitChanger[2] = 0;
patternBitChanger[3] = 0;

value1 = patternTests - 1;


//example patternTests = 0 -- > 0000
//example patternTests = 1 -- > 0001
//example patternTests = 2 -- > 0010
//example patternTests = 3 -- > 0011
//example patternTests = 4 -- > 0100
//example patternTests = 5 -- > 0101
//example patternTests = 6 -- > 0110
//example patternTests = 7 -- > 0111

if(value1 >= 0) then
begin

if(mod(value1,2) = 1) or value1 = 1 then patternBitChanger[0] = 1;
value2 = value1 - patternBitChanger[0] * 1;

if(value2 >= 7) then begin
patternBitChanger[3] = 1;
value2 = value2 - 8;
end;

if(value2 >= 4) then begin
patternBitChanger[2] = 1;
value2 = value2 - 4;
end;
if(value2 = 2) then patternBitChanger[1] = 1;
end;

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

patternString = "";

if c[0]> c[1] and c[0] > o[0] and c[0] > (h[0] + l[0])/2 then patternString = "----";
if c[0]> c[1] and c[0] > o[0] and c[0] < (h[0] + l[0])/2 then patternString = "---+";
if c[0]> c[1] and c[0] < o[0] and c[0] > (h[0] + l[0])/2 then patternString = "--+-";
if c[0]> c[1] and c[0] < o[0] and c[0] < (h[0] + l[0])/2 then patternString = "--++";
if c[0]< c[1] and c[0] > o[0] and c[0] > (h[0] + l[0])/2 then patternString = "-+--";
if c[0]< c[1] and c[0] > o[0] and c[0] < (h[0] + l[0])/2 then patternString = "-+-+";
if c[0]< c[1] and c[0] < o[0] and c[0] > (h[0] + l[0])/2 then patternString = "-++-";
if c[0]< c[1] and c[0] < o[0] and c[0] < (h[0] + l[0])/2 then patternString = "-+++";


if(barNumber = 1) then print(elDateToString(date)," pattern ",patternTest," ",patternTests-1);
if(patternString = patternTest) then
begin

// print(date," ",patternString," ",patternTest); //uncomment this and you can print out the pattern
if (enterNextBarAtOpen) then
begin
if(LorS = 2) then SellShort("PatternSell") next bar on open;
if(LorS = 1) then buy("PatternBuy") next bar at open;
end
else
begin
if(LorS = 2) then SellShort("PatternSellBO") next bar at open of tomorrow - avgTrueRange(atrAvgLen) * orbAmount stop;
if(LorS = 1) then buy("PatternBuyBO") next bar at open of tomorrow + avgTrueRange(atrAvgLen) * orbAmount stop;
end;


end;

if(holdDays = 0 ) then setExitonClose;
if(holdDays > 0) then
begin
if(barsSinceEntry = holdDays and LorS = 2) then BuyToCover("xbarLExit") next bar at open;
if(barsSinceEntry = holdDays and LorS = 1) then Sell("xbarSExit") next bar at open;
end;
Bar Scoring Testing Template

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.

Question on Multiple Time Indicator [Discrete Bars]

A reader of this blog proffered an excellent question on this indicator.  I hope this post answers his question and I am always open to any input that might improve my coding!

Because I use BarNumber in my MODULUS calculation the different time frames that I keep track of may not align with the time frames on the chart; your 10-minute bar O, H, L, and C values may not align with the values I am storing in my 10-minute bar container.    Take a look at this snapshot of a spreadsheet.

Here I  print out a 5-minute bar of the ES.D.  Because I use BarNumber in my Modulus calculation, I don’t get to a zero remainder until  9:50 in the 10, 15, and 20 minute time frames.  At 9:50 I start building fresh 10, 15, 20 minute bars by resetting the O, H, L and C to those of the 5-minute bars.  From there I keep track of the highest highs and lowest lows by extracting the data from the 5-minute bar.  I always set the close of the different time frames to the current 5-minute bar’s close.   Once the modulus for the different time frames reaches zero I close out the bar and start fresh again.  The 25-minute bar didn’t reach zero until the 10:05 bar.

I will see if I can come up with some code that will sync with the data on the chart.

Backtesting with [Trade Station,Python,AmiBroker, Excel]. Intended for informational and educational purposes only!