All posts by George Pruitt

George Pruitt - Author - Blogger - Programmer - Technician Bachelor of Science in Computer Science from UNC-Asheville Levo Oculos Meos In Montes

ES.D Strategy with Ratcheting Trailing Stop [Intra-Day] – EasyLanguage

ES.D Strategy Utilizing Ratchet Trailing Stop

A reader of this blog wanted a conversion from my Ratchet Trailing Stop indicator into a Strategy.  You will notice a very close similarity with the indicator code as the code for this strategy.  This is a simple N-Bar [Hi/Lo] break out with inputs for the RatchetAmt and TrailAmt.  Remember RatchetAmt is how far the market must move in your favor before the stop is pulldown the TrailAmt.  So if the RatchetAmt is 12 and the TrailAmt is 6, the market would need to move 12 handles in your favor and the Trail Stop would move to break even.  If it moves another 12 handles then the stop would be moved up/down by 6 handles.  Let me know if you have any questions – this system is similar to the one I just posted.

Notice how the RED line Ratchets Up by the Fixed Amount [8/6]
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 t < calcTime(sess1Endtime,-3*barInterval) then
Begin
if buysToday = 0 then buy("NBar-Range-B") next bar stb stop;
if shortsToday = 0 then sellShort("NBar-Range-S") next bar sts stop;
end;

mp = marketPosition;
If mp = 1 then
begin
lep = entryPrice;
buysToday = 1;
end;
If mp =-1 then
begin
sep = entryPrice;
shortsToday = 1;
end;


// 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;
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;

setExitOnClose;
I Used my Ratchet Indicator for the Basis of this Strategy

A Simple Break Out Algorithm Demonstrating Time Optimization

What is Better:  30, 60, or 120 Minute Break-Out on ES.D

Here is a simple tutorial you can use as a foundation to build a potentially profitable day trading system.  Here we wait N minutes after the open and then buy the high of the day or short the low of the day and apply a protective stop and profit objective.  The time increment can be optimized to see what time frame is best to use.  You can also optimize the stop loss and profit objective – this system gets out at the end of the day.  This system can be applied to any .D data stream in TradeStation or Multicharts.

Logic Description

  1. get open time
  2. get close time
  3. get N time increment
    1. 15 – first 15 minute of day
    2. 30 – first 30 minute of day
    3. 60 – first hour of day
  4. get High and Low of day
  5. place stop orders at high and low of day – no entries late in day
  6. calculate buy and short entries – only allow one each*
  7. apply stop loss
  8. apply profit objective
  9. get out at end of day if not exits have occurred

Optimization Results [From 15 to 120 by 5 minutes] on @ES.D 5 Minute Chart – Over Last Two Years

Optimization of Time: Look How the # Trades Decrease as the Time Increment Increases

Simple Orbo EasyLanguage

I threw this together rather quickly in a response to a reader’s question.  Let me know if you see a bug or two.  Remember once you gather your stops you must allow the order to be issued on every subsequent bar of the trading day.  The trading day is defined to be the time between timeIncrement and endTradeMinB4Close.  Notice how I used the EL function calcTime to calculate time using either a +positive or -negative input.  I want to sample the high/low of the day at timeIncrement and want to trade up until endTradeMinB4Close time.  I use the HighD and LowD functions to extract the high and low of the day up to that point.  Since I am using a tight stop relative to today’s volatility you will see more than 1 buy or 1 short occurring.  This happens when entry/exit occurs on the same bar and MP is not updated accordingly.  Somewhere  hidden in this tome of a blog you will see a solution for this.  If you don’t want to search I will repost it tomorrow.


//Optimizing Time to determine a simple break out
//Only works on .D data streams
Inputs: timeIncrement(15),endTradeMinB4Close(-15),stopLoss$(500),profTarg$(1000);

vars: firstBarTime(0),lastBarTime(0),buyStop(0),shortStop(0),
calcStopTime(0),quitTradeTime(0),buysToday(0),shortsToday(0),mp(0);

firstBarTime = sessionStartTime(0,1);
lastBarTime = sessionEndTime(0,1);

calcStopTime = calcTime(firstBarTime,timeIncrement);
quitTradeTime = calcTime(lastBarTime,endTradeMinB4Close);



If time = calcStopTime then
begin
buyStop = HighD(0);
shortStop = LowD(0);
buysToday = 0;
shortsToday = 0;
End;

if time >= calcStopTime and time < quitTradeTime then
begin
if buysToday = 0 then Buy next bar at buyStop stop;
if shortsToday = 0 then Sell short next bar at shortStop stop;
end;

mp = marketPosition;

If mp = 1 then buysToday = 1;
If mp = -1 then shortsToday = 1;

SetStopLoss(stopLoss$);
setProfitTarget(profTarg$);
setExitOnClose;
Orbo EasyLanguage Code

 

 

 

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

 

Implementing Finite State Machine Functionality with EasyLanguage (Last Trade Was Loser Filter a la Turtle)

Last Trade Was a Loser Filter – To Use or Not To Use

Premise

A major component of the Turtle algorithm was to skip the subsequent 20-day break out if the prior was a winner.  I guess Dennis believed the success/failure of a trade had an impact on the outcome of the subsequent trade.  I have written on how you can implement this in EasyLanguage in prior posts, but I have been getting some questions on implementing FSM in trading and thought this post could kill two birds with one stone: 1) provide a template that can be adapted to any LTL mechanism and 2) provide the code/structure of setting up a FSM using EasyLanguage’s Switch/Case structure.

Turtle Specific LTL Logic

The Turtle LTL logic states that a trade is a loser if a 2N loss occurs after entry.  N is basically an exponential-like moving average of TrueRange.  So if the market moves 2N against a long or short position and stops you out, you have a losing trade.  What makes the Turtle algorithm a little more difficult is that you can also exit on a new 10-day low/high depending on your position.  The 10-day trailing exit does not signify a loss.  Well at least in this post it doesn’t.  I have code that says any loss is a loss, but for this explanation let’s just stick to a 2N loss to determine a trade’s failure.

How To Monitor Trades When Skipping Some Of Them

This is another added layer of complexity.  You have to do your own trade accounting behind the scenes to determine if a losing trade occurs.  Because if you have a winning trade you skip the next trade and if you skip it how do you know if it would have been a winner or a loser.  You have to run a theoretical system in parallel with the actual system code.

Okay let’s start out assuming the last trade was a winner.  So we turn real trading off.  As the bars go by we look for a 20-Day high or low penetration.  Assume a new 20-Day high is put in and a long position is established at the prior 20-Day high.  At this point you calculate a 2N amount and subtract if from the theoretical entry price to obtain the theoretical exit price.  So you have a theoMP (marketPosition) and a theoEX (exit price.)  This task seems pretty simple, so you mov on and start looking for a day that either puts in a new 10-Day low or crosses below your theoEX price.  If a new 10-Day low is put in then you continue on looking for a new entry and a subsequent 2N loss.  If a 2N loss occurs, then you turn trading back on and continue monitoring the trades – turning trading off and then back on when necessary.  In the following code I use these variables:

  • state – 0: looking for an entry or 1: looking for an exit
  • lep – long entry price
  • sep– short entry price
  • seekLong – I am seeking a long position
  • seekShort – I am seeking a short position
  • theoMP – theoretical market position
  • theoEX – theoretical exit price
  • lxp – long exit price
  • sxp – short exit price

Let’s jump into the Switch/Case structure when state = 0:

	Switch(state)
Begin
Case 0:
lep = highest(h[1],20) + minMove/priceScale;
sep = lowest(l[1],20) - minMove/priceScale;
If seekLong and h >= lep then
begin
theoMP = 1;
theoEX = maxList(lep,o) - 2 * atr;
// print(d," entered long >> exit at ",theoEX," ",atr);
end;
If seekShort and l <= sep then
begin
theoMP = -1;
theoEX = minList(sep,o) + 2 * atr;
end;
If theoMP <> 0 then
begin
state = 1;
cantExitToday = True;
end;
State 0 (Finite State Set Up)

The Switch/Case is a must have structure in any programming language.  What really blows my mind is that Python doesn’t have it.  They claim its redundant to an if-then structure and it is but its so much easier to read and implement.  Basically you use the Switch statement and a variable name and based on the value of the variable it will flow to whatever case the variable equates to.  Here we are looking at state 0.  In the CASE: 0  structure the computer calculates the lep and sep values – long and short entry levels.  If you are flat then you are seeking a long or a short position.  If the high or low of the bar penetrates it respective trigger levels then theoMP is set to 1 for long or -1 for short.  TheoEX is then calculated based on the atr value on the day of entry.  If theoMP is set to either a 1 or -1, then we know a trade has just been triggered.  The Finite State Machine then switches gears to State 1.  Since State = 1 the next Case statement is immediately evaluated.  I don’t want to exit on the same bar as I entered (wide bars can enter and exit during volatile times) I use a variable cantExitToday.  This variable delays the Case 1: evaluation by one bar.

State = 1 code:

		Case 1:
If not(cantExitToday) then
begin
lxp = maxList(theoEX,lowest(l[1],10)-minMove/priceScale);
sxp = minList(theoEX,highest(h[1],10)+minMove/priceScale);
If theoMP = 1 and l <= lxp then
begin
theoMP = 0;
seekLong = False;
if lxp <= theoEX then
ltl = True
Else
ltl = False;
end;
If theoMP =-1 and h >= sxp then
begin
theoMP = 0;
seekShort = False;
if sxp >= theoEX then
ltl = True
else
ltl = False;
end;
If theoMP = 0 then state = 0;
end;
cantExitToday = False;
end;
State = 1 (Switching Gears)

Once we have a theoretical position, then we only examine the code in the Case 1: module.  On the subsequent bar after entry, the lxp and sxp (long exit and short exit prices) are calculated.  Notice these values use maxList or minList to determine whichever is closer to the current market action – the 2N stop or the lowest/highest low/high for the past 10-daysLxp and sxp are assigned whichever is closer.  Each bar’s high or low is compared to these values.  If theoMP = 1 then the low is compared to lxp.  If the low crosses below lxp, then things are set into motion.  The theoMP is immediately set to  0 and seekLong is turned to False.  If lxp <= a 2N loss then ltl (last trade loser) is set to true.  If not, then ltl is set to False.   If theoMP = 0 then we assume a flat position and switch the FSM back to State 0 and start looking for a new trade.  The ltl variable is then used in the code to allow a real trade to occur.

Strategy Incorporates Our FSM Output

vars:N(0),mp(0),NLossAmt(0);
If barNumber = 1 then n = avgTrueRange(20);
if barNumber > 1 then n = (n*19 + trueRange)/20;

If useLTLFilter then
Begin
if ltl then buy next bar at highest(h,20) + minMove/priceScale stop;
if ltl then sellShort next bar at lowest(l,20) -minMove/priceScale stop;
end
Else
Begin
buy next bar at highest(h,20) + minMove/priceScale stop;
sellShort next bar at lowest(l,20) -minMove/priceScale stop;
end;

mp = marketPosition;

If mp <> 0 and mp[1] <> mp then NLossAmt = 2 * n;

If mp = 1 then
Begin
Sell("LL10-LX") next bar at lowest(l,10) - minMove/priceScale stop;
Sell("2NLS-LX") next bar at entryPrice - NLossAmt stop;
end;
If mp =-1 then
Begin
buyToCover("HH10-SX") next bar at highest(h,10) + minMove/priceScale stop;
buyToCover("2NLS-SX") next bar at entryPrice + NLossAmt stop;
end;
Strategy Code Using ltl filter

This code basically replicates what we did in the FSM, but places real orders based on the fact that the Last Trade Was A Loser (ltl.)

Does It Work – Only Trade After a 2N-Loss

Last Trade Loser In Action

Without Filter on the last 10-years in Crude Oil

With Filter on the last 10-years in Crude Oil

I have programmed this into my TradingSimula-18 software and will show a portfolio performance with this filter a little later at www.trendfollowingsystems.com.

I had to do some fancy footwork with some of the code due to the fact you can exit and then re-enter on the same bar.  In the next post on this blog I will so you those machinations .  With this template you should be able to recreate any last trade was a loser mechanism and see if it can help out with your own trading algorithms.  Shoot me an email with any questions.

 

 

 

Turn of the Month Trading Strategy [Stock Indices Only]

The System

This system has been around for several years.  Its based on the belief that fund managers start pouring money into the market near the end of the month and this creates momentum that lasts for just a few days.  The original system states to enter the market on the close of the last bar of the day if the its above a certain moving average value.  In the Jaekle and Tomasini book, the authors describe such a trading system.  Its quite simple, enter on the close of the month if its greater than X-Day moving average and exit either 4 days later or if during the trade the closing price drops below the X-Day moving average.

EasyLanguage or Multi-Charts Version

Determining the end of the month should be quite easy -right?  Well if you want to use EasyLanguage on TradeStation and I think on Multi-Charts you can’t sneak a peek at the next bar’s open to determine if the current bar is the last bar of the month.  You can try, but you will receive an error message that you can’t mix this bar on close with next bar.  In other words you can’t take action on today’s close if tomorrow’s bar is the first day of the month.  This is designed, I think, to prevent from future leak or cheating.  In TradeStation the shift from backtesting to trading is designed to be a no brainer, but this does provide some obstacles when you only want to do a backtest.

LDOM function – last day of month for past 15 years or so

So I had to create a LastDayOfMonth function.  At first I thought if the day of the month is the 31st then it is definitely the last bar of the month.  And this is the case no matter what.  And if its the 30th then its the last day of the month too if the month is April, June, Sept, and November.  But what happens if the last day of the month falls on a weekend.  Then if its the 28th and its a Friday and the month is blah, blah, blah.  What about February?  To save time here is the code:

Inputs: movAvgPeriods(50);
vars: endOfMonth(false),theDayOfWeek(0),theMonth(0),theDayOfMonth(0),isLeapYear(False);

endOfMonth = false;
theDayOfWeek = dayOfWeek(date);
theMonth = month(date);
theDayOfMonth = dayOfMonth(date);
isLeapYear = mod(year(d),4) = 0;

// 29th of the month and a Friday
if theDayOfMonth = 29 and theDayOfWeek = 5 then
endOfMonth = True;
// 30th of the month and a Friday
if theDayOfMonth = 30 and theDayOfWeek = 5 then
endOfMonth = True;
// 31st of the month
if theDayOfMonth = 31 then
endOfMonth = True;
// 30th of the month and April, June, Sept, or Nov
if theDayOfMonth = 30 and (theMonth=4 or theMonth=6 or theMonth=9 or theMonth=11) then
endOfMonth = True;
// 28th of the month and February and not leap year
if theDayOfMonth = 28 and theMonth = 2 and not(isLeapYear) then
endOfMonth = True;
// 29th of the month and February and a leap year or 28th, 27th and a Friday
if theMonth = 2 and isLeapYear then
Begin
If theDayOfMonth = 29 or ((theDayOfMonth = 28 or theDayOfMonth = 27) and theDayOfWeek = 5) then
endOfMonth = True;
end;
// 28th of the month and Friday and April, June, Sept, or Nov
if theDayOfMonth = 28 and (theMonth = 4 or theMonth = 6 or
theMonth = 9 or theMonth =11) and theDayOfWeek = 5 then
endOfMonth = True;
// 27th, 28th of Feb and Friday
if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 27 then
endOfMonth = True;
// 26th of Feb and Friday and not LeapYear
if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 26 and not(isLeapYear) then
endOfMonth = True;
// Memorial day adjustment
If theMonth = 5 and theDayOfWeek = 5 and theDayOfMonth = 28 then
endOfMonth = True;
//Easter 2013 adjustment
If theMonth = 3 and year(d) = 113 and theDayOfMonth = 28 then
endOfMonth = True;
//Easter 2018 adjustment
If theMonth = 3 and year(d) = 118 and theDayOfMonth = 29 then
endOfMonth = True;

if endOfMonth and c > average(c,movAvgPeriods) then
Buy("BuyDay") this bar on close;

If C <average(c,movAvgPeriods) then
Sell("MovAvgExit") this bar on close;
If BarsSinceEntry=4 then
Sell("4days") this bar on close;
Last Day Of Month Function and Strategy

All the code is generic except for the hard code for days that are a consequence of Good Friday.

All this code because I couldn’t sneak a peek at the date of tomorrow.  Here are the results of trading the ES futures sans execution costs for the past 15 years.

Last Day Of Month Buy If C > 50 Day Mavg

What if it did the easy way and executed the open of the first bar of the month.

If c > average(c,50) and month(d) <> month(d of tomorrow) then 
buy next bar at open;

If barsSinceEntry >=3 then
sell next bar at open;

If marketPosition = 1 and c < average(c,50) then
sell next bar at open;
Buy First Day Of Month
First Day of Month If C > 50 Day Mavg

The results aren’t as good but it sure was easier to program.

TradingSimula-18 Version

Since you can use daily bars we can test this with my TradingSimula-18 Python platform.  And we will execute on the close of the month.  Here is the snippet of code that you have to concern yourself with.  Here I am using Sublime Text and utilizing their text collapsing tool to hide non-user code:

Small Snippet of TS-18 Code

This was easy to program in TS-18 because I do allow Future Leak – in other words I will let you sneak a peek at tomorrow’s values and make a decision today.  Now many people might say this is a huge boo-boo, but with great power comes great responsibility.  If you go in with eyes wide open, then you will only use the data to make things easier or even doable, but without cheating.  Because you are only going to cheat yourself.  Its in your best interest do follow the rules.  Here is the line that let’s you leak into the future.

If isNewMonth(myDate[curBar+1])

The curBar is today and curBar+1 is tomorrow.  So I am saying if tomorrow is the first day of the month then buy today’s close.  Here you are leaking into the future but not taking advantage of it.  We all know if today is the last day of the month, but try explaining that to a computer.  You saw the EasyLanguage code.  So things are made easier with future leak, but not taking advantage of .

Here is a quick video of running the TS-18 Module of 4 different markets.

 

Ratio Adjusted versus Point Adjusted Contracts in TradeStation Part 2

Thomas Stridsman quote from  his “Trading Systems That Work Book”

The benefits of the RAD contract also become evident when you want to put together a multimarket portfolio…For now we only state that the percentage based calculations do not take into consideration how many contracts you’re trading and, therefore, give each market an equal weighting in the portfolio.

The Stridsman Function I presented in the last post can be used to help normalize a portfolio of different markets.  Here is a two market portfolio (SP – 250price and JY -125Kprice contract sizes) on a PAD contract.

1-Contract SP and JY on PAD data

Here is the performance of the same portfolio on a RAD contract.

Equal rating of SP and JY on RAD data

 

The curve shapes are similar but look at the total profit and the nearly $125K draw down.  I was trying to replicate Thomas’ research so this data is from Jan. 1990 to Dec. 1999.  A time period where the price of the SP increased 3 FOLD!  Initially you would start trading 1 JY to 2 SP but by the time it was over you would be trading nearly 3 JY to 1 SP.  Had you traded at this allocation the PAD numbers would be nearly $240K in profit.  Now this change occurred through time so the percentage approach is applied continuously.  Also the RAD data allows for a somewhat “unrealistic” reinvestment or compounding mechanism.  Its unrealistic because you can’t trade a partial futures contract.  But it does give you a glimpse of the potential.  The PAD test does not show reinvestment of profit.  I have code for that if you want to research that a little bit more.  Remember everything is in terms of Dec. 31 1999 dollars.  That is another beauty of the RAD contract.

Another Stridsman Quote

Now, wait a minute, you say, those results are purely hypothetical.  How can I place all the trades in the same market at presumably the same point in time?  Well, you can’t, so that is a good and valid question; but let me ask you, can you place any of these trades for real, no matter, how you do it?  No, of course not.  They all represent foregone opportunities.  Isn’t it better then to at least place them hypothetically in today’s marketplace to get a feel for what might happen today, rather in a ten-year-old market situation to get a feel for how the situation was back then?  I think wall can agree that it is better to know what might happen today, rather than what happened ten years ago.

That is a very good point.  However, convenience and time is import and when developing an algorithm.  And most platforms, including my TS-18, are geared toward PAD data.  However TS-18 can look at the entire portfolio balance and all the market data for each market up to that point in time and can adjust/normalize based on portfolio and data metrics.  However, I will add a percentage module a little later, but I would definitely use the StridsmanFunc that I presented in the last post to validate/verify your algorithm in today’s market place if using TradeStation.

Email me if you want the ELD of the function.

Ratio Adjusted versus Pointed Adjusted Contracts in TradeStation – Part 1

If you play around with TradeStation’s custom futures capabilities you will discover you can create different adjusted continuous contracts.  Take a look at this picture:

Panama vs Ratio Adjusted

Both charts look the same and the trades enter and exit at the same locations in relation to the respective price charts.   However, take a look at the Price Scale on the right and the following pictures.

As you can see from the P/L from each trade list there is a big difference.  The top list is using RAD and the second is the generally accepted Panama Adjusted Data (PAD.)  Ratio adjusted data takes the percentage difference between the expiring contract and the new contract and propagates the value throughout the entire back history.  This is different than the PAD we have all used, where the actual point difference is propagated.  These two forms of adjustment have their own pros and cons but many industry leaders prefer the RAD.  I will go over a little bit of the theory in my next post, but in the mean time I will direct you to Thomas Stridsman’s excellent work on the subject in his book, “Trading Systems That Work – Building and Evaluating Effective Trading Systems”.

Here is another look at draw down metrics between the two formats.

RAD V PAD DrawDown

Who would want to trade a system on 1 contract of crude and have a $174K draw down?  Well you can’t look at it like that.  Back in 2008 crude was trading at $100 X 1000 = $100,000 a contract.  Today May 11th 2020 it is around $25,000.  So a drawdown of $44K back then would be more like $176K in today’s terms.  In my next post I will go over the theory of  RAD, but for right now you just need to basically ignore TradeStation’s built in performance metrics and use this function that I developed in most part by looking at Thomas’ book.

//Name this function StridsmanFunc1

Vars: FName(""), offset(1),TotTr(0), Prof(0), CumProf(1), ETop(1), TopBar(0), Toplnt(0),BotBar(0), Botlnt(0), EBot(1), EDraw(1), TradeStr2( "" );
Arrays: tradesPerArr[1000](0),drawDownArr[1000](0);
Vars: myEntryPrice(0),myEntryDate(0),myMarketPosition(0),myExitDate(0),myExitPrice(0);
If CurrentBar = 1 Then
Begin
FName = "C:\Temp\" + LeftStr(GetSymbolName, 3) + ".csv";
FileDelete(FName);
TradeStr2 = "E Date" + "," + "Position" + "," + "E Price" + "," + "X Date" +"," + "X Price" + "," + "Profit" + "," + "Cum. prof." + "," + "E-Top" + "," +"E-Bottom" + "," + "Flat time" + "," + "Run up" + "," + "Drawdown" +NewLine;
FileAppend(FName, TradeStr2);
End;
TotTr = TotalTrades;
If TotTr > TotTr[1] or (lastBarOnChart and marketPosition <> 0) Then
Begin

if TotTr > TotTr[1] then
begin
if (EntryPrice(1) <> 0) then Prof = 1 + PositionProfit(1)/(EntryPrice(1) * BigPointValue);
End
else
begin
Value99 = iff(marketPosition = 1,c - entryPrice, entryPrice - c);
Prof = 1 + (Value99*bigPointValue) /(Entryprice *BigPointValue);
// print(d," StridsmanFunc1 ",Value99," ",Prof," ",(Value99*bigPointValue) /(Entryprice *BigPointValue):5:4);
TotTr = totTr + 1;
end;
tradesPerArr[TotTr] = Prof - 1;
CumProf = CumProf * Prof;
ETop = MaxList(ETop, CumProf);
If ETop > ETop[1] Then
Begin
TopBar = CurrentBar;
EBot = ETop;
End;

EBot = MinList(EBot, CumProf);

If EBot<EBot[1] Then BotBar = CurrentBar;

Toplnt = CurrentBar - TopBar;

Botlnt = CurrentBar - BotBar;

if ETop <> 0 then EDraw = CumProf / ETop;

drawDownArr[TotTr] = (EDraw - 1);

myEntryDate = EntryDate(1);
myMarketPosition = MarketPosition(1);
myEntryPrice = EntryPrice(1);
myExitDate = ExitDate(1);
myExitPrice = ExitPrice(1);
If lastBarOnChart and marketPosition <> 0 then
Begin
myEntryDate = EntryDate(0);
myMarketPosition = MarketPosition(0);
myEntryPrice = EntryPrice(0);
myExitDate = d;
myExitPrice =c;
end;
TradeStr2 = NumToStr(myEntryDate, 0) + "," +NumToStr(myMarketPosition, 0) + "," + NumToStr(myEntryPrice, 2) + ","
+ NumToStr(myExitDate, 0) + "," + NumToStr(myExitPrice, 2) + ","+ NumToStr((Prof - 1) * 100, 2) + "," + NumToStr((CumProf - 1) *100, 2) + ","
+ NumToStr((ETop - 1) * 100, 2) + "," + NumToStr((EBot - 1) * 100, 2) + "," + NumToStr(Toplnt, 0) + "," + NumToStr(Botlnt, 0) + "," + NumToStr((EDraw - 1) * 100, 2) +
NewLine;

FileAppend(FName, TradeStr2);
End;
vars: tradeStr3(""),
ii(0),avgTrade(0),avgTrade$(0),cumProf$(0),trdSum(0),
stdDevTrade(0),stdDevTrade$(0),
profFactor(0),winTrades(0),lossTrades(0),perWins(0),perLosers(0),
largestWin(0),largestWin$(0),largestLoss(0),largestLoss$(0),avgProf(0),avgProf$(0),
winSum(0),lossSum(0),avgWin(0),avgWin$(0),avgLoss(0),avgLoss$(0),maxDD(0),maxDD$(0),cumProfit(0),cumProfit$(0);

If lastBarOnChart then
begin
stdDevTrade = standardDevArray(tradesPerArr,TotTr,1);
stdDevTrade$ = stdDevTrade*c*bigPointValue;
For ii = 1 to TotTr
Begin
trdSum = trdSum + tradesPerArr[ii];
// print(d," ",ii," ",tradesPerArr[ii]);
If tradesPerArr[ii] > 0 then
begin
winTrades = winTrades + 1;
winSum = winSum + tradesPerArr[ii];
end;
If tradesPerArr[ii] <=0 then
begin
lossTrades = lossTrades + 1;
lossSum = lossSum + tradesPerArr[ii];
end;
If tradesPerArr[ii] > largestWin then
begin
largestWin = tradesPerArr[ii];
// print("LargestWin Found ",largestWin);
end;
If tradesPerArr[ii] < largestLoss then largestLoss = tradesPerArr[ii];
If drawDownArr[ii] < maxDD then maxDD = drawDownArr[ii];
end;
// print("TradeSum: ",trdSum);
if TotTr <> 0 then avgTrade = trdSum/TotTr;
avgTrade$ = avgTrade*c*bigPointValue;
largestWin = largestWin;
largestLoss = largestLoss;
largestWin$ = largestWin*c*bigPointValue;
largestLoss$ = largestLoss*c*bigPointValue;
if TotTr <> 0 then perWins = winTrades/TotTr;
if TotTr <> 0 then perLosers = lossTrades/TotTr;
If winTrades <> 0 then avgWin = winSum / winTrades;
avgWin$ = avgWin*c*bigPointValue;
if lossTrades <> 0 then avgLoss= lossSum / lossTrades;
avgLoss$ = avgLoss*c*bigPointValue;
maxDD$ = maxDD *c*bigPointValue;
if lossTrades <>0 and avgLoss$ <> 0 then profFactor = (winTrades*avgWin$)/(lossTrades*avgLoss$);
CumProf = cumProf - 1;
CumProf$ = cumProf*c*bigPointValue;

TradeStr3 = "Total Trades,,"+NumToStr(TotTr,0)+",Num. Winners,"+NumToStr(winTrades,0)+","+NumToStr(perWins,3)+", Num. Losses,"+NumToStr(lossTrades,0)+","+NumToStr(perLosers,3)+NewLine+
"Profit Factor,,"+NumToStr(profFactor,3)+",Largest Win ,"+NumToStr(largestWin,3)+","+NumToStr(largestWin$,0)+",Largest Loss,"+NumToStr(largestLoss,3)+","+NumToStr(largestLoss$,0)+NewLine+
"Avg Profit,"+NumToStr(avgTrade,3)+","+NumToSTr(avgTrade$,0)+",Avg Win,"+NumToStr(avgWin,3)+","+NumToStr(avgWin$,0)+",Avg Loss,"+NumToStr(avgLoss,3)+","+NumToStr(avgLoss$,0)+NewLine+
"Std. Dev,"+NumToStr(stdDevTrade,3)+","+NumToStr(stdDevTrade$,0)+",Cum Profit,"+NumToStr(cumProf,3)+","+NumToStr(cumProf$,3)+",Draw Down,"+numToStr(maxDD,3)+","+numToStr(maxDD$,0)+NewLine;
FileAppend(FName, TradeStr3);
{ Print("Total Trades ",totalTrades," Num. Winners ",winTrades," ",perWins," Num. Losses ",lossTrades," ",perLosers);
Print("Profit Factor ",profFactor," Largest Win ",largestWin:5:2," ",largestWin$," Largest Loss ",largestLoss:5:2," ",largestLoss$);
Print("Avg Profit ",avgTrade," ",avgTrade$," Avg Win ",avgWin," ",avgWin$," Avg Loss ",avgLoss," ",avgLoss$);
Print("St Dev ",stdDevTrade," ",stdDevTrade$," Cum Profit ",cumProf," ",cumProf$," Drawdown ",maxDD," ",maxDD$);}


end;



StridsmanFunc1 = 1;
Conversion of Performance Metrics to Percentages Instead of $Dollars

This function will out put a file that looks like this.  Go ahead and play with the code – all you have to do is call the function from within an existing strategy that you are working with.  In part two I will go over the code and explain what its doing and how arrays and strings were used to archive the trade history and print out this nifty table.

Stridsman Function Output.

In this output if you treat the return from each trade as a function of the entry price and accumulate the returns you can convert the value to today’s current market price of the underlying.  In this case a 15 -year test going through the end of last year, ended up making almost $70K.

RAD TradeStation Metrics:

Profit $350K – Draw Down $140K

PAD TradeStation Metrics:

Profit $89K – Draw Down $45K

Stridsman Func on RAD:

Profit $70K – Draw Down $48K

At this point you can definitely determine that the typical RAD/TS metrics are not all that usuable.  The PAD/TS results look very similar to RAD/StridsmanFunc results.  Stay tuned for my next post and I will hopefully explain why RAD/StridsmanFunc is probably the most accurate performance metrics of the three.

Super Combo Day Tradng System A 2020 Redo!

If you have some time on your hands and you want to build your own Frankenstein monster from a parts bin, here is your chance.  The Super Combo Day Trading System was originally published in my “Building Winning Trading Systems” book back in 2001.  I designed it to be more of a tutorial than a pure trading system.    You should be able to get the spare parts you need to create your own day trading system.  Back in 2001, I wanted to show how to control and monitor different entry and exit techniques in one complete algorithm.  The system was designed to day-trade the big SP and the results at the time looked promising.  Since the transition to the ES and the higher levels of volatility that we have seen over the years and the adoption of overnight trading,  the system hasn’t fared that well, but the concepts are still viable as an instructional tool today as they were 20 years ago.  EasyLanguage has been improved over this time period so the coding for the Super Combo can definitely take advantage of the new enhancements.

Here are the main premises of the logic:

  • take advantage of a buyEasier and shortEasier pattern setup
  • incorporate daily and 5-minute time frames in one chart
  • include a breakOut, failedBreakOut and reverseOnLiquidation trade entry techniques
  • monitor which signal is currently online and apply the correct exit signal
  • monitor profit and incorporate a break even stop
  • monitor time and incorporate a trailing stop
  • provide an interface into the logic via inputs

Okay here we go – there is quite a bit of code here so let’s divide an conquer by examining just one module at a time.  This first module includes the inputs and variables section plus once per day calculations.

[LegacyColorValue = true]; 

{Super Combo by George Pruitt - redo 2020
This intra-day trading system will illustrate the multiple data
handling capabilities of TradeStation. All pertinent buy and sell
calculations will be based on daily bars and actual trades will be
executed on 5-min bars. I have made most of the parameters input
variables}

Inputs:waitPeriodMins(30),initTradesEndTime(1430),liqRevEndTime(1200),
thrustPrcnt1(0.30),thrustPrcnt2(0.60),breakOutPrcnt(0.25),
failedBreakOutPrcnt(0.25),protStopPrcnt1(0.30),protStopPrcnt2(0.20),
protStopAmt(3.00),breakEvenPrcnt(0.50),avgRngLength(10);

Variables:averageRange(0),canTrade(0),buyEasierDay(FALSE),
sellEasierDay(FALSE),buyBOPoint(0),sellBOPoint(0),longBreakPt(0),
shortBreakPt(0),longFBOPoint(0),shortFBOPoint(0),barCount(0),buysToday(0),
sellsToday(0),mp(0),longLiqPoint(0),shortLiqPoint(0),
longLiqPoint1(0),shortLiqPoint1(0),intraTradeHigh(0),intraTradeLow(999999);


{Just like we did in the psuedo code -- let's start out with the daily
bar calculations. If Date <> Date[1] -- first bar of day}
if(Date <> Date[1]) then {save time by doing these calculations once per day}
begin
averageRange = Average(Range,10) of Data2; {Data 2 points to daily bars}

canTrade = 0;
if range of data2 < averageRange then canTrade = 1;

{use close of data2 - seems to be more accurate than CloseD(1)
buyEasierDay =Close of Data2 >= Close[1] of Data2;
sellEasierDay = Close of Data2 < Close[1] of Data2;

buyBOPoint = Open + thrustPrcnt1*averageRange;
sellBOPoint= Open - thrustPrcnt2*averageRange;

if(sellEasierDay) then
begin
sellBOPoint= Open - thrustPrcnt1*averageRange;
buyBOPoint = Open + thrustPrcnt2*averageRange;
end;

longBreakPt = HighD(1) + breakOutPrcnt*averageRange;
shortBreakPt= LowD(1) - breakOutPrcnt*averageRange;

shortFBOPoint = HighD(1) - failedBreakOutPrcnt*averageRange;
longFBOPoint= LowD(1) + failedBreakOutPrcnt*averageRange;

{Go ahead and initialize any variables that we may need later on in the day}

barCount = 0;
buysToday = 0;sellsToday = 0;{You can put multiple statements on one line}
end;
First Modules of SuperCombo 2020

Here I am just setting up the inputs and variables that I will need to execute the algorithm.  If you are using .D data then the code

if date <> date[1] then

is a valid test for the first bar of the day.  A new date will represent the beginning of the next day.  The code controlled by this if-then construct is only executed one time per day.  So if you can put the lion’s share of daily calculations here, then it should speed stuff up.  The first thing I do is calculate the average range of the last 10 daily bars.  I access this date from data2.  Can you build a loop and accumulate the difference between the HighD and LowD function calls?

  1. for i = 1 to 10 begin
  2.      sum = sum + (HighD(i) – LowD(i));
  3. end;

The HighD() and LowD() functions are EasyLanguage enhancements that can help eliminate the need for a multi-data chart.  However, if you do this, you will get an warning message that its not a good idea.  I have done this and it seems to work, but to be safe just use Data2.    Next I determine if there has been a narrow range or range compression by comparing yesterday’s range to the averageRange.  If so, then I allow trading.  This is an old filter that looks for range expansion after compression.  The concept of a buyDay and sellDay was originated in the 1930s by George W. Cole (correct me if I am wrong here).  I use this idea by comparing the prior two bars closing relationships.  If there has been an up close, then I consider the next day to be a buyEasier day.  If the opposite is true, then its a sellEasier day.   This system isn’t unidirectional and does allow buying  and shorting in the same session – hence the word easier.   Continuing I calculate the levels that if the market reaches will hopefully trigger a short term trend in that direction.  This is the once highly respected open range break out or ORBO.  This methodology has lost its luster over the last 10 years or so due to overnight trading and allowing pent up buying and selling to be expressed in the overnight sessions.  Twenty years ago it was still viable.  The next bit of code creates the break out levels based on the buyEasier or sellEasier days.   The thrust is calculated by multiplying the range by thrustPrcnt1 and thrustPrcnt2.

So that is method 1 – break out.  Hope the market breaks out and continues to the close.  I wish it were this easy.  Since its not, the second methodolgy, FailedBreakOut, is calculated.  This is also known as the “ClearOut” trade.   The market is pushed to take out all the buy stops and then pulled back for the professionals to feast on the amateurs.  SuperCombo tries to take advantage of this by calculating the two points to determine a failed break out.  On the long side, it is the two points the market rises up to and then falls back to.  If the market breaches the longBreakPt, then look to sellShort at the shortFBOPoint.    Here is the next module

{Now lets trade and manage on 5-min bars}

barCount = barCount + 1; {count the number of bars of intraday data}
if(barCount >= waitPeriodMins/BarInterval and canTrade = 1) then {have we waited long enough}
begin
if(MarketPosition = 1) then buysToday = 1;
if(MarketPosition =-1) then sellsToday= 1;

if(buysToday = 0 and Time < initTradesEndTime) then
Buy("LBreakOut") next bar at buyBOPoint stop;

if(sellsToday= 0 and Time < initTradesEndTime) then
SellShort("SBreakout") next bar at sellBOPoint stop;

if(highD(0) > longBreakPt and sellsToday = 0 and Time < initTradesEndTime) then
SellShort("SfailedBO") next bar at shortFBOPoint stop;
if(lowD(0) < shortBreakPt and buysToday = 0 and Time < initTradesEndTime) then
Buy("BfailedBO") next bar at longFBOPoint stop;
Monitor Market Action and Place Trades Accordingly

 

if(barCount>= waitPeriodMins/BarInterval and canTrade = 1) then

Forces the logic to flow only if canTrade is 1 and we have waited for amateur hour to be completed – well 30 minutes to be accurate.  Is the first hour really amateur hour?  I don’t think this applies, but if you think it does this is how you control trading prior to the completion of this period.  By dividing by BarInterval and counting each bar you can generalize this code for any time resolution.   If MarketPosition is 1 then you know you entered a long position and the opposite is true for short positions.  Only place the break out orders if time is less than initTradesEndTime.  If the market penetrates the long and shortBreakPts, then prepare to take advantage of a failed breakout.  Only go short if a short position has not already been entered – same for longs.  So, this logic places the breakOut and failedBreakOut orders.  Now for the last module.

{The next module keeps track of positions and places protective stops}

mp = marketPosition;
if(MarketPosition = 1) then
begin
longLiqPoint = EntryPrice-protStopPrcnt1*averageRange;
longLiqPoint = MinList(longLiqPoint,EntryPrice - protStopAmt);
longLiqPoint1 = EntryPrice - protStopPrcnt2*averageRange;
longLiqPoint1 = MinList(longLiqPoint1,EntryPrice - protStopAmt);
if Maxpositionprofit >= breakEvenPrcnt*averageRange*bigPointValue then
begin
longLiqPoint = EntryPrice; {Breakeven trade}
longLiqPoint1 = EntryPrice; {Breakeven trade}
end;
if(Time >= initTradesEndTime) then
begin
longLiqPoint = MaxList(longLiqPoint,Lowest(Low,3)); {Trailing stop}
longLiqPoint1 = MaxList(longLiqPoint1,Lowest(Low,3)); {Trailing stop}
end;
if(Time < liqRevEndTime and sellsToday = 0 and
longLiqPoint <> EntryPrice and BarsSinceEntry >= 4) then
SellShort("LongLiqRev") next bar at longLiqPoint stop;

Sell("LongLiq-BO") from entry("LBreakOut") next bar at longLiqPoint stop;
Sell("LongLiq-FBO") from entry("BFailedBO") next bar at longLiqPoint stop;
Sell("LongLiq-RLoss") from entry("ShortLiqRev") next bar at longLiqPoint1 stop;
end;
if(MarketPosition =-1) then
begin
shortLiqPoint = EntryPrice+protStopPrcnt1*averageRange;
shortLiqPoint = MaxList(shortLiqPoint,EntryPrice + protStopAmt);
shortLiqPoint1 = EntryPrice + protStopPrcnt2*averageRange;
shortLiqPoint1 = MaxList(shortLiqPoint1,EntryPrice + protStopAmt);
if maxPositionProfit >= breakEvenPrcnt*averageRange*bigPointValue then
begin
shortLiqPoint = EntryPrice; {Breakeven trade}
shortLiqPoint1 = EntryPrice;
end;
if(Time >= initTradesEndTime) then
begin
shortLiqPoint = MinList(shortLiqPoint,Highest(High,3)); {Trailing stop}
shortLiqPoint1 = MinList(shortLiqPoint1,Highest(High,3)); {Trailing stop}
end;
if(Time < liqRevEndTime and buysToday = 0 and
shortLiqPoint <> EntryPrice and BarsSinceEntry >= 4) then
Buy("ShortLiqRev") next bar at shortLiqPoint stop;

BuyToCover("ShortLiq-BO") from entry("SBreakOut") next bar at shortLiqPoint stop;
BuyToCover("ShortLiq-FBO") from entry("SFailedBO") next bar at shortLiqPoint stop;
BuyToCover("ShortLiq-RLoss") from entry("LongLiqRev") next bar at shortLiqPoint1 stop;
end;
end;
SetExitOnClose;
TradeManagement (Enter on Stop Loss or Not?)

This code looks a little hairy, but its not.  Let’s just look at the long side logic to save time here.  First let’s calculate the LongLiqPoints (1 and 2.)  Twenty years ago I thought it would be better to have a smaller stop for entries that occurred on a LiquidationReversal.  Oh yeah that code is in here to.  Back in the day I wanted to make sure the stop was at least 3 handles – ha, ha, ha – no really I am serious.  Really.  Stop laughing!! That code could be eliminated.  After calculating these two points I start to monitor profit and if it reaches a predetermined level I pull the the longLiqPoints toa  BreakEven stop.  If you are fortunate to still be in a trade after initTradesEndTime, then I start trailing the stop by the lowest low of the last 3 five minute bars – I don’t want to turn a small winner into a loser.  Now this is the fun stuff.

  1. if(Time < liqRevEndTime and sellsToday = 0 and
    longLiqPoint <> EntryPrice and BarsSinceEntry >= 4) then
  2.      SellShort(“LongLiqRev”) next bar at longLiqPoint stop;

If time is less than liqRevEndTime and BarsSinceEntry, then reverse and go short at the longLiqPoint stop.  Do this instead of liquidating.  I thought if the market reversed course quickly, then I wanted to take advantage of this counter trend move.  Eliminating this to see if it has any impact would be where I would start to play around with the template.  Okay now the liquidations based on from Entry take place next.  If I am long from a “ShortLiqRev“, then I use longLiqPoint1 instead of longLiqPoint.  Okay that last part was the kitchen sink.  Now you have enough code to make your own day trading system – really too much code, but you should be able to hobble something together from these parts.  Let me know if you can create your own Frankenstein monster.  I will update the parameters to see if there is any hope to the system as a whole.  Keep checking back for updated performance metrics.  Best to all and be safe!