Tag Archives: EasyLanguage

EasyLanguage Programming Workshop Part 1: 2D Array, Print Format, and Loops

Storing Trades for Later Use in a 2D Array

Since this is part 1 we are just going to go over a very simple system:  SAR (stop and reverse) at highest/lowest high/low for past 20 days.

A 2D Array in EasyLanguage is Immutable

Meaning that once you create an array all of the data types must be the same.  In a Python list you can have integers, strings, objects whatever.   In C and its derivatives you also have a a data structure (a thing that stores related data) know as a Structure or Struct.  We can mimic a structure in EL by using a 2 dimensional array.  An array is just a list of values that can be referenced by an index.

array[1] = 3.14

array[2] = 42

array[3] = 2.71828

A 2 day array is similar but it looks like a table

array[1,1], array[1,2], array[1,3]

array[2,1], array[2,2], array[2,3]

The first number in the pair is the row and the second is the column.  So a 2D array can be very large table with many rows and columns.  The column can also be referred to as a field in the table.  To help use a table you can actually give your fields names.  Here is a table structure that I created to store trade information.

  1. trdEntryPrice (0) – column zero – yes we can have a 0 col. and row
  2. trdEntryDate(1)
  3. trdExitPrice (2)
  4. trdExitDate(3)
  5. trdID(4)
  6. trdPos(5)
  7. trdProfit(6)
  8. trdCumuProfit(7)

So when I refer to tradeStruct[0, trdEntryPrice] I am referring to the first column in the first row.

This how you define a 2D array and its associate fields.

arrays: tradeStruct[10000,7](0);

vars: trdEntryPrice (0),
      trdEntryDate(1), 
      trdExitPrice (2),
      trdExitDate(3),
      trdID(4),
      trdPos(5),
      trdProfit(6),
      trdCumuProfit(7);
2D array and its Fields

In EasyLanguage You are Poised at the Close of a Yesterday’s Bar

This paradigm allows you to sneak a peek at tomorrow’s open tick but that is it.  You can’t really cheat, but it also limits your creativity and makes things more difficult to program when all you want is an accurate backtest.   I will go into detail, if I haven’t already in an earlier post, the difference of sitting on Yesterday’s close verus sitting on Today’s close with retroactive trading powers.  Since we are only storing trade information when can use hindsight to gather the information we need.

Buy tomorrow at highest(h,20) stop;

SellShort tomorrow at lowest(l,20) stop;

These are the order directives that we will be using to execute our strategy.  We can also run a Shadow System, with the benefit of hindsight, to see where we entered long/short and at what prices. I call it a Shadow because its all the trades reflected back one bar.   All we need to do is offset the highest and lowest calculations by 1 and compare the values to today’s highs and lows to determine trade entry.  We must also test the open if a gap occurred and we would have been filled at the open.  Now this code gets a bit hairy, but stick with it.

Shadow System

stb = highest(h,20);
sts = lowest(l,20);
stb1 = highest(h[1],20);
sts1 = lowest(l[1],20);

buy("Sys-L") 1 contract next bar at stb stop;
sellShort("Sys-S") 1 contract next bar at sts stop;

mp = marketPosition*currentContracts;
totTrds = totalTrades;

if mPos <> 1 then
begin
	if h >= stb1 then
	begin
		if mPos < 0 then // close existing short position
		begin
			mEntryPrice = tradeStruct[numTrades,trdEntryPrice];
			mExitPrice = maxList(o,stb1);
			tradeStruct[numTrades,trdExitPrice] = mExitPrice;
			tradeStruct[numTrades,trdExitDate] = date;
			mProfit = (mEntryPrice - mExitPrice) * bigPointValue - mCommSlipp;
			cumuProfit += mProfit;
			tradeStruct[numTrades,trdCumuProfit] = cumuProfit;
			tradeStruct[numTrades,trdProfit] = mProfit;
			print(d+19000000:8:0," shrtExit ",mEntryPrice:4:5," ",mExitPrice:4:5," ",mProfit:6:0," ",cumuProfit:7:0);
		print("-------------------------------------------------------------------------");	
		end;	
		numTrades +=1;
		mEntryPrice = maxList(o,stb1);
		tradeStruct[numTrades,trdID] = 1;
		tradeStruct[numTrades,trdPOS] = 1;
		tradeStruct[numTrades,trdEntryPrice] = mEntryPrice;
		tradeStruct[numTrades,trdEntryDate] = date;
		mPos = 1;
		print(d+19000000:8:0," longEntry ",mEntryPrice:4:5);
	end;
end;
if mPos <>-1 then
begin
	if l <= sts1 then
	begin
		if mPos > 0 then // close existing long position
		begin
			mEntryPrice = tradeStruct[numTrades,trdEntryPrice];
			mExitPrice = minList(o,sts1);
			tradeStruct[numTrades,trdExitPrice] = mExitPrice;
			tradeStruct[numTrades,trdExitDate] = date;
			mProfit = (mExitPrice - mEntryPrice ) * bigPointValue - mCommSlipp;
			cumuProfit += mProfit;
			tradeStruct[numTrades,trdCumuProfit] = cumuProfit;
			tradeStruct[numTrades,trdProfit] = mProfit;
			print(d+19000000:8:0," longExit ",mEntryPrice:4:5," ",mExitPrice:4:5," ",mProfit:6:0," ",cumuProfit:7:0);
			print("---------------------------------------------------------------------");
		end;	
		numTrades +=1;
		mEntryPrice =minList(o,sts1);
		tradeStruct[numTrades,trdID] = 2;
		tradeStruct[numTrades,trdPOS] =-1;
		tradeStruct[numTrades,trdEntryPrice] = mEntryPrice;
		tradeStruct[numTrades,trdEntryDate] = date;
		mPos = -1;
		print(d+19000000:8:0," ShortEntry ",mEntryPrice:4:5);
	end;
end;
Shadow System - Generic forany SAR System

Notice I have stb and stb1.  The only difference between the two calculations is one is displaced a day.  I use the stb and sts in the EL trade directives.  I use stb1 and sts1 in the Shadow System code.  I guarantee this snippet of code is in every backtesting platform out there.

All the variables that start with the letter m, such as mEntryPrice, mExitPrice deal with the Shadow System.  Theyare not derived from TradeStation’s back testing engine only our logic.  Lets look at the first part of just one side of the Shadow System:

if mPos <> 1 then
begin
	if h >= stb1 then
	begin
		if mPos < 0 then // close existing short position
		begin
			mEntryPrice = tradeStruct[numTrades,trdEntryPrice];
			mExitPrice = maxList(o,stb1);
			tradeStruct[numTrades,trdExitPrice] = mExitPrice;
			tradeStruct[numTrades,trdExitDate] = date;
			mProfit = (mEntryPrice - mExitPrice) * bigPointValue - mCommSlipp;
			cumuProfit += mProfit;
			tradeStruct[numTrades,trdCumuProfit] = cumuProfit;
			tradeStruct[numTrades,trdProfit] = mProfit;
			print(d+19000000:8:0," shrtExit ",mEntryPrice:4:5," ",mExitPrice:4:5," ",mProfit:6:0," ",cumuProfit:7:0);
		print("-------------------------------------------------------------------------");	
		end;	

mPos and mEntryPrice and mExitPrice belong to the Shadow System

if mPos <> 1 then the Shadow Systems [SS] is not long.  So we test today’s high against stb1 and if its greater then we know a long position was put on.  But what if mPos = -1 [short], then we need to calculate the exit and the trade profit and the cumulative trade profit.  If mPos = -1 then we know a short position is on and we can access its particulars from the tradeStruct 2D arraymEntryPrice = tradeStruct[numTrades,trdEntryPrice].  We can gather the other necessary information from the tradeStruct [remember this is just a table with fields spelled out for us.]  Once we get the information we need we then need to stuff our calculations back into the Structure or table so we can regurgitate later.  We stuff date in to the following fields trdExitPrice, trdExitDate, trdProfit and trdCumuProfit in the table.

Formatted Print: mEntryPrice:4:5

Notice in the code how I follow the print out of variables with :8:0 or :4:5?  I am telling TradeStation to use either 0 or 5 decimal places.  The date doesn’t need decimals but prices do.  So I format that so that they will line up really pretty like.

Now that I take care of liquidating an existing position all I need to do is increment the number of trades and stuff the new trade information into the Structure.

		numTrades +=1;
		mEntryPrice = maxList(o,stb1);
		tradeStruct[numTrades,trdID] = 1;
		tradeStruct[numTrades,trdPOS] = 1;
		tradeStruct[numTrades,trdEntryPrice] = mEntryPrice;
		tradeStruct[numTrades,trdEntryDate] = date;
		mPos = 1;
		print(d+19000000:8:0," longEntry ",mEntryPrice:4:5);

The same goes for the short entry and long exit side of things.  Just review the code.  I print out the trades as we go along through the history of crude.  All the while stuffing the table.

If LastBarOnChart -> Regurgitate

On the last bar of the chart we know exactly how many trades have been executed because we were keeping track of them in the Shadow System.  So it is very easy to loop from 0 to numTrades.

if lastBarOnChart then
begin
	print("Trade History");
	for arrIndx = 1 to numTrades
	begin
		value20 = tradeStruct[arrIndx,trdEntryDate];
		value21 = tradeStruct[arrIndx,trdEntryPrice];
		value22 = tradeStruct[arrIndx,trdExitDate];
		value23 = tradeStruct[arrIndx,trdExitPrice];
		value24 = tradeStruct[arrIndx,trdID];
		value25 = tradeStruct[arrIndx,trdProfit];
		value26 = tradeStruct[arrIndx,trdCumuProfit];
		
		print("---------------------------------------------------------------------");
		if value24 = 1 then
		begin
			string1 = buyStr;
			string2 = sellStr;
		end;
		if value24 = 2 then
		begin
			string1 = shortStr;
			string2 = coverStr;
		end;	
		print(value20+19000000:8:0,string1,value21:4:5," ",value22+19000000:8:0,string2,
			  value23:4:5," ",value25:6:0," ",value26:7:0);
	end;
end;

Add 19000000 to Dates for easy Translation

Since all trade information is stored in the Structure or Table then pulling the information out using our Field Descriptors is very easy.  Notice I used EL built-in valueXX to store table information.  I did this to make the print statements a lot shorter.  I could have just used tradeStruct[arrIndx, trdEntry] or whatever was needed to provide the right information, but the lines would be hard to read.  To translate EL date to a normal looking data just add 19,000,000 [without commas].

If you format your PrintLog to a monospaced font your out put should look like this.

 

PrintLog OutPut

Why Would We Want to Save Trade Information?

The answer to this question will be answered in Part 2.  Email me with any other questions…..

Why Do I Need to Test with Intraday Data

Why Can’t I Just Test with Daily Bars and Use Look-Inside Bar?

Good question.  You can’t because it doesn’t work accurately all of the time.   I just default to using 5 minute or less bars whenever I need to.  A large portion of short term, including day trade, systems need to know the intra day market movements to know which orders were filled accurately.  It would be great if you could just flip a switch and convert a daily bar system to an intraday system and Look Inside Bar(LIB) is theoretically that switch.  Here I will prove that switch doesn’t always work.

Daily Bar System

  • Buy next bar at open of the day plus 20% of the 5 day average range
  • SellShort next at open of the day minus 20% of the 5 day average range
  • If long take a profit at one 5 day average range above entryPrice
  • If short take a profit at one 5 day average range below entryPrice
  • If long get out at a loss at 1/2 a 5 day average range below entryPrice
  • If short get out at a loss at 1/2 a 5 day average range above entry price
  • Allow only 1 long and 1 short entry per day
  • Get out at the end of the day

Simple Code for the System

value1 = .2 * average(Range,5);
value2 = value1 * 5;

Buy next bar at open of next bar + value1 stop;
sellShort next bar at open of next bar - value1 stop;

setProfitTarget(value2*bigPointValue);
setStopLoss(value2/2*bigPointValue);
setExitOnClose;
Simplified Daily Bar DayTrade System using ES.D Daily
Daily Bar Using 5 min Look Inside Bar

Looks great with just the one hiccup:  Bot @ 3846.75 and the Shorted @ 3834.75 and then took nearly 30 handles of profit.

Now let’s see what really happened.

What Really Happened – Bot – Shorted – Stopped Out

Intraday Code to Control Entry Time and Number of Longs and Shorts

Not an accurate representation so let’s take this really simple system and apply it to intraday data.  Approaching this from a logical perspective with limited knowledge about TradeStation you might come up with this seemingly valid solution.  Working on the long side first.

//First Attempt


if d <> d[1] then value1 = .2 * average(Range of data2,5);
value2 = value1 * 5;
if t > sess1startTime then buy next bar at opend(0) + value1 stop;
setProfitTarget(value2*bigPointValue);
setStopLoss(value2/2*bigPointValue);
setExitOnClose;
First Simple Attempt

This looks very similar to the daily bar system.  I cheated a little by using

if d <> d[1] then value1 = .2 * average(Range of data2,5);

Here I am only calculating the average once a day instead of on each 5 minute bar.  Makes things quicker.  Also I used

if t > sess1StartTime then buy next bar at openD(0) + value1 stop;

I did that because if you did this:

buy next bar at open of next bar + value1 stop;

You would get this:

Cannot Sneak a Peek with Data2

That should do it for the long side, right?

Didn’t work quite right!

So now we have to monitor when we can place a trade and monitor the number of long and short entries.

How does this look!

Correct Execution!

So here is the code.  You will notice the added complexity.  The important things to know is how to control when an entry is allowed and how to count the number of long and short entries.  I use the built-in keyword/function totalTrades to keep track of entries/exits and marketPosition to keep track of the type of entry.

Take a look at the code and you can see how the daily bar system is somewhat embedded in the code.  But remember you have to take into account that you are stepping through every 5 minute bar and things change from one bar to the next.

vars: buysToday(0),shortsToday(0),curTotTrades(0),mp(0),tradeZoneTime(False);


if d <> d[1] then 
begin
	curTotTrades = totalTrades;
 	value1 = .2 * average(Range of data2,5);
	value2 = value1 * 5;	
	buysToday = 0;
	shortsToday = 0;
	tradeZoneTime = False;
end;

mp = marketPosition;

if totalTrades > curTotTrades then
begin
	if mp <> mp[1] then 
	begin
		if mp[1] = 1 then buysToday = buysToday + 1;
		if mp[1] = -1 then shortsToday = shortsToday + 1;
	end;
	if mp[1] = -1 then print(d," ",t," ",mp," ",mp[1]," ",shortsToday);
	curTotTrades = totalTrades;
end;
if t > sess1StartTime and t < sess1EndTime then tradeZoneTime = True;

if tradeZoneTime and buysToday = 0 and mp <> 1 then 
	buy next bar at opend(0) + value1 stop;
	
if tradeZoneTime and  shortsToday = 0 and mp <> -1 then 
	sellShort next bar at opend(0) - value1 stop;

setProfitTarget(value2*bigPointValue);
setStopLoss(value2/2*bigPointValue);
setExitOnClose;
Proper Code to Replicate the Daily Bar System with Accuracy

Here’s a few trade examples to prove our code works.

Looks Right!

Okay the code worked but did the system?

Uh? NO!

Conclusion

If you need to know what occurred first – a high or a low in a move then you must use intraday data.  If you want to have multiple entries then of course your only alternative is intraday data.   This little bit of code can get you started converting your daily bar systems to intraday data and can be a framework to develop your own day trading/or swing systems.

Can I Prototype A Short Term System with Daily Data?

You can of course use Daily Bars for fast system prototyping.  When the daily bar system was tested with LIB turned on, it came close to the same results as the more accurately programmed intraday system.  So you can prototype to determine if a system has a chance.  Our core concept buyt a break out, short a break out, take profits and losses and have no overnight exposure sounds good theoretically.  And if you only allow 2 entries in opposite directions on a daily bar you can determine if there is something there.

A Dr. Jekyll and Mr. Hyde Scenario

While playing around with this I did some prototyping of a daily bar system and created this equity curve.  I mistakenly did not allow any losses – only took profits and re-entered long.

Wow! Awesome! Holy Grail Uncovered. Venalicius Cave!

Venalicius Cave!  Don’t take a loser you and will reap the benefits.  The chart says so – so its got to be true – I know right?

The same chart from a different perspective.

You Start and End at the Same Place. But What A Ride. Yikes!

Moral of the Story – always look at your detailed Equity Curve.  This curve is very close to a simple buy and hold strategy.   Maybe a little better.

Daily Bar Ratcheting Stop and Conditional Optimization

Happy New Year!  My First Post of 2021!

In this post I simply wanted to convert the intraday ratcheting stop mechanism that I previously posted into a daily bar mechanism.  Well that got me thinking of how many different values could be used as the amount to ratchet.  I came up with three:

I have had requests for the EasyLanguage in an ELD – so here it is – just click on the link and unZip.

RATCHETINGSTOPWSWITCH

Ratcheting Schemes

  • ATR of N days
  • Fixed $ Amount
  • Percentage of Standard Deviation of 20 Days

So this was going to be a great start to a post, because I was going to incorporate one of my favorite programming constructs : Switch-Case.  After doing the program I thought wouldn’t it be really cool to be able to optimize over each scheme the ratchet and trail multiplier as well as the values that might go into each scheme.

In scheme one I wanted to optimize the N days for the ATR calculation.  In scheme two I wanted to optimize the $ amount and the scheme three the percentage of a 20 day standard deviation.  I could do a stepwise optimization and run three independent optimizations – one for each scheme.  Why not just do one global optimization you might ask?  You could but it would be a waste of computer time and then you would have to sift through the results.  Huh?  Why?  Here is a typical optimization loop:

Scheme Ratchet Mult Trigger Mult Parameter 1
1 : ATR 1 1 ATR (2)
2 : $ Amt 1 1 ATR (2)
3 : % of Dev. Amt 1 1 ATR (2)
1 : ATR 2 1 ATR (2)
2 : $ Amt 2 1 ATR (2)

Notice when we switch schemes the Parameter 1 doesn’t make sense.  When we switch to $ Amt we want to use a $ Value as Parameter 1 and not ATR.  So we could do a bunch of optimizations across non sensical values, but that wouldn’t really make a lot of sense.  Why not do a conditional optimization?  In other words, optimize only across a certain parameter range based on which scheme is currently being used.  I knew there wasn’t an overlay available to use using standard EasyLanguage but I thought maybe OOP,  and there is an optimization API that is quite powerful.  The only problem is that it was very complicated and I don’t know if I could get it to work exactly the way I wanted.

EasyLanguage is almost a full blown programming language.  So should I not be able to distill this conditional optimization down to something that I could do with such a powerful programming language?  And the answer is yes and its not that complicated.  Well at least for me it wasn’t but for beginners probably.  But to become a successful programmer you have to step outside your comfort zones, so I am going to not only explain the Switch/Case construct (I have done this in earlier posts)  but incorporate some array stuff.

When performing conditional optimization there are really just a few things you have to predefine:

  1. Scheme Based Optimization Parameters
  2. Exact Same Number of Iterations for each Scheme [starting point and increment value]
  3. Complete Search Space
  4. Total Number of Iterations
  5. Staying inside the bounds of your Search Space

Here are the optimization range per scheme:

  • Scheme #1 – optimize number of days in ATR calculation – starting at 10 days and incrementing by 2 days
  • Scheme #2 – optimize $ amounts – starting at $250 and incrementing by $100
  • Scheme #3 – optimize percent of 20 Bar standard deviation – starting at 0,25 and incrementing by 0.25

I also wanted to optimize the ratchet and target multiplier.  Here is the base code for the daily bar ratcheting system with three different schemes.  Entries are based on penetration of 20 bar highest/lowest close.

inputs: 
ratchetMult(2),trailMult(2),
volBase(True),volCalcLen(20),
dollarBase(False),dollarAmt(250),
devBase(False),devAmt(0.25);


vars:longMult(0),shortMult(0);
vars:ratchetAmt(0),trailAmt(0);
vars:stb(0),sts(0),mp(0);
vars:lep(0),sep(0);



if volBase then 
begin
	ratchetAmt = avgTrueRange(volCalcLen) * ratchetMult;
	trailAmt = avgTrueRange(volCalcLen) * trailMult;
end;
if dollarBase then 
begin
	ratchetAmt =dollarAmt/bigPointValue * ratchetMult;
	trailAmt = dollarAmt/bigPointValue * trailMult;
end;
if devBase then 
begin
	ratchetAmt = stddev(c,20) * devAmt * ratchetMult;
	trailAmt = stddev(c,20) * devAmt * trailMult;
end;


if c crosses over highest(c[1],20) then buy next bar at open;
if c crosses under lowest(c[1],20) then sellshort next bar at open;

mp = marketPosition;
if mp <> 0 and mp[1] <> mp then
begin
	longMult = 0;
	shortMult = 0;
end;


If mp = 1 then lep = entryPrice;
If mp =-1 then sep = entryPrice;


// 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 multiples 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;
	Sell("LongTrail") next bar at (lep + (longMult - 1) *  trailAmt) stop;
end;

If mp = -1 then 
Begin
	If l <= sep - (shortMult + 1) * ratchetAmt then	shortMult = shortMult + 1;
	buyToCover("ShortTrail") next bar (sep - (shortMult - 1) *  trailAmt) stop;
end;
Daily Bar Ratchet System

This code is fairly simple.  The intriguing inputs are:

  • volBase [True of False] and  volCalcLen [numeric Value]
  • dollarBase [True of False] and  dollarAmt [numeric Value]
  • devBase [True of False] and devAmt [numeric Value]

If volBase is true then you use the parameters that go along with that scheme.  The same goes for the other schemes.  So when you run this you would turn one scheme on at a time and set the parameters accordingly.  if I wanted to use dollarBase(True) then I would set the dollarAmt to a $ value.  The ratcheting mechanism is the same as it was in the prior post so I refer you back to that one for further explanation.

So this was a pretty straightforward strategy.  Let us plan out our optimization search space based on the different ranges for each scheme.  Since each scheme uses a different calculation we can’t simply optimize across all of the different ranges – one is days, and the other two are dollars and percentages.

Enumerate

We know how to make TradeStation loop based on the range of a value.  If you want to optimize from $250 to $1000 in steps of $250, you know this involves [$1000 – $250] / $250 + 1 or 3 + 1 or 4 interations.   Four loops will cover this entire search space.  Let’s examine the search space for each scheme:

  • ATR Scheme: start at 10 bars and end at 40 by steps of 2 or [40-10]/2 + 1 = 16
  • $ Amount Scheme: start at $250 and since we have to have 16 iterations [remember # of iterations have to be the same for each scheme] what can we do to use this information?  Well if we start $250 and step by $100 we cover the search space $250, $350, $450, $550…$1,750.  $250 + 15 x 250.  15 because $250 is iteration 1.
  • Percentage StdDev Scheme:  start at 0.25 and end at 0.25 + 15 x 0.25  = 4

So we enumerate 16 iterations to a different value.  The easiest way to do this is to create a map.  I know this seems to be getting hairy but it really isn’t.  The map will be defined as an array with 16 elements.  The array will be filled with the search space based on which scheme is currently being tested.  Take a look at this code where I show how to define an array of 16 elements and introduce my Switch/Case construct.

array: optVals[16](0);

switch(switchMode)
begin
	case 1:
		startPoint = 10; // vol based
		increment = 2;
	case 2:
		startPoint = 250/bigPointValue; // $ based
		increment = 100/bigPointValue;
	case 3:
		startPoint = 0.25; //standard dev
		increment = 0.25*minMove/priceScale;
	default:
		startPoint = 1;
		increment = 1;
end;

vars: cnt(0),loopCnt(0);
once 
begin
	for cnt = 1 to 16 
	begin
		optVals[cnt] = startPoint + (cnt-1) * increment;
	end;
end
Set Up Complete Search Space for all Three Schemes

This code creates a 16 element array, optVals, and assigns 0 to each element.  SwitchMode goes from 1 to 3.

  • if switchMode is 1: ATR scheme [case: 1] the startPoint is set to 10 and increment is set to 2
  • if switchMode is 2: $ Amt scheme [case: 2] the startPoint is set to $250 and increment is set to $100
  • if switchMode is 3: Percentage of StdDev [case: 3] the startPoint is set to 0.25 and the increment is set to 0.25

Once these two values are set the following 15 values can be spawned by the these two.  A for loop is great for populating our search space.  Notice I wrap this code with ONCE – remember ONCE  is only executed at the very beginning of each iteration or run.

once
begin
   for cnt = 1 to 16
   begin
     optVals[cnt] = startPoint + (cnt-1) * increment;
   end;
end

Based on startPoint and increment the entire search space is filled out.  Now all you have to do is extract this information stored in the array based on the iteration number.

Switch(switchMode) 
Begin
	Case 1:
		ratchetAmt = avgTrueRange(optVals[optLoops]) * ratchetMult;
		trailAmt = avgTrueRange(optVals[optLoops]) * trailMult;	
	Case 2:
		ratchetAmt =optVals[optLoops] * ratchetMult;
		trailAmt = optVals[optLoops] * trailMult;
	Case 3: 
		ratchetAmt =stddev(c,20) * optVals[optLoops] * ratchetMult;
		trailAmt = stddev(c,20) * optVals[optLoops] * trailMult;
	Default:
		ratchetAmt = avgTrueRange(optVals[optLoops]) * ratchetMult;
		trailAmt = avgTrueRange(optVals[optLoops]) * trailMult;
end;


if c crosses over highest(c[1],20) then buy next bar at open;
if c crosses under lowest(c[1],20) then sellshort next bar at open;

mp = marketPosition;
if mp <> 0 and mp[1] <> mp then
begin
	longMult = 0;
	shortMult = 0;
end;


If mp = 1 then lep = entryPrice;
If mp =-1 then sep = entryPrice;


// 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 multiples 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;
	Sell("LongTrail") next bar at (lep + (longMult - 1) *  trailAmt) stop;
end;

If mp = -1 then 
Begin
	If l <= sep - (shortMult + 1) * ratchetAmt then	shortMult = shortMult + 1;
	buyToCover("ShortTrail") next bar (sep - (shortMult - 1) *  trailAmt) stop;
end;
Extract Search Space Values and Rest of Code

Switch(switchMode)
Begin
Case 1:
  ratchetAmt = avgTrueRange(optVals[optLoops])ratchetMult;
  trailAmt = avgTrueRange(optVals[optLoops]) trailMult;
Case 2:
  ratchetAmt =optVals[optLoops] * ratchetMult;
  trailAmt = optVals[optLoops] * trailMult;
Case 3:
  ratchetAmt =stddev(c,20)optVals[optLoops]
ratchetMult;
  trailAmt = stddev(c,20) * optVals[optLoops] * trailMult;

Notice how the optVals are indexed by optLoops.  So the only variable that is optimized is the optLoops and it spans 1 through 16.  This is the power of enumerations – each number represents a different thing and this is how you can control which variables are optimized in terms of another optimized variable.   Here is my optimization specifications:

Opimization space

And here are the results:

Optimization Results

The best combination was scheme 1 [N-day ATR Calculation] using a 2 Mult Ratchet and 1 Mult Trail Trigger.  The best N-day was optVals[2] for this scheme.  What in the world is this value?  Well you will need to back engineer a little bit here.  The starting point for this scheme was 10 and the increment was 2 so if optVals[1] =10 then optVals[2] = 12 or ATR(12).    You can also print out a map of the search spaces.

vars: cnt(0),loopCnt(0);
once 
begin
	loopCnt = loopCnt + 1;
//	print(switchMode," : ",d," ",startPoint);
//	print("  ",loopCnt:2:0,"  --------------------");
	for cnt = 1 to 16 
	begin
		optVals[cnt] = startPoint + (cnt-1) * increment;
//		print(cnt," ",optVals[cnt]," ",cnt-1);
	end;
end;	
  Scheme 1
  --------------------
   1.00   10.00    0.00 10 days
   2.00   12.00    1.00
   3.00   14.00    2.00
   4.00   16.00    3.00
   5.00   18.00    4.00
   6.00   20.00    5.00
   7.00   22.00    6.00
   8.00   24.00    7.00
   9.00   26.00    8.00
  10.00   28.00    9.00
  11.00   30.00   10.00
  12.00   32.00   11.00
  13.00   34.00   12.00
  14.00   36.00   13.00
  15.00   38.00   14.00
  16.00   40.00   15.00
 
  Scheme2
  --------------------
   1.00    5.00    0.00 $ 250
   2.00    7.00    1.00 $ 350
   3.00    9.00    2.00 $ 400
   4.00   11.00    3.00 $ ---
   5.00   13.00    4.00
   6.00   15.00    5.00
   7.00   17.00    6.00
   8.00   19.00    7.00
   9.00   21.00    8.00
  10.00   23.00    9.00
  11.00   25.00   10.00
  12.00   27.00   11.00
  13.00   29.00   12.00
  14.00   31.00   13.00
  15.00   33.00   14.00
  16.00   35.00   15.00 $1750

  Scheme 3
  --------------------
   1.00    0.25    0.00 25 % stdDev
   2.00    0.50    1.00
   3.00    0.75    2.00
   4.00    1.00    3.00
   5.00    1.25    4.00
   6.00    1.50    5.00
   7.00    1.75    6.00
   8.00    2.00    7.00
   9.00    2.25    8.00
  10.00    2.50    9.00
  11.00    2.75   10.00
  12.00    3.00   11.00
  13.00    3.25   12.00
  14.00    3.50   13.00
  15.00    3.75   14.00
  16.00    4.00   15.00

This was a elaborate post so please email me with questions.  I wanted to demonstrate that we can accomplish very sophisticated things with just the pure and raw EasyLanguage which is a programming language itself.

Highly Illogical – Best Guess Doesn’t Match Reality

An ES Break-Out System with Unexpected Parameters

I was recently testing the idea of a short term VBO strategy on the ES utilizing very tight stops.  I wanted to see if using a tight ATR stop in concert with the entry day’s low (for buys) would cut down on losses after a break out.  In other words, if the break out doesn’t go as anticipated get out and wait for the next signal.  With the benefit of hindsight in writing this post, I certainly felt like my exit mechanism was what was going to make or break this system.  In turns out that all pre conceived notions should be thrown out when volatility enters the picture.

System Description

  • If 14 ADX < 20 get ready to trade
  • Buy 1 ATR above the midPoint of the past 4 closing prices
  • Place an initial stop at 1 ATR and a Profit Objective of 1 ATR
  • Trail the stop up to the prior day’s low if it is greater than entryPrice – 1 ATR initially, and then trail if a higher low is established
  • Wait 3 bars to Re-Enter after going flat – Reversals allowed

That’s it.  Basically wait for a trendless period and buy on the bulge and then get it out if it doesn’t materialize.  I knew I could improve the system by optimizing the parameters but I felt I was in the ball park.  My hypothesis was that the system would fail because of the tight stops.  I felt the ADX trigger was OK and the BO level would get in on a short burst.  Just from past experience I knew that using the prior day’s price extremes as a stop usually doesn’t fair that well.

Without commission the initial test was a loser: -$1K and -$20K draw down over the past ten years.  I thought I would test my hypothesis by optimizing a majority of the parameters:

  • ADX Len
  • ADX Trigger Value
  • ATR Len
  • ATR BO multiplier
  • ATR Multiplier for Trade Risk
  • ATR Multiplier for Profit Objective
  • Number of bars to trail the stop – used lowest lows for longs

Results

As you can probably figure, I  had to use the Genetic Optimizer to get the job done.  Over a billion different permutations.  In the end here is what the computer pushed out using the best set of parameters.

No Commission or Slippage – Genetic Optimized Parameter Selection

Optimization Report – The Best of the Best

Top Parameters – notice the Wide Stop Initially and the Trailing Stop Look-Back and also the Profit Multiplier – but what really sticks out is the ADX inputs

ADX – Does it Really Matter?

Take a look at the chart – the ADX is mostly in Trigger territory – does it really matter?

A Chart is Worth a 1000 Words

What does this chart tell us?

70% of Profit was made in last 40 trades

Was the parameter selection biased by the heightened level of volatility?  The system has performed on the parameter set very well over the past two or three years.  But should you use this parameter set going into the future – volatility will eventually settle down.

Now using my experience in trading I would have selected a different parameter set.   Here are my biased results going into the initial programming.  I would use a wider stop for sure, but I would have used the generic ADX values.

George’s More Common Sense Parameter Selection – wow big difference

I would have used 14 ADX Len with a 20 trigger and risk 1 to make 3 and use a wider trailing stop.  With trend neutral break out algorithms, it seems you have to be in the game all of the time.  The ADX was supposed to capture zones that predicated break out moves, but the ADX didn’t help out at all.  Wider stops helped but it was the ADX values that really changed the complexion of the system.  Also the number of bars to wait after going flat had a large impact as well.  During low volatility you can be somewhat picky with trades but when volatility increases you gots to be in the game. – no ADX filtering and no delay in re-Entry.  Surprise, surprise!

Alogorithm Code

Here is the code – some neat stuff here if you are just learning EL.  Notice how I anchor some of the indicator based variables by indexing them by barsSinceEntry.  Drop me a note if you see something wrong or want a little further explanation.

Inputs: adxLen(14),adxTrig(25),atrLen(10),atrBOMult(1),atrRiskMult(1),atrProfMult(2),midPtNumBar(3),posMovTrailNumBars(2),reEntryDelay(3);
vars: mp(0),trailLongStop(0),trailShortStop(0),BSE(999),entryBar(0),tradeRisk(0),tradeProf(0);
vars: BBO(0),SBO(0),ATR(0),totTrades(0);

mp = marketPosition;
totTrades = totalTrades;
BSE = barsSinceExit(1);
If totTrades <> totTrades[1] then BSE = 0;
If totalTrades = 0 then BSE = 99;


ATR = avgTrueRange(atrLen);

SBO = midPoint(c,midPtNumBar) - ATR * atrBOMult;
BBO = midPoint(c,midPtNumBar) + ATR * atrBOMult;

tradeRisk = ATR * atrRiskMult;
tradeProf = ATR * atrProfMult;

If mp <> 1 and adx(adxLen) < adxTrig and BSE > reEntryDelay and open of next bar < BBO then buy next bar at BBO stop;
If mp <>-1 and adx(adxLen) < adxTrig AND BSE > reEntryDelay AND open of next bar > SBO then sellshort next bar at SBO stop;

If mp = 1 and mp[1] <> 1 then
Begin
	trailLongStop = entryPrice - tradeRisk;
end;

If mp = -1 and mp[1] <> -1 then
Begin
	trailShortStop = entryPrice + tradeRisk;
end;
	
if mp = 1 then sell("L-init-loss") next bar at entryPrice - tradeRisk[barsSinceEntry] stop;
if mp = -1 then buyToCover("S-init-loss") next bar at entryPrice + tradeRisk[barsSinceEntry] stop;


if mp = 1 then 
begin
	sell("L-ATR-prof") next bar at entryPrice + tradeProf[barsSinceEntry] limit;
	trailLongStop = maxList(trailLongStop,lowest(l,posMovTrailNumBars));
	sell("L-TL-Stop") next bar at trailLongStop stop;
end; 
if mp =-1 then 
begin
	buyToCover("S-ATR-prof") next bar at entryPrice -tradeProf[barsSinceEntry] limit;
	trailShortStop = minList(trailShortStop,highest(h,posMovTrailNumBars));
//	print(d, " Short and trailStop is : ",trailShortStop);
	buyToCover("S-TL-Stop") next bar at trailShortStop stop;
end;

Converting A String Date To A Number – String Manipulation in EasyLanguage

EasyLanguage Includes a Powerful String Manipulation Library

I thought I would share this function.  I needed to convert a date string (not a number per se) like “20010115” or “2001/01/15” or “01/15/2001” or “2001-01-15” into a date that TradeStation would understand.  The function had to be flexible enough to accept the four different formats listed above.

String Functions

Most programming languages have functions that operate strictly on strings and so does EasyLanguage.  The most popular are:

  • Right String (rightStr) – returns N characters from the right side of the string.
  • Left String (leftStr) – returns N character starting from the left side of the string
  • Mid String (midStr) – returns the middle portion of a string starting at a specific place in the string and advance N characters
  • String Length (strLen) – returns the number of characters in the string
  • String To Number (strToNum) – converts the string to a numeric representation.  If the string has a character, this function will return 0
  • In String (inStr) – returns location of a sub string inside a larger string ( a substring can be just one character long)

Unpack the String

If the format is YYYYMMDD format then all you need to do is remove the dashes or slashes (if there are any) and then convert what is left over to a number.   But if the format is MM/DD/YYYY format then we are talking about a different animal.  So how can you determine if the date string is in this format?  First off you need to find out if the month/day/year separator is a slash or a dash.  This is how you do this:

whereIsAslash = inStr(dateString,”/”);
whereIsAdash = inStr(dateString,”-“);

If either is a non zero then you know there is a separator.  The next thing to do is locate the first “dash or slash” (the search character or string).  If it is located within the first four characters of the date string then you know its not a four digit year.  But, lets pretend the format is “12/14/2001” so if the first dash/slash is the 3rd character you can extract the month string by doing this:

firstSrchStrLoc = inStr(dateString,srchStr);
mnStr= leftStr(dateString,firstSrchStrLoc-1);

So if firstSrchStrLoc = 3 then we want to leftStr the date string and extract the first two characters and store them in mnStr.  We then store what’s left of the date string in tempStr by using rightStr:

strLength = strLen(dateString);

tempStr = rightStr(dateString,strLength-firstSrchStrLoc);

Here I pass dateString and the strLength-firstSrchStrLoc – so if the dateString is 10 characters long and the firstSrchStrLoc is 3, then we can create a tempstring by taking [10 -3  = 7 ] characters from right side of the string:

“12/14/2001” becomes “14/2001” – once that is done we can pull the first two characters from the tempStr and store those into the dyStr [day string.]  I do this by searching for the “/” and storing its location in srchStrLoc.  Once I have that location I can use that information and leftStr to get the value I need.   All that is left now is to use the srchStrLoc and the rightStr function.

srchStrLoc = inStr(tempStr,srchStr);
dyStr = leftStr(tempStr,srchStrLoc-1);
yrStr = rightStr(tempStr,strLen(tempStr)-srchStrLoc);

Now convert the strings to numbers and multiply their values accordingly.

DateSTrToYYYMMDD = strToNum(yrStr) X 10000-19000000 + strToNum(mnStr) X 100 + strToNum(dyStr)

To get the date into TS format I have to subtract 19000000 from the year.  Remember TS represents the date in YYYMMDD  format.

Now what do  you do if the date is in the right format but simply includes the dash or slash separators.  All you need to do here is loop through the string and copy all non dash or slash characters to a new string and then convert to a number.  Here is the loop:

        tempStr = "";
        iCnt = 1;
        While iCnt <= strLength
        Begin
            If midStr(dateString,iCnt,1) <> srchStr then
            	tempStr += midStr(dateString,iCnt,1);
            	iCnt+=1;
        end;
        tempDate = strToNum(tempStr);
        DateStrToYYYMMDD = tempDate-19000000;

Here I use midStr to step through each character in the string.  MidStr requires a string and the starting point and how many characters you want returned from the string.  Notice I step through the string with iCnt and only ask for 1 character at a time.  If the character is not a dash or slash I concatenate tempStr with the non dash/slash character.  At the end of the While loop I simply strToNum the string and subtract 19000000.  That’s it!  Remember EasyLanguage is basically a full blown programming language with a unique set of functions that relate directly to trading.

Here is the function and testFunc caller.

STRINGFUNCANDFUNCCALLER

 

The Complete Turtle EasyLanguage [Well About as Close as You Can Get]

The Complete Turtle EasyLanguage – Almost!

I have seen a plethora of posts on the Turtle trading strategies where the rules and code are provided.  The codes range from a mere Donchian breakout to a fairly close representation.  Without dynamic portfolio feedback its rather impossible to program the portfolio constraints as explained by Curtis Faith in his well received “Way Of The Turtle.”  But the other components can be programmed rather closely to Curtis’ descriptions.   I wanted to provide this code in a concise manner to illustrate some of EasyLanguage’s esoteric constructs and some neat shortcuts.  First of all let’s take a look at how the system has performed on Crude for the past 15 years.

Turtle Performance on Crude past 15 years

If a market trends, the Turtle will catch it.  Look how the market rallied in 2007 and immediately snapped back in 2008, and look at how the Turtle caught the moves – impressive.  But see how the system stops working in congestion.  It did take a small portion of the 2014 move down and has done a great job of catching the pandemic collapse and bounce.  In my last post, I programmed the LTL (Last Trader Loser) function to determine the success/failure of the Turtle System 1 entry.  I modified it slightly for this post and used it in concert with Turtle System 2 Entry and the 1/2N AddOn pyramid trade to get as close as possible to the core of the Turtle Entry/Exit logic.

Can Your Program This – sure you CAN!

Can You Program This?

I will provide the ELD so you can review at your leisure, but here are the important pieces of the code that you might not be able to derive without a lot of programming experience.

If mp[1] <> mp and mp <> 0 then 
begin
	if mp = 1 then
	begin	
		origEntry = entryPrice;
		origEntryName = "Sys1Long";
		If ltl = False and h >= lep1[1] then origEntryName = "Sys2Long";
	end;
	if mp =-1 then
	begin	
		origEntry = entryPrice;
		origEntryName = "Sys1Short";
		If ltl = False and l <= sep1[1] then origEntryName = "Sys2Short";
	end;
end;
Keeping Track Of Last Entry Signal Price and Name

This code determines if the current market position is not flat and is different than the prior bar’s market position.  If this is the case then a new trade has been executed.  This information is needed so that you know which exit to apply without having to forcibly tie them together using EasyLanguage’s from Entry keywords.  Here I just need to know the name of the entry.  The entryPrice is the entryPrice.  Here I know if the LTL is false, and the entryPrice is equal to or greater/less  than (based on current market position) than System 2 entry levels, then I know that System 2 got us into the trade.

If mp = 1 and origEntryName = "Sys1Long" then Sell currentShares shares next bar at lxp stop;
If mp =-1 and origEntryName = "Sys1Short" then buyToCover currentShares shares next bar at sxp stop;

//55 bar component - no contingency here
If mp = 0 and ltl = False then buy("55BBO") next bar at lep1 stop;
If mp = 1 and origEntryName = "Sys2Long" then sell("55BBO-Lx") currentShares shares next bar at lxp1 stop;

If mp = 0 and ltl = False then sellShort("55SBO") next bar at sep1 stop;
If mp =-1 and origEntryName = "Sys2Short" then buyToCover("55SBO-Sx") currentShares shares next bar at sxp1 stop;
Entries and Exits

The key to this logic is the keywords currentShares shares.  This code tells TradeStation to liquidate all the current shares or contracts at the stop levels.  You could use currentContracts contracts if you are more comfortable with futures vernacular.

AddOn Pyramiding Signal Logic

Before you can pyramid you must turn it on in the Strategy Properties.

Turn Pyramiding ON
If mp = 1 and currentShares < 4 then buy("AddonBuy") next bar at entryPrice + (currentShares * .5*NValue) stop;
If mp =-1 and currentShares < 4 then sellShort("AddonShort") next bar at entryPrice - (currentShares * .5*NValue) stop;

This logic adds positions on from the original entryPrice in increments of 1/2N.  The description for this logic is a little fuzzy.  Is the N value the ATR reading when the first contract was put on or is it dynamically recalculated?  I erred on the side of caution and used the N when the first contract was put on.  So to calculate the AddOn long entries you simply take the original entryPrice and add the currentShares * .5N.  So if currentShares is 1, then the next pyramid level would be entryPrice + 1* .5N.  If currentShares is 2 ,then entryPrice + 2* .5N and so on an so forth.  The 2N stop trails from the latest entryPrice.  So if you put on 4 contracts (specified in Curtis’ book), then the trailing exit would be 2N from where you added the 4th contract.  Here is the code for that.

Liquidate All Contracts at Last Entry –  2N

vars: lastEntryPrice(0);
If cs <= 1 then lastEntryPrice = entryPrice;
If cs > 1 and cs > cs[1] and mp = 1 then lastEntryPrice = entryPrice + ((currentShares-1) * .5*NValue);
If cs > 1 and cs > cs[1] and mp =-1 then lastEntryPrice = entryPrice - ((currentShares-1) * .5*NValue);

//If mp = -1 then print(d," ",lastEntryPrice," ",NValue);

If mp = 1 then sell("2NLongLoss") currentShares shares next bar at lastEntryPrice-2*NValue stop;
If mp =-1 then buyToCover("2NShrtLoss") currentShares shares next bar at lastEntryPrice+2*NValue Stop;
Calculate Last EntryPrice and Go From There

I introduce a new variable here: cs.  CS stands for currentShares and I keep track of it from bar to bar.  If currentShares or cs is less than or equal to1 I know that the last entryPrice was the original entryPrice.  Things get a little more complicated when you start adding positions – initially I couldn’t remember if EasyLanguage’s entryPrice contained the last entryPrice or the original – turns out it is the original – good to know.  So, if currentShares is greater than one and the current bar’s currentShares is greater than the prior bar’s currentShares, then I know I added on another contract and therefore must update lastEntryPrice.  LastEntryPrice is calculated by taking the original entryPrice and adding (currentShares-1) * .5N.  Now this is the theoretical entryPrice, because I don’t take into consideration slippage on entry.  You could make this adjustment.  So, once I know the lastEntryPrice I can determine 2N from that price.

Getting Out At 2N Trailing Stop

If mp = 1 then sell("2NLongLoss") currentShares shares next bar at lastEntryPrice-2*NValue stop;
If mp =-1 then buyToCover("2NShrtLoss") currentShares shares next bar at lastEntryPrice+2*NValue Stop;
Get Out At LastEntryPrice +/-2N

That’s all of the nifty code.  Below is the function and ELD for my implementation of the Turtle dual entry system.   You will see some rather sophisticated code when it comes to System 1 Entry and this is because of these scenarios:

  • What if you are theoretically short and are theoretically stopped out for a true loser and you can enter on the same bar into a long trade.
  • What if you are theoretically short and the reversal point would result in a losing trade.  You wouldn’t  record the loser in time to enter the long position at the reversal point.
  • What if you are really short and the reversal point would results in a true loser, then you would want to allow the reversal at that point

There are probably some other scenarios, but I think I covered all bases.  Just let me know if that’s not the case.  What I did to validate the entries was I programmed a 20/10 day breakout/failure with a 2N stop and then went through the list and deleted the trades that followed a non 2N loss (10 bar exit for a loss or a win.)  Then I made sure those trades were not in the output of the complete system.  There was quite a bit of trial and error.  If you see a mistake, like I said, just let me know.

Remember I published the results of different permutations of this strategy incorporating dynamic portfolio feedback at my other site www.trendfollowingsystems.com.  These results reflect the a fairly close portfolio that Curtis suggests in his book.

TURTLELTLFUNCTEST

 

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.

 

 

How to Keep Track of BuysToday and SellsToday

The Useful MP

We all know how to use the reserved word/function MarketPosition – right?  Brief summary if not – use MarketPosition to see what your current position is: -1 for short, +1 for long and 0 for flat.  MarketPosition acts like a function because you can index it to see what you position was prior to the current position – all you need to do is pass a parameter for the number of positions ago.  If you pass it a one (MarketPosition(1)) then it will return the your prior position.  If you define a variable such as MP you can store each bars MarketPosition and this can come in handy.

mp = marketPosition;

If mp[1] <> 1 and mp = 1 then buysToday = buysToday + 1;
If mp[1] <> -1 and mp = -1 then sellsToday = sellsToday + 1;
Keeping Track of Buy and Sell Entries on Daily Basis

The code compares prior bar’s MP value with the current bar’s.   If there is a change in the value, then the current market position has changed.   Going from not 1 to 1 indicates a new long position.  Going from not -1 to -1 implies a new short.  If the criteria is met, then the buysToday or sellsToday counters are incremented.  If you want to keep the number of buys or sells to a certain level, let’s say once or twice,  you can incorporate this into your code.

If  time >= startTradeTime and t < endTradeTime and 
	buysToday < 1 and 
	rsi(c,rsiLen) crosses above rsiBuyVal then buy this bar on close;
If  time >= startTradeTime and t < endTradeTime and 
	sellsToday < 1 and 
	rsi(c,rsiLen) crosses below rsiShortVal then sellShort this bar on close;
Using MP to Keep Track of BuysToday and SellsToday

This logic will work most of the time, but it depends on the robustness of the builtin MarketPosition function Look how this logic fails in the following chart:

I didn't want entries in the same direction per day!
I only wanted 1 short entry per day!

MarketPosition Failure

Failure in the sense that the algorithm shorted twice in the same day.  Notice on the first trade how the profit objective was hit on the very next bar.  The problem with MarketPosition is that it only updates at the end of the bar one bar after the entry.  So MarketPosition stays 0 during the duration of this trade.  If MarketPosition doesn’t change then my counter won’t work.  TradeStation should update MarketPosition at the end of the entry bar.  Alas it doesn’t work this way.  I figured a way around it though.  I will push the code out and explain it later in more detail.

Input: rsiLen(14),rsiBuyVal(30),rsiShortVal(70),profitObj$(250),protStop$(300),startTradeTime(940),endTradeTime(1430);

Vars: mp(0),buysToday(0),sellsToday(0),startOfDayNetProfit(0);

If d <> d[1] then
Begin
	buysToday = 0;
	sellsToday = 0;
	startOfDayNetProfit = netProfit;
end;

{mp = marketPosition;

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

If entriesToday(date) > buysToday + sellsToday then 
Begin
	If marketPosition = 1 then buysToday = buysToday + 1;
	If marketPosition =-1 then sellsToday = sellsToday + 1;
	If marketPosition = 0 then
	Begin
		if netProfit > startOfDayNetProfit then
		begin
			if exitPrice(1) > entryPrice(1) then buysToday = buysToday + 1;
			If exitPrice(1) < entryPrice(1) then sellsToday = sellsToday + 1;
		end;;
		if netProfit < startOfDayNetProfit then
		Begin
			if exitPrice(1) < entryPrice(1) then buysToday = buysToday + 1;
			If exitPrice(1) > entryPrice(1) then sellsToday = sellsToday + 1;
		end;
	end;
	print(d," ",t," ",buysToday," ",sellsToday);
end;

If  time >= startTradeTime and t < endTradeTime and 
	buysToday < 1 and 
	rsi(c,rsiLen) crosses above rsiBuyVal then buy this bar on close;
If  time >= startTradeTime and t < endTradeTime and 
	sellsToday < 1 and 
	rsi(c,rsiLen) crosses below rsiShortVal then sellShort this bar on close;

SetProfittarget(profitObj$);
SetStopLoss(protStop$);

SetExitOnClose;
A Better Buy and Short Entries Counter

TradeStation does update EntriesToday at the end of the bar so you can use this keyword/function to help keep count of the different type of entries.  If MP is 0 and EntriesToday increments then you know an entry and an exit has occurred (takes care of the MarketPosition snafu) – all you need to do is determine if the entry was a buy or a sell.  NetProfit is also updated when a trade is closed.   I establish the StartOfDayNetProfit on the first bar of the day (line 9 in the code) and then examine EntriesToday and if NetProfit increased or decreased.  EntryPrice and ExitPrice are also updated at the end of the bar so I can also use them to extract the information I need.   Since MarketPosition is 0  I have to pass 1 to the EntryPrice and ExitPrice functions – prior position’s prices.  From there I can determine if a Long/Short entry occurred.  This seems like a lot of work for what you get out of it, but if you are controlling risk by limiting the number of trades (exposure) then an accurate count is so very important.

An alternative is to test on a higher resolution of data – say 1 minute bars.  In doing this you give a buffer to the MarketPosition function – more bars to catch up.

 

Pyramiding and then Scaling Out at Different Price Levels – EasyLanguage

TOTAL, TOTAL, TOTAL – an important keyword

I just learned something new!  I guess I never programmed a strategy that pyramided at different price levels and scaled out at different price levels.

Initially I thought no problem.  But I couldn’t get it to work – I tried everything and then I came across the keyword Total and then I remembered.  If you don’t specify Total in you exit directives then the entire position is liquidated.  Unless you are putting all your positions on at one time – like I did in my last post.   So remember if you are scaling out of a pyramid position use Total in your logic.

vars: maxPosSize(2);

If currentContracts < maxPosSize - 1 and c > average(c,50) and c = lowest(c,3) then buy("L3Close") 1 contract this bar on close;
If currentContracts < maxPosSize and c > average(c,50) and c = lowest(c,4) then buy("L4Close") 1 contract this bar on close;


If currentContracts = 2 and c = highest(c,5) then sell 1 contract total this bar on close;
If currentContracts = 1 and c = highest(c,10) then sell 1 contract total this bar on close;
Scaling Out Of Pyramid

Why you have to use the Total I don’t know.  You specify the number of contracts in the directive and that is sufficient if you aren’t pyramiding.  The pyramiding throws a “monkey wrench” in to the works.

Multiple Ouput function in EasyLanguage

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

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