Daily Bar Ratcheting Stop and Conditional Optimization

Happy New Year!  My First Post of 2021!

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

Ratcheting Schemes

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

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

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

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

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

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

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

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

Here are the optimization range per scheme:

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

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

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


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



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


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

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


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


// Okay initially you want a X point stop and then pull the stop up
// or down once price exceeds a multiple of Y points
// longMult keeps track of the number of Y point multiples of profit
// always key off of lep(LONG ENTRY POINT)
// notice how I used + 1 to determine profit
// and -  1 to determine stop level

If mp = 1 then 
Begin
	If h >= lep + (longMult + 1) * ratchetAmt then	longMult = longMult + 1;
	Sell("LongTrail") next bar at (lep + (longMult - 1) *  trailAmt) stop;
end;

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

This code is fairly simple.  The intriguing inputs are:

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

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

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

Enumerate

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

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

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

array: optVals[16](0);

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

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

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

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

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

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

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

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


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

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


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


// Okay initially you want a X point stop and then pull the stop up
// or down once price exceeds a multiple of Y points
// longMult keeps track of the number of Y point multiples of profit
// always key off of lep(LONG ENTRY POINT)
// notice how I used + 1 to determine profit
// and -  1 to determine stop level

If mp = 1 then 
Begin
	If h >= lep + (longMult + 1) * ratchetAmt then longMult = longMult + 1;
	Sell("LongTrail") next bar at (lep + (longMult - 1) *  trailAmt) stop;
end;

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

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

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

Opimization space

And here are the results:

Optimization Results

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

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

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

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

Please follow and like us: