Optimizing sortino ratio in OnTester() using genetic algorithms?

 
This is the code I have so far but for the sharpe ratio.


int OnInit () {
   if (IsTesting () || IsOptimization ()) GlobalVariableSet ("InitialBalance", AccountBalance ());

   return (0);
}

void OnDeinit (const int reason) {
   if (IsTesting () || IsOptimization ()) GlobalVariableDel ("InitialBalance");
}


double SharpeRatio(double Balance){   
   int i;
   int CalcMonth = 0;
   int TradeMonths = 0;
   double MonthlyProfit[];

   for(i = 0; i < OrdersHistoryTotal(); i++){
      if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) == false) break;

      int CloseMonth = TimeMonth(OrderCloseTime());
      if(CalcMonth != CloseMonth){
         CalcMonth = CloseMonth;
         TradeMonths++;
         ArrayResize(MonthlyProfit, TradeMonths);
      }
      MonthlyProfit[TradeMonths - 1] += OrderProfit();
   }

   double MonthlyEarningRate[];   
   ArrayResize(MonthlyEarningRate, ArraySize(MonthlyProfit));
   double SumMER = 0;

   for(i = 0; i < ArraySize(MonthlyProfit); i++){
      MonthlyEarningRate[i] = MonthlyProfit[i] / Balance;
      SumMER += MonthlyEarningRate[i];
      Balance += MonthlyProfit[i];
   }

   double MER_Average = SumMER / TradeMonths;
   double MER_SD = iStdDevOnArray(MonthlyEarningRate, 0, TradeMonths, 0, 0, 0);
   double SR = 1;
   if(MER_SD != 0) SR = MER_Average / MER_SD; //

   return SR;
}

double OnTester()
{
  double SR = SharpeRatio (GlobalVariableGet ("InitialBalance"));
   Print ("SharpeRatio =" + (string) SR);

   return SR;
}

Can somebody who knows mql4 better than me help convert this function SharpeRatio() to work as a Sortino ratio instead? Thank you for your time
 
Alain Verleyen:

how do I move???
 
QuantCoder:
how do I move???
You can't a moderator will do it.
 
Looks like nobody has an idea?


if(MER_SD != 0) SR = MER_Average / MER_SD; //

This is the line that needs to be modified but I'm stumped
 
QuantCoder:
Looks like nobody has an idea?

This is the line that needs to be modified but I'm stumped

It's this line:

for(i = 0; i < ArraySize(MonthlyProfit); i++){
   // skip positive results
   if (MonthlyProfit[i] > 0) MonthlyEarningRate[i] = 0;
   else                      MonthlyEarningRate[i] = MonthlyProfit[i] / Balance;

   SumMER += MonthlyEarningRate[i];
   Balance += MonthlyProfit[i];
}
 
alphatrading:

It's this line:

@alphatrading , doing this, Sortino ratio always returns 1

 
QuantCoder:

...doing this, Sortino ratio always returns 1

The 1 is caused by this part:

double MER_SD = iStdDevOnArray(MonthlyEarningRate, 0, TradeMonths, 0, 0, 0);    // returns 0
double SR = 1;
if(MER_SD != 0) SR = MER_Average / MER_SD;                                      // this line never gets executed

iStdDevOnArray() can be considered undocumented. In the last 10 years I never found a working example here or anywhere at the web. So until some person smarter then me comes up with the right syntax I calculate the std-dev manually if I need it:

// calculate deviation manually (for some reason iStdDevOnArray() fails)
//dev = iStdDevOnArray(buffer, WHOLE_ARRAY, periods, 0, MODE_SMA, bar) * stdDevMultiplier;
double sum = 0;

for (int i=0; i < periods; i++) {
    double value    = series[i];
    double ma       = iMAOnArray(series, WHOLE_ARRAY, periods, 0, MODE_SMA...
    double distance = value - ma;
    sum += distance * distance;
}
double stdDev = MathSqrt(sum/periods);
double myDev  = stdDev * stdDevMultiplier;

Essentially this is the manual way to calculate std-dev. You have to adapt the iMAOnArray() part to your profit series.

 
@alphatrading


Could you provide a working example with the sortino ratio? Would really appreciate it thanks
 
QuantCoder: @alphatrading  Could you provide a working example with the sortino ratio? Would really appreciate it thanks

There are a few things to consider:

You can't calculate statistics for a test the way a fund manager would do it for annual reporting. Summing up results by month will hide all the in-between drawdowns, that's exactly what you try to avoid by calculating those values in the first place. So you have to take the actual results of every trade as inputs.

The calculated ratios need to be normalized to become comparable. A fund manager does this by normalizing "monthly" which is a simplification but for funds this is OK as returns and in-between deviations are always very small. But for a test in the tester this approach doesn't work. First we need a sufficient large sample, so each test should have at least 100 trades. Now how to compare a test on M15 over 3 months with 100 trades with a test on H4 over 9 months with 200 trades? It's not easily possible.

As a general rule tests should be non-compounding, so we are able to simplify and to compare absolute values (not growth rates). This let's us take the factor "money"/"balance" out of the equation. Instead we calculate trade results in positive or negative pips. It means that a test should use a standard lotsize over the whole testing period. If the strategy scales in/out or uses oherwise different trade sizes those results need to be normalized again (pips * lotsize). Later when we are satisfied by the results we may still run the strategy with compounding by simple multiplication.

By calculating stats for each single trade we take the factor "time" out of the equation. 100 trades on M15 over 3 months become comparable over 100 trades on H4 over a different time period. We still need the same/similar number of trades. How can I compare 30 trades on one period with 200 trades on another? This is the most complicated part and I will not cover it in the following calculations. Solution: Markets (and equity curves) are fractal and instead of using trade closes as input we need to normalize the whole equity curve. Both equity curves (from a 30 trade test and from a 200 trade tests) need to be split into the same number of units (equity points) and those points become input of the stat calculations. For this we need equity curves and because most people will not be able to produce those I'll skip it here.

After trade results are normalized and ratios calculated the raw results can be normalized back to let's say a monthly ratio. Again I'll skip it as it depends highly on the strategy. You can't compare a scalper to a long term trend follower. As can be seen it takes a lot of effort to get comparable results over different test periods, timeframes and/or trade numbers.

Finally the calculations:

/**
 *
 */
void CalculateRatios() {
   int trades, orders = OrdersHistoryTotal();
   double totalPips, pips[];
   ArrayResize(pips, orders);

   int    pipDigits = Digits & (~1);
   double pip       = NormalizeDouble(1/MathPow(10, pipDigits), pipDigits);

   for (int i=0; i < orders; i++) {
      if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol()==Symbol() && OrderType()<=OP_SELL) {
         pips[trades] = (OrderType()-OrderType()^1) * (OrderOpenPrice()-OrderClosePrice()) / pip;
         totalPips += pips[trades];
         trades++;
      }
   }
   ArrayResize(pips, trades);

   double avgPips = totalPips/trades;
   double sharpe  = CalculateSharpeRatio(pips, totalPips);
   double sortino = CalculateSortinoRatio(pips, totalPips);

   Print("trades="+ trades +"  totalPips="+ DoubleToStr(totalPips, 1) +"  avgPips="+ DoubleToStr(avgPips, 2) +"  sharpe="+ DoubleToStr(sharpe, 4) +"  sortino="+ DoubleToStr(sortino, 4));
}


/**
 *
 */
double CalculateSharpeRatio(double values[], double sum) {
   int size = ArraySize(values);
   if (!size) return(NULL);

   double mean = sum / size;
   double sharpe = mean / MathStdev(values, mean);
   return(sharpe);
}


/**
 *
 */
double CalculateSortinoRatio(double values[], double sum) {
   int size = ArraySize(values);
   if (!size) return(NULL);

   double losses[];
   ArrayResize(losses, size);

   for (int i, n=0; i < size; i++) {
      if (values[i] <= 0) {
         losses[n] = values[i];
         n++;
      }
   }
   ArrayResize(losses, n);

   double mean = sum / size;
   double sortino = mean / MathStdev(losses, mean);
   return(sortino);
}


/**
 *
 */
double MathStdev(double values[], double mean) {
   int size = ArraySize(values);
   if (!size) {
      double N_INF = MathLog(0);         // negative infinity
      double P_INF = -N_INF;             // positive infinity
      double NaN   =  N_INF - N_INF;     // not-a-number
      return(NaN);
   }

   double sum;
   for (int i=0; i < size; i++) {
      sum += MathPow((values[i] - mean), 2);
   }
   double stdDev = MathSqrt(sum / size);
   return(stdDev);
}
 

Calculating average trade results in pip gives you another good and easily interpretable measure. You immediately see the probable influence of slippage/commissions/swap on your results. If your average trade gains less than 5 pip the probability is high that most of your theoretical gains will be eaten away by trading costs.

Another option in the calulation of the sortino ratio is to not skip positive results but instead skip all results better then the mean. This way your sortino ratio describes all trades worse then the average trade which may make sense, too. It depends on what you prefer/want to know with your stats...

Reason: