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

Roman Zamozhnyy | 7 三月, 2014

概念

在创建 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() 函数调用:

//+------------------------------------------------------------------+
//|  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:

在算法上,自优化 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 函数)接收优化变量的输入)。如果算法基于移动平均线的相交,则优化变量包括:

以下示例说明了用于测试的 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"(仅开盘价)模式中接收报价过程的第一根柱开始,以及做出交易决定:

  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


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