A user-supplied objective function - also known as 'fitness function' - can be used for calculating the optimization target by 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 10 trades, and treats the highest win and worst loss as outliers:
// optimizing objective based on PRR
var objective()
{
if(NumWinTotal < 2 || NumWinTotal+NumLossTotal < MinTrades)
return 0.; // needs at least 10 trades, 2 of them positive
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, put 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)/fix0(DrawDownMax);
}
// alternative objective function based on Sharpe ratio
var objective()
{
if(!NumWinTotal && !NumLossTotal) return 0.;
return ReturnMean/fix0(ReturnStdDev);
}
// alternative objective function that just hunts for max profit
var objective()
{
return WinTotal-LossTotal;
}
// 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;
}