A user-supplied objective function can be used for calculating individual optimization targets or for analyzing the preceding training run. It is called at the end of every optimization step and supposed to return a performance metric based on the statistics or trade results of that step. If required, it can also be used to store results in a file or dataset for further evaluation, such as plotting a 3D parameter chart. A default objective function is located in the default.c file. It uses the Pessimistic Return Ratio (PRR) for the optimization target, requires at least 5 trades, and treats the highest win and worst loss as outliers:
// optimizing objective based on PRR var objective() { if(NumWinTotal < 2 || NumWinTotal+NumLossTotal < 5) return 0.; // needs at least 5 trades var wFac = 1./sqrt(1.+NumWinTotal); var lFac = 1./sqrt(1.+NumLossTotal); var Win = WinTotal, Loss = LossTotal; // remove possible outlier wins/losses if(NumLossTotal > 1) { Loss -= (NumLossTotal-1)*LossMaxTotal/NumLossTotal; Win -= (NumWinTotal-1)*WinMaxTotal/NumWinTotal; } // return PRR return (1.-wFac)/(1.+lFac)*(1.+Win)/(1.+Loss); }
For some strategies, for instance binary options, the above PRR is not the best optimization target. For alternative targets, use an individual objective function in your script. It will override the default objective. Examples:
// alternative objective function based on Calmar ratio var objective() { return(WinTotal-LossTotal)/max(1,DrawDownMax); } // alternative objective function based on Sharpe ratio var objective() { if(!NumWinTotal && !NumLossTotal) return 0.; return ReturnMean/ReturnStdDev; } // alternative objective function for binary options var objective() { return ((var)(NumWinLong+NumWinShort))/max(1,NumLossLong+NumLossShort); }
Alternatively to Zorro's own Ascent, Genetic, or Brute Force algorithms, parameters can be optimized with an external optimization algorithm in an external DLL, R package, or Python library, or with a script-based algorithm (Zorro S 2.32 or above). For this a parameters function must be supplied and the ASCENT, BRUTE, GENETIC flags must be off. The parameters function is automatically called after the initial run (INITRUN) of any optimization step. Its purpose is setting up the Value elements of the Parameters list for the subsequent training. The Values can be either directly calculated or retrieved with a function from an external library. The parameters function is supposed to return 0 when the last step is reached and the optimization is finished after that step.
Below an example of a parameters function for a script-based brute force optimization. The condition StepCycle == 1 intializes the parameters, afterwards they are counted up until all have reached their maximum.
int parameters() { // set up parameters for brute force optimization int i; for(i=0; i<NumParameters; i++) { if(StepCycle == 1 || Parameters[i].Value >= Parameters[i].Max) Parameters[i].Value = Parameters[i].Min; else { // count up Parameters[i].Value += Parameters[i].Step; break; } } // last step reached? for(i=0; i<NumParameters; i++) if(Parameters[i].Value < Parameters[i].Max) return 1; // not yet return 0; // yes, optimization complete }
The objective function now got the job not only to return the performance metric, but also to store the best result in the BestResult variable and its parameter values in the Best elements of the Parameters list. Alternatively, the best parameter values can be retrieved from an external library in the objective call after the last optimization step.
var objective() { // calculate performance metric from profit factor var Result = ifelse(LossTotal > 0,WinTotal/LossTotal,10); // store best result and best parameters int i; if(BestResult < Result) { BestResult = Result; for(i=0; i<NumParameters; i++) Parameters[i].Best = Parameters[i].Value; } return Result; }
// assumed library functions: // set_experiment(Parameters) // initialize the module with a list of parameters // get_suggested(Parameters) // return suggested parameter assignment // set_observation(Result) // deliver the result from the suggested parameters // get_optimized(Parameters) // return the best parameter set int MaxSteps = 1000; // optimization budget int parameters() { // initialize librariy and send parameter boundaries if(StepCycle == 1) set_experiment(Parameters); // get parameter assignment for the current step get_suggested(Parameters); // optimization complete? if(StepCycle == MaxSteps) return 0; // yes else return 1; } var objective() { // calculate performance metric from profit factor var Result = ifelse(LossTotal > 0,WinTotal/LossTotal,10); BestResult = max(Result,BestResult); // send result to library set_observation(Result); // after the last step, get best parameter set if(StepCycle == MaxSteps) get_optimized(Parameters); return Result; }