OnTester

Tester事件发生时在EA中调用这个函数,测试之后执行必要的操作。

double  OnTester(void);

返回值

用于评估测试结果的自定义标准优化的值。  

注意

OnTester()函数只有在测试EA时可被使用,主要用于计算在优化输入参数时用作‘Custom max(最大自定义)’准则的值。

在遗传优化过程中,在一代内对结果进行降序排列。这意味着从优化准则的角度来看,具有最高价值的结果被认为是最高的。这种排序最糟糕的值被排在最后,并随后丢弃。因此,它们不参与形成下一代。

因此,OnTester()函数不仅允许您创建和保存您自己的测试结果报告,还可以控制优化过程,找出交易策略的最佳参数。

以下就是计算自定义准则优化的示例。意在计算结余图形的线性回归。这在使用结余图形优化策略,并将结果与“结余 + 最大夏普比率”准则进行比较文章中进行描述。

//+------------------------------------------------------------------+

//|                                              OnTester_Sample.mq5 |

//|                        Copyright 2018, MetaQuotes Software Corp. |

//|                                             https://www.mql5.com |

//+------------------------------------------------------------------+

#property copyright "Copyright 2018, MetaQuotes Software Corp."

#property link      "https://www.mql5.com"

#property version   "1.00"

#property description "Sample EA with the OnTester() handler"

#property description "As a custom optimization criterion, "

#property description "the ratio of the balance graph linear regression"

#property description "divided by the deviation mean-square error is returned"

//--- 包含交易操作类

#include <Trade\Trade.mqh>

//--- EA输入参数

input double Lots               = 0.1;     // 交易量

input int    Slippage           = 10;      // 可允许滑移

input int    MovingPeriod       = 80;      // 移动平均线时间

input int    MovingShift        = 6;       // 移动平均线移位

//--- 全局变量

int    IndicatorHandle=0;  // 指标句柄

bool   IsHedging=false;    // 账户标识

CTrade trade;              // 为执行交易操作

//--- 

#define EA_MAGIC 18052018

//+------------------------------------------------------------------+

//| 检查持仓开仓条件                                                   |

//+------------------------------------------------------------------+

void CheckForOpen(void)

  {

   MqlRates rt[2];

//--- 仅在新柱形图开始进行交易

   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)

     {

      Print("CopyRates of ",_Symbol," failed, no history");

      return;

     }

//--- 报价量

   if(rt[1].tick_volume>1)

      return;

//--- 接收移动平均线值

   double   ma[1];

   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)

     {

      Print("CopyBuffer from iMA failed, no data");

      return;

     }

//--- 检查信号是否存在

   ENUM_ORDER_TYPE signal=WRONG_VALUE;

//--- 蜡烛图开盘高于移动平均线但收盘低于移动平均线

   if(rt[0].open>ma[0] && rt[0].close<ma[0])

      signal=ORDER_TYPE_BUY;    //买入信号

   else // 蜡烛图开盘低于移动平均线但收盘高于移动平均线

     {

      if(rt[0].open<ma[0] && rt[0].close>ma[0])

         signal=ORDER_TYPE_SELL;// 卖出信号

     }

//--- 附加检查

   if(signal!=WRONG_VALUE)

     {

      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)

        {

         double price=SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK);

         trade.PositionOpen(_Symbol,signal,Lots,price,0,0);

        }

     }

//---

  }

//+------------------------------------------------------------------+

//| 检查持仓平仓条件                                                   |

//+------------------------------------------------------------------+

void CheckForClose(void)

  {

   MqlRates rt[2];

//--- 仅在新柱形图开始进行交易

   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)

     {

      Print("CopyRates of ",_Symbol," failed, no history");

      return;

     }

   if(rt[1].tick_volume>1)

      return;

//--- 接收移动平均线值

   double   ma[1];

   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)

     {

      Print("CopyBuffer from iMA failed, no data");

      return;

     }

//--- 已使用PositionSelect()提前选择持仓

   bool signal=false;

   long type=PositionGetInteger(POSITION_TYPE);

//--- 蜡烛图开盘高于移动平均线但收盘低于移动平均线——关闭卖出持仓

   if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])

      signal=true;

//--- 蜡烛图开盘低于移动平均线但收盘高于移动平均线——关闭买入持仓

   if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])

      signal=true;

//--- 附加检查

   if(signal)

     {

      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)

         trade.PositionClose(_Symbol,Slippage);

     }

//---

  }

//+-------------------------------------------------------------------+

//| 选择一个考虑账户类型的持仓:单边持仓或锁仓持仓                            |

//+-------------------------------------------------------------------+

bool SelectPosition()

  {

   bool res=false;

//--- 选择一个锁仓账户系统的持仓

   if(IsHedging)

     {

      uint total=PositionsTotal();

      for(uint i=0; i<total; i++)

        {

         string position_symbol=PositionGetSymbol(i);

         if(_Symbol==position_symbol && EA_MAGIC==PositionGetInteger(POSITION_MAGIC))

           {

            res=true;

            break;

           }

        }

     }

//--- 选择一个单边账户系统的持仓

   else

     {

      if(!PositionSelect(_Symbol))

         return(false);

      else

         return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---check Magic number

     }

//--- 执行结果

   return(res);

  }

//+------------------------------------------------------------------+

//| EA交易初始化函数                                                   |

//+------------------------------------------------------------------+

int OnInit(void)

  {

//--- 设置一个交易类型:单边交易类型或锁仓交易类型

   IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);

//--- 初始化一个用于正确持仓控制的对象

   trade.SetExpertMagicNumber(EA_MAGIC);

   trade.SetMarginMode();

   trade.SetTypeFillingBySymbol(Symbol());

   trade.SetDeviationInPoints(Slippage);

//--- 创建移动平均线指标

   IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);

   if(IndicatorHandle==INVALID_HANDLE)

     {

      printf("Error creating iMA indicator");

      return(INIT_FAILED);

     }

//--- ok

   return(INIT_SUCCEEDED);

  }

//+------------------------------------------------------------------+

//| EA报价函数                                                        |

//+------------------------------------------------------------------+

void OnTick(void)

  {

//--- 如果已开仓,请检查平仓条件

   if(SelectPosition())

      CheckForClose();

// 检查持仓开仓条件

   CheckForOpen();

//---

  }

//+------------------------------------------------------------------+

//| 测试函数                                                          |

//+------------------------------------------------------------------+

double OnTester()

  {

//--- 自定义准则优化值(越高越好)

   double ret=0.0;

//--- 将交易结果转到数组

   double array[];

   double trades_volume;

   GetTradeResultsToArray(array,trades_volume);

   int trades=ArraySize(array);

//--- 如果少于10个交易,那么测试结果没有正值

   if(trades<10)

      return (0);

//--- 每个交易的平均结果

   double average_pl=0;

   for(int i=0;i<ArraySize(array);i++)

      average_pl+=array[i];

   average_pl/=trades;

//--- 显示单测试模式的消息

   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))

      PrintFormat("%s: Trades=%d, Average profit=%.2f",__FUNCTION__,trades,average_pl);

//--- 计算利润图的线性回归比

   double a,b,std_error;

   double chart[];

   if(!CalculateLinearRegression(array,chart,a,b))

      return (0);

//--- 计算回归线与图表偏差的误差

   if(!CalculateStdError(chart,a,b,std_error))

      return (0);

//--- 计算趋势利润与标准偏差的比率

   ret=(std_error == 0.0) ? a*trades : a*trades/std_error;

//--- 返回自定义标准优化值

   return(ret);

  }

//+------------------------------------------------------------------+

//| 获取交易的利润/亏损的数组                                           |

//+------------------------------------------------------------------+

bool GetTradeResultsToArray(double &pl_results[],double &volume)

  {

//--- 请求完整的交易历史

   if(!HistorySelect(0,TimeCurrent()))

      return (false);

   uint total_deals=HistoryDealsTotal();

   volume=0;

//--- 设置预付款数组的初始大小 - 根据历史中的交易数量

   ArrayResize(pl_results,total_deals);

//--- 解决交易结果的交易计数器 - 盈利或亏损

   int counter=0;

   ulong ticket_history_deal=0;

//--- 检查所有交易

   for(uint i=0;i<total_deals;i++)

     {

      //--- 选择一个交易 

      if((ticket_history_deal=HistoryDealGetTicket(i))>0)

        {

         ENUM_DEAL_ENTRY deal_entry  =(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal,DEAL_ENTRY);

         long            deal_type   =HistoryDealGetInteger(ticket_history_deal,DEAL_TYPE);

         double          deal_profit =HistoryDealGetDouble(ticket_history_deal,DEAL_PROFIT);

         double          deal_volume =HistoryDealGetDouble(ticket_history_deal,DEAL_VOLUME);

         //--- 我们只对交易操作感兴趣        

         if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))

            continue;

         //--- 只有解决盈利/亏损的交易

         if(deal_entry!=DEAL_ENTRY_IN)

           {

            //--- 将交易结果写入数组并增加交易的计数器

            pl_results[counter]=deal_profit;

            volume+=deal_volume;

            counter++;

           }

        }

     }

//--- 设置数组的最终大小

   ArrayResize(pl_results,counter);

   return (true);

  }

//+------------------------------------------------------------------+

//| 计算线性回归 y=a*x+b                                               |

//+------------------------------------------------------------------+

bool CalculateLinearRegression(double  &change[],double &chartline[],

                               double  &a_coef,double  &b_coef)

  {

//--- 检查数据充分性

   if(ArraySize(change)<3)

      return (false);

//--- 创建一个累积图表数组

   int N=ArraySize(change);

   ArrayResize(chartline,N);

   chartline[0]=change[0];

   for(int i=1;i<N;i++)

      chartline[i]=chartline[i-1]+change[i];

//--- 现在,计算回归比率

   double x=0,y=0,x2=0,xy=0;

   for(int i=0;i<N;i++)

     {

      x=x+i;

      y=y+chartline[i];

      xy=xy+i*chartline[i];

      x2=x2+i*i;

     }

   a_coef=(N*xy-x*y)/(N*x2-x*x);

   b_coef=(y-a_coef*x)/N;

//---

   return (true);

  }

//+------------------------------------------------------------------+

//|  计算指定a和b的平均-平方偏差误差                                     |

//+------------------------------------------------------------------+

bool  CalculateStdError(double  &data[],double  a_coef,double  b_coef,double &std_err)

  {

//--- 平方和错误

   double error=0;

   int N=ArraySize(data);

   if(N<=2)

      return (false);

   for(int i=0;i<N;i++)

      error+=MathPow(a_coef*i+b_coef-data[i],2);

   std_err=MathSqrt(error/(N-2));

//--- 

   return (true);

  }

另见

测试交易策略TesterHideIndicators处理优化结果TesterStatisticsOnTesterInitOnTesterDeinitOnTesterPassMQL_TESTERMQL_OPTIMIZATIONFileOpenFileWriteFileLoadFileSave