記事「MetaTrader 5での取引の視覚的な評価と調整」についてのディスカッション

 

新しい記事「MetaTrader 5での取引の視覚的な評価と調整」はパブリッシュされました:

ストラテジーテスターは、単に自動売買ロボットのパラメータを最適化するだけでなく、さらに幅広い活用が可能です。本記事では、口座の取引履歴を事後に評価し、ストラテジーテスター上でポジションのストップロスを変更することで取引の調整をおこなう方法を紹介します。

次のような状況を想像してみましょう。ある口座では、比較的長期間にわたり、さまざまな金融商品で複数のエキスパートアドバイザー(EA)を使用して、場合によっては手動でも取引がおこなわれていました。そして現在、その成果を確認したいと考えています。もちろん、ターミナル上でAlt+Eキーを押すことで標準の取引レポートを確認することができます。また、チャート上に取引アイコンを表示し、ポジションのエントリーおよび決済時刻を確認することも可能です。しかし、取引の動きをより詳細に把握したい場合、ポジションがどのように開かれ、どのように閉じられたのかを見たい場合はどうでしょうか。各通貨ペアごと、またはすべてまとめて確認することもできます。開閉のタイミング、ストップ注文を置いたレベル、それが適切であったかどうかを把握することが可能です。さらに、「もしこうしていたら…」というシナリオを検討したい場合もあるでしょう。たとえば、異なるストップを設定した場合、異なるアルゴリズムや基準を用いた場合、トレーリングストップを使用した場合、あるいはストップをブレイクイーブンに移動した場合など、さまざまな「もし」を明確かつ視覚的にテストすることができます。

実は、このような課題を解決するための環境は既に整っています。必要なのは、口座の履歴、つまりすべての完了した取引をファイルに保存し、そのファイルを読み込んでポジションの開閉をおこなうEAをストラテジーテスターで実行するだけです。このEAに、ポジションの決済条件を変更するコードを追加すれば、取引がどのように変化するか、あるいは「もし…だったらどうなったか」を比較することができます。

この手法の利点は何でしょうか。口座上でしばらく稼働している取引の最適化や調整をおこなうための、もう一つのツールになるという点です。ビジュアルテストにより、特定の金融商品のポジションが正しく開かれたか、適切なタイミングで決済されたかを動的に確認できます。さらに、新しいアルゴリズムをEAに追加し、テストして結果を確認し、必要に応じて口座上で稼働しているEAを調整することも可能です。


作者: Artyom Trishkin

 

私は少し違ったアプローチをしている。各ロボットはトレードのログを書くので、スクリプトを作れば十分だ。それをシンボル・チャート上で実行すると、ロボットの番号(マジック)が要求され、このシンボルとこの番号を名前に持つファイルからチャートにトレードをロードする。この方法は、私にとって基本的に重要なネット取引を可能にします。また、テストモードで取引するロボットでも、取引を行わず、シミュレーションと計算だけを行い、結果をファイルに記録することができます。

 
この記事にはとても感謝している。
構造体にトランザクションの値とインジケータを記録するコードの断片が必要なんだ。
 
//+------------------------------------------------------------------+
//|| トランザクションの構造。トランザクションの履歴ファイルを作成するために使用される。
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // ディール・チケット
   long              order;                  // 取引開始の根拠となった順序
   long              pos_id;                 // ポジション識別子
   long              time_msc;               // ミリ秒単位の時間
   datetime          time;                   // 時間。
   double            volume;                 // ボリューム
   double            price;                  // 価格
   double            profit;                 // 利益
   double            commission;             // 取引手数料
   double            swap;                   // 決算時のスワップ累積額
   double            fee;                    // 取引が行われた直後に請求される。
   double            sl;                     // ストップ・ロス・レベル
   double            tp;                     // 利益確定レベル
   ENUM_DEAL_TYPE    type;                   // タイプ
   ENUM_DEAL_ENTRY   entry;                  // ポジション変更方法
   ENUM_DEAL_REASON  reason;                 // 取引を行った理由または情報源
   long              magic;                  // エキスパートID
   int               digits;                 // 数字
   ushort            symbol[16];             // 記号
   ushort            comment[64];            // 取引の解説
   ushort            external_id[256];       // (取引所の)外部取引システムにおけるディール識別子
   
//--- 文字列プロパティの設定
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- 文字列プロパティを返す
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
  };

//+------------------------------------------------------------------+
//| 履歴からトランザクションを配列に保存する。
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
//--- トランザクション構造
   SDeal deal={};
   
//--- 取引開始から現在までの取引履歴を要求する。 
   if(!HistorySelect(0, TimeCurrent()))
     {
      Print("HistorySelect() failed. Error ", GetLastError());
      return 0;
     }
   
//--- リストに含まれる案件の総数 
   int total=HistoryDealsTotal(); 

//--- 各トランザクションを処理する 
   for(int i=0; i<total; i++) 
     { 
      //--- 次のディールのチケットを取得する (ディールは自動的に選択され、プロパティが取得される)
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket==0)
         continue;
      
      //--- 貸借対照表と貿易取引のみ保存
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE)
         continue;
      
      //--- 構造体にトランザクションのプロパティを格納する
      deal.ticket=ticket;
      deal.type=deal_type;
      deal.order=HistoryDealGetInteger(ticket, DEAL_ORDER);
      deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY);
      deal.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket, DEAL_REASON);
      deal.time=(datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
      deal.time_msc=HistoryDealGetInteger(ticket, DEAL_TIME_MSC);
      deal.pos_id=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      deal.volume=HistoryDealGetDouble(ticket, DEAL_VOLUME);
      deal.price=HistoryDealGetDouble(ticket, DEAL_PRICE);
      deal.profit=HistoryDealGetDouble(ticket, DEAL_PROFIT);
      deal.commission=HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      deal.swap=HistoryDealGetDouble(ticket, DEAL_SWAP);
      deal.fee=HistoryDealGetDouble(ticket, DEAL_FEE);
      deal.sl=HistoryDealGetDouble(ticket, DEAL_SL);
      deal.tp=HistoryDealGetDouble(ticket, DEAL_TP);
      deal.magic=HistoryDealGetInteger(ticket, DEAL_MAGIC);
      deal.SetSymbol(HistoryDealGetString(ticket, DEAL_SYMBOL));
      deal.SetComment(HistoryDealGetString(ticket, DEAL_COMMENT));
      deal.SetExternalID(HistoryDealGetString(ticket, DEAL_EXTERNAL_ID));
      deal.digits=(int)SymbolInfoInteger(deal.Symbol(), SYMBOL_DIGITS);
      
      //--- 配列をインクリメントし
      int size=(int)array.Size();
      ResetLastError();
      if(ArrayResize(array, size+1, total)!=size+1)
        {
         Print("ArrayResize() failed. Error ", GetLastError());
         continue;
        }
      //--- トランザクションを配列に格納する
      array[size]=deal;
      //--- 有効な場合、保存されたトランザクションの説明をジャーナルに出力する。
      if(logs)
         DealPrint(deal, i);
     }
//--- 配列に格納されているディールの数を返す
   return (int)array.Size();
  }

なぜアーキテクチャーのロジックが無視されるのか理解できない。

  • 配列が要素ごとに満たされるなら、要素を満たす関数があるはずだ。
  • 要素が構造体なら、それ自体で満たされるはずだ。
  • 一度に配列の予備領域を確保するのであれば、なぜ充填のたびに「リサイズ」を行う必要があるのでしょうか?


例えば、このように書いてはどうだろうか?

//+------------------------------------------------------------------+
//|| トランザクションの構造。トランザクションの履歴ファイルを作成するために使用される。
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // ディール・チケット
   long              order;                  // 取引開始の根拠となった順序
   long              pos_id;                 // ポジション識別子
   long              time_msc;               // ミリ秒単位の時間
   datetime          time;                   // 時間。
   double            volume;                 // ボリューム
   double            price;                  // 価格
   double            profit;                 // 利益
   double            commission;             // 取引手数料
   double            swap;                   // 決算時のスワップ累積額
   double            fee;                    // 取引が行われた直後に請求される。
   double            sl;                     // ストップ・ロス・レベル
   double            tp;                     // 利益確定レベル
   ENUM_DEAL_TYPE    type;                   // タイプ
   ENUM_DEAL_ENTRY   entry;                  // ポジション変更方法
   ENUM_DEAL_REASON  reason;                 // 取引を行った理由または情報源
   long              magic;                  // エキスパートID
   int               digits;                 // 数字
   ushort            symbol[16];             // 記号
   ushort            comment[64];            // 取引の解説
   ushort            external_id[256];       // (取引所の)外部取引システムにおけるディール識別子
   
//--- 文字列プロパティの設定
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- 文字列プロパティを返す
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
   
   bool Set( const ulong _ticket )   
   {
      this.ticket=_ticket;
      this.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(_ticket, DEAL_TYPE);
      this.order=HistoryDealGetInteger(_ticket, DEAL_ORDER);
      this.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(_ticket, DEAL_ENTRY);
      this.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(_ticket, DEAL_REASON);
      this.time=(datetime)HistoryDealGetInteger(_ticket, DEAL_TIME);
      this.time_msc=HistoryDealGetInteger(_ticket, DEAL_TIME_MSC);
      this.pos_id=HistoryDealGetInteger(_ticket, DEAL_POSITION_ID);
      this.volume=HistoryDealGetDouble(_ticket, DEAL_VOLUME);
      this.price=HistoryDealGetDouble(_ticket, DEAL_PRICE);
      this.profit=HistoryDealGetDouble(_ticket, DEAL_PROFIT);
      this.commission=HistoryDealGetDouble(_ticket, DEAL_COMMISSION);
      this.swap=HistoryDealGetDouble(_ticket, DEAL_SWAP);
      this.fee=HistoryDealGetDouble(_ticket, DEAL_FEE);
      this.sl=HistoryDealGetDouble(_ticket, DEAL_SL);
      this.tp=HistoryDealGetDouble(_ticket, DEAL_TP);
      this.magic=HistoryDealGetInteger(_ticket, DEAL_MAGIC);
      this.SetSymbol(HistoryDealGetString(_ticket, DEAL_SYMBOL));
      this.SetComment(HistoryDealGetString(_ticket, DEAL_COMMENT));
      this.SetExternalID(HistoryDealGetString(_ticket, DEAL_EXTERNAL_ID));
      this.digits=(int)SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS);
      
      return((bool)this.time);
   }
  };

bool SetDeal( SDeal &deal, const ulong ticket )
{
  //--- 貸借対照表と貿易取引のみ保存
  const ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);

  return((deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) && deal.Set(ticket));
}

//+------------------------------------------------------------------+
//| 履歴からトランザクションを配列に保存する。
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
   int Amount = 0;
   
//--- 取引開始から現在までの取引履歴を要求する。 
   if(HistorySelect(0, INT_MAX))
   {
  //--- リストに含まれる案件の総数 
     const int total=ArrayResize(array, HistoryDealsTotal()); 
  
  //--- 各トランザクションを処理する 
     for(int i=0; i<total; i++) 
       if (SetDeal(array[Amount], HistoryDealGetTicket(i))) 
         Amount++;
   }

//--- 配列に格納されているディールの数を返す
   return(ArrayResize(array, Amount));
  }


私自身、模範的なコードの書き方とはほど遠い。しかし、どうにかしてもっと分別のある人間になろう。

 
fxsaber #:
  • エレメントが構造体である場合、それはセルフ・ポピュレーションでなければならない。

また始まった。

//+------------------------------------------------------------------+
//| トランザクションの説明を返す|
//+------------------------------------------------------------------+
string DealDescription(SDeal &deal, const int index)
  {
   string indexs=StringFormat("% 5d", index);
   if(deal.type!=DEAL_TYPE_BALANCE)
      return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.pos_id, deal.Symbol(), deal.magic, deal.digits, deal.price,
                          TimeToString(deal.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.digits, deal.sl, deal.digits, deal.tp));
   else
      return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.profit, AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.time)));
  }

なぜ構造体が勝手に印刷されないのか?

 
fxsaber #:
要素が構造体である場合、それは自己充填すべきである。

標準的なセルフパブリッシングとセルフフィリングの構造を示してください。

これらのコードは理解を簡単にするために書かれています。

たとえそれが機能するとしても、すべてを読みにくい1行の怪物で包むという目的はない。

記事は、コード記述の妙技ではなく、本質をできるだけ明確に伝えるために書かれている。名人芸のための特別なスレッドがあり、あなたはそこに参加している。そしてそれらは有用だ。

記事には別の目的がある。

 
Artyom Trishkin #:

標準的なセルフプリントとセルフフィリングの構造を示してください。

MQ構造について話しているのであれば、それらはMQL5では利用できない。作者は何が、そして誰がそれらを必要とするのか知ることができなかった。したがって、ユーザーが必要とする機能を標準構造体に付与するためのベースと、そこからの継承の可能性があるだけです。

これらのコードは、理解を簡単にするために書かれている。

たとえそれが機能するとしても、すべてを1行に詰め込んだ読めない怪物に包み込む目的はない。

そのようなことは提案しない。

記事は、コード記述の妙技ではなく、本質をできるだけ明確に伝えるために書かれる。名人芸のための特別なスレッドがあり、あなたはそこに参加している。そしてそれらは有用だ。

記事には別の目的がある。

名人芸は教科書の著者です。私は論理性を支持する。


もしある物体が、それ自体で助けなしに何かをすることができるなら、その物体はそれをすべきであり、その物体のためにすべきではない。コード記述のアーキテクチャには最小限の階層があるべきだ。あなたのコードでは、トランザクション・プロパティの複数の別々の配列を、構造体を使用したトランザクション・プロパティの単一の配列に置き換えることしか提案していない。さて、あなたは構造体からほとんど何も使用していない(読み書きと等化)。トランザクション・プロパティを扱う手続き的なスタイルは同じである。

 
fxsaber #:

もしMQ構造について話しているのであれば、それらはMQL5では利用できない。作者は何が、そして誰がそれを必要とするのかを知ることができなかった。したがって、ユーザーが必要とする機能を標準構造体に持たせるためのベースと、そこからの継承の可能性しかない。

私はそのようなものは提供しない。

バーチュオシティはチュートリアルの著者である。私は論理性を提唱する。


あるオブジェクトが、助けを借りずにそれ自体で何かできるのであれば、そのオブジェクトはそれをすべきなのであって、そのオブジェクトのために何かをすべきなのではない。コード記述のアーキテクチャには最小限の階層があるべきだ。あなたのコードでは、トランザクション・プロパティの複数の別々の配列を、構造体を使用したトランザクション・プロパティの単一の配列に置き換えることしか提案していない。さて、あなたは構造体からほとんど何も使用していない(読み書きと等化)。トランザクション・プロパティを扱う手続き的なスタイルは同じである。

私は、ライブラリーの記事で提案されている機能を使用しています。ここでは必要ありません。

 
//+------------------------------------------------------------------+
//|| ファイルを書き込み用に開き、ハンドルを返す。
//+------------------------------------------------------------------+
bool FileOpenToWrite(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- 成功
   return true;
  }
//+------------------------------------------------------------------+
//|| ファイルを開いて読み込み、ハンドルを返す。
//+------------------------------------------------------------------+
bool FileOpenToRead(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_READ|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- 成功
   return true;
  }
//+------------------------------------------------------------------+
//| 配列からトランザクションデータをファイルに保存する。
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size)
  {
//--- 空の配列が渡された場合、それを報告してfalseを返す。
   if(array.Size()==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
     
//--- ファイルを書き込み用に開き、そのハンドルを取得する。
   int handle=INVALID_HANDLE;
   if(!FileOpenToWrite(handle))
      return false;
   
//--- ファイルポインタをファイルの最後に移動する。 
   bool res=true;
   ResetLastError();
   res&=FileSeek(handle, 0, SEEK_END);
   if(!res)
      PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError());
   
//--- 配列データをファイルの最後に書き込む 
   file_size=0;
   res&=(FileWriteArray(handle, array)==array.Size());
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- ファイルを閉じる 
   FileClose(handle);
   return res;
  }
//+------------------------------------------------------------------+
//| ファイルから配列にトランザクションデータをロードする。
//+------------------------------------------------------------------+
bool FileReadDealsToArray(SDeal &array[], ulong &file_size)
  {
//--- ファイルを読み取り用に開き、そのハンドルを取得する。
   int handle=INVALID_HANDLE;
   if(!FileOpenToRead(handle))
      return false;
   
//--- ファイルポインタをファイルの最後に移動する。 
   bool res=true;
   ResetLastError();
   
//--- ファイルからデータを配列に読み込む
   file_size=0;
// res=(FileReadArray(handle, array)>0);
   res=(ArrayResize(array, FileReadArray(handle, array))>0);
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- ファイルを閉じる 
   FileClose(handle);
   return res;
  }

ハイライトして修正するミスが頻繁にある。

それにしても、なぜこのコードをすべて違うように書かないのだろうか?

//+------------------------------------------------------------------+
//| 配列からトランザクションデータをファイルに保存する。
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], long &file_size)
{
  return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0);
}

//+------------------------------------------------------------------+
//| ファイルから配列にトランザクションデータをロードする。
//+------------------------------------------------------------------+ 
bool FileReadDealsToArray2(SDeal &array[], long &file_size)
{
  return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0);
}
 
fxsaber #:
それにしても、なぜこのコードをすべて違うように書かないのだろう?

この記事は、かなり低いレベルのトレーニングを対象としている。読者の広い部分をカバーすることを意図している。経験豊富なベテランの人たちには、このような記事は必要ない。彼らには彼ら自身のヒゲがある )

 
Artyom Trishkin #:

この記事は、かなり低レベルのトレーニング向けのものである。幅広い読者に読んでもらうためである。

取引、自動取引システム、取引戦略のテストに関するフォーラム

MetaTrader 5 Strategy Tester:エラー、バグ、機能改善の提案

fxsaber, 2019.09.06 15:45

あなたが無視している良い機能

MqlTick tiks[];

if (FileLoad("deribit1.out.bin", ticks))
{
// ....