トレードラブ博士または いかに心配することを止め、自習 Expert Advisorを作成したか

Roman Zamozhnyy | 28 10月, 2015


コンセプト

Expert Advisor を作成したあと、われわれは内蔵ストラテジーテスタ を使用して最適なパラメータを選択するためすべて再ソートします。それらを選択する際、Expert Advisor を実行し、そこに何か重大な変更が起これば Expert Advisor が停止しストラテジーテスタを用いて繰り返し最適化する、などです。

再最適化の意思決定、そしてその動作を当然妨げないで Expert Advisorに対するプロセスとしての再最適化を割り当てることはできるのでしょうか?

この問題に対する解決法のひとつは Quantum によって彼の記事 "Adaptive Trading Systems and Their Use in MetaTrader5 Terminal"の中で提案されました。その内容は、これまでもっとも収益を上げてきた戦略が選択される仮想トレーディング戦略のいくつか(数に制限なし)と共に現実のトレーディング戦略を利用することに特化したものです。トレーディング戦略を変更する判断は特定の固定されたバーの値を越えたあとに行われます。

jooの記事 "Genetic Algorithms - It's Easy!"から借用して遺伝的アルゴリズム(GA)のコードセットを利用することを提案します。そのような Expert Advisor の実装を見ていきます(下記の例の一つは自動売買チャンピオンシップ 2011に参加するために提案されたEAです)。


進行中の作業

というわけで Expert Advisor の機能を決める必要があります。まず言うまでもありませんが、選択された戦略を用いてトレードを行うことです。二番目に再最適化を行う(あらためて入力パラメータの最適化を行うこと)タイミングかどうか判断することです。三番目に GAを利用して再最適化を行うことです。まず初めに、もっともシンプルな再最適化を再検討します。 - 戦略はあるので新しいパラメータを選択するだけです。それから GAを利用して変動するマーケットにおいて別の戦略を選択できるか、もしできるならどのようにそれを行うか見ていきます。

のちに適応度関数(以降FF)内でのシミュレーションを促すため、一つのインスツルメントにおいて完了したバーのみトレードを行うように決めます。他にポジションはなく部分的なクローズもありません。トレーリングストップ同様固定されたストップやテイクを利用することを好む方は記事"Tick Generation Algorithm in MetaTrader5 Strategy Tester"を参照してFF内で「ストップロス」および「テイクプロフィット」のチェックを取り入れてください。私は下記の賢いフレーズを拡張します。

FF内で「テスター」に"Open Prices Only(始値のみ)"として知られる検証モードをシミュレーションします。しかし!これがFFにおける唯一可能な検証プロセスシミュレーションであるというわけではありません。きちょうめんな方は「ティック毎」モードでFF検証を実装したいと思われるかもしれません。そういう方には一から始めたり「ティック毎」を作成することのないよう、MetaQuotesが作った既存のアルゴリズムに注目いただきたいと思います。別の言い方をすると、本稿を読んだあと FF内でストップやテイクを正しくシミュレーションするために必要な条件のFFにおいて「ティック毎」をシミュレーションすることができるようになっているのです。

重点である戦略実装に進む前に、専門的な用語を振り返りポジションオープンおよびクローズを行い、新規バーのオープンを判断する予備関数を実装します。

//+------------------------------------------------------------------+
//| 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);
     }
  }
//+------------------------------------------------------------------+

慎重に見るとポジションオープン関数内に重要なパラメータが3つあるのが判ります。変数 s およびoptF と 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));
  }

記述の順序をわずかに壊し、全 Expert Advisorsに共通で第二段階の基礎である関数をもう2件導入します。

//+------------------------------------------------------------------+
//|  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);
  }

ここでどんなことが判るでしょうか?最初の関数はアカウント残高の最大値を判断します。二番間の関数はそれに関連するアカウントの現在ドローダウンを計算します。それらの特殊性は第二段階の詳しい記述に述べられます。

そのような Expert Advisors に移っていきます。われわれは初心者なので戦略を選択するための Expert Advisor ではなく以下の戦略を持 Expert Advisors を2件正確に作成していきます。

アルゴリズムとしては、自己最適化 Expert Advisor は以下のように実証されます。

  1. Expert Advisorによって使用される変数の初期化:インディケータバッファを得意義し初期化、またはニューラルネットワークのトポロジーを設定(階層内の階層/ニューロン数;ニューロン数が全階層で同一であるシンプルなニューラルネットワークが例として提供されています)し、時間枠を設定します。そののち、おそらくもっとも重要なステップです。-これを最初の関数であるFFを呼び出す「遺伝的最適化」関数と言います。

    重要! それぞれのトレーディング戦略には新しい FF があります。すなわちFFは毎回新たに作成されます。たとえば単一移動平均に対する FF は完全に2つの移動平均に対する FF とは異なり、ニューラルネットワーク FFとも大きく異なります。

    私の Expert Advisors でのFFパフォーマンス結果は相対ドローダウンが外部変数として設定されている 臨界値(われわれの例では0.5)を越えていなければ残高最大値です。言い換えると、次の GA 実行による残高が 100.000で、相対ドローダウンが-0.6なら、FF=0.0ということです。親愛なる読者のみなさんの場合は FF 結果は全く異なる基準で現れるかもしれません。

    「遺伝的アルゴリズム」のパフォーマンス結果を収集します。移動平均の交点に対しては明らかに移動平均期間があり、ニューラルネットワークの場合シナプスのウェイトがあります。そして両者に共通の結果(私の他の Expert Advisorsについても)は、次回の再最適化までトレードが行わわれるインスツルメントです。すでにわれわれにとってはなじみのある optF、すなわちトレーディングに使用されるデポジットの一部です。みなさんはご自身の判断において自由に FF に最適化されたパラメータを追加することができます。たとえば時間枠や他のパラメータも自由に選択できます。

    初期化の最終ステップは最大アカウント残高値を求めることです。なぜそれが重要なのでしょうか?それは、これが最適化意思決定の出発点だからです。

    重要! 再最適化についての判断はどのように行われるのでしょうか。相対「残高」ドローダウンが外部変数として設定された特定の臨界値(われわれの例では0.2となっています)に到達するやいなや再最適化が必要です。Expert Advisor が臨界ドローダウンに到達したバーごとに再最適化を行うことのないように、残高最高値を現在値と置換します。

    親愛なる読者のみなさんは再最適化実装に対して全く異なる基準をお持ちのことでしょう。

  2. トレードは進行しています。

  3. 各クローズポジションで残高ドローダウンが臨界値に達したかどうかチェックします。臨界値に達していたら GA を実行し、処理結果を収集します。(それが再最適化です!)

  4. そしてforexの責任者からの世界を破産させないようにというお願い、または(こちらの方がより頻繁にありますが)ストップアウト、マージンコール、救急車、、などの要請の連絡を待ちます。

移動平均戦略(ソースコードも利用可能です)およびニューラルネットワークを使用して上記手順を Expert Advisorに実装するプログラム を下記ですべてソースコードとしてご覧ください。

コードは 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];                                        
  }
//+------------------------------------------------------------------+

アルゴリズムの主要関数FFを見ていきます。

自己最適化 Expert Advisor の背後にある考えはすべてFFにおける期間内(たとえば1,000バーの履歴)トレーディングプロセス(MetaQuotesによる標準的Tester における)のシミュレーションに基づいています。 FFは「遺伝的アルゴリズム」(GA function())から最適化された変数のインプットを受け取ります。移動平均の交点を基にしたアルゴリズムの場合、最適化された変数は以下を含みます。

下記の例は「テスト用」Expert Advisor のバリアントです。

「実際のトレード」コードは Market Watch ウィンドウからのインスツルメントリストを利用することで大幅に簡素化することができます。

FF および GetTrainResults() 関数内でコメント "//--- GA is selecting a pair" and "//--- saving the best pair" を使用してこれを行うには以下を書くだけです。

//--- 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に転送されます。

のちに履歴内の利用可能なバー番号で、1万番目のバーか「始値のみ」モードでクオートを受け取るプロセスをシミュレーションするのに利用可能な最初のバーのどちらか、そしてトレーディング判断をするものを確認します。

  1. ポジションのクローズをシミュレーションし、残高において変更します。現在残高は現在残高値んいよって増加します。それはトレードされるデポジット部分によって乗じられ、始値と終値の差で乗じられ、 ピップ価格(おおよその)によって乗じられます。
  2. 現在残高がトレードシミュレーションの履歴について最高値に達したか確認し、達していなければ資金の残高ドローダウン最高値を計算します。
  3. 以前に計算された資金ドローダウンを残高における相対ドローダウンに換算します。

利用可能は履歴と仮想トレードのシミュレーション全体を確認したら、最終的なFF 値を計算します。計算された残高に対する相対ドローダウンが検証用セットより低ければ、FF=balanceです。それ以外はotherwise FF=0です。「遺伝的アルゴリズム」が狙いとするのはFFの最大化なのです!

すべてが終わると、インスツルメントのさまざまな値、デポジット部分、移動平均期間の条件で、「遺伝的アルゴリズム」が相対ドローダウン最低値(最低値はユーザーが設定します)で残高を最大にする値を見つけます。


おわりに

手短に結論を述べます。自己トレーニング Expert Advisorを作成するのは簡単です。難しいのは何をインプットするか見つけることです(重要なことは考え方です。実装は単に技術的な問題に過ぎません)。

悲観的な方からの「それは動作するの?」という質問を想定します。答えは「動作しますとも」。楽天的な方への私の言葉はこうです。「これは 『魔法の機器』ではないのですから」

本稿で提案した手法とQuantumによる手法との根本的違いは何でしょうか?MAを使用するExpert Advisorsを比較するともっともよくできます。

  1. 「適応型トレーディングシステム」において MA期間を判断するのはコンパイルし厳密にコード化しする前で、選択はこの制限されたバリアントの数以外で可能です。「遺伝的に最適化された」Expert Advisorではコンパイル前に期間判断はせず、この判断は GAによって行われ、バリアント数は常識の範囲で決められます。
  2. 適応型トレーディングシステムにおける仮想トレードはバーからバーです。「遺伝的に最適化された」Expert Advisorでは それは稀で、再最適化に対する条件が発生するときのみ行われます。増加する戦略、パラメータ、インスツルメント数へのコンピュータパフォーマンスは適応型トレーディングシステムに対する制約要因となるかもしれません。


付録

最適化せずテスターでニューラルネットワークを実行する場合の結果があります。それは2010年1月1日からの日次チャートに基づくものです。

ストラテジーテスタレポート
MetaQuotesデモ (Build 523)
Expert Advisor: 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 Sharpe 比率: -0.06 OnTester 結果: 0
残高ドローダウン:
Abs. 残高ドローダウン: 9 102.56 残高ドローダウン最高値: 11 464.70 (92.74%) 相対残高ドローダウン: 92.74% (11 464.70)
資本ドローダウン:
Abs. 資本ドローダウン: 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

以下は選択された再最適化の3とおりのバリアントです。:

1番目...

時刻 取り引き シンボル タイプ 方向 ボリューム 価格 注文 スワップ 収益 残高
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


2番目..

時刻 取り引き シンボル タイプ 方向 ボリューム 価格 注文 コミッション スワップ 収益 残高
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


3番目

時刻 取り引き シンボル タイプ 方向 ボリューム 価格 注文 スワップ 収益 残高
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


追記 宿題:特定システムのパラメータを選択するだけでなく、定められた時にマーケットに最も適したシステムを選択するには?(ヒント-システムバンクより)