MQL5 クックブック:ディールヒストリーのファイルへの書き込みと シンボルごとの残高チャートの Excel形式での作成
はじめに
さまざまなフォーラムのコミュニケーションの際、Microsoft Excel チャート形式のスクリーンショットとして表示される検証結果の例を多く使いました。そしてそのようなチャートの作成方法を教えてほしいと頻繁に質問を受けました。Excel はチャート作成の豊富な機能を備えており、それを話題にした書籍も数多くあります。本で必要な情報を見つけるにはそれをすべて読む必要があります。ついに本稿でそれをすべて説明する時間を得ました。
先行記 MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach およびMQL5 Cookbook: Developing a Multi-Currency Expert Advisor with Unlimited Number of Parameters 2件の中で MQL5でのマルチ通貨 EAを作成することを取り上げました。MetaTrader 5 での検証結果が一般的な残高/資金曲線として表示されることはわかっています。すなわちシンボルごとに個別に結果を閲覧する必要があれば、なんどもなんども Expert Advisor の外部変数に行き、結果が必要とされるシンボル以外のすべてを無効化し、それからふたたび検証を実行するということです。これでは不便です。
そのため今回私はマルチ通貨 Expert Advisor の集積結果と共にすべてのシンボルに対する残高チャートを数回クリックするだけで1枚の Excel 図上に取得するシンプルな方法をお伝えします。例を再構築するには 先行記事からマルチ通貨 Expert Advisor を取り入れます。検証完了時すべてのシンボルに対してディールヒストリーと残高曲線を .csvファイルに書きこむ関数で強化します。その上もう一つ別の行をレポートに追加しすべての極値からのドローダウンを表示します。
データファイルと連動することができるようExcel のブックを設定します。ブックはいつでも開くことができます。よって別の検証実行前にそれを閉じる必要はありません。検証完了時、レポートとチャート上の変化を確認できるよう特定のキーを押してデータのリフレッシュをするようにするだけです。
Expert Advisor の作成
われわれの EAには大きな変更はありません。関数を数個追加するだけです。メインファイルにシンボルバランスに対するストラクチャと配列を追加することから始めましょう。
//--- Arrays for balances struct Balance { double balance[]; }; //--- Array of balances for all symbols Balance symbol_balance[];
そして個別のインクルードファイル Report.mqh を検証レポートを作成する関数に作成し、それを Expert Advisor のメインファイル(下記コード行の強調表示を参照ください)にインクルードします。
//--- Include custom libraries #include "Include/Auxiliary.mqh" #include "Include/Enums.mqh" #include "Include/Errors.mqh" #include "Include/FileFunctions.mqh" #include "Include/InitializeArrays.mqh" #include "Include/Report.mqh" #include "Include/ToString.mqh" #include "Include/TradeFunctions.mqh" #include "Include/TradeSignals.mqh"
まずディールプロパティのストラクチャを作成します。それはすでにポジションおよびシンボルプロパティのプロジェクトで取得済みです。このためにEnums.mqh ファイルにプロパティ識別子の列挙を追加します。
//+------------------------------------------------------------------+ //| Enumeration of deal properties | //+------------------------------------------------------------------+ enum ENUM_DEAL_PROPERTIES { D_SYMBOL = 0, // Deal symbol D_COMMENT = 1, // Deal comment D_TYPE = 2, // Deal type D_ENTRY = 3, // Deal entry - entry in, entry out, reverse D_PRICE = 4, // Deal price D_PROFIT = 5, // Deal result (profit/loss) D_VOLUME = 6, // Deal volume D_SWAP = 7, // Cumulative swap on close D_COMMISSION = 8, // Deal commission D_TIME = 9, // Deal time D_ALL = 10 // All of the above mentioned deal properties };
その後、Further, in the Report.mqh ファイルに ディールプロパティのストラクチャとディールプロパティを返すGetHistoryDealProperties() 関数を作成します。関数はパラメータを2個受けつけます。ディールティケットとプロパティ識別子です。
以下にストラクチャと GetHistoryDealProperties() 関数のコードを確認することができます。
//--- Deal properties in the history struct HistoryDealProperties { string symbol; // Symbol string comment; // Comment ENUM_DEAL_TYPE type; // Deal type ENUM_DEAL_ENTRY entry; // Direction double price; // Price double profit; // Profit/Loss double volume; // Volume double swap; // Swap double commission; // Commission datetime time; // Time }; //--- Variable of deal properties HistoryDealProperties deal; //+------------------------------------------------------------------+ //| Gets deal properties by ticket | //+------------------------------------------------------------------+ void GetHistoryDealProperties(ulong ticket_number,ENUM_DEAL_PROPERTIES history_deal_property) { switch(history_deal_property) { case D_SYMBOL : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL); break; case D_COMMENT : deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT); break; case D_TYPE : deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE); break; case D_ENTRY : deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); break; case D_PRICE : deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE); break; case D_PROFIT : deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT); break; case D_VOLUME : deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME); break; case D_SWAP : deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP); break; case D_COMMISSION : deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION); break; case D_TIME : deal.time=(datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME); break; case D_ALL : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL); deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT); deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE); deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE); deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT); deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME); deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP); deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION); deal.time=(datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME); break; //--- default: Print("The passed deal property is not listed in the enumeration!"); return; } }
またディールプロパティを文字列値に変換する関数が複数必要です。これらシンプルな関数は渡される値が空かゼロならハイフン("-")を返します。ToString.mqh ファイルにそれらを書き込みます。
//+------------------------------------------------------------------+ //| Returns the symbol name, otherwise - dash | //+------------------------------------------------------------------+ string DealSymbolToString(string deal_symbol) { return(deal_symbol=="" ? "-" : deal_symbol); } //+------------------------------------------------------------------+ //| Converts deal type to string | //+------------------------------------------------------------------+ string DealTypeToString(ENUM_DEAL_TYPE deal_type) { string str=""; //--- switch(deal_type) { case DEAL_TYPE_BUY : str="buy"; break; case DEAL_TYPE_SELL : str="sell"; break; case DEAL_TYPE_BALANCE : str="balance"; break; case DEAL_TYPE_CREDIT : str="credit"; break; case DEAL_TYPE_CHARGE : str="charge"; break; case DEAL_TYPE_CORRECTION : str="correction"; break; case DEAL_TYPE_BONUS : str="bonus"; break; case DEAL_TYPE_COMMISSION : str="commission"; break; case DEAL_TYPE_COMMISSION_DAILY : str="commission daily"; break; case DEAL_TYPE_COMMISSION_MONTHLY : str="commission monthly"; break; case DEAL_TYPE_COMMISSION_AGENT_DAILY : str="commission agent daily"; break; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY : str="commission agent monthly"; break; case DEAL_TYPE_INTEREST : str="interest"; break; case DEAL_TYPE_BUY_CANCELED : str="buy canceled"; break; case DEAL_TYPE_SELL_CANCELED : str="sell canceled"; break; //--- Unknown deal type default : str="unknown"; } //--- return(str); } //+------------------------------------------------------------------+ //| Converts direction of deal to string | //+------------------------------------------------------------------+ string DealEntryToString(ENUM_DEAL_ENTRY deal_entry) { string str=""; //--- switch(deal_entry) { case DEAL_ENTRY_IN : str="in"; break; case DEAL_ENTRY_OUT : str="out"; break; case DEAL_ENTRY_INOUT : str="in/out"; break; case DEAL_ENTRY_STATE : str="status record"; break; //--- Unknown direction type default : str="unknown"; } //--- return(str); } //+------------------------------------------------------------------+ //| Converts volume to string | //+------------------------------------------------------------------+ string DealVolumeToString(double deal_volume) { return(deal_volume<=0 ? "-" : DoubleToString(deal_volume,2)); } //+------------------------------------------------------------------+ //| Converts price to string | //+------------------------------------------------------------------+ string DealPriceToString(double deal_price,int digits) { return(deal_price<=0 ? "-" : DoubleToString(deal_price,digits)); } //+------------------------------------------------------------------+ //| Converts deal result to string | //+------------------------------------------------------------------+ string DealProfitToString(string deal_symbol,double deal_profit) { return((deal_profit==0 || deal_symbol=="") ? "-" : DoubleToString(deal_profit,2)); } //+------------------------------------------------------------------+ //| Converts swap to string | //+------------------------------------------------------------------+ string DealSwapToString(double deal_swap) { return(deal_swap<=0 ? "-" : DoubleToString(deal_swap,2)); }
これでレポート用データを準備しそれをLastTest.csvファイルに書き込む CreateSymbolBalanceReport() 関数を書く準備はすべて整いました。ひじょうに簡単です。まずヘッダ(検証が複数のシンボルに対して行われたら文字列がどのように調整されるか注意します)を書きます。それからレポートに必要なディールプロパティがのちにファイルに書き込まれる文字列に連続して結合されます。
下記はCreateSymbolBalanceReport() 関数のコードです。
//+------------------------------------------------------------------+ //| Creates the test report on deals in .csv format | //+------------------------------------------------------------------+ void CreateSymbolBalanceReport() { int file_handle =INVALID_HANDLE; // File handle string path =""; // File path //--- If an error occurred when creating/getting the folder, exit if((path=CreateInputParametersFolder())=="") return; //--- Create file to write data in the common folder of the terminal file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON); //--- If the handle is valid (file created/opened) if(file_handle>0) { int digits =0; // Number of decimal places in the price int deals_total =0; // Number of deals in the specified history ulong ticket =0; // Deal ticket double drawdown_max =0.0; // Maximum drawdown double balance =0.0; // Balance //--- string delimeter =","; // Delimiter string string_to_write =""; // To generate the string for writing //--- Generate the header string string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE"; //--- If more than one symbol is involved, modify the header string if(SYMBOLS_COUNT>1) { for(int s=0; s<SYMBOLS_COUNT; s++) StringAdd(headers,","+InputSymbols[s]); } //--- Write the report headers FileWrite(file_handle,headers); //--- Get the complete history HistorySelect(0,TimeCurrent()); //--- Get the number of deals deals_total=HistoryDealsTotal(); //--- Resize the array of balances according to the number of symbols ArrayResize(symbol_balance,SYMBOLS_COUNT); //--- Resize the array of deals for each symbol for(int s=0; s<SYMBOLS_COUNT; s++) ArrayResize(symbol_balance[s].balance,deals_total); //--- Iterate in a loop and write the data for(int i=0; i<deals_total; i++) { //--- Get the deal ticket ticket=HistoryDealGetTicket(i); //--- Get all the deal properties GetHistoryDealProperties(ticket,D_ALL); //--- Get the number of digits in the price digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS); //--- Calculate the overall balance balance+=deal.profit+deal.swap+deal.commission; //--- Generate a string for writing via concatenation StringConcatenate(string_to_write, deal.time,delimeter, DealSymbolToString(deal.symbol),delimeter, DealTypeToString(deal.type),delimeter, DealEntryToString(deal.entry),delimeter, DealVolumeToString(deal.volume),delimeter, DealPriceToString(deal.price,digits),delimeter, DealSwapToString(deal.swap),delimeter, DealProfitToString(deal.symbol,deal.profit),delimeter, MaxDrawdownToString(i,balance,max_drawdown),delimeter, DoubleToString(balance,2)); //--- If more than one symbol is involved, write their balance values if(SYMBOLS_COUNT>1) { //--- Iterate over all symbols for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If the symbols are equal and the deal result is non-zero if(deal.symbol==InputSymbols[s] && deal.profit!=0) { //--- Display the deal in the balance for the corresponding symbol // Take into consideration swap and commission symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+ deal.profit+ deal.swap+ deal.commission; //--- Add to the string StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } //--- Otherwise write the previous value else { //--- If the deal type is "Balance" (the first deal) if(deal.type==DEAL_TYPE_BALANCE) { //--- the balance is the same for all symbols symbol_balance[s].balance[i]=balance; StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } //--- Otherwise write the previous value to the current index else { symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]; StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } } } } //--- Write the generated string FileWrite(file_handle,string_to_write); //--- Mandatory zeroing out of the variable for the next string string_to_write=""; } //--- Close the file FileClose(file_handle); } //--- If the file could not be created/opened, print the appropriate message else Print("Error creating file: "+IntegerToString(GetLastError())+""); }
上記コード内で強調表示されている MaxDrawdownToString() 関数は極値からのドローダウンをすべて計算し、新しい極値の時間を表す文字列を返します。その他の場合、この関数は "-" (ハイフン)を持つ文字列を返します。
//+------------------------------------------------------------------+ //| Returns the maximum drawdown from the local maximum | //+------------------------------------------------------------------+ string MaxDrawdownToString(int deal_number,double balance,double &max_drawdown) { //--- The string to be displayed in the report string str=""; //--- To calculate the local maximum and drawdown static double max=0.0; static double min=0.0; //--- If this is the first deal if(deal_number==0) { //--- No drawdown yet max_drawdown=0.0; //--- Set the initial point as the local maximum max=balance; min=balance; } else { //--- If the current balance is greater than in the memory if(balance>max) { //--- calculate the drawdown using the previous values max_drawdown=100-((min/max)*100); //--- update the local maximum max=balance; min=balance; } else { //--- Return zero value of the drawdown max_drawdown=0.0; //--- Update the minimum min=fmin(min,balance); } } //--- Determine the string for the report if(max_drawdown==0) str="-"; else str=DoubleToString(max_drawdown,2); //--- Return result return(str); }
レポート作成に必要な関数はすべて準備できました。上記すべての使い方を確認するだけです。これには検証完了時に呼ばれる OnTester() 関数が必要です。MQL5 参考資料で必ずこの関数の詳しい記述を確認してください。
レポート作成必要時 OnTester() 関数本文にただコードを数行書きます。以下は対応するコードのスニペットです。
//+------------------------------------------------------------------+ //| Handler of the event of testing completion | //+------------------------------------------------------------------+ double OnTester() { //--- Write the report only after testing if(IsTester() && !IsOptimization() && !IsVisualMode()) //--- Generate the report and write it to the file CreateSymbolBalanceReport(); //--- return(0.0); }
これでストラテジーテスタで Expert Advisor を実行すれば、検証の終わりで共通ターミナルフォルダに作成された Expert Advisor のフォルダが表示されます。それは C:\ProgramData\MetaQuotes\Terminal\Common\Filesです。そしてレポートファイル LastTest.csv が Expert Advisorのフォルダに作成されます。ノートパッドでこのファイルを開けば、それは次のような表示となります。
図1 .csv 形式のレポートファイル
Excelでのチャート作成
作成したファイルを Excel で開き、各データタイプが個別の行にあるか確認できます。データ表示方法は閲覧に都合のよいものです。この時点で技術的にはチャート作成およびファイルを Excel のブック形式 *.xlsx で保存する準備ができています。ただあとで検証を実行しブックを再び開くとまだ古いデータがあるのが判ります。
LastTest.csv ファイルがすでに Excelで使用された状態でデータをリフレッシュしようとすると、ファイルは更新されません。これは別のアプリケーションで使用されていると Expert Advisor がそのファイルに書きこむためにそれを開くことができないからです。
図2 Excel 2010での .csv形式のレポートファイル
われわれの場合それを使用できるようにする方法があります。まずお好きな任意のフォルダ *.xlsx 形式の Excel ブックを作成します。そしてそれを開きデータ タブに行きます。
図3 Excel 2010 2010のデータタブ
このタブのリボン上でテキストからオプションを選択します。"LastTest.csv"ファイル選択が必要な箇所にテキストファイルのインポートダイアログがポップアップします。ファイルを選択し開くボタンをクリックします。テキストインポートウィザード-ステップ1/3 ダイアログがポップアップし次のように表示されます。
図4 『テキストインポートウィザード-ステップ1/3』ダイアログ
上記のように設定を調整し 次へ>をクリックします。ここで(ステップ2/3)データファイル内で使用される区切り文字を指定する必要があります。われわれのファイルではそれは "," (コンマ)です。
図5 『テキストインポートウィザード-ステップ2/3』ダイアログ
テキストインポートウィザード-ステップ3/3に進むため次へ >をクリックします。ここですべての行に対するデータフォーマットとして 一般 のままにします。フォーマットはのちに変更することができます。
図6 『テキストインポートウィザード-ステップ3/3』ダイアログ
終了ボタンを押したらワークシートとデータインポートのセルを指定する必要のある箇所にインポートデータウィンドウが表示されます。
図7 Excel 2010でデータインポート用セルの選択
通常は一番上左の A1セルを選択します。OKをクリックする前にプロパティ... ボタンをクリックし外部データレンジプロパティを設定します。下のようなダイアログが表示されます。
図8 Excel 2010にテキストファイルからデータをインポートするときの「外部データレンジプロパティ」
上記に示されているように正確に設定を調整し、今表示されているウィンドウおよび次のウィンドウで OK をクリックします。
結果、ただ .csv をロードしたかのようにデータが表示されます。ただしここでは Excel のブックを閉じることなく MetaTrader 5で繰り返し検証を実行することが可能です。検証実行後必要なことはCtrl+Alt+F5 ショートカットまたは データ タブリボン上のすべてをリフレッシュ ボタンによってただデータをリフレッシュするだけです。
ホームタブリボン上の条件付きフォーマット オプションを利用するとデータ表現に必要な視覚プロパティの設定ができます。
図9 Excel 2010での条件付きフォーマット
ここで Excel チャートにデータを表示する必要があります。1件のチャートがすべての残高チャートを表示し、別のチャートがヒストグラムとして極値からのドローダウンをすべて表示します。
まず残高チャート用ダイアグラムを作成します。すべての残高のヘッダとその他配列全体を上から下まで選択します(Shift キーを押したままEnd キーを押し、それから下矢印 キーを押します)。そして挿入 タブで希望のチャートタイプを選択します。
図10 Excel 2010でのチャートタイプの選択
結果、便宜上別のワークシートに移動することができるチャートが作成されます。このためにはただそれを選択しCtrl+X(切り取り)を押します。それから新しく作成したワークシートに移動し、A1 セルを選んでCtrl+V(貼り付け)を押します。
作成されたチャートはデフォルトで下の画像内に表示されます。
図11 デフォルト設定チャートのルック&フィール
チャートのエレメントはどれもカスタマイズ(サイズ、色、スタイルその他の変更)できます。
上記の画像では横軸がディール数を表示しています。それを変更して代わりに日付を表示するようにしてみます。このためにはチャートを右クリックし、コンテクストメニューから データを選択するオプションを選びます。データソースを選択するダイアログがポップアップします。編集ボタンをクリックし、それからTIME 行で必要なデータ範囲を選択し、OKをクリックします。
図12 『データソースを選択する』ダイアログボックス
ご自身のドローダウンチャートを作成しそれを最初のチャートの下に入れてみます。これで必要に応じ視覚的プロパティをカスタマイズすることができます。個人的に私はたいていカスタマイズします。
図13 Excel 2010でカスタマイズされたチャート
おわりに
かなりまともに見える検証結果の Excel チャートを取得しました。後の記事でより有益なレポート作成方法をお伝えしたいと思います。みなさんが考察するための Expert Advisor ファイルを伴いダウンロードできるアーカイブが本稿に添付されています。
このファイルをアーカイブから抽出したら、MetaTrader 5\MQL5\ExpertsディレクトリのReportInExcelに入れます。その後、EventsSpy.mq5 インディケータは MetaTrader 5\MQL5\Indicators ディレクトリに入れる必要があります。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/651
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索