下载MetaTrader 5

Dr. Tradelove 或我如何不再担忧并创建一个自训练 EA 交易

7 三月 2014, 07:21
Roman Zamozhnyy
0
1 483

概念

在创建 EA 交易之后,我们都求助内置的策略测试程序来选择最佳参数。在选择这些参数时,我们运行 EA,并且一旦出现任何重大变化,则 EA 将会停止,并且一次又一次地使用策略测试程序进行优化。

我们能够将再优化决策和再优化作为一个进程分配给 EA 而不需要自然地中断其工作吗?

Quantum 在其《《自适应交易系统以及它们在 MetaTrader 5 客户端中的运用》一文中提出了这个问题的一个解决方案,专门致力于使用真实交易系统以及几个(在数量上没有限制)虚拟交易策略,从中选择一个目前为止具有最大盈利的策略。在超过某个固定的柱值之后,更改交易策略的决定被采纳。

我建议使用 joo 在《《遗传算法 - 很简单!》一文中提出的遗传算法 (GA) 代码。让我们看一看此类 EA 交易的实施(以下例子中的一个是为参加 2011 年自动交易锦标赛而写的 EA)。


正在进行的工作

那么,我们需要定义 EA 交易应能够做什么。首先,不言而喻的是使用选定的策略进行交易。其次,做出决定:是否应该再优化(执行输入参数的新优化)。再其次,利用 GA 进行再优化。首先,我们回顾一下最简单的再优化 - 有一个策略,并且我们只选择新的参数。然后,我们将看看是否能够利用 GA 在变化后的市场环境中选择其他策略,以及如果能够,如何进行。

此外,为了有利于适应度函数中的模拟,我们决定在一个工具上仅交易已完成柱。将不会有添加仓位和部分平仓。对于那些喜欢使用固定止损和获利以及跟踪止损的人,请参阅《MetaTrader 5 策略测试程序中的价格变动生成算法》一文,以在适应度函数中实施止损和获利订单检查。我将展开下面的习语:

在适应度函数中,我模拟了一种测试模式,这种模式在测试程序中被称为 "Open Prices Only"(仅开盘价)。但是!这并不意味着这是适应度函数中唯一可能的测试过程模拟。更加小心谨慎的人可能希望使用 "Every Tick"(每一价格变动)模式实施适应度函数测试。为了不重复劳动或拼凑“每一价格变动”,我愿意提醒他们注意 MetaQuotes 开发的现有算法。换言之,读过本文之后,读者将能够在适应度函数中模拟 "Every Tick"(每一价格变动)模式,这是在适应度函数中正确模拟止损和获利的必要条件。

在继续要点(策略实施)之前,让我们简短地回顾一下定义新柱的开盘以及建仓和平仓的技术性和实施辅助函数。

//+------------------------------------------------------------------+
//| Define whether a new bar has opened                              |
//+------------------------------------------------------------------+
bool isNewBars()
  {
   CopyTime(s,tf,0,1,curBT);
   TimeToStruct(curBT[0],curT);
   if(tf==PERIOD_M1||
      tf==PERIOD_M2||
      tf==PERIOD_M3||
      tf==PERIOD_M4||
      tf==PERIOD_M5||
      tf==PERIOD_M6||
      tf==PERIOD_M10||
      tf==PERIOD_M12||
      tf==PERIOD_M15||
      tf==PERIOD_M20||
      tf==PERIOD_M30)
      if(curT.min!=prevT.min)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_H1||
      tf==PERIOD_H2||
      tf==PERIOD_H3||
      tf==PERIOD_H4||
      tf==PERIOD_H6||
      tf==PERIOD_H8||
      tf==PERIOD_M12)
      if(curT.hour!=prevT.hour)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_D1||
      tf==PERIOD_W1)
      if(curT.day!=prevT.day)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_MN1)
      if(curT.mon!=prevT.mon)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   return(false);
  }
//+------------------------------------------------------------------+
//|  ClosePosition                                                   |
//+------------------------------------------------------------------+
void ClosePosition()
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=PositionGetSymbol(0);
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) request.type=ORDER_TYPE_SELL; 
   else request.type=ORDER_TYPE_BUY;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   while(PositionsTotal()>0)
     {
      request.volume=NormalizeDouble(MathMin(PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(PositionGetSymbol(0),SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
     }
  }
//+------------------------------------------------------------------+
//|  OpenPosition                                                    |
//+------------------------------------------------------------------+
void OpenPosition()
  {
   double vol;
   request.action=TRADE_ACTION_DEAL;
   request.symbol=s;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   vol=MathFloor(AccountInfoDouble(ACCOUNT_FREEMARGIN)*optF*AccountInfoInteger(ACCOUNT_LEVERAGE)
       /(SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP)))*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP);
   vol=MathMax(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MIN));
   vol=MathMin(vol,GetPossibleLots()*0.95);
   if(SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)!=0) vol=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)),2);
   request.volume=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
   while(PositionSelect(s)==false)
     {
      if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); 
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
   while(PositionGetDouble(POSITION_VOLUME)<vol)
     {
      request.volume=NormalizeDouble(MathMin(vol-PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
  }
//+------------------------------------------------------------------+

在经过仔细考虑之后,您可能注意到建仓函数中的三个重要参数:soptF 变量以及 GetPossibleLots() 函数调用:

  • s - 交易工具,GA 优化的变量之一,
  • optF - 用于交易的保证金部分(GA 优化的另一个变量), 
  • GetPossibleLots() 函数 - 返回用于交易的保证金部分:
//+------------------------------------------------------------------+
//|  GetPossibleLots                                                 |
//+------------------------------------------------------------------+
double GetPossibleLots()
  {
   request.volume=1.0;
   if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
   else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
   OrderCheck(request,check);
   return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,2));
  }

稍微打乱一下讲述顺序,我们另外介绍两个所有 EA 都有并且在第二阶段非常重要的函数:

//+------------------------------------------------------------------+
//|  InitRelDD                                                       |
//+------------------------------------------------------------------+
void InitRelDD()
  {
   ulong DealTicket;
   double curBalance;
   prevBT[0]=D'2000.01.01 00:00:00';
   TimeToStruct(prevBT[0],prevT);
   curBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   maxBalance=curBalance;
   HistorySelect(D'2000.01.01 00:00:00',TimeCurrent());
   for(int i=HistoryDealsTotal();i>0;i--)
     {
      DealTicket=HistoryDealGetTicket(i);
      curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT);
      if(curBalance>maxBalance) maxBalance=curBalance;
     }
  }
//+------------------------------------------------------------------+
//|  GetRelDD                                                        |
//+------------------------------------------------------------------+
double GetRelDD()
  {
   if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance);
  }

我们在这里看到了什么?第一个函数确定最大帐户余额值,第二个函数计算帐户的相对当前亏损。将在第二阶段的说明中详细列出它们的特性。

移到 EA。因为我们仅仅是初学者,我们不会获得一个从中选择策略的 EA,但会严格实施两个具有以下策略的 EA:
  • 一个使用移动平均线的相交进行交易(黄金交叉 - 我们买入金融工具,死亡交叉 - 我们卖出);
  • 另一个是简单的神经网络,接收最后五个交易会话中 [0..1] 范围内的价格变动。

在算法上,自优化 EA 交易的工作可通过以下例子来说明:

  1. EA 交易使用的变量的初始化:定义和初始化指标缓存,或设置神经网络拓扑结构(层数/一层中的神经元数量;以一个简单的神经网络为例,其中神经元的数量在所有层中都是相同的),设置工作时间框架。此外,很可能是最重要的步骤 - 我们调用遗传优化函数,该函数解决最重要的函数 - 适应度函数(以下简称为 FF)。

    重要须知!每一个交易策略有一个新的 FF,即每一次都会重新创建一个新的 FF。针对一条移动平均线的 FF 与针对两条移动平均线的 FF 完全不同,并且与神经网络 FF 完全不同。

    我的 EA 交易中的 FF 性能结果是最大余额,前提是相对亏损未超过作为外部变量设置的临界值(在我们的例子中为 0.5)。换言之,如果下一次 GA 运行得出 100,000 的余额,而相对余额亏损为 -0.6,则 FF=0.0。亲爱的读者,在您的例子中,FF 结果可能带来完全不同的标准。

    收集遗传算法性能结果:对于移动平均线的相交,这些显然是移动平均周期,对于神经网络,有突触权重,它们共有的结果(以及我的其他 EA 的结果)是直到下一次再优化为止要交易的金融工具,以及我们已经熟悉的 optF,即要用于交易的保证金的一部分。由您自主决定向您的 FF 添加优化参数,例如还可以选择时间框架或其他参数。

    初始化的最后一步是找出最大帐户余额值。为什么它很重要?因为这是再优化决策的起点。

    重要须知!再优化的决策过程:一旦相对余额亏损达到作为外部变量设置的某个临界值(在我们的例子中为 0.2),则我们需要再优化。为了不让 EA 在到达临界亏损时在每一根柱上实施再优化,最大余额值被当前值所代替。

    亲爱的读者,对于再优化的实施,您可能有完全不同的标准。

  2. 正在进行的交易。

  3. 在每一次平仓时,我们检查余额亏损是否达到临界值。如果达到临界值,我们运行 GA 并收集其性能结果(这就是再优化)!

  4. 我们要么等待 Forex 的董事打电话来,要求不要破坏世界,或者(更有可能)止损离场、追加保证金或等待救护车。

请在下面找出针对使用移动平均线策略的 EA(提供了源代码)和使用神经网络的 EA 的上述要点的编程实施 - 所有一切都作为源代码提供。

代码是依据 GPL 许可条款与条件提供的。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   tf=Period();
//---for bar-to-bar test...
   prevBT[0]=D'2001.01.01';
//---... long ago
   TimeToStruct(prevBT[0],prevT);
//--- historical depth (should be set since the optimisation is based on historical data)
   depth=10000;
//--- copies at a time (should be set since the optimisation is based on historical data)
   count=2;
   ArrayResize(LongBuffer,count);
   ArrayResize(ShortBuffer,count);
   ArrayInitialize(LongBuffer,0);
   ArrayInitialize(ShortBuffer,0);
//--- calling the neural network genetic optimisation function
   GA();
//--- getting the optimised neural network parameters and other variables
   GetTrainResults();
//--- getting the account drawdown
   InitRelDD();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBars()==true)
     {
      bool trig=false;
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(trig==true)
        {
         //--- if the account drawdown has exceeded the allowable value:
         if(GetRelDD()>maxDD)
           {
            //--- calling the neural network genetic optimisation function
            GA();
            //--- getting the optimised neural network parameters and other variables
            GetTrainResults();
            //--- readings of the drawdown will from now on be based on the current balance instead of the maximum balance
            maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
           }
        }
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         request.type=ORDER_TYPE_SELL;
         OpenPosition();
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         request.type=ORDER_TYPE_BUY;
         OpenPosition();
        }
     };
  }
//+------------------------------------------------------------------+
//| Preparing and calling the genetic optimizer                      |
//+------------------------------------------------------------------+
void GA()
  {
//--- number of genes (equal to the number of optimised variables), 
//--- all of them should be specified in the FitnessFunction())
   GeneCount      =OptParamCount+2;    
//--- number of chromosomes in a colony
   ChromosomeCount=GeneCount*11;
//--- minimum search range
   RangeMinimum   =0.0;
//--- maximum search range
   RangeMaximum   =1.0;
//--- search pitch
   Precision      =0.0001;
//--- 1 is a minimum, anything else is a maximum
   OptimizeMethod =2;                                                 
   ArrayResize(Chromosome,GeneCount+1);
   ArrayInitialize(Chromosome,0);
//--- number of epochs without any improvement
   Epoch          =100;                                               
//--- ratio of replication, natural mutation, artificial mutation, gene borrowing, 
//--- crossingover, interval boundary displacement ratio, every gene mutation probabilty, %
   UGA(100.0,1.0,1.0,1.0,1.0,0.5,1.0);                                
  }
//+------------------------------------------------------------------+
//| Fitness function for neural network genetic optimizer:           | 
//| selecting a pair, optF, synapse weights;                         |
//| anything can be optimised but it is necessary                    |
//| to carefully monitor the number of genes                         |
//+------------------------------------------------------------------+
void FitnessFunction(int chromos)
  {
   int    b;
//--- is there an open position?
   bool   trig=false;
//--- direction of an open position
   string dir="";
//--- opening price
   double OpenPrice=0;
//--- intermediary between a gene colony and optimised parameters
   int    z;
//--- current balance
   double t=cap;
//--- maximum balance
   double maxt=t;
//--- absolute drawdown
   double aDD=0;
//--- relative drawdown
   double rDD=0.000001;
//--- fitness function proper
   double ff=0;
//--- GA is selecting a pair
   z=(int)MathRound(Colony[GeneCount-1][chromos]*12);
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
   MAshort=iMA(s,tf,(int)MathRound(Colony[1][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Colony[2][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
   
//--- GA is selecting the optimal F
   optF=Colony[GeneCount][chromos];                                   
   
   leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
   contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
   b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
   
//--- for a neural network using historical data - where the data is copied from
   for(from=b;from>=1;from--) 
     {
      CopyBuffer(MAshort,0,from,count,ShortBuffer);
      CopyBuffer(MAlong,0,from,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="SELL";
            trig=true;
           }
         else
           {
            if(dir=="BUY")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="SELL";
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="BUY";
            trig=true;
           }
         else
           {
            if(dir=="SELL")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="BUY";
               trig=true;
              }
           }
        }
     }
   if(rDD<=trainDD) ff=t; else ff=0.0;
   AmountStartsFF++;
   Colony[0][chromos]=ff;
  }

//+---------------------------------------------------------------------+
//| getting the optimized neural network parameters and other variables |
//| should always be equal to the number of genes                       |
//+---------------------------------------------------------------------+
void GetTrainResults()
  {
//---  intermediary between a gene colony and optimised parameters
   int z;                                                             
   MAshort=iMA(s,tf,(int)MathRound(Chromosome[1]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Chromosome[2]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   CopyBuffer(MAshort,0,from,count,ShortBuffer);
   CopyBuffer(MAlong,0,from,count,LongBuffer);
//--- save the best pair
   z=(int)MathRound(Chromosome[GeneCount-1]*12);                      
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
//--- saving the best optimal F
   optF=Chromosome[GeneCount];                                        
  }
//+------------------------------------------------------------------+

让我们看一看算法的主函数 - 适应度函数。

自优化 EA 交易的整个理念基于适应度函数中某个时间段(比如 10000 根柱的历史)内的交易过程(与在 MetaQuotes 的标准测试程序中一样)的模拟,该函数从遗传算法(GA 函数)接收优化变量的输入)。如果算法基于移动平均线的相交,则优化变量包括:

  • 工具(在 Forex 中为货币对);是的,它是典型的多货币 EA 交易并且遗传算法选择一个工具(因为代码来自为参加锦标赛而编写的 EA 交易,它有与锦标赛的货币对相对应的货币对;一般而言,可以是经纪人报价的任何工具)。
    注:很不幸,EA 交易不能在测试模式中从 MarketWatch (市场报价)窗口中获得货币对列表(感谢 MetaQuotes 用户,我们在这里澄清这一点 - 没门!)。因此,如果您想在测试程序中单独为外汇和股票运行 EA 交易,请在 FF 和 GetTrainResults() 函数中指定您自己的工具。
  • 用于交易的保证金部分;
  • 两条移动平均线的周期。

以下示例说明了用于测试的 EA 交易的一种情形!

可以使用来自 MarketWatch (市场报价)窗口的工具列表显著简化真实交易代码。

为此,在带有注释 "//--- GA is selecting a pair"(GA 正在选择一个货币对) 和 "//--- saving the best pair"(保存最佳货币对) 的 FF 和 GetTrainResults() 函数中,仅仅写为:

//--- GA is selecting a pair
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));
  s=SymbolName(z,true);

这样,在 FF 的开头,我们指定并在必要时初始化用于模拟基于历史的交易的变量。在下一阶段,我们将从遗传算法收集优化变量的不同值,例如从 "optF=Colony[GeneCount][chromos] 一行;保证金部分值从 GA 传输到 FF。

我们进一步检查历史中的可用柱数,我们或从第 10000 根柱开始,或从模拟 "Open prices only"(仅开盘价)模式中接收报价过程的第一根柱开始,以及做出交易决定:

  • 将移动平均线的值复制到缓存;
  • 检查是否为死亡交叉。
  • 如果有死亡交叉,并且没有未平仓位 (if(trig==false)) - 建立一个虚拟卖出仓位(仅仅记住建仓价和方向);
  • 如果有死亡交叉,并且有未平买入仓位 (if(dir=="BUY")) - 取柱的开盘价并注意三条非常重要的线,如下所示:
  1. 模拟平仓和余额变更:当前余额增加当前余额值,乘以要交易的保证金部分,再乘以开盘价和收盘价之差,再乘以价格利润点价格(近似);
  2. 检查当前余额是否达到交易模拟历史中的最大值;如果未达到,计算资金的最大余额亏损;
  3. 将先前计算出来的资金亏损转换为相对余额亏损;
  • 建立一个虚拟卖出仓位(仅仅记住建仓价和方向);
  • 对黄金交叉进行相同的检查和计算。

遍历整个可用历史并模拟虚拟交易之后,计算最终的 FF 值:如果计算出来的相对余额亏损小于为测试设置的亏损,则 FF=余额,否则 FF=0。遗传算法旨在让适应度函数最大化!

最后,给出工具、保证金部分和移动平均线的周期等各个值之后,遗传算法将找出以最小相对亏损(最小值由用户设定)实现最大余额的值。


总结

以下是简要总结:创建一个自训练 EA 交易很容易,困难之处在于找出要输入什么(重要的是想法,实施只是一个技术问题)。

悲观主义者会问 - “它有用吗?”,我的回答 - 有用;对于乐观主义者 - 这不是万能圣杯。

所提议的方法与 Quantum 的方法有什么根本区别?可以通过比较使用移动平均线的 EA 交易来最好地说明这一点:

  1. 自适应训练系统中移动平均线的周期应在编译和严格编码之前决定,并且只能在这种数量有限的情形之中做出选择;在遗传优化 EA 交易中,我们在编译之前不做任何决定,此决定由 GA 做出,并且情形的数量仅受判断力的限制。
  2. 自适应交易系统中的虚拟交易是柱到柱;在遗传优化 EA 交易中很少如此 - 并且仅在再优化的条件出现时。受策略、参数、工具数量不断增长影响的计算机性能或许是自适应交易系统的一个限制因素。


附录

以下是在测试程序中用 2010 年 1 月 1 起的日线图数据,在没有经过任何优化的情况下运行神经网络所得到的结果:

策略测试程序报告
MetaQuotes-Demo (Build 523)
 
EA 交易: ANNExample
交易品种: EURUSD
周期: 每日 (2010.01.01 - 2011.09.30)
输入参数: trainDD=0.9
  maxDD=0.1
经纪人: Alpari NZ Limited
货币: USD
初始存入: 10 000.00
杠杆率: 1:100
结果
历史质量: 100%
柱数: 454 价格变动: 2554879
总净利润: -9 094.49 毛利: 29 401.09 毛损: -38 495.58
获利系数: 0.76 预计获利: -20.53 保证金水平: 732.30%
回收系数: -0.76 夏普比率: -0.06 OnTester 结果: 0
 
余额亏损:
绝对余额亏损: 9 102.56 最大余额亏损: 11 464.70 (92.74%) 相对余额亏损: 92.74% (11 464.70)
市值亏损:
绝对市值亏损: 9 176.99 最大市值亏损: 11 904.00 (93.53%) 相对市值亏损: 93.53% (11 904.00)
 
总交易次数: 443 短线交易次数(获利%): 7 (14.29%) 长线交易次数(获利%): 436 (53.44%)
总成交次数: 886 盈利交易次数(总交易次数的%): 234 (52.82%) 亏损交易次数(总交易次数的%): 209 (47.18%)
  最大获利交易: 1 095.57 最大亏损交易: -1 438.85
  平均获利交易: 125.65 平均亏损交易: -184.19
  最大连续盈利次数(盈利金额): 8 (397.45) 最大连续亏损次数(亏损金额): 8 (-1 431.44)
  最大连续收益金额(盈利次数): 1 095.57 (1) 最大连续损失金额(亏损次数): -3 433.21 (6)
  平均连续盈利次数: 2 平均连续亏损次数: 2

以下是供选择的三种再优化情形:

第一种...

时间 成交 交易品种 类型 方向 交易量 价格 订单 库存费 盈利 余额
2010.01.01 00:00 1   余额         0.00 10 000.00 10 000.00
2010.01.04 00:00 2 AUDUSD 0.90 0.89977 2 0.00 0.00 10 000.00
2010.01.05 00:00 3 AUDUSD 0.90 0.91188 3 5.67 1 089.90 11 095.57
2010.01.05 00:00 4 AUDUSD 0.99 0.91220 4 0.00 0.00 11 095.57
2010.01.06 00:00 5 AUDUSD 0.99 0.91157 5 6.24 -62.37 11 039.44
2010.01.06 00:00 6 AUDUSD 0.99 0.91190 6 0.00 0.00 11 039.44
2010.01.07 00:00 7 AUDUSD 0.99 0.91924 7 18.71 726.66 11 784.81


第二种...

时间 成交 交易品种 类型 方向 交易量 价格 订单 手续费 库存费 盈利 余额
2010.05.19 00:00 189 AUDUSD 0.36 0.86110 189 0.00 2.27 -595.44 4 221.30
2010.05.19 00:00 190 EURAUD 0.30 1.41280 190 0.00 0.00 0.00 4 221.30
2010.05.20 00:00 191 EURAUD 0.30 1.46207 191 0.00 7.43 -1 273.26 2 955.47
2010.05.20 00:00 192 AUDUSD 0.21 0.84983 192 0.00 0.00 0.00 2 955.47


第三种

时间 成交 交易品种 类型 方向 交易量 价格 订单 库存费 盈利 余额
2010.06.16 00:00 230 GBPCHF 0.06 1.67872 230 0.00 0.00 2 128.80
2010.06.17 00:00 231 GBPCHF 0.06 1.66547 231 0.13 -70.25 2 058.68
2010.06.17 00:00 232 GBPCHF 0.06 1.66635 232 0.00 0.00 2 058.68
2010.06.18 00:00 233 GBPCHF 0.06 1.64705 233 0.04 -104.14 1 954.58
2010.06.18 00:00 234 AUDUSD 0.09 0.86741 234 0.00 0.00 1 954.58
2010.06.21 00:00 235 AUDUSD 0.09 0.87184 235 0.57 39.87 1 995.02
2010.06.21 00:00 236 AUDUSD 0.09 0.88105 236 0.00 0.00 1 995.02
2010.06.22 00:00 237 AUDUSD 0.09 0.87606 237 0.57 -44.91 1 950.68
2010.06.22 00:00 238 AUDUSD 0.09 0.87637 238 0.00 0.00 1 950.68
2010.06.23 00:00 239 AUDUSD 0.09 0.87140 239 0.57 -44.73 1 906.52
2010.06.23 00:00 240 AUDUSD 0.08 0.87197 240 0.00 0.00 1 906.52
2010.06.24 00:00 241 AUDUSD 0.08 0.87385 241 1.51 15.04 1 923.07
2010.06.24 00:00 242 AUDUSD 0.08 0.87413 242 0.00 0.00 1 923.07
2010.06.25 00:00 243 AUDUSD 0.08 0.86632 243 0.50 -62.48 1 861.09
2010.06.25 00:00 244 AUDUSD 0.08 0.86663 244 0.00 0.00 1 861.09
2010.06.28 00:00 245 AUDUSD 0.08 0.87375 245 0.50 56.96 1 918.55
2010.06.28 00:00 246 AUDUSD 0.08 0.87415 246 0.00 0.00 1 918.55
2010.06.29 00:00 247 AUDUSD 0.08 0.87140 247 0.50 -22.00 1 897.05
2010.06.29 00:00 248 AUDUSD 0.08 0.87173 248 0.00 0.00 1 897.05
2010.07.01 00:00 249 AUDUSD 0.08 0.84053 249 2.01 -249.60 1 649.46
2010.07.01 00:00 250 EURGBP 0.07 0.81841 250 0.00 0.00 1 649.46
2010.07.02 00:00 251 EURGBP 0.07 0.82535 251 -0.04 -73.69 1 575.73
2010.07.02 00:00 252 EURGBP 0.07 0.82498 252 0.00 0.00 1 575.73
2010.07.05 00:00 253 EURGBP 0.07 0.82676 253 -0.04 -18.93 1 556.76
2010.07.05 00:00 254 EURGBP 0.06 0.82604 254 0.00 0.00 1 556.76
2010.07.06 00:00 255 EURGBP 0.06 0.82862 255 -0.04 -23.43 1 533.29


附言:作为一项家庭作业:不仅选择某个系统的参数,还选择在某个时刻最符合市场的系统(提示 - 从系统库中选择)。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/334

附加的文件 |
anntrainlib.mqh (9.76 KB)
matrainlib.mqh (8.94 KB)
ugalib.mqh (33.26 KB)
annexample.mq5 (4.32 KB)
maexample.mq5 (4.22 KB)
musthavelib.mqh (8.14 KB)
依据价格相关性的统计数据过滤信号 依据价格相关性的统计数据过滤信号

在过去的价格行为和其将来的趋势之间是否有任何相关性?为什么今天的价格重复以前的每日运行特征呢?统计能用于预测价格动态吗?有一个答案,并且是积极的答案。如果您有任何疑问,则本文正好为您释疑解惑。我将告诉您如何用 MQL5 为一个交易系统创建一个有效的过滤器,展现价格变动中有趣的图形。

Box-Cox 变换 Box-Cox 变换

本文旨在使读者了解 Box-Cox 变换。文章阐述了变换的使用,并给出一些示例以允许使用随机序列和真实报价来评估变换效率。

摆脱自制的 DLL 摆脱自制的 DLL

如果 MQL5 语言的功能性不足以完成任务,MQL5 程序员不得不诉诸于其他工具。他们必须转向其他编程语言并创建中间 DLL。MQL5 可提供各种数据类型并将它们传递至 API,但遗憾的是,MQL5 无法解决从收到的指针提取数据的相关问题。在本文中,我们将循规蹈矩,说明交换和使用复杂数据类型的简单机制。

MQL5 编程基础:时间 MQL5 编程基础:时间

本文着重于讲述处理时间的标准 MQL5 函数,以及创建 EA 交易和指标时所需的处理时间的编程技巧和实用函数。更格外注意时间测量的一般性理论。本文面对的主要是 MQL5 编程新手。