Optimization Using Multiple Indicators

I have a strategy that uses several technical indicators. I want to run an Optimization that trys all possible combinations of my 3 indicators. I searched but cannot find a non-recursive algorithm to produce the permutations for every possible combination of indicators:

More Info:

Given Ind1, Ind2, ind3, what are all possible combinations for every length (1 element, 2 elements, 3 elements).

desired result:
ind1
ind1, Ind2
Ind1, Ind3
ind1, ind2, ind3
ind2
Ind2, ind3
Ind3

@SwingTradeMonkey I probably do not understand exactly what you want to do, but what you listed as a desired result is not a set of “permutations” but simply the possible “combinations”, where the order is of the elements irrelevant; I mean that for you probably [ind3, ind2] is functionallye equivalent to [ind2, ind3] since you’ll use both the indicators.

In such a case you could use a loop using a “binary” number bitmask from 1 to (2 ^ Lenght(elements)) -1 and use the “1” values of that binary mask to establish what indicators to use in each step.

@SwingTradeMonkey, I’m not sure if this is what you mean. I’m using switch & Case statements as below to test few combinations of indicators:

Buy_CondIndex = Optimize("BuyCondIndex", 2,1,11,1);

Buyx = True;
switch (Buy_CondIndex)
{
	Case 1:  Buyx = PriceLimit AND VolLimit_H AND StochOversold_H AND ADXHighTrend_H AND StochOversoldCross_H; break;
	Case 2:  Buyx = PriceLimit AND VolLimit_H AND VolLimit_D AND StochOversold_H AND StochOversold_D AND ADXHighTrend_H AND ADXHighTrend_D AND StochOversoldCross_H AND StochOversoldCross_D ; break;
	Case 3:  Buyx = PriceLimit AND VolLimit_D AND VolLimit_W AND StochOversold_D AND StochOversold_W AND ADXHighTrend_D AND ADXHighTrend_W AND StochOversoldCross_D AND StochOversoldCross_W ; break;
	Case 4:  Buyx = PriceLimit AND VolLimit_D AND StochOversold_D AND ADXHighTrend_D AND StochOversoldCross_D; break;
	Case 5:  Buyx = StochD() < 20.00 AND ADX() > 30 AND RSI() > 50 AND Cross(StochK(),StochD()); break;
	Case 6:  Buyx = Cross(C,MA(C,200)) AND C > 0.50 AND Close < Ref(LLV(Low,260),-1); break;
	Case 7:  Buyx = Cross(C,MA(C,200)) AND Close < Ref(LLV(Low,260),-1); break;
	Case 8:  Buyx = Close < Ref(LLV(Low,20),-1) AND ADX() > 30 AND Cross(StochK(),StochD()); break;
	Case 9:  Buyx = ADX() > 30 AND Cross(StochK(),StochD()); break;
	Case 10:  Buyx = pricelimit AND StochOversoldCross_D AND StochOversoldCross_W; break;
	Case 11:  Buyx = pricelimit AND StochOversoldCross_D; break;
}

Buy = Buyx;

@bursaware, while hard encoding is clearly one possible way to do it (and in many cases the fastest way), it becomes more and more complex if you want to test various combinations.

What I was suggesting is to use a bit of logic to cover all the possible combinations (as I think was requested in the OP).

If applicable to the original request, how to translate this to an optimization process and how long it will take to complete with actual code I leave to @SwingTradeMonkey to figure out!

Here is a skeleton - nonrecursive - code to process all the combinations (no permutations) of a certain numbers of choices (in my example the letters of the alphabet correspond to different indicators using a switch case).

To understand what this code does follow the _TRACE output with a log viewer (I execute it directly from the editor window hitting the start debugging button)

function binary( number, reversed)
{
    bitstring = "";
    while( number > 0 )
    {
        bit = number % 2;
        quotient = int(number / 2) ;
        if (reversed) {
			bitString = bitString + NumToStr( bit, 1.0 );
        }
        else 
        {
        	bitString = NumToStr( bit, 1.0 ) + bitString;
        }
        number = quotient;
    }
    return bitString;
}


procedure applyCombination(combination) {
	length = StrLen(combination);
	for (i = 0; i < length; i++) {
		key = StrMid(combination, i, 1);
		switch (key) {
			case "a":
				_TRACE("Ind 1");
				break;
			case "b":
				_TRACE("Ind 2");
				break;
			case "c":
				_TRACE("Ind 3");
				break;
			case "d":
			    _TRACE("Ind 4");
			    break;
			case "e":
				_TRACE("Ind 5");
				break;
			case "f":
				_TRACE("Ind 6");
				break;
			case "g":
				_TRACE("Ind 7");
				break;
			case "h":
			    _TRACE("Ind 8");
			    break;
		}						
	}
}	


procedure processCombinations(str) {
	length = StrLen(str);
    total =  (2 ^ length) - 1; // Corresponding in Binary to an only "1"xLength string 
    _TRACE("Total combinations = " + NumToStr(total, 1.0));
    for (i = 1; i <= total; i++) { // 0 skipped since it is an empty combination
        binaryMask = binary(i, True); // we need a reversed binary mask
        combination = "";
		_TRACEF("i = %1.0f", i);        
        _TRACE("Mask = " + binaryMask);
        for (j = 0; j < strLen(binaryMask); j++) {
            if (StrMid(binaryMask, j, 1) == "1") {
	            combination = combination + StrMid(str, j, 1);
            }
        }
        _TRACE("----- Combination " + NumToStr(i, 1.0) + " --- [" + combination + "]");
        applyCombination(combination);
        _TRACE("----------------------------------");
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Print the result to the TRACE view 
_TRACE("!CLEAR!"); 
// Suppose we want to use 8 indicators corresponding
// to the a/b/c/d/e/f/g/h cases in applyCombination()
// but want to test all the combinations of only some
// of them 
_TRACE("---Testing [ abc ] ------------------------------");
processCombinations("abc"); // Ind1/Ind2/Ind3
_TRACE("\n\n");

_TRACE("---Testing [ gefd ] -----------------------------");
processCombinations("gefd"); // Ind8/Ind6/Ind4/Ind2  						
_TRACE("\n\n");

_TRACE("---Testing [ acdhe ] ----------------------------");
processCombinations("acdhe"); // Ind1/Ind3/Ind4/Ind8/Ind6  						
_TRACE("\n\n");

4 Likes

@SwingTradeMonkey: why are you not just using a standard optimization? There are multiple ways that you could disable an indicator based on it current value in the optimization run. Here’s a simple example using RSI:

maxEntryRSI = Optimize("Entry RSI Threshold", 20, 0, 30, 10);
...
Buy = IIf((maxEntryRSI > 0,  RSI(2) < maxEntryRSI , True) AND
      <other entry logic here>;

In this example you will test without any RSI threshold and also with the values 10, 20, and 30.

Use this same pattern with all three of your indicators, and the AB Optimization will do the work of finding all combinations.

@beppe Thank you! You are correct in that I am seeking “Combinations” (not permutations). The approach and skeleton code provided should enable me to control the order for each “test” performed, which is exactly what I am seeking to accomplish.

@mradtke, Given my desired Result initially presented above, I am unsure how the Optimization approach can be used to “flip” the combinations on in a sequential manner.

@SwingTradeMonkey OK, here’s another example for you. In this case, we will just enable/disable each of your indicators:

useInd1 = Optimize("Use Indicator 1", 1, 0, 1, 1); 
useInd2 = Optimize("Use Indicator 2", 1, 0, 1, 1);
useInd3 = Optimize("Use Indicator 3", 1, 0, 1, 1);
...
Buy = IIf(useInd1, <indicator 1 logic>, True) AND
      IIf(useInd2, <indicator 2 logic>, True) AND
      IIf(useInd3, <indicator 3 logic>, True);
3 Likes

@mradtke I will try this.Thank you!

I have experimented with @mradtke approach. It seems to work in my sandbox. The only caveate is that the order of execution is unpredictable due to the way Optimize apparently executes. Here is my sandbox and result for anyone that might be interested.

//Sandbox - Optimize by using Every Possible Combination of 3 technical indicators.
//Setup: Use this code in a New Analysis. Start DbgView.exe (filter on "AFL"). Click Optimize button in Analysis window.
#pragma maxthreads 1  


useInd1 = Optimize("Use Indicator 1", 1, 0, 1, 1); 
useInd2 = Optimize("Use Indicator 2", 1, 0, 1, 1);
useInd3 = Optimize("Use Indicator 3", 1, 0, 1, 1);

//Housekeeping
IndCount = (3*2*1)+2; //n factorial + 1 + 1 extra for Amibroker

if ( Status("stocknum") == 0 AND StaticVarGet("CodePass") == Null){StaticVarSet("CodePass",1);}
else{StaticVarSet("CodePass",StaticVarGet("CodePass")+1); }

if (StaticVarGet("CodePass") > IndCount){StaticVarSet("CodePass",1);}
 

//Test
_TRACE("AFL - Executing Code Pass:"+ StaticVarGet("CodePass"));
Buy = IIf(Close > 0 AND useInd1,True, false);  
  	  if (UseInd1) {_TRACE("AFL - Ind_1");}
 
Buy = Buy + IIf(Close > 0 AND useInd2,True, False);
	  if (UseInd2) {_TRACE("AFL - Ind_2");}

Buy = Buy + IIf(Close > 0 AND useInd3, True, False);
	  if (UseInd3) {_TRACE("AFL - Ind_3");}

Sell = False; 
 

sandboxresult

Instead of doing this

useInd1 = Optimize("Use Indicator 1", 1, 0, 1, 1); 
useInd2 = Optimize("Use Indicator 2", 1, 0, 1, 1);
useInd3 = Optimize("Use Indicator 3", 1, 0, 1, 1);

Buy = IIf( useInd1, Cross( C, MA( C, 50 ) ), True ) AND
      IIf( useInd2, Cross( 30, RSI() ), True ) AND
      IIf( useInd3, Cross( MACD(), Signal() ), True );
	
Sell = 0; 
 
Short = Cover = 0;

(I mean, what if you have 100 indicator conditions, would you seriously write 200 lines of code (100 Optimize() lines and 100 IIf() lines??)

So since we don’t like over-complication and code inflation (do we?)…
It is more appropriate to use looping for repetitive actions.
Then you only have to write your indicator logic and rest is taken care of by those loop iterations. And you only have just one Optimize variable.

/// @link http://forum.amibroker.com/t/optimization-using-multiple-indicators/3690/10
/// code response suggestion by fxshrat
condnum = 3;// number of conditions to be combined.

comb = Optimize("CombinationNum", 1, 1, 2^condnum, 1);

Buy1 = Cross( C, MA( C, 50 ) );
Buy2 = Cross( 30, RSI() );
Buy3 = Cross( MACD(), Signal() );

BuySum = 0;
for (n = 0; n < condnum; n++)
	BuySum += IIf((comb & 2^n), VarGet("Buy" + (n+1)), 1); 

Buy = BuySum == condnum;

Sell = 0; 

Short = Cover = 0;

And best of all analysis results are the same ones.

11 Likes

@fxshrat This is a good improvement and another innovative way to address my desire to back test using all the varioius possible combinations of technical indicators that I am using (for this strategy, I am using Aroon, RSI, Chaikin, OBV among others) to look for confirming signals. I probably don’t need agreement or confirmation across so many trend indicators or oscillators. I’m looking forward to trying this approach!

Here is the @fxshrat formula with a couple of trace statements so we can visualize the result.

This solution is elegant and requires minimal code. AB's Optimize also shows the results, for every combination, in the analysis window. Looking at the dbgview window, we can see from the Trace statements the individual Buy signals (indicators) evaluated for each combination (wish this could be shown in the Analysis result columns!).

/// @link http://forum.amibroker.com/t/optimization-using-multiple-indicators/3690/10
/// code response suggestion by fxshrat
condnum = 3;// number of conditions to be combined.

comb = Optimize("CombinationNum", 1, 1, 2^condnum, 1); //Optimize(Descr, Default, Min, Max, Step)

Buy1 = Cross( C, MA( C, 50 ) );
Buy2 = Cross( 30, RSI() );
Buy3 = Cross( MACD(), Signal() );

BuySum = 0;

_TRACE("AFL Execution Combination = " + comb);

for (n = 0; n < condnum; n++)
	{
	BuySum += IIf((comb & 2^n), VarGet("Buy" + (n+1)), 1); 
	
    //Proof that the combination is being used
    if (IIf((comb & 2^n), true, false))
       _TRACE("AFL using " +  "Buy_"+ (n+1));}
    
    
Buy = BuySum == condnum;

Sell = 0; 
Short = Cover = 0;

Combo-Capture

@SwingTradeMonkey: The solution that I gave you is less compact than the solution provided by @fxshrat, but the Analysis output would show you which indicators were being used. As with many coding decisions, it comes down to tradeoffs.

1 Like

@mradtke You are correct. Columns for each optimization’s description are be added to the Analysis window.

@fxshrat solution adds one column called “CombinationNum” to the Analysis window.
Anyone know of a way to extract this column’s value so I can use it in a custom metric?

The BO.GetPerformanceStats(0) does not contain this column. I’m unsure if there is another object that would!

Optimize( “description”, default, min , max, step )
…“description” is a string that is used to identify the optimization variable and is displayed as a column name in the optimization result list.

The value of CombinationNum is already assigned to a variable named comb. You can simply reference that variable in your CBT if you want to use it to generate other custom metrics.

Actually, during backtest, i will use Comb to save which indicators I have used. But, I need to retrieve the Comb value from the Optimize Result List in order to add my custom metric.
So in my backtest/Optimize I add:

 _TRACE("AFL using " +  "Buy_"+ (n+1));
VarSetText("Signal_"+comb,"Buy_1"+ (n+1));

And in my custom metrics:

stats = bo.GetPerformanceStats(0); 
Comb = stats.GetValue("CombinationNum"); //this line of code DOES NOT WORK.
bo.AddCustomMetric( "Signals In Use", VarGetText("Signal_"+Comb));

Combo-Capture

Perhaps @mradtke or @fxshrat @Tomasz or can suggest a way to retrieve the values for this column.

Just remove the line that includes the comment “this line of code DOES NOT WORK”.

1 Like

Can you please make this a complete system with Buy / Sell entry and exits(using Applystop)?

@santy The complete system (sandbox - not a real trading system) with Buy / Sell is located in another thread:
How To - Use Optimize Value In Custom Metrics.

Incidentally, if you are looking for a variety of systems, there is a nice set of formulas and systems
in the Member Zone - AFL Library