トレーダミネーター 3:売買ロボットの台頭

Roman Zamozhnyy | 28 10月, 2015

序章

むかしむかし、遠くにあるフォーラム (MQL5) で2件の記事が発行されました。それはjoo 著"Genetic Algorithms - It's Easy!" と拙著"Dr. Tradelove..." でした。最初の記事では必要なものはなんでも最適化する力強いツールを提供してくれました。そこにはトレーディング戦略も含まれていました。それは MQL5 言語を用いて実装された遺伝的アルゴリズムでした。

このアルゴリズムを使い、後者の記事ではそれを基に自己最適化を行う Expert Advisor を開発しようとしました。本稿は最終的に次のタスクを考案しました。:Expert Advisor (もちろん自己最適化型)の作成。これは特定のトレーディングシステムに対してパラメータを最適化するだけではなく、開発されたすべてのトレーディング戦略からから最良のものを選ぶというものです。いったいそれは可能なのか、また可能であればどうやって実現するのか見ていきます。

売買ロボットについて

まず、自己最適化 Expert Advisorについて一般的な要件を作成します。

それは次が可能なことです(履歴データを基に)。

その上現実的には以下を可能とするものに限ります。

以下の図は提案のExpert Advisorのスキーム図です。


制限を持つ詳細スキームはファイル Scheme_enにあります。

範囲を把握するのは不可能であることを頭に置いて Expert Advisor ロジックに制約を加えます。以下に同意します(重要)。

  1. Expert Advisor は 新規バー 発生においてトレード判断を行います。(選択するあらゆる時間枠で)
  2. p.1に基づくがその限りではない Expert Advisor はテイクプロフィットとストップロスを使わないインディケータシグナルにおいてのみトレーリングストップは用いず適切にトレードのクローズを行います。
  3. 新たな最適化をスタートする条件:残高のドローダウンがレベル最適化中既定値を上回るとき。これは私の個人的条件であって、みなさんはそれぞれに特有の条件を選ぶことができることに留意ください。
  4. シミュレーションされたトレードの相対的残高ドローダウンは特定の規定値レベルを下回るという条件で適合度関数が履歴上のトレーディングをモデル化し、モデル化した残高を最適化します。またこれは私の個人的条件であって、みなさんはそれぞれに特有の条件を選ぶことができることに留意ください。
  5. また最適化されるパラメータ数を制限します。3つの一般的なもの(戦略、インスツルメント、デポジットシェア)を除き、インディケータバッファのパラメータは5個までとします。この制限は内蔵テクニカルインディケータに対してインディケータバッファの最大数 から理論的に導かれています。大きなインディケータバッファを伴うカスタムインディケータを使用する戦略を書こうと思っているなら、 main.mq5 ファイルにあるOptParamCount変数を望む数量に変えればいいだけです。

これで要件が指定され、制限が選択されました。これらすべてを実装するコードをみていきます。

すべてが実行される関数から始めます。

void OnTick()
{
  if(isNewBars()==true)
  {
    trig=false;
    switch(strat)
    {
      case  0: {trig=NeedCloseMA()   ; break;};                      //The number of case strings must be equal to the number of strategies
      case  1: {trig=NeedCloseSAR()  ; break;};
      case  2: {trig=NeedCloseStoch(); break;};
      default: {trig=NeedCloseMA()   ; break;};
    }
    if(trig==true)
    {
      if(GetRelDD()>maxDD)                                           //If a balance drawdown is above the max allowed value:
      {
        GA();                                                        //Call the genetic optimization function
        GetTrainResults();                                           //Get the optimized parameters
        maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);               //Now count the drawdown not from the balance maximum...
                                                                     //...but from the current balance
      }
    }
    switch(strat)
    {
      case  0: {trig=NeedOpenMA()   ; break;};                       //The number of case strings must be equal to the number of strategies
      case  1: {trig=NeedOpenSAR()  ; break;};
      case  2: {trig=NeedOpenStoch(); break;};
      default: {trig=NeedOpenMA()   ; break;};
    }
    Print(TimeToString(TimeCurrent()),";","Main:OnTick:isNewBars(true)",
          ";","strat=",strat);
  }
}

ここではなにが?図にあるように、新規バーがあるかどうか各ティックを見ます。新規バーがあれば、どの戦略がここで選択されているか判った上でその指定の関数を呼びオープンしているポジションがあるか確認し、必要であればそれをクローズします。ベストなブレークスルー戦略は SARであるとして個々に NeedCloseSAR 関数が呼ばれます。

bool NeedCloseSAR()
{
  CopyBuffer(SAR,0,0,count,SARBuffer);
  CopyOpen(s,tf,0,count,o);
  Print(TimeToString(TimeCurrent()),";","StrategySAR:NeedCloseSAR",
        ";","SAR[0]=",SARBuffer[0],";","SAR[1]=",SARBuffer[1],";","Open[0]=",o[0],";","Open[1]=",o[1]);
  if((SARBuffer[0]>o[0]&&SARBuffer[1]<o[1])||
     (SARBuffer[0]<o[0]&&SARBuffer[1]>o[1]))
  {
    if(PositionsTotal()>0)
    {
      ClosePosition();
      return(true);
    }
  }
  return(false);
}

ポジションクローズのあらゆる関数はブール形式でポジションをクローズするときは true を返します。これによりOnTick() 関数の次のコードブロックは新たな最適化が必要かどうか判断することができます。

    if(trig==true)
    {
      if(GetRelDD()>maxDD)                                           //If the balance drawdown is above the max allowed one:
      {
        GA();                                                        //Call the genetic optimization function
        GetTrainResults();                                           //Get optimized parameters
        maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);                   //Now count the drawdown not from the balance maximum...
                                                                     //...but from the current balance
      }
    }

現在の残高ドローダウンを取得し、それを許可されている最大のものと比較します。最大値を超えている場合、新たな最適化を実行します (GA())。GA() 関数は次々と Expert Advisorの中心部を呼びます。 - GAModule.mqh モジュールの適切度関数(int chromos) です。

void FitnessFunction(int chromos)                                    //A fitness function for the genetic optimizer:...
                                                                     //...selects a strategy, symbol, deposit share,...
                                                                     //...parameters of indicator buffers;...
                                                                     //...you can optimize whatever you need, but...
                                                                     //...watch carefully the number of genes
{
  double ff=0.0;                                                     //The fitness function
  strat=(int)MathRound(Colony[GeneCount-2][chromos]*StratCount);     //GA selects a strategy
 //For EA testing mode use the following code...
  z=(int)MathRound(Colony[GeneCount-1][chromos]*3);                  //GA selects a symbol
  switch(z)
  {
    case  0: {s="EURUSD"; break;};
    case  1: {s="GBPUSD"; break;};
    case  2: {s="USDCHF"; break;};
    case  3: {s="USDJPY"; break;};
    default: {s="EURUSD"; break;};
  }
//..for real mode, comment the previous code and uncomment the following one (symbols are selected in the MarketWatch window)
/*
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));//GA selects a symbol
  s=SymbolName(z,true);
*/
  optF=Colony[GeneCount][chromos];                                   //GA selects a deposit share
  switch(strat)
  {
    case  0: {ff=FFMA(   Colony[1][chromos],                         //The number of case strings must be equal to the number of strategies
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
    case  1: {ff=FFSAR(  Colony[1][chromos],
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
    case  2: {ff=FFStoch(Colony[1][chromos],
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
    default: {ff=FFMA(   Colony[1][chromos],
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
  }
  AmountStartsFF++;
  Colony[0][chromos]=ff;
  Print(TimeToString(TimeCurrent()),";","GAModule:FitnessFunction",
        ";","strat=",strat,";","s=",s,";","optF=",optF,
        ";",Colony[1][chromos],";",Colony[2][chromos],";",Colony[3][chromos],";",Colony[4][chromos],";",Colony[5][chromos]);
}

現在選択されている戦略に応じて、特定の戦略に指定されている適切度関数計算モジュールが呼ばれます。たとえば、GA がストキャスティクスを選ぶと、FFStoch () が呼ばれ、インディケータバッファの最適化パラメータがそこに転送されます。

double FFStoch(double par1,double par2,double par3,double par4,double par5)
{
  int    b;
  bool   FFtrig=false;                                               //Is there an open position?
  string dir="";                                                     //Direction of the open position
  double OpenPrice;                                                  //Position Open price
  double t=cap;                                                      //Current balance
  double maxt=t;                                                     //Maximum balance
  double aDD=0.0;                                                    //Absolute drawdown
  double rDD=0.000001;                                               //Relative drawdown
  Stoch=iStochastic(s,tf,(int)MathRound(par1*MaxStochPeriod)+1,
                         (int)MathRound(par2*MaxStochPeriod)+1,
                         (int)MathRound(par3*MaxStochPeriod)+1,MODE_SMA,STO_CLOSECLOSE);
  StochTopLimit   =par4*100.0;
  StochBottomLimit=par5*100.0;
  dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
  leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
  contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
  b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
  for(from=b;from>=1;from--)                                         //Where to start copying of history
  {
    CopyBuffer(Stoch,0,from,count,StochBufferMain);
    CopyBuffer(Stoch,1,from,count,StochBufferSignal);
    if((StochBufferMain[0]>StochBufferSignal[0]&&StochBufferMain[1]<StochBufferSignal[1])||
       (StochBufferMain[0]<StochBufferSignal[0]&&StochBufferMain[1]>StochBufferSignal[1]))
    {
      if(FFtrig==true)
      {
        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;
        }
        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;
        }
        FFtrig=false;
      }
   }
    if(StochBufferMain[0]>StochBufferSignal[0]&&StochBufferMain[1]<StochBufferSignal[1]&&StochBufferMain[1]>StochTopLimit)
    {
      CopyOpen(s,tf,from,count,o);
      OpenPrice=o[1];
      dir="SELL";
      FFtrig=true;
    }
    if(StochBufferMain[0]<StochBufferSignal[0]&&StochBufferMain[1]>StochBufferSignal[1]&&StochBufferMain[1]<StochBottomLimit)
    {
      CopyOpen(s,tf,from,count,o);
      OpenPrice=o[1];
      dir="BUY";
      FFtrig=true;
    }
  }
  Print(TimeToString(TimeCurrent()),";","StrategyStoch:FFStoch",
        ";","K=",(int)MathRound(par1*MaxStochPeriod)+1,";","D=",(int)MathRound(par2*MaxStochPeriod)+1,
        ";","Slow=",(int)MathRound(par3*MaxStochPeriod)+1,";","TopLimit=",StochTopLimit,";","BottomLimit=",StochBottomLimit,
        ";","rDD=",rDD,";","Cap=",t);
  if(rDD<=trainDD) return(t); else return(0.0);
}

ストキャスティクスの適切度関数はメイン関数に対してシミュレーションされた残高を返します。それは遺伝的アルゴリズムに渡されます。ある時点で GA は最適化の終了を判断し、GetTrainResults() 関数を使用して戦略、シンボル、デポジットシェア、インディケータバッファのパラメータの最良の現在値を基本プログラムに返します。またこれ以降の現実のトレーディングに対するインディケータを作成します。

void GetTrainResults()                                               //Get the best parameters
{
  strat=(int)MathRound(Chromosome[GeneCount-2]*StratCount);          //Remember the best strategy
//For EA testing mode use the following code...
  z=(int)MathRound(Chromosome[GeneCount-1]*3);                       //Remember the best symbol
  switch(z)
  {
    case  0: {s="EURUSD"; break;};
    case  1: {s="GBPUSD"; break;};
    case  2: {s="USDCHF"; break;};
    case  3: {s="USDJPY"; break;};
    default: {s="EURUSD"; break;};
  }
//...for real mode, comment the previous code and uncomment the following one (symbols are selected in the MarketWatch window)
/*
  z=(int)MathRound(Chromosome[GeneCount-1]*(SymbolsTotal(true)-1));  //Remember the best symbol
  s=SymbolName(z,true);
*/
  optF=Chromosome[GeneCount];                                        //Remember the best deposit share
  switch(strat)
  {
    case  0: {GTRMA(   Chromosome[1],                                //The number of case strings must be equal to the number of strategies
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
    case  1: {GTRSAR(  Chromosome[1],
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
    case  2: {GTRStoch(Chromosome[1],
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
    default: {GTRMA(   Chromosome[1],
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
  }
  Print(TimeToString(TimeCurrent()),";","GAModule:GetTrainResults",
        ";","strat=",strat,";","s=",s,";","optF=",optF,
        ";",Chromosome[1],";",Chromosome[2],";",Chromosome[3],";",Chromosome[4],";",Chromosome[5]);
}

void GTRMA(double par1,double par2,double par3,double par4,double par5)
{
  MAshort=iMA(s,tf,(int)MathRound(par1*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
  MAlong =iMA(s,tf,(int)MathRound(par2*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
  CopyBuffer(MAshort,0,from,count,ShortBuffer);
  CopyBuffer(MAlong, 0,from,count,LongBuffer );
  Print(TimeToString(TimeCurrent()),";","StrategyMA:GTRMA",
        ";","MAL=",(int)MathRound(par2*MaxMAPeriod)+1,";","MAS=",(int)MathRound(par1*MaxMAPeriod)+1);
}

これですべてが実行している地点に戻りました (OnTick())。いまベスト戦略はなにか判り、それがマーケットに参加するべきときかどうか確認します。

bool NeedOpenMA()
{
  CopyBuffer(MAshort,0,0,count,ShortBuffer);
  CopyBuffer(MAlong, 0,0,count,LongBuffer );
  Print(TimeToString(TimeCurrent()),";","StrategyMA:NeedOpenMA",
        ";","LB[0]=",LongBuffer[0],";","LB[1]=",LongBuffer[1],";","SB[0]=",ShortBuffer[0],";","SB[1]=",ShortBuffer[1]);
  if(LongBuffer[0]>LongBuffer[1]&&ShortBuffer[0]>LongBuffer[0]&&ShortBuffer[1]<LongBuffer[1])
  {
    request.type=ORDER_TYPE_SELL;
    OpenPosition();
    return(false);
  }
  if(LongBuffer[0]<LongBuffer[1]&&ShortBuffer[0]<LongBuffer[0]&&ShortBuffer[1]>LongBuffer[1])
  {
    request.type=ORDER_TYPE_BUY;
    OpenPosition();
    return(false);
  }
  return(true);
}

サークルが絞られました。

それがどのように動作するか確認します。4つの主要な通貨ペア:EURUSD、GBPUSD、USDCHF、USDJPYで1時間の時間枠における2011年のレポートがあります。:

ストラテジーテスタレポート
InstaForex-Server (Build 567)
設定
エキスパート Main
シンボル: EURUSD
期間: H1 (2011.01.01 ~ 2011.12.31)
入力パラメータ: trainDD=0.50000000
maxDD=0.20000000
ブローカー: InstaForex Companies Group
通貨: USD
初期デポジット: 10 000.00
レバレッジ: 1:100
結果
履歴クオリティー: 100%
バー: 6197 ティック: 1321631
トータル収益: -538.74 粗利: 3 535.51 総損失: -4 074.25
プロフィットファクター: 0.87 予想損益: -89.79 マージンレベル: 85.71%
リカバリーファクター: -0.08 Sharpe比率: 0.07 OnTester 結果: 0
残高ドローダウン:
初期投資からの残高ドローダウン: 4 074.25 残高最大ドローダウン: 4 074.25 (40.74%) 初期投資からの残高ドローダウン: 40.74% (4 074.25)
資本ドローダウン:
初期投資からの資本ドローダウン: 4 889.56 資本最大ドローダウン: 6 690.90 (50.53%) 初期投資からの資本ドローダウン: 50.53% (6 690.90)
トータルトレード値: 6 ショートトレード(勝率%) : 6 (16.67%) ロングトレード(勝率%) : 0 (0.00%)
トータルトレード値: 12 収益トレード(トータルの% ): 1 (16.67%) 負けトレード(トータルの% ): 5 (83.33%)
最大収益トレード: 3 535.51 最大損失トレード: -1 325.40
平均収益トレード: 3 535.51 平均損失トレード: -814.85
最大連続勝利: 1 (3 535.51) 最大連続負け: 5 (-4 074.25)
最大連続収益(数): 3 535.51 (1) 最大連続損失(数): -4 074.25 (5)
平均連続勝利: 1 平均連続損失: 5


 

注文
開始時刻 注文 シンボル タイプ ボリューム 価格 S / L T / P 時刻 状態 備考
2011.01.03 01:002USDCHF売り28.21 / 28.210.93212011.01.03 01:00オープン成立
2011.01.03 03:003USDCHF買い28.21 / 28.210.93652011.01.03 03:00オープン成立
2011.01.03 06:004USDCHF売り24.47 / 24.470.93522011.01.03 06:00オープン成立
2011.01.03 09:005USDCHF買い24.47 / 24.470.93722011.01.03 09:00オープン成立
2011.01.03 13:006USDCHF売り22.99 / 22.990.93522011.01.03 13:00オープン成立
2011.01.03 16:007USDCHF買い22.99 / 22.990.93752011.01.03 16:00オープン成立
2011.01.03 18:008USDJPY売り72.09 / 72.0981.572011.01.03 18:00オープン成立
2011.01.03 21:009USDJPY買い72.09 / 72.0981.662011.01.03 21:00オープン成立
2011.01.04 01:0010USDJPY売り64.54 / 64.5481.672011.01.04 01:00オープン成立
2011.01.04 02:0011USDJPY買い64.54 / 64.5481.782011.01.04 02:00オープン成立
2011.10.20 21:0012USDCHF売り56.30 / 56.300.89642011.10.20 21:00オープン成立
2011.10.21 12:0013USDCHF買い56.30 / 56.300.89082011.10.21 12:00オープン成立
取り引き
時刻 取り引き シンボル タイプ 方向 ボリューム 価格 注文 コミッション スワップ 収益 残高 備考
2011.01.01 00:001残高0.000.0010 000.0010 000.00
2011.01.03 01:002USDCHF売りイン28.210.932120.000.000.0010 000.00
2011.01.03 03:003USDCHF買いアウト28.210.936530.000.00-1 325.408 674.60
2011.01.03 06:004USDCHF売りイン24.470.935240.000.000.008 674.60
2011.01.03 09:005USDCHF買いアウト24.470.937250.000.00-522.198 152.41
2011.01.03 13:006USDCHF売りイン22.990.935260.000.000.008 152.41
2011.01.03 16:007USDCHF買いアウト22.990.937570.000.00-564.027 588.39
2011.01.03 18:008USDJPY売りイン72.0981.5780.000.000.007 588.39
2011.01.03 21:009USDJPY買いアウト72.0981.6690.000.00-794.536 793.86
2011.01.04 01:0010USDJPY売りイン64.5481.67100.000.000.006 793.86
2011.01.04 02:0011USDJPY買いアウト64.5481.78110.000.00-868.115 925.75
2011.10.20 21:0012USDCHF売りイン56.300.8964120.000.000.005 925.75
2011.10.21 12:0013USDCHF買いアウト56.300.8908130.00-3.783 539.299 461.26
0.00 -3.78 -534.96 9 461.26
コピーライト 2001-2011, MetaQuotes Software Corp.

チャート上でマークされた箇所について説明します。(説明はログ分析から取っています。)

  1. Expert Advisorを起動したあと、遺伝的アルゴリズムが 28%のトレードデポジットシェアを持つUSDCHFにおいてブレークスルー戦略SAR を選択しました。それから1月3日の夕刻まで取引を行い、残高の 20% 以上を失い再度最適化を始めました。
  2. その後、Expert Advisor はUSDJPYについて SAR ブレークスルー取り引きを行うことを判断しましたがデポジット全体 (98%)を使いました。当然ロングではトレードできず、そのため1月4日朝に再び最適化が開始されました。
  3. 今回、それはUSDCHFにおいて再びデポジットを全額投じてゴールデンと移動平均のデッドクロスで取り引きする判断をしました。それから10月20日まで最初のデッドクロスを待ち、最大値で売り、損失をすべて取り返しました。その後、年末まで Expert Advisor はマーケットに参加する好ましい条件を見つけませんでした。

続く?

続けられるのでしょうか?Expert Advisorsの次世代はどんなものでしょうか?戦略を発見しそこからベストなものを選択する Expert Advisor です。さらに資金管理、より力強いハードウェア、チャネルを購入など。。

リスク警告

この短い書面 はすべてのリスクおよび信用取引における外為トレーディングの特殊な側面を完璧に開示するものではありません。トレーディングの性質とリスクに対する露出範囲を理解する必要があります。経験、目的、経済的リソース、その他関連のある状況を踏まえてトレーディングがご自身にとって適切なものであるか注意してよく考える必要があります。

外為マーケットは収益性があるだけでなく、リスクの高いものでもあります。信用取引に関しては、相対的に小さい為替相場の変動がトレーダーのアカウントに大きな影響を与え、初期デポジットやポジションオープンを確保するための追加デポジットと同じ額の損失を産む結果となりえます。資金に余裕のない場合は投資すべきではありません。トレードを決める前に、すべてのリスクとご自身の経験レベルを考慮することを忘れないでください必要であれば個別のアドバイスをあたってみてください。

ライセンス

モジュール UGAlib.mqh が開発され別名jooの Andrey Dik氏のBSD ライセンスの下で配布されています。

Expert Advisor と予備モジュールは本稿に添付されており、著者Roman Richのライセンスの下、配布されています。ライセンステキストはファイル Lic.txtで利用可能です。