The following is from the RealCode website – I have just copied and pasted this here. Here is the header information that provides credit to the original programmer.
Here is the description of the Indicator via ProRealCode. Please check out the website for further information regarding the indicator and how to use it.
The RMI Trend Sniper indicator is designed to identify market trends and trading signals with remarkable precision.
This tool combines the analysis of the Relative Strength Index (RSI) with the Money Flow Index (MFI) and a unique approach to range-weighted moving average to offer a comprehensive perspective on market dynamics.
Configuration and Indicator Parameters
The RMI Trend Sniper allows users to adjust various parameters according to their trading needs, including:
RMI Length: Defines the calculation period for the RMI.
Positive and Negative Momentum (Positive above / Negative below): Sets thresholds to determine the strength of bullish and bearish trends.
Range MA Visualization (Show Range MA): Enables users to visualize the range-weighted moving average, along with color indications to quickly identify the current market trend.
Cool Shading – right?
Many of my clients ask me to convert indicators from different languages. One of my clients came across this from ProRealCode and asked me to convert for his MulitCharts. Pro Real code is very similar to EasyLanguage with a few exceptions. If you are savvy in EL, then I think you could pick up PRC quite easily. Here it is. It is a trend following indicator. It is one of a few that I could not find the Easylanguage equivalent so I thought I would provide it. Play around with it and let me know what you think. Again, all credit goes to:
if positive = 1 then begin rwma = rwma-band; r=0; g=188; b=212; end else if negative = 1 then begin rwma = rwma+band; r=255; g=82; b=82; end else rwma = 0;
//------------------------------------------------------------// //-----Calculate MA bands-------------------------------------// vars: mitop(0),mibot(0);
mitop = rwma+band20; mibot = rwma-band20;
plot1(mitop,"TOP"); plot2((mitop+mibot)/2,"TOP-BOT"); plot3((mitop+mibot)/2,"BOT-TOP"); plot4(mibot,"BOT"); if positive = 1 then begin plot5(rwma,"Pos",GREEN); noPlot(plot4); end; if negative =1 then begin plot6(rwma,"Neg",RED); noPlot(plot3); end;
Ignore the RGB Color Codes
Getting Creative to Shade Between Points on the Chart
TradeStation doesn’t provide an easy method to do shading, so you have to get a little creative. The plot TOP is of type Bar High with the thickest line possible. The plot TOP-BOT (bottom of top) is of type Bar Low. I like to increase transparency as much as possible to see what lies beneath the shading . The BOT-TOP (top of bottom) is Bar High and BOT is Bar Low. Pos and Neg are of type Point. I have colored them to be GREEN or RED.
A study between ice cream sales and crime rate demonstrated a high level of correlation. However, it would be illogical to assume that buying more ice cream leads to more crime. There are just too many other factors and variables involved to draw a conclusion. So, data mining with EasyLanguage may or may not lead to anything beneficial. One thing is you cannot hang your hat completely on this type of research. A reader of my books asked if there was evidence that pointed to the best time to enter and exit a day trade. Is it better to enter in the morning or in the afternoon or are there multiple trading windows throughout the day? I thought I would try to answer the question using TradeStation’s optimization capabilities.
Create a Search Space of Different Window Opening Times and Open Duration
My approach is just one of a few that can be used to help answer this question. To cut down on time and the size of this blog we will only look at day trading the @ES.D from the long side. The search space boundaries can be defined by when we open the trading window and how long we leave it open. These two variables will be defined by inputs so we can access the optimization engine. Here is how I did it with EasyLanguage code.
if t >= calcTime(openWindowTime,openWindowOffset) and t < calcTime(openWindowTime,openWindowOffset+windowDuration) Then Begin if entriesToday(d) = 0 and canBuy Then buy next bar at market; if entriesToday(d) = 0 and canShort Then sellshort next bar at market ; end;
if t = calcTime(openWindowTime,openWindowOffset+windowDuration) Then Begin if marketPosition = 1 then sell next bar at open; if marketPosition =-1 then buyToCover next bar at open; end; setExitOnClose;
Optimize when to open and how long to leave open
The openWindowTime input is the basis from where we open the trading window. We are working with the @ES.D with an open time of 9:30 AM eastern. The openWindowOffset will be incremented in minutes equivalent to the data resolution of the chart, five minutes. We will start by opening the window at 9:35 and leave it open for 60 minutes. The next iteration in the optimization loop will open the window at 9:40 and keep it open for 60 minutes as well. Here are the boundaries that I used to define our search space.
window opening times offset: 5 to 240 by 5 minutes
window opening duration: 60 to 240 by 5 minutes
Optimization Ranges for Window Open and Open Duration
A total of 1739 iterations will span our search space. The results state that waiting for twenty minutes before buying and then exiting 190 minutes later, worked best. But also entering 90 minutes after the open and exiting 4 hours later produced good results as well (no trade execution fee were utilized.) Initially I was going to limit entry to once per day, but then I thought it might be worthwhile to enter a fresh position, if the first one is stopped out, or pyramid if it hasn’t. I also thought, each entry should have its own protective stop amount. Would entering later require a small stop – isn’t most of the volatility, on average, expressed during the early part of the day.
Results of different opening and duration times.
Build a Strategy that Takes on a Secondary Trade as a New Position or One that is Pyramided.
This is not a simple strategy. It sounds simple and it requires just a few lines of code. But there is a trick in assigning each entry with its own exit. As you can see there is a potential for trade overlap. You can get long 20 minutes after the open and then add on 70 minutes (90 from the open) later. If the first position hasn’t been stopped out, then you will pyramid at the second trade entry. You have to tell TradeStation to allow this to happen.
Allow TradeStation to Pyramid up to 2 positions with different signals.
System Rules
Enter long 20 minutes after open
Enter long 90 minutes after open
Exit 1st entry 190 minutes later or at a fixed $ stop loss
Exit 2nd entry 240 minutes later or at a fixed $ stop loss
Make sure you are out at the end of the day
Sounds pretty simple, but if you want to use different stop values for each entry, then the water gets very muddy.
AvgEntryPrice versus EntryPrice
Assume you enter long and then you add on another long position. If you examine EntryPrice you will discover that it reflects the initial entry price only. The built-in variable AvgEntryPrice will be updated with the average price between the two entries. If you want to key off of the second entry price, then you will need to do a little math.
Using this formula and simple algebra we can arrive at ep2 using this formula: ep2 = 2*ap – ep1. Since we already know ep1 and ap, ep2 is easy to get to. We will need this information and also the functionality of from entry. You tie entries and exits together with the keywords from entry. Here are the entry and exit trade directives.
if time = calcTime(openTime,entryTime1Offset) then buy("1st buy") next bar at open;
if time = calcTime(openTime,entryTime2Offset) then buy("2nd buy") next bar at open;
if time = calcTime(openTime,entryTime1Offset + exitTime1Offset) then sell("1st exit") from entry("1st buy") next bar at open;
if time = calcTime(openTime,entryTime2Offset + exitTime2Offset) then sell("2nd exit") from entry("2nd buy") next bar at open;
if mp = 1 Then Begin value1 = avgEntryPrice; if currentShares = 2 then value1 = avgEntryPrice*2 - entryPrice; sell("1st loss") from entry("1st buy") next bar at entryPrice - stopLoss1/bigPointValue stop; sell("2nd loss") from entry("2nd buy") next bar at value1 - stopLoss2/bigPointValue stop; end;
if mp = 1 and t = openTime + barInterval then sell("oops") next bar at open;
Entry and Exit Directives Code
The trade entry directives are rather simple, but you must use the calcTime function to arrive at the correct entry and exit times. Here we are using the benchmark, openTime and the offsets of entryTime1Offset and entryTime2Offset. This function adds (or subtracts if the offset is negative) the offset to the benchmark. This takes care of when the trading windows open, but you must add the entry1TimeOffset to exit1TimeOffset to calculate the duration the trading window is to remain open. This goes for the second entry window as well.
Now let’s look at the exit directives. Notice how I exit the 1st buy entry with the code from entry (“1st buy”). This ties the entry and exit directives together. This is pretty much straightforward as well. The tricky part arrives when we try to apply different money management stops to each entry. Exiting from the 1st buy requires us to simply subtract the $ in terms of points from entryPrice. We must use our new equation to derive the 2nd entry price when two contracts are concurrent. But what if we get stopped out of the first position prior to entering the second position? Should we continue using the formula. No. We need to fall back to entryPrice or avgEntryPrice: when only one contract or unit is in play, these two variables are equal. We initially assign the variable value1 to the avgEntryPrice and only use our formula when currentShares = 2. This code will work a majority of the time. But take a look at this trade:
Didn’t have an intervening bar to update the 2nd entry price. The first entry price was used as the basis to calculate the stop loss!
This is an anomaly, but anomalies can add up. What happened is we added the second position and the market moved down very quickly – too quickly for the correct entry price to be updated. The stop out (2nd loss) was elected by using the 1st entry price, not the second. You can fix this with the following two solutions:
Increase data resolution and hope for an intervening bar
Force the second loss to occur on the subsequent trading bar after entry. This means you will not be stopped out on the bar of entry but will have to wait five minutes or whatever bar interval you are working with.
Fixed – just told TS to place the order at the close of the bar where we were filled!
OK – Now How Do We Make this a Viable Trading System
If you refer back to the optimization results you will notice that the average trade (before execution costs) was around $31. Keep in mind we were trading every day. This is just the beginning of your research – did we find a technical advantage? No. We just found out that you can enter and exit at different times of the trading day, and you can expect a positive outcome. Are there better times to enter and exit? YES. You can’t trade this approach without adding some technical analysis – a reason to enter based on observable patterns. This process is called FILTERING. Maybe you should only enter after range compression. Or after the market closed up on the prior day, or if the market was an NR4 (narrow range 4.) I have added all these filters so you can iterate across them all using the optimization engine. Take a look:
filter1 = True; filter2 = True;
if filtNum1 = 1 then filter1 = close of data2 > close[1] of data2; if filtNum1 = 2 Then filter1 = close of data2 < close[1] of data2; if filtNum1 = 3 Then filter1 = close of data2 > open of data2; if filtNum1 = 4 Then filter1 = close of data2 < open of data2; if filtNum1 = 5 Then filter1 = close of data2 > (h data2 + l data2 + c data2)/3; if filtNum1 = 6 Then filter1 = close of data2 < (h data2 + l data2 + c data2)/3; if filtNum1 = 7 Then filter1 = openD(0) > close data2; if filtNum1 = 8 Then filter1 = openD(0) < close data2;
if filtNum2 = 1 Then filter2 = trueRange data2 < avgTrueRange(10) data2; if filtNum2 = 2 Then filter2 = trueRange data2 > avgTrueRange(10) data2; if filtNum2 = 3 Then filter2 = range data2 = lowest(range data2,4); if filtNum2 = 4 Then filter2 = range data2 = highest(range data2,4);
Filter1 and Filter2 - filter1 looks for a pattern and filter2 seeks range compression/expansion.
Let’s Search – And Away We Go
I will optimize across the different patterns and range analysis and different $ stops for each entry (1st and 2nd.)
Optimize across patterns and volatility and protective stops for 1st and 2nd entries.
Best Total Profit
Trade when today’s open is greater than yesterday’s close and don’t worry about the volatility. Use $550 for the first entry and $600 for the second.
Best W:L Ratio and Respectable Avg. Trade
This curve was created by waiting for yesterday’s close to be below the prior day’s and yesterday being an NR4 (narrow range 4). And using a $500 protective stop for both the 1st and 2nd entries.
Did We Find the Holy Grail? Gosh No!
This post served two purposes. One, how to set up a framework for data mining and two, create code that can handle things that aren’t apparently obvious – entryPrice versus avgEntryPrice!
if filter1 and filter2 Then begin if time = calcTime(openTime,entryTime1Offset) then buy("1st buy") next bar at open;
if time = calcTime(openTime,entryTime2Offset) then buy("2nd buy") next bar at open; end;
Incorporating Filter1 and Filter2 in the Entry Logic
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.
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;
This system has been around for several years. Its based on the belief that fund managers start pouring money into the market near the end of the month and this creates momentum that lasts for just a few days. The original system states to enter the market on the close of the last bar of the day if the its above a certain moving average value. In the Jaekle and Tomasini book, the authors describe such a trading system. Its quite simple, enter on the close of the month if its greater than X-Day moving average and exit either 4 days later or if during the trade the closing price drops below the X-Day moving average.
EasyLanguage or Multi-Charts Version
Determining the end of the month should be quite easy -right? Well if you want to use EasyLanguage on TradeStation and I think on Multi-Charts you can’t sneak a peek at the next bar’s open to determine if the current bar is the last bar of the month. You can try, but you will receive an error message that you can’t mix this bar on close with next bar. In other words you can’t take action on today’s close if tomorrow’s bar is the first day of the month. This is designed, I think, to prevent from future leak or cheating. In TradeStation the shift from backtesting to trading is designed to be a no brainer, but this does provide some obstacles when you only want to do a backtest.
LDOM function – last day of month for past 15 years or so
So I had to create a LastDayOfMonth function. At first I thought if the day of the month is the 31st then it is definitely the last bar of the month. And this is the case no matter what. And if its the 30th then its the last day of the month too if the month is April, June, Sept, and November. But what happens if the last day of the month falls on a weekend. Then if its the 28th and its a Friday and the month is blah, blah, blah. What about February? To save time here is the code:
// 29th of the month and a Friday if theDayOfMonth = 29 and theDayOfWeek = 5 then endOfMonth = True; // 30th of the month and a Friday if theDayOfMonth = 30 and theDayOfWeek = 5 then endOfMonth = True; // 31st of the month if theDayOfMonth = 31 then endOfMonth = True; // 30th of the month and April, June, Sept, or Nov if theDayOfMonth = 30 and (theMonth=4 or theMonth=6 or theMonth=9 or theMonth=11) then endOfMonth = True; // 28th of the month and February and not leap year if theDayOfMonth = 28 and theMonth = 2 and not(isLeapYear) then endOfMonth = True; // 29th of the month and February and a leap year or 28th, 27th and a Friday if theMonth = 2 and isLeapYear then Begin If theDayOfMonth = 29 or ((theDayOfMonth = 28 or theDayOfMonth = 27) and theDayOfWeek = 5) then endOfMonth = True; end; // 28th of the month and Friday and April, June, Sept, or Nov if theDayOfMonth = 28 and (theMonth = 4 or theMonth = 6 or theMonth = 9 or theMonth =11) and theDayOfWeek = 5 then endOfMonth = True; // 27th, 28th of Feb and Friday if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 27 then endOfMonth = True; // 26th of Feb and Friday and not LeapYear if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 26 and not(isLeapYear) then endOfMonth = True; // Memorial day adjustment If theMonth = 5 and theDayOfWeek = 5 and theDayOfMonth = 28 then endOfMonth = True; //Easter 2013 adjustment If theMonth = 3 and year(d) = 113 and theDayOfMonth = 28 then endOfMonth = True; //Easter 2018 adjustment If theMonth = 3 and year(d) = 118 and theDayOfMonth = 29 then endOfMonth = True;
if endOfMonth and c > average(c,movAvgPeriods) then Buy("BuyDay") this bar on close;
If C <average(c,movAvgPeriods) then Sell("MovAvgExit") this bar on close; If BarsSinceEntry=4 then Sell("4days") this bar on close;
Last Day Of Month Function and Strategy
All the code is generic except for the hard code for days that are a consequence of Good Friday.
All this code because I couldn’t sneak a peek at the date of tomorrow. Here are the results of trading the ES futures sans execution costs for the past 15 years.
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.
What will you learn : string manipulation, for-loops, optimization
Before proceeding I would suggest reading my original post on this subject. If you believe the relationship of the last few bars of data can help determine future market direction, then this post will be in you wheel house. Another added benefit is that you will also learn some cool EasyLanguage.
Original post was limited to four day patterns!
This version is limitless (well not really, but pretty close). Let’s stick with the original string pattern nomenclature (+ + – – : two up closes followed by two down closes.) Let’s also stick with our binary pattern representation:
Pattern #
2^3
2^2
2^1
1
3
0
0
1
1
4
0
1
0
0
5
0
1
0
1
6
0
1
1
1
Remember a 0 represents a down close and a 1 represents an up close. We will deviate from the original post by doing away with the array and stick with only strings (which are really just arrays of characters.) This way we won’t have to worry about array manipulation.
How to create a dynamic length string pattern
This was the difficult part of the programming. I wanted to be able to optimize 3, 4 and 5 day patterns and I wanted to control this with using just inputs. I discovered that pattern three is different in a three day pattern than it is in a four day pattern: in a three day pattern it is 011 or – + + and in a four day pattern it is 0011 or – – + +. Since I am counting 0’s as down closes, pattern #3 depends on the ultimate size of the pattern string. No worries I will have eventually have another version where I utilize a different value for down closes and we can then have holes in our string patterns. But I digress – so to differentiate the patterns based on the pattern length I included a maxPatternLen input. So if maxPatternLen is three and we are trying to match pattern #3 then we will be looking for 011 and not 0011. That was an easy fix. But then I wanted to build a string pattern based on this input and the pattern number dynamically. Here is some psuedo code on how I figured it out.
{Psuedo code to translate pattern number into binary number} patternNumber = 3 maxPatternLen = 3
numBits = 0 // stick with binary representation testValue = 0 // temporary test value numBits = maxPatternLen-1 // how many bits will it take to get to the // center of - or numBits to represent max // number of patterns or 2^numBits currentBit =numBits // start wit current bit as total numBits
value1 = patternOptTest // value1 represents current pattern number testString = "" // build test string from ground up
for icnt = numBits downto 0 //building string from left to right begin // notice keyword downto if power(2,currentBit) > value1 then // must use power function in EL begin // if the very far left bit value > testString = testString + "-" // patten number then plug in a "-" end else begin // else plug in a "+" and deccrement by testString = testString + "+" // that bits value - if its the 3rd bit value1 = value1 - power(2,currentBit)// then decrement by 8 end; currentBit = currentBit - 1 // move onto the next bit to the right end;
Pseudocode for Binary Representation of Pattern #
Now if you want to optimize then you must make sure your pattern number search space or range can be contained within maxPatternLen. For example, if you want to test all the different combinations of a four day pattern, then your maxPatternLen would naturally be four and you would optimize the pattern number from 0 to 15. Don’t use 1-16 as I use zero as the base. A five day pattern would include the search space from 0 – 31. The rest of the code was basically hacked from my original post. Here is the rest of the code to do optimizations on different length pattern strings. Notice how I use strings, for-loops and comparisons.
If patternOptimize then begin numBits = 0; testValue = 0; value = maxPatternLen; numBits = maxPatternLen-1; currentBit =numBits; remainder = patternOptTest; testString = ""; for icnt = numBits downto 0 begin if power(2,currentBit) > remainder then {note this originally had value1 instead of remainder} begin testString = testString + "-"; end else begin testString = testString + "+"; remainder = remainder - power(2,currentBit); end; currentBit = currentBit - 1; end; numCharsInBuyPattern = maxPatternLen; numCharsInSellPattern = maxPatternLen; if patternOptBuySell = 1 then Begin buyPatternMatch = testString; sellPatternMatch = "0"; end; If patternOptBuySell = 2 then Begin buyPatternMatch = "0"; sellPatternMatch = testString; end; end;
buyPatternString = ""; sellPatternString = "";
For icnt = numCharsInBuyPattern-1 downto 0 Begin If close[icnt] >= close[icnt+1] then buyPatternString = buyPatternString + "+"; If close[icnt] < close[icnt+1] then buyPatternString = buyPatternString + "-"; end; For icnt = numCharsInSellPattern-1 downto 0 Begin If close[icnt] >= close[icnt+1] then sellPatternString = sellPatternString + "+"; If close[icnt] < close[icnt+1] then sellPatternString = sellPatternString + "-"; end;
okToBuy = false; okToSell = false;
if buyPatternMatch <> "" then If buyPatternString = buyPatternMatch then okToBuy = true; If buyPatternMatch = "" then okToBuy = true; If sellPattern <> "" then If sellPatternString = sellPatternMatch then okToSell = true; If sellPatternMatch = "" then okToSell = true;
If okToBuy then buy next bar at open; If okToSell then sellshort next bar at open;
If marketPosition = 1 and barsSinceEntry > holdDays then sell next bar at open; If marketPosition = -1 and barsSinceEntry > holdDays then buytocover next bar at open;
If lastBarOnChart then print(d," ",buyPatternMatch);
Final Version of New Pattern Smasher
Also see how I incorporate a profit target and protective stop. I use the built in BarsSinceEntry function to count the number of days I am in a trade so I can utilize a time based exit. Here is an interesting equity curve I developed using a two day pattern ( – –) to go long.
Register on the website and I will email you an ELD of the improved Pattern Smasher. Or just shoot me an email.
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 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.
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;
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.
Okay let’s see how I was able to add some eloquence to the brute force approach to this pyramiding algorithm. The original code included multiple entry directives and a ton of hard coded numerical values. So let me show you how I was able to refine the logic/code and in doing so make it much more flexible. We might lose a little bit of the readability, but we can compensate by using extra commentary.
First off, let’s add flexibility by employing input variables. In this case, we need to inform the algorithm the distance from the open to add additional positions and the max number of entries allowed for the day.
inputs : pyramidDistance(5),maxDailyEntries(3);
Now we need to set somethings up for the first bar of the day. Comparing the date of today with the date of yesterday is a good way to do this.
if d<>d[1] then begin canSell = true; sellMult = 1; sellStop = -999999; entries = 0; end;
First bar of the day housekeeping.
Here is a neat way to keep track of the number of entries as they occur throughout the trading day. Remember the function EntriesToday(date) will not provide the information we need.
mp = marketPosition * currentShares;
if mp[1] <> mp and mp <> 0 then entries = entries + 1;
How to track the number of entries for today.
If the last bar’s mp[1] is not equal to the current bar’s mp then and mp is not equal to zero then we know we have added on another entry. Okay now let’s think about eliminating the “brute force” approach.
Instead of placing multiple order entry directives I only want to use one with a variable stop level. This stop level will be guided by the variable SellMult. We start the day with a wacky sell stop level and then calculate it based on the SellMult variable and PyramidDistance input.
if low <= sellStop then begin sellMult = sellMult + 1; end;
sellStop = openD(0) - sellMult * pyramidDistance;
Calculate and adapt sell stop level as we go along.
So on the first bar of the day the sellStop = openD(0) – sellMult * pyramidDistance or sellStop = openD(0) – 1 * 5. Or 5 handles below the open. Note you an change the pyramidDistance input and make it three to match the previous examples.
if entries = maxDailyEntries then canSell = false; if time < sess1EndTime and canSell then sellShort 1 contract next bar at sellStop stop; if mp <=-1 {and barsSinceEntry > 0} then buyToCover next bar at sellStop + 2* pyramidDistance stop;
setexitonclose;
That's it! Pretty simple isn't it?
Ok, we need to tell the computer to turn off the ability to place orders if one of two things happens: 1) we have reached the maxDailyEntries or 2) time >= sess1EndTime. You could make the time to stop entering trades an input as well. If neither criteria applies then place an order to sellShort at our sellStop level. If price goes below our sell stop level then we know we have been filled and the new sellStop level needs to be recalculated. See how we use a calculation to adapt the stop level with a single order placement directive? This is where the eloquence comes into play. QED.
Now you code the opposite side and then see if you can make money (hypothetically speaking of course) with it. If you think about it, why does this not work. And the not so obvious reason is that it trades too much. Other than trading too much it makes perfect sense – buy or sell by taking a nibbles at the market. If the market takes off then take a big bite. The execution costs of the nibbles are just way too great. So we need to think of a filtering process to determine when it is either better to buy or sell or when to trade at all. Good Luck with this ES [emini S&P ]day trading algorithm!
if d<>d[1] then begin canSell = true; sellMult = 1; sellStop = -999999; entries = 0; end;
mp = marketPosition * currentShares;
if mp[1] <> mp and mp <> 0 then entries = entries + 1; if mp[1] = -1 and mp[0] = 0 then canSell = false; if time > 1430 then canSell = false;
if low <= sellStop then begin sellMult = sellMult + 1; end;
sellStop = openD(0) - sellMult * pyramidDistance; if entries = maxDailyEntries then canSell = false; if time < sess1EndTime and canSell then sellShort 1 contract next bar at sellStop stop; if mp <=-1 {and barsSinceEntry > 0} then buyToCover next bar at sellStop + 2* pyramidDistance stop;
Here is the finalized tutorial on building the pyramiding ES-day-trade system that was presented in the last post.
I will admit this video should be half as long as the end result. I get a bit long-winded. However, I think there are some good pointers that should save you some time when programming a similar system.
EasyLanguage Source:
Here is the final code from the video:
vars: mp(0),lastTradePrice(0),canSell(true);
mp = marketPosition * currentContracts;
if date[0] <> date[1] then begin canSell = true; // canSell on every day end;
if mp = -1 then canSell = false; // one trade on - no more if time > 1430 then canSell = false; //no entries afte 230 central
if mp = 0 and canSell = true then sellShort next bar at OpenD(0) - 3 stop;
if mp = -1 then sellShort next bar at OpenD(0) - 6 stop; //add 1 if mp = -2 then sellShort next bar at OpenD(0) - 9 stop; //add 2
if mp = -1 then lastTradePrice = OpenD(0) - 3; //keep track of entryPrice if mp = -2 then lastTradePrice = OpenD(0) - 6; if mp = -3 then lastTradePrice = OpenD(0) - 9;
if mp <> 0 then buyToCover next bar at lastTradePrice + 3 stop; // 3 handle risk on last trade
// next line provides a threshold prior to engaging trailing stop if mp = -3 and barsSinceEntry > 0 and lowD(0) < lastTradePrice - 3 then buyToCover next bar at lowD(0) + 3 stop;
setExitOnClose;
EasyLanguage for Pyramiding and Day-Trading ES
What we learned here:
can’t use entriesToday(date) to determine last entry price
must use logic to not issue an order to execute on the first bar of the next day
mp = marketPosition * currentContracts is powerful stuff!
In the next few days, I will publish the long side version of this code and also a more eloquent approach to the programming that will allow for future modifications and flexibility.
Let me know how it works out for you.
Take this code and add some filters to prevent trading every day or a filter to only allow long entries!
Which equity curve do you like best? (created with EasyLanguage script) This one…
Or this one?
Obviously the first one. Even though it had a substantial draw down late in the test. What if I told you that the exact same system logic generated both curves? Here is the EasyLanguage code for this simple system.
Buy next bar at open of next bar + .25 *avgTrueRange(10) stop; Sellshort next bar at open of next bar - .25*avgTrueRange(10) stop;
setStopLoss(500); setProfitTarget(1000);
Open Range Break Out with Profit and Loss Objective
This algorithm relies heavily on needing to know which occurred first: the high or the low of the day. The second chart tells the true story because it looks inside the daily bar to see what really happened. The first chart uses an algorithm to try to determine which happened first and applies this to the trades. In some instances, the market looks like it opens then has a slight pull back and then goes up all day. As a result the system buys and holds the trade through the close and onto the next day, but in reality the market opens, goes up and triggers a long entry, then retraces and you get stopped out. What was a nice winner turns into a bad loss. Here is an example of what might have happened during a few trades:
Nice flow – sold, bought, sold, bought, sold again and finally a nice profit. But this is what really happened:
Sold, bought, reversed short on same day and stopped out on same day. Then sold and reversed long on same day and finally sold and took profit. TradeStation’s Look Inside Bar feature helps out when your system needs to know the exact path the market made during the day. In many cases, simply clicking this feature to on will take care of most of your testing needs. However, this simple algorithm needs to place or replace orders based on what happens during the course of the day. With daily bars you are sitting on the close of the prior day spouting off orders. So once the new day starts all of your orders are set. You can’t see this initially on the surface, because it seems the algorithm is so simple. Here is another consequence of day bar testing when the intra-day market movement is paramount:
Here the computer is doing exactly what you told it! Sell short and then take a profit and sell short 25% of the ATR below the open. Well once the system exited the short it realized it was well below the sell entry point so it immediately goes short at the exact same price (remember TS doesn’t allow stop limit orders). You told the computer that you wanted to be short if the market moves a certain amount below the open. These were the orders that were place on yesterday’s close This may not be exactly what you wanted, right? You probably wanted to take the profit and then wait for the next day to enter a new trade. Even if you did want to still be short after the profit level was obtained you wouldn’t want to exit and then reenter at the same price (practically impossible) and be levied a round-turn slip and commission. You could fiddle around with the code and try to make it work, but I guarantee you that a system like this can only be tested properly on intra-day data. Let’s drop down to a lower time frame, program the system and see what the real results look like:
Looks very similar to the daily bar chart with Look Inside Bar turned on. However, it is different. If you wan’t to gauge a systems potential with a quick program, then go ahead and test on daily bars with LIB turned on. If it shows promise, then invest the time and program the intra-day version just to validate your results. What do you mean spend the time? Can’t you simply turn your chart from daily bars to five minute bars and be done with it. Unfortunately no! You have to switch paradigms and this requires quite a bit more programming. Here is our simple system now in EasyLanguage:
{Use highD() and XXXXD(0) functions to capture the highs, lows, and closes for the past 10 days. I could have just used a daily bar as data2. I am looking at five minute bars so we know how the market flows through the day. }
{This loop kicks out a warning message, but seems to work Just do this once at the beginning of the day - faster}
{remember true range is either the higher of todays high Or yesterdays close minus the lower of todays low or yesterdays close}
{ tradeStation time stamps at the close of the bar so we capture the opening of the open time plus the bar interval - in this case 5 minute - so at 1800 + 5 (1805) I capture the open of the day}
if time = sess1StartTime + barInterval then begin Value1 = 0.0; for icnt = 1 to 10 begin Value1 = value1 + maxList(closeD(icnt-1),highD(icnt)) - minList(closeD(icnt-1),lowD(icnt)); end; atr = value1/10.0; stb = open + .25* atr; sts = open - .25* atr; buysToday = 0; sellsToday = 0; end;
mp = marketPosition; {The ole mp trick}
If mp = 1 and mp[1] <> 1 then buysToday = buysToday + 1; If mp =-1 and mp[1] <> -1 then sellsToday = sellsToday + 1;
if buysToday = 0 and time < sess1EndTime and close <= stb then buy next bar at stb stop; if sellsToday = 0 and time < sess1EndTime and close >= sts then sellshort next bar at sts stop;
setStopLoss(500); setProfitTarget(1000);
Open Range Break Out Utilizing Five Minute Bars
Here is a validation that Look Inside Bar does work:
This is the trade from June 1st. Scroll back up to the second chart where LIB is turned on.
Camarilla – A group of confidential, often scheming advisers; a cabal.
An attentive reader of this blog, Walter Baker, found some typos in my code. I have corrected them in the code section – if you have used this code make sure you copy and paste the code in its entirety into your EasyLanguage editor and replace your prior version.
I wanted to elaborate on the original version of Camarilla. The one that users have been downloading from this website is pure reversion version. The Camarilla Equation was by created by Nick Scott, a bond day trader, in 1989. The equation uses just yesterday’s price action to project eight support/resistance price levels onto today’s trading action. These levels, or advisers, as the name of the equation suggests provides the necessary overlay to help predict turning points as well as break outs. Going through many charts with the Camarilla indicator overlay it is surprising how many times the market does in fact turn at one of these eight price levels. The equations that generate the support/resistance levels are mathematically simple:
Resistance #4 = Close + Range * 1.1 / 2;
Resistance #3 = Close + Range * 1.1/4;
Resistance #2 = Close + Range * 1.1/6;
Resistance #1 = Close + Range * 1.1/12;
Support #1 = Close – Range * 1.1/12;
Support #2 = Close – Range * 1.1/6;
Support #3 = Close – Range * 1.1/4;
Support #4 = Close – Range * 1.1/2;
The core theory behind the equation and levels is that prices have a tendency to revert to the mean. Day trading the stock indices would be easy if price broke out and continued in that direction throughout the rest of the day. We all know that “trend days” occur very infrequently on a day trade basis; most of the time the indices just chop around without any general direction. This is where the Camarilla can be effective. Take a look at the following chart [ ES 5-minute day session] where the indicator is overlaid. and how the strategy was able to take advantage of the market’s indecisiveness. This particular example shows the counter-trend nature of the Camarilla. The original Camarilla looked at where the market opened to make a trading decision. The chart below is an adapted version of the one I send out when one registers on for the download. I thought it would be a good idea to show the original that incorporates a break out along with the counter trend mechanism. I will go over the code in the next post. You can copy the code below and paste directly into you EasyLanguage editor.
Carmarilla in Reversion Mode
Original Camarilla rules:
If market opens between R3 and R4 go with the break out of R4. This is the long break out part of the strategy.
If market opens between R3 and S3 then counter trend trade at the R3 level. In other words, sell short at R3. If the market moves down, then buy S3. As you can see this is the mean reversion portion of the strategy.
If market open between S3 and S4 go with the break out of S4 – the short break out method.
Stops are placed in the following manner:
If long from a R4 break-out, then place stop at R3.
If short from a S4 break-out, then place stop at S3.
If long from a R3 countertrend, then place stop at R4.
If short from a S3 countertrend, then place stop at S4.
Profit objectives can be placed at opposite resistance/support levels:
If short from a R3 countertrend, then take profits at S1, S2, or S3.
If long from a S3 countertrend, then take profits at R1, R2, or R3.
Profit objectives for all trades can be a dollar, percent of price of ATR multiple.
If openD(0)> s4 and openD(0) <= s3 then s3_s4 = 1; If openD(0)> s3 and openD(0) <= s2 then s2_s3 = 1; If openD(0)> s2 and openD(0) <= s1 then s1_s2 = 1;
If openD(0)> s1 and openD(0) <= r1 then r1_s1 = 1;
If openD(0)> r1 and openD(0) <= r2 then r1_r2 = 1; If openD(0)> r2 and openD(0) <= r3 then r2_r3 = 1; If openD(0)> r3 and openD(0) <= r4 then r3_r4 = 1;
If openD(0)> r4 then r4_r5 = 1;
if openD(0) < r3 and openD(0) > s3 then r3_s3 = 1; If openD(0) < r4 and openD(0) > s4 then r4_s4 = 1;
if time < endTradeTime and time > 930 then begin
if r3_r4 = 1 and entriesToday(date) < 3 and c < r4 then buy("R4-BrkOut") next bar at r4 stop; if s3_s4 = 1 and entriesToday(date) < 3 and c > s4 then sellShort("S4-BrkOut") next bar at s4 stop;
if c > r2 then r2Pen = 1; if c > r3 then r3Pen = 1; if c > r4 then r4Pen = 1;
if r3_s3 = 1 and r3Pen = 1 and c > r3 and entriesToday(date) < 3 then sellShort("R3Sell") next bar at r3 stop;
if r4Pen = 1 and c < r4 then r4Pen = 0; if r3Pen = 1 and c < r3 then r3Pen = 0; if r2Pen = 1 and c < r2 then r2Pen = 0;
if c < r1 then begin r2Pen = 0; r3Pen = 0; r4Pen = 0; end; if c > s1 then begin s2Pen = 0; s3Pen = 0; s4Pen = 0; end;
if c < s2 then s2Pen = 1; if c < s3 then s3Pen = 1; if c < s4 then s4Pen = 1;
if r3_s3 = 1 and s3Pen = 1 and c < s3 and entriesToday(date) < 3 then buy("S3Buy") next bar at s3 stop;
if s4Pen = 1 and c > s4 then s4Pen = 0; if s3Pen = 1 and c > s3 then s3Pen = 0; if s2Pen = 1 and c > s2 then s2Pen = 0;
if marketPosition = 1 then begin sell from entry("S3Buy") next bar at s4 stop; sell from entry("R4-BrkOut") next bar at r3 stop; end;
if marketPosition = -1 then begin buyToCover from entry("R3Sell") next bar at r4 stop; buyToCover from entry("S4-BrkOut") next bar at s3 stop; end;
end;
setExitOnClose;
Camarilla Strategy EasyLanguage Source
Backtesting with [Trade Station,Python,AmiBroker, Excel]. Intended for informational and educational purposes only!
Get All Five Books in the Easing Into EasyLanguage Series - The Trend Following Edition is now Available!
Announcement – A Trend Following edition has been added to my Easing into EasyLanguage Series! This edition will be the fifth and final installment and will utilize concepts discussed in the Foundation editions. I will pay respect to the legends of Trend Following by replicating the essence of their algorithms. Learn about the most prominent form of algorithmic trading. But get geared up for it by reading the first four editions in the series now. Get your favorite QUANT the books they need!
The Foundation Edition. The first in the series.
This series includes five editions that covers the full spectrum of the EasyLanguage programming language. Fully compliant with TradeStation and mostly compliant with MultiCharts. Start out with the Foundation Edition. It is designed for the new user of EasyLanguage or for those you would like to have a refresher course. There are 13 tutorials ranging from creating Strategies to PaintBars. Learn how to create your own functions or apply stops and profit objectives. Ever wanted to know how to find an inside day that is also a Narrow Range 7 (NR7?) Now you can, and the best part is you get over 4 HOURS OF VIDEO INSTRUCTION – one for each tutorial.
Hi-Res Edition Cover
This book is ideal for those who have completed the Foundation Edition or have some experience with EasyLanguage, especially if you’re ready to take your programming skills to the next level. The Hi-Res Edition is designed for programmers who want to build intraday trading systems, incorporating trade management techniques like profit targets and stop losses. This edition bridges the gap between daily and intraday bar programming, making it easier to handle challenges like tracking the sequence of high and low prices within the trading day. Plus, enjoy 5 hours of video instruction to guide you through each tutorial.
Advanced Topics Cover
The Advanced Topics Edition delves into essential programming concepts within EasyLanguage, offering a focused approach to complex topics. This book covers arrays and fixed-length buffers, including methods for element management, extraction, and sorting. Explore finite state machines using the switch-case construct, text graphic manipulation to retrieve precise X and Y coordinates, and gain insights into seasonality with the Ruggiero/Barna Universal Seasonal and Sheldon Knight Seasonal methods. Additionally, learn to build EasyLanguage projects, integrate fundamental data like Commitment of Traders, and create multi-timeframe indicators for comprehensive analysis.
Get Day Trading Edition Today!
The Day Trading Edition complements the other books in the series, diving into the popular approach of day trading, where overnight risk is avoided (though daytime risk still applies!). Programming on high-resolution data, such as five- or one-minute bars, can be challenging, and this book provides guidance without claiming to be a “Holy Grail.” It’s not for ultra-high-frequency trading but rather for those interested in techniques like volatility-based breakouts, pyramiding, scaling out, and zone-based trading. Ideal for readers of the Foundation and Hi-Res editions or those with EasyLanguage experience, this book offers insights into algorithms that shaped the day trading industry.
Trend Following Cover.
For thirty-one years as the Director of Research at Futures Truth Magazine, I had the privilege of collaborating with renowned experts in technical analysis, including Fitschen, Stuckey, Ruggiero, Fox, and Waite. I gained invaluable insights as I watched their trend-following methods reach impressive peaks, face sharp declines, and ultimately rebound. From late 2014 to early 2020, I witnessed a dramatic downturn across the trend-following industry. Iconic systems like Aberration, CatScan, Andromeda, and Super Turtle—once thriving on robust trends of the 1990s through early 2010s—began to falter long before the pandemic. Since 2020 we have seen the familiar trends return. Get six hours of video instruction with this edition.
Pick up your copies today – e-Book or paperback format – at Amazon.com