Thanks! Of course, the price of a script depends on its quality ;).
Back to the script. The strategy is now divided into 3 different functions: tradeTrend() for trend trading, tradeCounterTrend() for counter trend trading, and the run() function that sets up the parameters, selects the assets, and calls the two trade functions.
The tradeTrend() function uses the same trade algorithm as at in the first script of this course, only this time the LowPass time period and the stop loss value are optimized. Note that the time period parameter has a different value here, as one bar is now 240 minutes, compared to 60 minutes in the first version of the strategy. So the parameter must be smaller by a factor 4 for getting the same time period.
The tradeCounterTrend() function and the begin of the run() function are similar to the final version of the counter trend trading script. However, the core of the strategy looks very different:
while(asset(loop("EUR/USD","USD/CHF")))
while(algo(loop("TRND","CNTR")))
{
if(Algo == "TRND")
tradeTrend();
else if(Algo == "CNTR")
tradeCounterTrend();
}
We have two ‘nested’ while loops, each with two nested functions. Let’s untangle them from the inside out:
loop(“EUR/USD”,“USD/CHF”)
The loop function takes a number of arguments, and returns one of them every time it is called. On the first call it returns the first parameter, which is the string “EUR/USD”. We have learned in the first programming lesson that a string is a variable that contains text instead of numbers. Strings can be passed to functions and returned from functions just as numerical variables. If the loop function in the above line is called the next time, it returns “USD/CHF”. And on all futher calls it returns 0, as there are no further arguments.
asset(loop(“EUR/USD”,“USD/CHF”))
The strings returned by the loop function are now used as arguments to the asset function. This function selects the asset to trade with in the following part of the code - just as it it had been choosen with the [Asset] scrollbox. The string passed to asset must correspond to an asset subscribed with the broker. If asset is called with 0 instead of a string, it does nothing and returns 0. Otherwise it returns a nonzero value. This can be taken advantage of in a while loop:
while(asset(loop(“EUR/USD”,“USD/CHF”)))
This loop is repeated as long as the while argument, which is the return value of the asset function, is nonzero (a nonzero comparison result is equivalent to ‘true’). And asset returns nonzero when the loop function returns nonzero. Thus, the while loop is repeated exactly two times, once with “EUR/USD” and once with “USD/CHF” as the selected asset. If Alice had wanted to trade the same strategy with more assets - for instance, also with commodities or stock indices - she only needed to add more arguments to the loop function, like this:
while(asset(loop(“EUR/USD”,“USD/CHF”,“USD/JPY”,“GBP/USD”,“XAU/USD”,“USOil”,“SPX500”,“NAS100”,…)))
and the rest of the code would remain the same. However, Alice does not only want to trade with several assets, she also wants to trade different algorithms. For this, nested inside the first while loop is another while loop:
while(algo(loop(“TRND”,“CNTR”)))
The algo function basically copies the argument string into the predefined Algo string variable that is used for identifying a trade algorithm in the performance statistics. It also returns 0 when it gets 0, so it can be used to control the second while loop, just as the asset function in the first while loop. Thus, for every repetition of the first loop, the second loop repeats 2 times, first with “TRND” and then with “CNTR”. As these strings are now stored in the Algo variable, Alice uses them for calling the trade function in the inner code of the double loop:
if(Algo == "TRND")
tradeTrend();
else if(Algo == "CNTR")
tradeCounterTrend();
As we see, a string variable can be compared to a string constant just as if we would compare a numeric variable to a numeric constant. Due to the two nested loops, this inner code is now run four times per run() call:
-
First with “EUR/USD” and “TRND”
-
Then with “EUR/USD” and “CNTR”
-
Then with “USD/CHF” and “TRND”
-
And finally with “USD/CHF” and “CNTR”
If Alice had instead entered 10 assets and 5 algorithm strings in the loop arguments, the inner code would be run fifty (10*5) times every bar.
Now it’s time to [Train] the strategy. Because we have now four components - asset/algorithm combinations - the training process now takes four times longer than with the last script. As soon as it’s finished, let’s examine the resulting parameters. Use the SED editor to open the Workshop6_1_1.par file in the Data folder. This file contains the optimized parameters from the first WFO cycle. It should look like this:
EUR/USD:TRND 420 1.62 => 1.896
EUR/USD:CNTR 0.998 0.976 6.15 => 2.605
USD/CHF:TRND 673 1.34 => 1.343
USD/CHF:CNTR 1.20 0.812 2.38 => 1.980
As we can see, every line begins with the identifier for the asset and algorithm; then follows the list of parameters. This allows Zorro to load the correct parameters at every asset() and algo() call. We can see that the tradeTrend() function uses 2 parameters and the tradeCounterTrend() function uses 3 parameters, and their optimal values are slightly different for the “EUR/USD” and the “USD/CHF” assets. The last number behind the “=>” is not a parameter, but the result of the objective function for the final optimization; it can be ignored.
Finally, let’s have a look at the performance of this portfolio strategy. Click [Test], then click [Result].
You can see in the chart that now two different strategies are traded simulaneously. The long green lines are from trend trading where positions are hold a relatively long time, the shorter lines are from counter-trend trading. The combined strategy does both at the same time.
Now look at the end of the performance sheet that popped up when clicking [Result]. You can see how the single components were performing during the test:
Trade details OptF ProF Win/Loss Cycles
EUR/USD total .048 2.42 37/57 /XXXXXX
USD/CHF total .059 1.55 27/62 XXX\XX/
CNTR total .052 2.14 48/51 XXXXX/X
TRND total .055 1.57 16/68 XXXXXXX
EUR/USD:CNTR:L .025 1.54 7/9 ///\\/\
EUR/USD:CNTR:S .086 6.34 20/6 //././/
EUR/USD:TRND:L .034 1.43 7/18 ..\//\\
EUR/USD:TRND:S .000 0.80 3/24 .\\\\\/
USD/CHF:CNTR:L .000 0.97 12/21 /\\\\//
USD/CHF:CNTR:S .044 1.88 9/15 \\/\//.
USD/CHF:TRND:L .088 2.20 3/13 //\\.\/
USD/CHF:TRND:S .044 2.58 3/13 \\/\./.
The performance is listed separately for long (":L") and short (":S") trades. We can see that the components performed very different: The best performer is EUR/USD:CNTR with a profit factor (ProF) 1.54 for long, and 6.34 for short trades - that’s the algorithm from the last workshop. The worst performer is EUR/USD trend trading; however with the USD/CHF asset, trend trading performs much better.
Wouldn’t it be wise to invest more money in the more profitable components, less money in the less profitable, and no money at all in the components that turned out unprofitable in the test? That’s called “money management” and will be the topic of the next lesson.