私は少し違ったアプローチをしている。各ロボットはトレードのログを書くので、スクリプトを作れば十分だ。それをシンボル・チャート上で実行すると、ロボットの番号(マジック)が要求され、このシンボルとこの番号を名前に持つファイルからチャートにトレードをロードする。この方法は、私にとって基本的に重要なネット取引を可能にします。また、テストモードで取引するロボットでも、取引を行わず、シミュレーションと計算だけを行い、結果をファイルに記録することができます。
//+------------------------------------------------------------------+ //|| トランザクションの構造。トランザクションの履歴ファイルを作成するために使用される。 //+------------------------------------------------------------------+ 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)); }
私自身、模範的なコードの書き方とはほど遠い。しかし、どうにかしてもっと分別のある人間になろう。
また始まった。
//+------------------------------------------------------------------+ //| トランザクションの説明を返す| //+------------------------------------------------------------------+ 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))); }
なぜ構造体が勝手に印刷されないのか?
MQ構造について話しているのであれば、それらはMQL5では利用できない。作者は何が、そして誰がそれらを必要とするのか知ることができなかった。したがって、ユーザーが必要とする機能を標準構造体に付与するためのベースと、そこからの継承の可能性があるだけです。
これらのコードは、理解を簡単にするために書かれている。
たとえそれが機能するとしても、すべてを1行に詰め込んだ読めない怪物に包み込む目的はない。
そのようなことは提案しない。
記事は、コード記述の妙技ではなく、本質をできるだけ明確に伝えるために書かれる。名人芸のための特別なスレッドがあり、あなたはそこに参加している。そしてそれらは有用だ。
記事には別の目的がある。
名人芸は教科書の著者です。私は論理性を支持する。
もしある物体が、それ自体で助けなしに何かをすることができるなら、その物体はそれをすべきであり、その物体のためにすべきではない。コード記述のアーキテクチャには最小限の階層があるべきだ。あなたのコードでは、トランザクション・プロパティの複数の別々の配列を、構造体を使用したトランザクション・プロパティの単一の配列に置き換えることしか提案していない。さて、あなたは構造体からほとんど何も使用していない(読み書きと等化)。トランザクション・プロパティを扱う手続き的なスタイルは同じである。
もし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); }
MetaTrader 5 Strategy Tester:エラー、バグ、機能改善の提案
fxsaber, 2019.09.06 15:45
あなたが無視している良い機能
MqlTick tiks[]; if (FileLoad("deribit1.out.bin", ticks)) { // ....
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
新しい記事「MetaTrader 5での取引の視覚的な評価と調整」はパブリッシュされました:
次のような状況を想像してみましょう。ある口座では、比較的長期間にわたり、さまざまな金融商品で複数のエキスパートアドバイザー(EA)を使用して、場合によっては手動でも取引がおこなわれていました。そして現在、その成果を確認したいと考えています。もちろん、ターミナル上でAlt+Eキーを押すことで標準の取引レポートを確認することができます。また、チャート上に取引アイコンを表示し、ポジションのエントリーおよび決済時刻を確認することも可能です。しかし、取引の動きをより詳細に把握したい場合、ポジションがどのように開かれ、どのように閉じられたのかを見たい場合はどうでしょうか。各通貨ペアごと、またはすべてまとめて確認することもできます。開閉のタイミング、ストップ注文を置いたレベル、それが適切であったかどうかを把握することが可能です。さらに、「もしこうしていたら…」というシナリオを検討したい場合もあるでしょう。たとえば、異なるストップを設定した場合、異なるアルゴリズムや基準を用いた場合、トレーリングストップを使用した場合、あるいはストップをブレイクイーブンに移動した場合など、さまざまな「もし」を明確かつ視覚的にテストすることができます。
実は、このような課題を解決するための環境は既に整っています。必要なのは、口座の履歴、つまりすべての完了した取引をファイルに保存し、そのファイルを読み込んでポジションの開閉をおこなうEAをストラテジーテスターで実行するだけです。このEAに、ポジションの決済条件を変更するコードを追加すれば、取引がどのように変化するか、あるいは「もし…だったらどうなったか」を比較することができます。
この手法の利点は何でしょうか。口座上でしばらく稼働している取引の最適化や調整をおこなうための、もう一つのツールになるという点です。ビジュアルテストにより、特定の金融商品のポジションが正しく開かれたか、適切なタイミングで決済されたかを動的に確認できます。さらに、新しいアルゴリズムをEAに追加し、テストして結果を確認し、必要に応じて口座上で稼働しているEAを調整することも可能です。
作者: Artyom Trishkin