
HTML レポートを使用したトレード結果の分析
イントロダクション
トレーダーが投資家に話を振るとき、トレード結果を確認したいと言われる可能性が高いです。 そのため、トレーダーは、結果を示すためにトレードヒストリーを提示することになります。 MetaTrader5 では、トレードヒストリーをファイルに保存することができます (ツールボックス-トレードタブ-コンテキストメニュー-レポート)。 レポートは、XLSX (Microsoft Excel で分析される) として、および任意のブラウザで表示できる HTML ファイルとして保存できます。 ブラウザは誰でも持っていますが、Excel は持っていない可能性があるため、2番目のオプションはより人気があります。 したがって、トレード結果を含む HTML レポートは、潜在的な投資家に適しています。
このようなレポートで使用できる標準メトリックに加えて、レポートから抽出できるトレードデータを使用して、独自の値を計算することもできます。 この記事では、HTML レポートからデータを抽出する方法について考察します。 まず、レポートの構造を分析し、解析する関数を記述します。 レポートの名前が関数に渡されます。 この関数は、関連するデータを持つ構造体を返します。 この構造により、任意のレポート値に直接アクセスできます。 この構造を使用すると、必要なメトリックを使用して、独自のレポートを簡単かつ迅速に生成できます。
トレードレポートに加えて、MetaTrader5 はEAのテストと最適化レポートを保存することができます。 トレードヒストリーと同様に、テストレポートは、XLSX と HTML の2つの形式で保存できますが、最適化レポートは XML で保存されます。
この記事では、html テストレポート、XML 最適化レポート、および html トレードヒストリーレポートについて説明します。
HTML および XML ファイル
HTML ファイルは、実際にはテキスト (表示されたデータ) とタグで構成されるテキストファイルであり、データの表示方法を示します (図1)。 任意のタグは、文字 "<" で始まり、 ">" で終わります。 たとえば、タグ<br>は、続くテキストが新しい行で始まる必要があることを意味します。<p>はテキストが新しい段落 (新しい行だけでなく、空の行の後) で始まる必要があることを意味します。 追加の属性は、タグ内に配置することができます。<p color=""red"">タグの後に続くテキストが新しい段落で始まり、赤で書かれていることを意味します。
図1. メモ帳で開かれた HTML ファイルの断片
タグアクションをキャンセルするには、開始タグに類似した閉じ札が使用され、さらに先頭にスラッシュ "/" が付きます。 例えば<p>段落の終了タグです。 一部のタグは、閉じることなく使用します。<br>などです。 一部のタグは、必要に応じて終了タグと共に使用できます。 前の段落を閉じることなく、新しいパラチャートを開始することができます。 ただし、上記の例のように、タグ内で color 属性が使用されている場合は、<p>、終了タグを使用して、さらにテキストの色分けをキャンセルする必要があります。 クローズが必要なタグがあります。<table>.</table>などです。 テーブルは常に終了タグで終わる必要があります。 テーブルは、開始によって示される行で構成されます。<tr>とクロージング</tr>タグです。 行内のセルは、<td>と</td>です。 セルにタグが付いている場合があります。<th> (ヘッダーセル)。</th> 1つのテーブルには、両方のセルにタグが付いています。<td>と<th>です。 行とセルの終了タグが必要です。
現在、ほとんどの html 属性は実際には使用されていません。 要素の外観が記述されている代わりに、1つの "style" 属性が使用します。 例えば:<p style=""color:" red"="">は、赤の段落を示しますが、一般的な方法ではありません。 最も一般的な方法は、スタイルクラス名だけが指定されている "class " 属性を使用することです。<p class=""small"">、クラス自体 (スタイルの説明) は HTML ドキュメントの先頭または別の CSS ファイル (カスケードスタイルシート) にあります。
XML ファイル (図2) は HTML によく似ています。 主な差は、HTML は厳密に限定されたタグセットをサポートし、XML ではタグセットの拡張とカスタムタグの追加を可能にすることです。
図2. メモ帳で開かれた XML ファイルの断片
HTML の主な目的はデータ表示であり、そに標準のタグセットがあります。 この HTML 文書が原因で、異なるブラウザでもほぼ同じ外観です。 XML の主な目的は、データを保存して渡すことであるため、任意のデータ構造の可能性が重要であり、このファイルを扱うユーザーは、その目的と抽出する必要のあるデータを理解する必要があります。
文字列関数または正規表現
通常、正規表現はテキストからデータを抽出するために使用します。 CodeBase では、式を操作するための正規表現ライブラリがあります。 また、使用する方法を説明する記事をチェックすることができます: "トレーダーの正規表現"。 もちろん、正規表現は、テキストデータを解析するための強力で便利なツールを構成します。 多くの場合、異なるタスクを実行しながら、データの解析を処理する必要がある場合は、間違いなく正規表現を使用する必要があります。
ただし、正規表現には欠点があります。使用する前に、適切に式を検討する必要があります。 正規表現に関連する全体のアイデアは広範です。 "必要なときに参照をチェックする" だけでは使用できません。 徹底的に理論を研究し、実践的なスキルがある必要があります。 正規表現の使用に必要な考え方は、通常のプログラミングタスクを解くために必要なものとは大きく異なると思います。 正規表現を使用する関数がある場合でも、正規表現でのタスクに切り替えるときに問題が発生することがあります。
データ解析タスクが難しくなく、頻繁でない場合は、標準の文字列関数を使用できます。 さらに、文字列関数がより速く動作するという意見があります。 おそらく、速度はタスクの種類と正規表現を適用する条件によって異なります。 ただし、文字列関数は、データ解析タスクの優れたソリューションを提供します。
言語リファレンスで利用可能なすべての文字列関数のうち、一部だけ必要です: Stringring()、 StringSubstr()、 StringReplace ()。 さらに、 StringLen()、 StringTrimLeft ()、 StringTrimRight() などのシンプルな関数を必要とするかもしれません。
テスターレポート
最大のデータ量はテスターレポートに含まれているので、ここから始めます。 これより、タスクを理解し、発生する可能性のあるすべての困難に対処することができます。 この記事を作成するとき、デフォルトのパラメータで定形EAテストレポートを使用しました (レポートは以下に添付されています)。 ブラウザでレポートを開き、扱っている内容を確認します (図3)。
図3. ブラウザで開かれた HTML のテストレポート。 青いテキストの赤い線は、メインレポートのセクションを示しています
レポートデータは、レポート名、パラメータ、結果、チャート、オーダートレード、合計の複数のセクションに分かれています。
各ブラウザにはページコードを表示するコマンドがあります: ページを右クリックしてコンテキストメニューを開き、[ページソースの表示] または同様のコマンドを選択します。
ソースコードの目視検査から、すべてのレポートデータがテーブルに配置されていることがわかります (<table>タグ)。 テスターレポートには2つのテーブルがあります。 最初のテーブルには一般的なデータが含まれています: レポート名から「平均ポジション保持時間 」値、すなわちオーダーセクションまで。 2番目の表には、オーダー、トレード、および最終資産の状態に関するデータがあります。 異なる型のデータは、複数のセルを結合する "colspan" 属性を使用して、1つのテーブル内に配置されます。 共通パラメータとその値の名前はテーブルの異なるセルにあり、セルは同じ行に表示され、場合によっては異なる行に配置されることがあります。
重要な箇所は、タグの一意の識別子である "id " 属性の可用性です。 id 値は、必要なデータを含むセルを検索するために使用できます。 ただし、 "id " 属性は使用されません。 そのため、行とセルを数えることによってパラメータを見つけることができます。 たとえば、"ストラテジーテスターレポート " という名前は、最初のテーブル、最初の行、および最初のセルにあります。
2番目のテーブルのオーダー数とトレード件数はすべてのレポートで異なりますので、オーダーの決済とトレードの開始を見つける必要があります。 1つのセルを持つ行が取引に従い、次にヘッダーを含む行になります (データを区切るためのインジケータです)。 ここでは、行とセルの数について詳しく説明しません。 便利な方法は後で提示します。
すべてのデータは、テーブルのセルに配置されているため、最初の段階で行う必要があるのは、テーブル、行、およびセルを抽出することだけです。
トレードヒストリーと最適化レポートを分析してみましょう。 ヒストリーレポートは、最終デポジット状態の追加セクションを除き、テストレポートとよく似ています (図4)。
図4. ブラウザで開かれた HTML のトレードヒストリーレポート。 青いテキストの赤い線は、メインレポートのセクションを示しています
トレードヒストリーの HTML コードを調べることで、セルが<td>タグだけでなく<th>で定義されていることがわかります。 さらに、すべてのデータが1つのテーブルに配置されます。
最適化レポートはシンプルなもので、1つのデータセクションにテーブルがあります。 ソースコードを使用して作成されていることがわかります、<Table>タグ、行が定義され<Row>、セルは<Cell>(すべてのタグは、適切な終了タグと一緒に使用されている) を定義します。
ファイルのダウンロード
レポートデータで操作を実行する前に、ファイルから読み取る必要があります。 次の関数を使用して、ファイルの内容全体を1つの文字列変数に返します。
bool FileGetContent(string aFileName,string & aContent){ int h=FileOpen(aFileName,FILE_READ|FILE_TXT); if(h==-1)return(false); aContent=""; while(!FileIsEnding(h)){ aContent=aContent+FileReadString(h); } FileClose(h); return(true); }
関数に渡される最初のパラメータは、レポートファイルの名前です。 2番目のパラメータは、ファイルの内容を格納する変数です。 この関数は、結果に応じて true/false を返します。
テーブルの抽出
2つの構造体を使用してテーブルデータを詳しく見ることができます。 1つのテーブル行を含む構造体:
struct Str{ string td[]; };
td [] 配列の各要素には、1つのセルの内容が含まれます。
テーブル全体 (すべての行) を含む構造体:
struct STable{
Str tr[];
};
このデータは、次のようにレポートから抽出されます。最初に、開始タグを使用してテーブルを詳しく見ることができます。 タグには属性がある場合があるので、タグを検索するのは "< table" です。 開始タグの先頭を見つけたら、「>」を探してみましょう。 次に、テーブルの終わり、つまり終了タグ "</table>" を詳しく見ます。 レポートにはネストされたテーブルがないため、つまり、各開始タグの後に終了タグが続くため、簡単です。
テーブルを検索した後で、行の先頭に "< tr" を使用し、最後に "</tr" を使って同じことを繰り返してみましょう。 次に、各行に "<td" and="" their="" end="" with=""></td">という先頭またはセルがあります。 セルは両方のタグを持つことができるので、このタスクは、行と少し複雑です<td>と<th>。 このため、StringFind() の代わりにカスタム TagFind() 関数を使用します。
int TagFind(string aContent,string & aTags[],int aStart,string & aTag){ int rp=-1; for(int i=0;i<ArraySize(aTags);i++){ int p=StringFind(aContent,"<"+aTags[i],aStart); if(p!=-1){ if(rp==-1){ rp=p; aTag=aTags[i]; } else{ if(p<rp){ rp=p; aTag=aTags[i]; } } } } return(rp); }
関数パラメータ:
- string aContent —検索する文字列。
- string & aTags[] — タグ付きの配列;
- int aStart — 検索を開始するポジション。
- string & aTag — 見つかったタグはレファレンスによって返されます。
StringFind() とは異なり、配列は検索された文字列ではなく TagFind() 関数に渡されます。 関数内の検索文字列に "< " を開くと追加されます。この関数は、最も近いタグのポジションを返します。
TagFind() を使用して、一貫して開始タグと終了タグを詳しく見ることができます。 間で見つかったすべてが配列に収集されます。
int TagsToArray(string aContent,string & aTags[],string & aArray[]){ ArrayResize(aArray,0); int e,s=0; string tag; while((s=TagFind(aContent,aTags,s,tag))!=-1 && !IsStopped()){ s=StringFind(aContent,">",s); if(s==-1)break; s++; e=StringFind(aContent,"</"+tag,s); if(e==-1)break; ArrayResize(aArray,ArraySize(aArray)+1); aArray[ArraySize(aArray)-1]=StringSubstr(aContent,s,e-s); } return(ArraySize(aArray)); }
関数パラメータ:
- string aContent —検索する文字列。
- string & aTags[] — タグ付きの配列;
- string aArray[] —見つかったすべてのタグの内容を含む配列が参照によって返されます。
検索したタグの配列は、セルを解析する場合にのみ TagsToArray() に渡されます。 その他の場合は、通常の文字列パラメータを使用して関数のアナログを記述します。
int TagsToArray(string aContent,string aTag,string & aArray[]){ string Tags[1]; Tags[0]=aTag; return(TagsToArray(aContent,Tags,aArray)); }
関数パラメータ:
- string aContent —検索する文字列。
- string & aTag — searched tag;
- string aArray[] —見つかったすべてのタグの内容を含む配列が参照によって返されます。
次に、テーブルの内容を抽出できる関数に進みましょう。 解析されたファイルの名前を持つ文字列パラメータ問い合わせるが関数に渡されます。 関数では、ファイルの内容のローカル文字列変数と、安定した構造体のローカル配列が使用します。
STable tables[];
string FileContent;
FileGetContent 関数を使用して、レポートの内容全体を取得します。
if(!FileGetContent(aFileName,FileContent)){ return(true); }
補助変数は次のとおりです。
string tags[]={"td","th"}; string ttmp[],trtmp[],tdtmp[]; int tcnt,trcnt,tdcnt;
セルタグの使用可能なオプションは、' tags ' 配列に用意されています。 テーブル、行、およびセルの内容は、それぞれ文字列配列 ttmp []、trtmp []、tdtmp [] に一時的に配置されます。
テーブルからのデータの取得:
tcnt=TagsToArray(FileContent,"table",ttmp); ArrayResize(tables,tcnt);
すべてのテーブルをループして行を抽出し、次に行からセルを抽出します。
for(int i=0;i<tcnt;i++){ trcnt=TagsToArray(ttmp[i],"tr",trtmp); ArrayResize(tables[i].tr,trcnt); for(int j=0;j<trcnt;j++){ tdcnt=TagsToArray(trtmp[j],tags,tdtmp); ArrayResize(tables[i].tr[j].td,tdcnt); for(int k=0;k<tdcnt;k++){ tables[i].tr[j].td[k]=RemoveTags(tdtmp[k]); } } }
テーブル、行、およびセルは、最初に一時配列に受け取られた後、セルの配列が構造体にコピーされます。 コピー中に、セルは RemoveTags() 関数によってフィルタリングされます。 テーブルセルには、ネストされたタグを使用できます。 RemoveTags() は必要なタグだけを残しながらを削除します。
RemoveTags() 関数は次のとおりです。
string RemoveTags(string aStr){ string rstr=""; int e,s=0; while((e=StringFind(aStr,"<",s))!=-1){ if(e>s){ rstr=rstr+StringSubstr(aStr,s,e-s); } s=StringFind(aStr,">",e); if(s==-1)break; s++; } if(s!=-1){ rstr=rstr+StringSubstr(aStr,s,StringLen(aStr)-s); } StringReplace(rstr," "," "); while(StringReplace(rstr," "," ")>0); StringTrimLeft(rstr); StringTrimRight(rstr); return(rstr); }
RemoveTags() 関数について考えてみましょう。 使用するデータの先頭には、 "s" 変数が使用します。 データは最初に行から始まるので、そのデータはまず0です。 開始山かっこ "< " は、タグの開始を意味し、"while" ループで検索されます。 タグの開始が検出されると、 "s" 変数で指定されたポジションから見つかったポジションまでのすべてのデータが、 "rstr" 変数にコピーされます。 その後、タグのトレーリングストップが検索され、有用なデータの新しい始まりになります。 ループの後、 "s" 変数の値が-1 に等しくない場合 (つまり、文字列は有用なデータで決済し、コピーされていないことを意味します)、データは "rstr " 変数にコピーされます。 関数の終わりでは、スペース文字 はシンプルなスペースに置き換えられますが、スペースの繰り返し、およびストリングの先頭とトレーリングストップのスペースは削除されます。
このステップでは、純粋なテーブルデータで埋められた構造体の "table" 配列があります。 この配列をテキストファイルに保存してみましょう。 保存中に、テーブル、行、およびセルの番号を設定します (データはfile 1.txt に保存されます)。
int h=FileOpen("1.txt",FILE_TXT|FILE_WRITE); for(int i=0;i<ArraySize(tables);i++){ FileWriteString(h,"table "+(string)i+"\n"); for(int j=0;j<ArraySize(tables[i].tr);j++){ FileWriteString(h," tr "+(string)j+"\n"); for(int k=0;k<ArraySize(tables[i].tr[j].td);k++){ FileWriteString(h," td "+(string)k+": "+tables[i].tr[j].td[k]+"\n"); } } } FileClose(h);
このファイルから、データがどのセルにあるかを理解できます。 以下はファイルの断片です:
table 0 tr 0 td 0: Strategy Test report tr 1 td 0: IMPACT-Demo (Build 1940) tr 2 td 0: tr 3 td 0: Settings tr 4 td 0:EA: td 1: ExpertMACD tr 5 td 0: Symbol: td 1: USDCHF tr 6 td 0: Period: td 1:2018.11。01 - 2018.12。01) tr 7 td 0: Parameters: td 1: Inp_Expert_Title=ExpertMACD tr 8 td 0: td 1: Inp_Signal_MACD_PeriodFast=12 tr 9 td 0: td 1: Inp_Signal_MACD_PeriodSlow=24 tr 10 td 0: td 1: Inp_Signal_MACD_PeriodSignal=9 tr 11
レポートデータの構造
ルーチンのビットです: レポートデータに対応する構造を記述し、テーブルの配列からのデータでこの構造を埋める必要があります。 このレポートはセクションに分かれています。 したがって、1つの一般的な構造に結合された構造を使用します。
設定セクションの構造:
struct SSettings{ string Expert; string Symbol; string Period; string Inputs; string Broker; string Currency; string InitialDeposit; string Leverage; };
構造フィールドの目的は、その名前からも明確です。 構造体のすべてのフィールドには、レポートに表示されるとおりのデータが含まれます。 エキスパートアドバイザのパラメータのリストは、1つの文字列変数 "input" にあります。
結果データセクションの構造:
struct SResults{ string HistoryQuality; string Bars; string Ticks; string Symbols; string TotalNetProfit; string BalanceDrawdownAbsolute; string EquityDrawdownAbsolute; string GrossProfit; string BalanceDrawdownMaximal; string EquityDrawdownMaximal; string GrossLoss; string BalanceDrawdownRelative; string EquityDrawdownRelative; string ProfitFactor; string ExpectedPayoff; string MarginLevel; string RecoveryFactor; string SharpeRatio; string ZScore; string AHPR; string LRCorrelation; string OnTesterResult; string GHPR; string LRStandardError; string TotalTrades; string ShortTrades_won_pers; string LongTrades_won_perc; string TotalDeals; string ProfitTrades_perc_of_total; string LossTrades_perc_of_total; string LargestProfitTrade; string LargestLossTrade; string AverageProfitTrade; string AverageLossTrade; string MaximumConsecutiveWins_cur; string MaximumConsecutiveLosses_cur; string MaximalConsecutiveProfit_count; string MaximalConsecutiveLoss_count; string AverageConsecutiveWins; string AverageConsecutiveLosses; string Correlation_Profits_MFE; string Correlation_Profits_MAE; string Correlation_MFE_MAE; string MinimalPositionHoldingTime; string MaximalPositionHoldingTime; string AveragePositionHoldingTime; };
1つのオーダーに関するデータの構造:
struct SOrder{ string OpenTime; string Order; string Symbol; string Type; string Volume; string Price; stringSL; stringTP; string Time; string State; string Comment; };
1つのトレードに関するデータの構造:
struct SDeal{ string Time; string Deal; string Symbol; string Type; string Direction; string Volume; string Price; string Order; string Commission; string Swap; string Profit; string Balance; string Comment; };
最終の資産状態の構造:
struct SSummary{ string Commission; string Swap; string Profit; string Balance; };
一般構造:
struct STestingReport{
SSettings Settings;
SResults Results;
SOrder Orders[];
SDeal Deals[];
SSummary Summary;
};
SOrder および SDeals 構造体配列は、オーダーおよびトレードに使用します。
データを使用した構造の埋め込み
日常的なタスクのこのビットも注意が必要です。 以前に受信したテキストファイルをテーブルデータで表示し、構造をインプットします。
aTestingReport.Settings.Expert=tables[0].tr[4].td[1]; aTestingReport.Settings.Symbol=tables[0].tr[5].td[1]; aTestingReport.Settings.Period=tables[0].tr[6].td[1]; aTestingReport.Settings.Inputs=tables[0].tr[7].td[1];
上記のコード例の直近の行では、インプットフィールドに値が割り当てられますが、その後、フィールドには1つのパラメータのデータのみが格納されます。 その後、他のパラメータが収集されます。 このパラメータは、行8から始まり、各パラメータ行の最初のセルは空です。 このループは、行の最初のセルが空である限り実行されます。
int i=8; while(i<ArraySize(tables[0].tr) && tables[0].tr[i].td[0]==""){ aTestingReport.Settings.Inputs=aTestingReport.Settings.Inputs+", "+tables[0].tr[i].td[1]; i++; }
下記は完全なテストレポート解析関数です。
bool TestingHTMLReportToStruct(string aFileName,STestingReport & aTestingReport){ STable tables[]; string FileContent; if(!FileGetContent(aFileName,FileContent)){ return(true); } string tags[]={"td","th"}; string ttmp[],trtmp[],tdtmp[]; int tcnt,trcnt,tdcnt; tcnt=TagsToArray(FileContent,"table",ttmp); ArrayResize(tables,tcnt); for(int i=0;i<tcnt;i++){ trcnt=TagsToArray(ttmp[i],"tr",trtmp); ArrayResize(tables[i].tr,trcnt); for(int j=0;j<trcnt;j++){ tdcnt=TagsToArray(trtmp[j],tags,tdtmp); ArrayResize(tables[i].tr[j].td,tdcnt); for(int k=0;k<tdcnt;k++){ tables[i].tr[j].td[k]=RemoveTags(tdtmp[k]); } } } //設定 セクション aTestingReport.Settings.Expert=tables[0].tr[4].td[1]; aTestingReport.Settings.Symbol=tables[0].tr[5].td[1]; aTestingReport.Settings.Period=tables[0].tr[6].td[1]; aTestingReport.Settings.Inputs=tables[0].tr[7].td[1]; int i=8; while(i<ArraySize(tables[0].tr) && tables[0].tr[i].td[0]==""){ aTestingReport.Settings.Inputs=aTestingReport.Settings.Inputs+", "+tables[0].tr[i].td[1]; i++; } aTestingReport.Settings.Broker=tables[0].tr[i++].td[1]; aTestingReport.Settings.Currency=tables[0].tr[i++].td[1]; aTestingReport.Settings.InitialDeposit=tables[0].tr[i++].td[1]; aTestingReport.Settings.Leverage=tables[0].tr[i++].td[1]; //結果セクション i+=2; aTestingReport.Results.HistoryQuality=tables[0].tr[i++].td[1]; aTestingReport.Results.Bars=tables[0].tr[i].td[1]; aTestingReport.Results.Ticks=tables[0].tr[i].td[3]; aTestingReport.Results.Symbols=tables[0].tr[i].td[5]; i++; aTestingReport.Results.TotalNetProfit=tables[0].tr[i].td[1]; aTestingReport.Results.BalanceDrawdownAbsolute=tables[0].tr[i].td[3]; aTestingReport.Results.EquityDrawdownAbsolute=tables[0].tr[i].td[5]; i++; aTestingReport.Results.GrossProfit=tables[0].tr[i].td[1]; aTestingReport.Results.BalanceDrawdownMaximal=tables[0].tr[i].td[3]; aTestingReport.Results.EquityDrawdownMaximal=tables[0].tr[i].td[5]; i++; aTestingReport.Results.GrossLoss=tables[0].tr[i].td[1]; aTestingReport.Results.BalanceDrawdownRelative=tables[0].tr[i].td[3]; aTestingReport.Results.EquityDrawdownRelative=tables[0].tr[i].td[5]; i+=2; aTestingReport.Results.ProfitFactor=tables[0].tr[i].td[1]; aTestingReport.Results.ExpectedPayoff=tables[0].tr[i].td[3]; aTestingReport.Results.MarginLevel=tables[0].tr[i].td[5]; i++; aTestingReport.Results.RecoveryFactor=tables[0].tr[i].td[1]; aTestingReport.Results.SharpeRatio=tables[0].tr[i].td[3]; aTestingReport.Results.ZScore=tables[0].tr[i].td[5]; i++; aTestingReport.Results.AHPR=tables[0].tr[i].td[1]; aTestingReport.Results.LRCorrelation=tables[0].tr[i].td[3]; aTestingReport.Results.tables[0].tr[i].td[5]; i++; aTestingReport.Results.GHPR=tables[0].tr[i].td[1]; aTestingReport.Results.LRStandardError=tables[0].tr[i].td[3]; i+=2; aTestingReport.Results.TotalTrades=tables[0].tr[i].td[1]; aTestingReport.Results.ShortTrades_won_pers=tables[0].tr[i].td[3]; aTestingReport.Results.LongTrades_won_perc=tables[0].tr[i].td[5]; i++; aTestingReport.Results.TotalDeals=tables[0].tr[i].td[1]; aTestingReport.Results.ProfitTrades_perc_of_total=tables[0].tr[i].td[3]; aTestingReport.Results.LossTrades_perc_of_total=tables[0].tr[i].td[5]; i++; aTestingReport.Results.LargestProfitTrade=tables[0].tr[i].td[2]; aTestingReport.Results.LargestLossTrade=tables[0].tr[i].td[4]; i++; aTestingReport.Results.AverageProfitTrade=tables[0].tr[i].td[2]; aTestingReport.Results.AverageLossTrade=tables[0].tr[i].td[4]; i++; aTestingReport.Results.MaximumConsecutiveWins_cur=tables[0].tr[i].td[2]; aTestingReport.Results.MaximumConsecutiveLosses_cur=tables[0].tr[i].td[4]; i++; aTestingReport.Results.MaximalConsecutiveProfit_count=tables[0].tr[i].td[2]; aTestingReport.Results.MaximalConsecutiveLoss_count=tables[0].tr[i].td[4]; i++; aTestingReport.Results.AverageConsecutiveWins=tables[0].tr[i].td[2]; aTestingReport.Results.AverageConsecutiveLosses=tables[0].tr[i].td[4]; i+=6; aTestingReport.Results.Correlation_Profits_MFE=tables[0].tr[i].td[1]; aTestingReport.Results.Correlation_Profits_MAE=tables[0].tr[i].td[3]; aTestingReport.Results.Correlation_MFE_MAE=tables[0].tr[i].td[5]; i+=3; aTestingReport.Results.MinimalPositionHoldingTime=tables[0].tr[i].td[1]; aTestingReport.Results.MaximalPositionHoldingTime=tables[0].tr[i].td[3]; aTestingReport.Results.AveragePositionHoldingTime=tables[0].tr[i].td[5]; //オーダー ArrayFree(aTestingReport.Orders); int ocnt=0; for(i=3;i<ArraySize(tables[1].tr);i++){ if(ArraySize(tables[1].tr[i].td)==1){ break; } ArrayResize(aTestingReport.Orders,ocnt+1); aTestingReport.Orders[ocnt].OpenTime=tables[1].tr[i].td[0]; aTestingReport.Orders[ocnt].Order=tables[1].tr[i].td[1]; aTestingReport.Orders[ocnt].Symbol=tables[1].tr[i].td[2]; aTestingReport.Orders[ocnt].Type=tables[1].tr[i].td[3]; aTestingReport.Orders[ocnt].Volume=tables[1].tr[i].td[4]; aTestingReport.Orders[ocnt].Price=tables[1].tr[i].td[5]; aTestingReport.Orders[ocnt].SL=tables[1].tr[i].td[6]; aTestingReport.Orders[ocnt].TP=tables[1].tr[i].td[7]; aTestingReport.Orders[ocnt].Time=tables[1].tr[i].td[8]; aTestingReport.Orders[ocnt].State=tables[1].tr[i].td[9]; aTestingReport.Orders[ocnt].Comment=tables[1].tr[i].td[10]; ocnt++; } //トレード i+=3; ArrayFree(aTestingReport.Deals); int dcnt=0; for(;i<ArraySize(tables[1].tr);i++){ if(ArraySize(tables[1].tr[i].td)!=13){ if(ArraySize(tables[1].tr[i].td)==6){ aTestingReport.Summary.Commission=tables[1].tr[i].td[1]; aTestingReport.Summary.Swap=tables[1].tr[i].td[2]; aTestingReport.Summary.Profit=tables[1].tr[i].td[3]; aTestingReport.Summary.Balance=tables[1].tr[i].td[4]; } break; } ArrayResize(aTestingReport.Deals,dcnt+1); aTestingReport.Deals[dcnt].Time=tables[1].tr[i].td[0]; aTestingReport.Deals[dcnt].Deal=tables[1].tr[i].td[1]; aTestingReport.Deals[dcnt].Symbol=tables[1].tr[i].td[2]; aTestingReport.Deals[dcnt].Type=tables[1].tr[i].td[3]; aTestingReport.Deals[dcnt].Direction=tables[1].tr[i].td[4]; aTestingReport.Deals[dcnt].Volume=tables[1].tr[i].td[5]; aTestingReport.Deals[dcnt].Price=tables[1].tr[i].td[6]; aTestingReport.Deals[dcnt].Order=tables[1].tr[i].td[7]; aTestingReport.Deals[dcnt].Commission=tables[1].tr[i].td[8]; aTestingReport.Deals[dcnt].Swap=tables[1].tr[i].td[9]; aTestingReport.Deals[dcnt].Profit=tables[1].tr[i].td[10]; aTestingReport.Deals[dcnt].Balance=tables[1].tr[i].td[11]; aTestingReport.Deals[dcnt].Comment=tables[1].tr[i].td[12]; dcnt++; } return(true); }
レポートファイルの名前が関数に渡されます。 また、関数に埋め込まれる STestingReport 構造体は、参照によって渡されます。
"Orders" と "Deals" のコメントで始まるコード部分に注意してください。 オーダーリストの開始行の番号は既に定義されていますが、オーダーリストのトレーリングストップは1つのセルを持つ行に基づいて決定されます。
if(ArraySize(tables[1].tr[i].td)==1){ break; }
3つの行は、オーダーの後にスキップされます。
//トレード i+=3;
その後、トレードに関するデータが収集されます。 トレードリストの最後は、6つのセルを持つ行によって決定されます-この行には、資産の最終ステータスに関するデータがあります。 ループを終了する前に、最終的なデポジット状態の構造が満たされます。
if(ArraySize(tables[1].tr[i].td)!=13){ if(ArraySize(tables[1].tr[i].td)==6){ aTestingReport.Summary.Commission=tables[1].tr[i].td[1]; aTestingReport.Summary.Swap=tables[1].tr[i].td[2]; aTestingReport.Summary.Profit=tables[1].tr[i].td[3]; aTestingReport.Summary.Balance=tables[1].tr[i].td[4]; } break; }
レポートデータを含む構造は完全に準備完了です。
トレードヒストリーレポート
トレードヒストリーレポートはストラテジーテスターレポートと同様に解析できますが、そこに含まれる最終的なデータ構造は異なります。
struct SHistory{
SHistoryInfo Info;
SOrder Orders[];
SDeal Deals[];
SSummary Summary;
SDeposit Deposit;
SHistoryResults Results;
};
SHistory 構造体には、次の構造体が含まれています: SHistoryInfo —一般的なアカウント情報、オーダーおよびトレードデータ構造を持つ配列、SSummary —トレード結果、SDeposit-最終資産状態、SHistoryResults —一般値 (利益、数ドローダウンなど)。
トレードレポートを解析するための完全な HistoryHTMLReportToStruct() 関数コードは以下に添付されています。 2つのパラメータが関数に渡されます。
- string FileName — レポートファイルの名前
- SHistory History — トレードレポートデータがインプットされる構造
最適化レポート
最適化レポートに関する最初の差は、異なるファイルタイプです。 レポートは ANSI で保存されるため、別の関数を使用して内容を読み取ります。
bool FileGetContentAnsi(string aFileName,string & aContent){ int h=FileOpen(aFileName,FILE_READ|FILE_TXT|FILE_ANSI); if(h==-1)return(false); aContent=""; while(!FileIsEnding(h)){ aContent=aContent+FileReadString(h); } FileClose(h); return(true); }
タグには別の差があります。 代わりに<table>,<tr>と<td>、次のタグを使用します。<Table>、<Row>および<Cell>。直近の主な差は、データ構造: です。
struct SOptimization{ string ParameterName[]; SPass Pass[]; };
この構造体には、最適化するパラメータの名前 (右端のレポート列) とSPass構造体の配列を含む文字列配列があります。
struct SPass{ string Pass; string Result; string Profit; string ExpectedPayoff; string ProfitFactor; string RecoveryFactor; string SharpeRatio; string Custom; string EquityDD_perc; string Trades; string Parameters[]; };
この構造には、レポート列に含まれるフィールドが含まれます。 直近のフィールドは文字列配列で、ParameterName() 配列の名前に従って最適化されたパラメータの値があります。
最適化レポートを解析するための OptimizerXMLReportToStruct() 関数の完全なコードは、以下に添付されています。 2つのパラメータが関数に渡されます。
- string FileName — レポートファイルの名前
- SOptimization Optimization — 最適化レポートデータがインプットされる構造
補助関数
この記事で作成されたすべての構造体と関数を1つのインクルードファイルに配置すると、レポートの解析は3行のコードで実装されます。
1. ファイルをインクルードします。
#include <HTMLReport.mqh>
2. 構造体を宣言します。
SHistory History;
3. 関数を呼び出します。
HistoryHTMLReportToStruct("ReportHistory-555849.html",History);
その後、すべてのレポートデータは、レポートに配置されているとおりに、対応する構造フィールドに配置されます。 すべてのフィールドは文字列型ですが、計算には使用できます。 そのためには、文字列を数値に変換する必要があります。 ただし、レポートフィールドと適切な構造フィールドには、2つの値があります。 たとえば、order ボリュームは、2つの値 "0.1/0.1 " で書き込まれ、最初の値はオーダーボリュームで、2番目の値はインプットされたボリュームを表します。 一部の合計には、2つの値 (メインと括弧内の追加値) もあります。 たとえば、トレード数は "11 (54.55%) " —実際の数と相対パーセント値として書くことができます。 したがって、このような値の使用をシンプル化するための補助関数を記述してみましょう。
個々のボリューム値を抽出するための関数:
string Volume1(string aStr){ int p=StringFind(aStr,"/",0); if(p!=-1){ aStr=StringSubstr(aStr,0,p); } StringTrimLeft(aStr); StringTrimRight(aStr); return(aStr); } string Volume2(string aStr){ int p=StringFind(aStr,"/",0); if(p!=-1){ aStr=StringSubstr(aStr,p+1); } StringTrimLeft(aStr); StringTrimRight(aStr); return(aStr); }
Volume1() 関数は、 "0.1/0.1 " のような文字列から最初のボリューム値を取得し、Volume2() は2番目の値を取得します。
値を抽出するための関数ダブルデータ:
string Value1(string aStr){ int p=StringFind(aStr,"(",0); if(p!=-1){ aStr=StringSubstr(aStr,0,p); } StringTrimLeft(aStr); StringTrimRight(aStr); return(aStr); } string Value2(string aStr){ int p=StringFind(aStr,"(",0); if(p!=-1){ aStr=StringSubstr(aStr,p+1); } StringReplace(aStr,")",""); StringReplace(aStr,"%",""); StringTrimLeft(aStr); StringTrimRight(aStr); return(aStr); }
Value1() 関数は、タイプが "8.02 (0.08%) " の文字列から最初の値を取得し、Value2() は2番目の値を取得し、閉じ括弧とパーセントシンボルがあれば削除します。
必要とされるもう一つの有用な関数は、テストレポート構造からパラメータに関するデータを変換するためのものです。 次の構造を使用して、便利な形式に変換します。
struct SInput{ string Name, string Value; }
Name フィールドはパラメータ名に使用され、値には Value を使用します。 値型は事前にわかっているので、値フィールドには文字列型があります。
次の関数は、パラメータを持つ文字列を構造体の SInputs 配列に変換します。
void InputsToStruct(string aStr,SInput & aInputs[]){ string tmp[]; string tmp2[]; StringSplit(aStr,',',tmp); int sz=ArraySize(tmp); ArrayResize(aInputs,sz); for(int i=0;i<sz;i++){ StringSplit(tmp[i],'=',tmp2); StringTrimLeft(tmp2[0]); StringTrimRight(tmp2[0]); aInputs[i].Name=tmp2[0]; if(ArraySize(tmp2)>1){ StringTrimLeft(tmp2[1]); StringTrimRight(tmp2[1]); aInputs[i].Value=tmp2[1]; } else{ aInputs[i].Value=""; } } }
パラメータを持つ文字列は、 ", " 文字に従って配列に分割されます。 次に、結果の配列の各要素は、文字 "= " に基づいて名前と値に分割されます。
これで、抽出されたデータを分析し、独自のレポートを生成する準備が整いました。
カスタムレポートの作成
レポートデータにアクセスできるようになったので、任意の方法で分析することができます。 多くのオプションがあります。 トレード結果を使用して、シャープレシオ、リカバリーファクタなどの一般的なメトリックを計算することができます。 カスタム html レポートを使用すると、すべての html、CSS、および JavaScript 関数にアクセスできます。 HTML を使用して、レポートを並べ替えることができます。 たとえば、オーダーやトレードのテーブルを作成したり、テーブルを分割したりできます。
CSS を使用すると、データの色分けが可能になり、レポートの分析が容易になります。 JavaScript はレポートを対話的に作成するのに役立ちます。 たとえば、トレード行にカーソルを合わせると、orders テーブルの適切なオーダー行が強調表示されます。 画像を生成し、HTML レポートに追加することができます。 必要に応じて、JavaScript は html5 のキャンバス要素内で直接画像を描画することができます。 すべてニーズ、想像力とスキルに依存します。
カスタム html トレードレポートを作成してみましょう。 1つのテーブルでオーダーとトレードを組み合わせてみましょう。 キャンセルされたオーダーは、関心のある情報を提供していないため、グレー表示になります。 また、オーダーの結果として実行されるトレードと同じ情報を提供するため、相場のオーダーにはグレーを使用します。 青と赤: トレードは明るい色で強調表示されます。 満たされた予約オーダーには、ピンクや青など目立つ色も必要です。
カスタムレポートテーブルには、オーダーテーブルとトレードテーブルのヘッダーが含まれます。 適切な配列を用意しましょう。
string TableHeader[]={ "Time", "Order", "Deal", "Symbol", "Type", "Direction", "Volume", "Price", "Order", "S/L", "T/P", "Time", "State", "Commission", "Swap", "Profit", "Balance", "Comment"};
レポートデータの構造が表示されます。
SHistory History;
HistoryHTMLReportToStruct("ReportHistory-555849.html",History);
生成されたレポートのファイルを開き、HTMLStart() 関数によって形成された HTML ページの標準の始まりを書きましょう。
int h=FileOpen("Report.htm",FILE_WRITE); if(h==-1)return; FileWriteString(h,HTMLStart("Report")); FileWriteString(h,"<table>\n"); FileWriteString(h,"\t<tr>\n"); for(int i=0;i<ArraySize(TableHeader);i++){ FileWriteString(h,"\t\t<th>"+TableHeader[i]+"</th>\n"); } FileWriteString(h,"\t</tr>\n");
HTMLStart() 関数コードを以下に示します。
string HTMLStart(string aTitle,string aCSSFile="style.css"){ string str="<!DOCTYPE html>\n"; str=str+"<html>\n"; str=str+"<head>\n"; str=str+"<link href=\""+aCSSFile+"\" rel=\"stylesheet\" type=\"text/css\">\n"; str=str+"<title>"+aTitle+"</title>\n"; str=str+"</head>\n"; str=str+"<body>\n"; return str; }
ページタイトルを含む文字列<title>タグ、およびスタイルを含むファイル名が関数に渡されます。
すべてのオーダーをループし、文字列の表示タイプを定義し、形成します。
int j=0; for(int i=0;i<ArraySize(History.Orders);i++){ string sc=""; if(History.Orders[i].State=="canceled"){ sc="PendingCancelled"; } else if(History.Orders[i].State=="filled"){ if(History.Orders[i].Type=="buy"){ sc="OrderMarketBuy"; } else if(History.Orders[i].Type=="sell"){ sc="OrderMarketSell"; } if(History.Orders[i].Type=="buy limit" || History.Orders[i].Type=="buy stop"){ sc="OrderPendingBuy"; } else if(History.Orders[i].Type=="sell limit" || History.Orders[i].Type=="sell stop"){ sc="OrderMarketSell"; } } FileWriteString(h,"\t<tr class=\""+sc+"\">\n"); FileWriteString(h,"\t\t<td>"+History.Orders[i].OpenTime+"</td>\n"); //時間 FileWriteString(h,"\t\t<td>"+History.Orders[i].Order+"</td>\n"); //オーダー FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //取引 FileWriteString(h,"\t\t<td>"+History.Orders[i].Symbol+"</td>\n"); //シンボル FileWriteString(h,"\t\t<td>"+History.Orders[i].Type+"</td>\n"); //型 FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //方向 FileWriteString(h,"\t\t<td>"+History.Orders[i].Volume+"</td>\n"); //ボリューム FileWriteString(h,"\t\t<td>"+History.Orders[i].Price+"</td>\n"); //価格 FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //オーダー FileWriteString(h,"\t\t<td>"+History.Orders[i].SL+"</td>\n"); //Sl FileWriteString(h,"\t\t<td>"+History.Orders[i].TP+"</td>\n"); // TP FileWriteString(h,"\t\t<td>"+History.Orders[i].Time+"</td>\n"); //時間 FileWriteString(h,"\t\t<td>"+History.Orders[i].State+"</td>\n"); //状態 FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); // Comission FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //スワップ FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //利益 FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //バランス FileWriteString(h,"\t\t<td>"+History.Orders[i].Comment+"</td>\n"); //コメント FileWriteString(h,"\t</tr>\n");
オーダーが満たされている場合は、適切なトレードを見つけ、その表示スタイルを定義し、その HTML コードを形成します。
//取引を探す if(History.Orders[i].State=="filled"){ for(;j<ArraySize(History.Deals);j++){ if(History.Deals[j].Order==History.Orders[i].Order){ sc=""; if(History.Deals[j].Type=="buy"){ sc="DealBuy"; } else if(History.Deals[j].Type=="sell"){ sc="DealSell"; } FileWriteString(h,"\t<tr class=\""+sc+"\">\n"); FileWriteString(h,"\t\t<td>"+History.Deals[j].Time+"</td>\n"); //時間 FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //オーダー FileWriteString(h,"\t\t<td>"+History.Deals[j].Deal+"</td>\n"); //取引 FileWriteString(h,"\t\t<td>"+History.Deals[j].Symbol+"</td>\n"); //シンボル FileWriteString(h,"\t\t<td>"+History.Deals[j].Type+"</td>\n"); //型 FileWriteString(h,"\t\t<td>"+History.Deals[j].Direction+"</td>\n"); //方向 FileWriteString(h,"\t\t<td>"+History.Deals[j].Volume+"</td>\n"); //ボリューム FileWriteString(h,"\t\t<td>"+History.Deals[j].Price+"</td>\n"); //価格 FileWriteString(h,"\t\t<td>"+History.Deals[j].Order+"</td>\n"); //オーダー FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //Sl FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); // TP FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //時間 FileWriteString(h,"\t\t<td>"+" "+"</td>\n"); //状態 FileWriteString(h,"\t\t<td>"+History.Deals[j].Commission+"</td>\n"); // Comission FileWriteString(h,"\t\t<td>"+History.Deals[j].Swap+"</td>\n"); //スワップ FileWriteString(h,"\t\t<td>"+History.Deals[j].Profit+"</td>\n"); //利益 FileWriteString(h,"\t\t<td>"+History.Deals[j].Balance+"</td>\n"); //バランス FileWriteString(h,"\t\t<td>"+History.Deals[j].Comment+"</td>\n"); //コメント FileWriteString(h,"\t</tr>\n"); break; } } }
その後、テーブルを閉じ、HTMLEnd() 関数を使用して形成された標準の HTML ページの終わりを追加します。
FileWriteString(h,"</table>\n"); FileWriteString(h,HTMLEnd("Report"));
HTMLEnd() 関数コード:
string HTMLEnd(){ string str="</body>\n"; str=str+"</html>\n"; return str; }
ここでは、スタイルファイルスタイル .css を記述するだけです。 css の学習はこの記事の範囲を超えているため、この点については詳しく説明しません。 以下に添付されているファイルを表示することができます。 また、添付ファイルには、レポートを作成するスクリプト HTMLReportCreate.mq5 があります。
準備完了レポートを次に示します。
図5. カスタム HTML レポートのフラグメント
結論
正規表現を使用する方が簡単かどうか疑問に思うかもしれません。 全体的な構造と原理は同じです。 テーブルの内容、行、およびセルを含む配列を個別に受け取ります。 TagsToArray() の代わりに、正規表現で関数を使用します。 残りの操作はよく似ています。
この記事で説明されているカスタムレポートの作成例は、レポートを表すためのオプションの1つにすぎません。 例としてのみ与えられます。 自身の便利で理解しやすい形式を使用することができます。 この記事の最も重要な結果は、すべてのレポートデータにアクセスできることです。
添付
- Include/HTMLReport.mqh —レポート解析関数を含むファイルをインクルード。
- Scripts/HTMLReportTest.mq5 — テスト、最適化、およびヒストリーレポートを解析するための HTMLReport の使用例。
- Scripts/HTMLReportCreate.mq5 — カスタム HTML レポート作成の例。
- Files/ReportTester-555849.html —ストラテジーテスターレポート。
- Files/ReportOptimizer-555849.xml —最適化レポート。
- Files/ReportHistory-555849.html —トレードヒストリーレポート。
- Files/Report.htm — HTMLReportCreate スクリプトを使用して作成されたレポートのファイル。
- Files/style.css — Report.htmのスタイルシート
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/5436





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索