Happy New Year! My First Post of 2021!
In this post I simply wanted to convert the intraday ratcheting stop mechanism that I previously posted into a daily bar mechanism. Well that got me thinking of how many different values could be used as the amount to ratchet. I came up with three:
- ATR of N days
- Fixed $ Amount
- Percentage of Standard Deviation of 20 Days
So this was going to be a great start to a post, because I was going to incorporate one of my favorite programming constructs : Switch-Case. After doing the program I thought wouldn’t it be really cool to be able to optimize over each scheme the ratchet and trail multiplier as well as the values that might go into each scheme.
In scheme one I wanted to optimize the N days for the ATR calculation. In scheme two I wanted to optimize the $ amount and the scheme three the percentage of a 20 day standard deviation. I could do a stepwise optimization and run three independent optimizations – one for each scheme. Why not just do one global optimization you might ask? You could but it would be a waste of computer time and then you would have to sift through the results. Huh? Why? Here is a typical optimization loop:
|Scheme||Ratchet Mult||Trigger Mult||Parameter 1|
|1 : ATR||1||1||ATR (2)|
|2 : $ Amt||1||1||ATR (2)|
|3 : % of Dev. Amt||1||1||ATR (2)|
|1 : ATR||2||1||ATR (2)|
|2 : $ Amt||2||1||ATR (2)|
Notice when we switch schemes the Parameter 1 doesn’t make sense. When we switch to $ Amt we want to use a $ Value as Parameter 1 and not ATR. So we could do a bunch of optimizations across non sensical values, but that wouldn’t really make a lot of sense. Why not do a conditional optimization? In other words, optimize only across a certain parameter range based on which scheme is currently being used. I knew there wasn’t an overlay available to use using standard EasyLanguage but I thought maybe OOP, and there is an optimization API that is quite powerful. The only problem is that it was very complicated and I don’t know if I could get it to work exactly the way I wanted.
EasyLanguage is almost a full blown programming language. So should I not be able to distill this conditional optimization down to something that I could do with such a powerful programming language? And the answer is yes and its not that complicated. Well at least for me it wasn’t but for beginners probably. But to become a successful programmer you have to step outside your comfort zones, so I am going to not only explain the Switch/Case construct (I have done this in earlier posts) but incorporate some array stuff.
When performing conditional optimization there are really just a few things you have to predefine:
- Scheme Based Optimization Parameters
- Exact Same Number of Iterations for each Scheme [starting point and increment value]
- Complete Search Space
- Total Number of Iterations
- Staying inside the bounds of your Search Space
Here are the optimization range per scheme:
- Scheme #1 – optimize number of days in ATR calculation – starting at 10 days and incrementing by 2 days
- Scheme #2 – optimize $ amounts – starting at $250 and incrementing by $100
- Scheme #3 – optimize percent of 20 Bar standard deviation – starting at 0,25 and incrementing by 0.25
I also wanted to optimize the ratchet and target multiplier. Here is the base code for the daily bar ratcheting system with three different schemes. Entries are based on penetration of 20 bar highest/lowest close.
This code is fairly simple. The intriguing inputs are:
- volBase [True of False] and volCalcLen [numeric Value]
- dollarBase [True of False] and dollarAmt [numeric Value]
- devBase [True of False] and devAmt [numeric Value]
If volBase is true then you use the parameters that go along with that scheme. The same goes for the other schemes. So when you run this you would turn one scheme on at a time and set the parameters accordingly. if I wanted to use dollarBase(True) then I would set the dollarAmt to a $ value. The ratcheting mechanism is the same as it was in the prior post so I refer you back to that one for further explanation.
So this was a pretty straightforward strategy. Let us plan out our optimization search space based on the different ranges for each scheme. Since each scheme uses a different calculation we can’t simply optimize across all of the different ranges – one is days, and the other two are dollars and percentages.
We know how to make TradeStation loop based on the range of a value. If you want to optimize from $250 to $1000 in steps of $250, you know this involves [$1000 – $250] / $250 + 1 or 3 + 1 or 4 interations. Four loops will cover this entire search space. Let’s examine the search space for each scheme:
- ATR Scheme: start at 10 bars and end at 40 by steps of 2 or [40-10]/2 + 1 = 16
- $ Amount Scheme: start at $250 and since we have to have 16 iterations [remember # of iterations have to be the same for each scheme] what can we do to use this information? Well if we start $250 and step by $100 we cover the search space $250, $350, $450, $550…$1,750. $250 + 15 x 250. 15 because $250 is iteration 1.
- Percentage StdDev Scheme: start at 0.25 and end at 0.25 + 15 x 0.25 = 4
So we enumerate 16 iterations to a different value. The easiest way to do this is to create a map. I know this seems to be getting hairy but it really isn’t. The map will be defined as an array with 16 elements. The array will be filled with the search space based on which scheme is currently being tested. Take a look at this code where I show how to define an array of 16 elements and introduce my Switch/Case construct.
This code creates a 16 element array, optVals, and assigns 0 to each element. SwitchMode goes from 1 to 3.
- if switchMode is 1: ATR scheme [case: 1] the startPoint is set to 10 and increment is set to 2
- if switchMode is 2: $ Amt scheme [case: 2] the startPoint is set to $250 and increment is set to $100
- if switchMode is 3: Percentage of StdDev [case: 3] the startPoint is set to 0.25 and the increment is set to 0.25
Once these two values are set the following 15 values can be spawned by the these two. A for loop is great for populating our search space. Notice I wrap this code with ONCE – remember ONCE is only executed at the very beginning of each iteration or run.
for cnt = 1 to 16
optVals[cnt] = startPoint + (cnt-1) * increment;
Based on startPoint and increment the entire search space is filled out. Now all you have to do is extract this information stored in the array based on the iteration number.
ratchetAmt = avgTrueRange(optVals[optLoops])ratchetMult;
trailAmt = avgTrueRange(optVals[optLoops]) trailMult;
ratchetAmt =optVals[optLoops] * ratchetMult;
trailAmt = optVals[optLoops] * trailMult;
ratchetAmt =stddev(c,20)optVals[optLoops] ratchetMult;
trailAmt = stddev(c,20) * optVals[optLoops] * trailMult;
Notice how the optVals are indexed by optLoops. So the only variable that is optimized is the optLoops and it spans 1 through 16. This is the power of enumerations – each number represents a different thing and this is how you can control which variables are optimized in terms of another optimized variable. Here is my optimization specifications:
And here are the results:
The best combination was scheme 1 [N-day ATR Calculation] using a 2 Mult Ratchet and 1 Mult Trail Trigger. The best N-day was optVals for this scheme. What in the world is this value? Well you will need to back engineer a little bit here. The starting point for this scheme was 10 and the increment was 2 so if optVals =10 then optVals = 12 or ATR(12). You can also print out a map of the search spaces.
This was a elaborate post so please email me with questions. I wanted to demonstrate that we can accomplish very sophisticated things with just the pure and raw EasyLanguage which is a programming language itself.