トレード戦略の色の最適化

Dmitry Fedoseev | 10 5月, 2019

イントロダクション

最適化後は、多種多様なパラメータセットのうち1つのみを選択する必要があります。 収益性、ドローダウン、リカバリファクター、またはパラメータの組み合わせ: そのようなセットを選択する際に使用する基準についての明確な答えはありません。 しかし、どのようにパラメータの組み合わせを評価するのでしょうか。

この記事では、ある実験をします。つまり、色の最適化の結果を行います。 色は、赤、緑、青 (RGB) のレベルの3つのパラメータによって決まります。 他にも3つのパラメータを使用した色分け方法があります。 したがって、3つのテストパラメータを1つの色に変換して、値を視覚的に表すことができます。 この記事の最後に、このような表現が役立つかどうかを確認します。

初期データ

HTML レポートを使用してトレード結果を分析の記事では、レポートファイル HTMLReport.mqh を解析する関数のライブラリを作成しました。 このライブラリには、最適化結果を持つ操作に設計された OptimizerXMLReportToStruct() 関数があります。 この関数を使用する2つのパラメータが関数に渡されます。

  • 文字列 aFileName は、最適化レポートを持つファイルの名前です。 このファイルは、ターミナルデータディレクトリの MQL5/Files フォルダで利用可能である必要があります。
  • SOptimization & aOptimization はリファレンスによって渡されます。 関数の実行後、レポートから抽出されたデータは、この構造体に配置します。

SOptimisation 構造体:

struct SOptimization{
   string ParameterName[];
   SPass Pass[];
};

この構造体には、文字列 ParameterName [] と SPass [] の2つの配列があります。 最適化されるパラメータの名前は ParameterName [] にあります。 主に2番目の配列 SPass [] に興味があります: この配列の1つの要素には、1つの最適化パスに関するデータがあります。

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[];
};

構造体項目:

  • Pass —最適化パスの数。
  • Result — 最適化後の最終的なバランス。
  • Profit — 結果の利益値;
  • ExpectedPayoff —期待されるペイオフ価値;
  • ExpectedPayoff —プロフィットファクタ値;
  • RecoveryFactor — リカバリファクタ;
  • SharpeRatio — シャープレシオ;
  • Custom — カスタムパラメータ;
  • EquityDD_perc — ドローダウン(%);
  • Trades —トレード数;
  • Parameters[] — 最適化されたパラメータの値を持つ配列。

トレード結果を分析するための最も一般的なパラメータを以下に示します。

  • 収益性、1トレードあたりの平均利益
  • ドローダウン、最大値に対するエクイティの減少
  • リカバリファクター, 最大ドローダウンに絶対的な利益の比率

まず、パラメータを使用します。 ただし、レポートには他の値が含まれているため、この使用性を提供する必要があります。 

任意のパラメータ選択を有効にするために、SPass を置き換えるための追加の構造体を作成します。 最適化パラメータは、この構造体内の二重配列に配置されます。 構造体を完全に書き直すことはありませんが、継承の利便性を使用します。 さて、実装に進みましょう:

1. ColorOptimization.mqh ファイルを作成します。 色付きのレポートを作成するためのすべての関数は、このファイルに配置されます。

2. ColorOptimization ファイルの先頭に HTMLReport.mqh ファイルを接続します。

#include <HTMLReport.mqh>

3. SPass 構造体のすべてのフィールドを継承し、factor[] および dParameters [] 配列を追加する新しい構造体を作成します。

struct SPass2:SPass{
   double factor[9];
   double dParameters[];  
};

どちらの配列も double 型です。 結果の9つの値は、factor[] 配列 (パス番号) と最適化されるパラメータを除くすべてに配置されます。 最適化パラメータの値は、sParameters [] 配列にあります。 すべてのデータは既に構造体で使用できますが、文字列形式で表示されるため、データを使用するたびに数値に変換する必要があります。 この配列は、便利な形式でデータを持つことができます。

4. 最適化データの最終的な構造体を作成します。

struct SOptimization2{
   string ParameterName[];
   SPass2 Pass[];
};

5. SOptimization 構造体から SOptimization2 にデータを変換する関数を作成します。

void ConvertOptimizationStruct(SOptimization & src,SOptimization2 & dst){

   ArrayCopy(dst.ParameterName,src.ParameterName);
   int cnt=ArraySize(src.Pass);
   ArrayResize(dst.Pass,cnt);   
   for(int i=0;i<cnt;i++){
      ArrayCopy(dst.Pass[i].Parameters,src.Pass[i].Parameters);
      
      dst.Pass[i].Pass=src.Pass[i].Pass;
      dst.Pass[i].Result=src.Pass[i].Result;
      dst.Pass[i].Profit=src.Pass[i].Profit;
      dst.Pass[i].ExpectedPayoff=src.Pass[i].ExpectedPayoff;
      dst.Pass[i].ProfitFactor=src.Pass[i].ProfitFactor;
      dst.Pass[i].RecoveryFactor=src.Pass[i].RecoveryFactor;
      dst.Pass[i].SharpeRatio=src.Pass[i].SharpeRatio;
      dst.Pass[i].Custom=src.Pass[i].Custom;
      dst.Pass[i].EquityDD_perc=src.Pass[i].EquityDD_perc;
      dst.Pass[i].Trades=src.Pass[i].Trades;

      dst.Pass[i].factor[0]=StringToDouble(src.Pass[i].Result);
      dst.Pass[i].factor[1]=StringToDouble(src.Pass[i].Profit);
      dst.Pass[i].factor[2]=StringToDouble(src.Pass[i].ExpectedPayoff);
      dst.Pass[i].factor[3]=StringToDouble(src.Pass[i].ProfitFactor);
      dst.Pass[i].factor[4]=StringToDouble(src.Pass[i].RecoveryFactor);
      dst.Pass[i].factor[5]=StringToDouble(src.Pass[i].SharpeRatio);
      dst.Pass[i].factor[6]=StringToDouble(src.Pass[i].Custom);
      dst.Pass[i].factor[7]=StringToDouble(src.Pass[i].EquityDD_perc);
      dst.Pass[i].factor[8]=StringToDouble(src.Pass[i].Trades);
      
      int pc=ArraySize(src.Pass[i].Parameters);
      
      ArrayResize(dst.Pass[i].dParameters,pc);
      
      for(int j=0;j<pc;j++){
         if(src.Pass[i].Parameters[j]=="true"){
            dst.Pass[i].dParameters[j]=1;
         }
         else if(src.Pass[i].Parameters[j]=="false"){
            dst.Pass[i].dParameters[j]=0;         
         }
         else{
            dst.Pass[i].dParameters[j]=StringToDouble(src.Pass[i].Parameters[j]);
         }
      }
   }   
}

このデータを持つデータ構造体は、最初のパラメータとして関数に渡され、新しい構造体は、2番目のパラメータとして参照によって返されます。 すべての最適化パスのループは関数内で実行されます。また、構造体の一部のフィールドはコピーされ、一部の項目タイプ変換は実行されます。 一般的なプロセスは複雑ではなく、関数コードから理解することができます。

factor[] 配列要素は、列挙体を使用してアクセスされます。

enum EOptimizatrionFactor{
   Result=0,
   Profit=1,
   ExpectedPayoff=2,
   ProfitFactor=3,
   RecoveryFactor=4,
   SharpeRatio=5,
   Custom=6,
   EquityDD_perc=7,
   Trades=8
};

列挙オプションの値は0から始まり、1ずつ増加するため、値を指定する必要はありません。 ただし、factor[] 配列と一致させることが重要であるため、値が示されています。 これは、プログラムへのさらなる改訂や追加で起こり得るエラーを回避するのに役立ちます。 

6. HTMLReport からの OptimizerXMLReportToStruct() に類似している SOptimization2 構造体にレポート・ファイルをロードするための関数を作成します。

bool OptimizerXMLReportToStruct2(string aFileName,SOptimization2 & aOptimization){
   SOptimization tmp;
   if(!OptimizerXMLReportToStruct(aFileName,tmp)){
      return(false);
   }
   ConvertOptimizationStruct(tmp,aOptimization);
   return(true);
}

レポート・ファイル名は、最初のパラメータとして関数に渡され、SOptimization2 構造体は2番目のパラメータとして戻されます。

これで、この記事の主なタスクを解決するための準備が整いました。 

カラーレポートの作成

カラーレポートを作成するための関数は、ColorOptimization.mqh に配置されます。 関数の呼び出しは、スクリプトから実行されます。

1. スクリプト ColorOptimization.mq5 を作成しましょう。

2. Mqh ColorOptimization を ColorOptimization に接続します。

#include <ColorOptimization.mqh>

3. スクリプトに外部パラメータを追加します。 最初に、プロパティウィンドウが存在することを示すプロパティを追加し、変数を追加します。

Property:

#property script_show_inputs

External variables:

input string               ReportName     =  "*.xml";
input string               OutputName     =  "ColorOptimization1-1.htm";
input EOptimizatrionFactor Factor1        =  Profit;
input EOptimizatrionFactor Factor2        =  EquityDD_perc;
input EOptimizatrionFactor Factor3        =  RecoveryFactor;
input bool                 Factor1Invert  =  false;
input bool                 Factor2Invert  =  true;
input bool                 Factor3Invert  =  false;
input bool                 Sort           =  true;

変数の説明:

  • ReportName - ソース最適化レポートファイルの名前。
  • OutputName —スクリプトによって作成されたレポートのファイル名。
  • Factor1 —レポートの色の決定に基づく最初の要素。
  • Factor2 —レポートの色の決定に基づく2番目の要素。
  • Factor3 —レポートの色の決定に基づく3番目の要素。
  • Factor1Invert —最初の要素を反転します。
  • Factor2Invert —2番目の要素を反転します。
  • Factor3Invert —3番目の要素を反転します。
  • Sort - 色の表示に従って最終レポートの並べ替えを有効にします。

4. スクリプトの OnStart() 関数では、SOptimisation2 型の変数を宣言し、ソースレポートデータを受信します。

SOptimization2 opt;

if(!OptimizerXMLReportToStruct2(ReportName,opt)){
   Alert("Error OptimizerXMLReportToStruct2");
   return;
}

5. RGB は多くの異なるカラーモデルの1つであるため、さらにライブラリの修正、特に他のカラーモデルの追加をしてみましょう。 そのため、RGB コンポーネントの値を計算するのではなく、0から1の抽象値を計算することから始めます。 次に、値を0から255までの RGB コンポーネントに変換します。 各最適化パスには個別のカラー表示を使用するため、カラーコンポーネントには SPass2の3つのフィールドを追加する必要があります。 3つのフィールドを追加する代わりに、1 3-要素配列を追加します。

double ColorComponent[3];

6. ColorOptimization の SolveColorComponents() 関数は、カラー成分を計算します。 次のパラメータを関数に渡す必要があります。

  • SOptimization2 & aOpt —ソース最適化レポートのデータ
  • int i1, int i2, int i3 —ソース最適化レポート値のインデックス (SPass 構造体のfactor[9] 配列)
  • bool r1 = false、bool r2 = false、bool r3 = false —値を反転するための変数

関数の実行後、SPass 構造体配列の ColorComponents [3] 配列に値がインプットされます。 

色の付いたコンポーネントの計算には、各パラメータの最小値と最大値を検索し、0 ~ 1 の範囲の値を計算する必要があります。 SolveColorComponents() 関数コード全体を以下に示します。

void SolveColorComponents(  SOptimization2 & aOpt,
                               int i1、int i2、int i3、
                              bool r1=false,bool r2=false,bool r3=false){
   
   double mx[3]={0,0,0};
   double mn[3]={DBL_MAX,DBL_MAX,DBL_MAX};
   
   int size=ArraySize(aOpt.Pass);
   
   for(int i=0;i<size;i++){
      mx[0]=MathMax(mx[0],aOpt.Pass[i].factor[i1]);
      mx[1]=MathMax(mx[1],aOpt.Pass[i].factor[i2]);
      mx[2]=MathMax(mx[2],aOpt.Pass[i].factor[i3]);
      mn[0]=MathMin(mn[0],aOpt.Pass[i].factor[i1]);
      mn[1]=MathMin(mn[1],aOpt.Pass[i].factor[i2]);
      mn[2]=MathMin(mn[2],aOpt.Pass[i].factor[i3]);      
   }

   double c1,c2,c3,d;
   
   for(int i=0;i<size;i++){      
   
      c1=0;
      c2=0;
      c3=0;
   
      d=mx[0]-mn[0];
      if(d!=0){
         c1=(aOpt.Pass[i].factor[i1]-mn[0])/d;
      }
      
      d=mx[1]-mn[1];
      if(d!=0){
         c2=(aOpt.Pass[i].factor[i2]-mn[1])/d; 
      }
      
      d=mx[2]-mn[2];
      if(d!=0){
         c3=(aOpt.Pass[i].factor[i3]-mn[2])/d;       
      }
      
      if(r1)c1=1.0-c1;
      if(r2)c2=1.0-c2;
      if(r3)c3=1.0-c3;
      
      aOpt.Pass[i].ColorComponent[0]=c1;
      aOpt.Pass[i].ColorComponent[1]=c2;      
      aOpt.Pass[i].ColorComponent[2]=c3;   
   }

}

スクリプトからこの関数を呼び出す方法は次のとおりです。

SolveColorComponents(opt,Factor1,Factor2,Factor3,Factor1Invert,Factor2Invert,Factor3Invert);

7. 外部スクリプトパラメータでソートが有効になっている場合は、ソートの係数を計算し、そのソートを実行する必要があります。 最適な最適化パスは、すべてのパラメータを組み合わせて最大値を持つものです。 パラメータが RGB に対応している場合、最良のオプションは白色です。 したがって、並べ替え係数は、3つの成分の算術平均として計算する必要があります。

SPass2 構造体にもう1つフィールドを追加してみましょう。

double SortFactor; 

並べ替え係数を計算する関数は、ColorOptimization.mqh ファイルに追加する必要があります。

void SolveSortFactor(SOptimization2 & aOpt){

   int size=ArraySize(aOpt.Pass);
   
   for(int i=0;i<size;i++){
      aOpt.Pass[i].SortFactor=0;
      for(int j=0;j<3;j++){
         aOpt.Pass[i].SortFactor+=aOpt.Pass[i].ColorComponent[j];
      }
      aOpt.Pass[i].SortFactor/=3;
   }
}

以下はソート関数です (バブルソートを使用)。

void SortFactorSort(SOptimization2 & aOpt){
   int size=ArraySize(aOpt.Pass);
   for(int i=size-1;i>0;i--){
      for(int j=0;j<i;j++){
         if(aOpt.Pass[j].SortFactor<aOpt.Pass[j+1].SortFactor){
            SPass2 tmp=aOpt.Pass[j];
            aOpt.Pass[j]=aOpt.Pass[j+1];
            aOpt.Pass[j+1]=tmp;
         }
      }
   }
}

これらの関数は、このスクリプトから呼び出します。 ソート係数はテーブルをソートするためだけでなく、ソート変数の値に関係なく SolveSortFactor() が呼び出されます。

SolveSortFactor(opt);
if(Sort){   
   SortFactorSort(opt);
}

これで、すべてのレポート作成の準備が整いました。 このレポートは2つのパートで構成されます。 最初の1つは、追加のカラーボタンを持つ最適化データテーブルのコピーです (図1.)。 2番目の部分は、最適化されたパラメータの各ペアについて複数の色付きの平面 (テーブル) から成り、各セルには、与えられた一組の最適化パラメータのテスト結果における変化を反映したスロープを表示します (図2)。

色を示すテーブル

追加のカラー表示を持つテーブルの作成は、TableContent() 関数で実行されます。 この関数は ColorOptimization.mqh ファイルにあり、テーブルの HTML コードを返します。

HTML テーブルの作成は簡単なタスクです。 カラー表示セルの色は、セルのスタイル (「background-color」属性) を指定することによって設定されます。 0 ~ 1 の範囲のカラーコンポーネントは、値を乗算することで 1 ~ 255 の範囲のコンポーネントに変換できます。 テーブルにより多くの視覚的な情報を提供するために、またはその色に対応する最適化パラメータについての詳細を追加してみましょう。 このデータは、[カラーインジケータ] 列の上のセルに指定され、パラメータの上位セルには適切な色 (図1) が含まれます。

色を示すレポートフラグメント
図1. 色を示すレポートフラグメント

TableContent() 関数全体を以下に示します。

string TableContent(SOptimization2 & aOpt,int i1,int i2,int i3){
   
   int size=ArraySize(aOpt.Pass);
     
   int pc=ArraySize(aOpt.ParameterName);

   int nc=ArraySize(co_names);
   
   string s="<table>";
   
   s=s+"<tr>";
   s=s+"<th>Pass</td>";
   
   for(int i=0;i<nc;i++){
      s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>";   
   }
   
   s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>";  
   
   for(int j=0;j<pc;j++){
      s=s+"<th>"+aOpt.ParameterName[j]+"</th>";       
   }
   s=s+"</tr>";     
   
   int r,g,b;
   
   for(int i=0;i<size;i++){    
   
      ComponentsToRGB(aOpt.Pass[i].ColorComponent[0],
                      aOpt.Pass[i].ColorComponent[1],
                      aOpt.Pass[i].ColorComponent[2],
                      r,g,b);
   
      s=s+"<tr>";
   
      s=s+"<td>"+aOpt.Pass[i].Pass+"</td>";
      s=s+"<td>"+aOpt.Pass[i].Result+"</td>";   
      s=s+"<td>"+aOpt.Pass[i].Profit+"</td>";         
      s=s+"<td>"+aOpt.Pass[i].ExpectedPayoff+"</td>";   
      s=s+"<td>"+aOpt.Pass[i].ProfitFactor+"</td>";               
      s=s+"<td>"+aOpt.Pass[i].RecoveryFactor+"</td>";        
      s=s+"<td>"+aOpt.Pass[i].SharpeRatio+"</td>";               
      s=s+"<td>"+aOpt.Pass[i].Custom+"</td>";   
      s=s+"<td>"+aOpt.Pass[i].EquityDD_perc+"</td>";        
      s=s+"<td>"+aOpt.Pass[i].Trades+"</td>";               
      
      string cs=RGBToStr(r,g,b);
      s=s+"<td title='"+cs+"' style='background-color: "+cs+"'>&nbsp</td>";        
      
      for(int j=0;j<pc;j++){
         s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>";       
      }

      s=s+"</tr>";   
   
   }
   
   s=s+"</table>";   

   return(s);
   
}

この関数をより詳細に考えてみましょう。 最適化パスの数を ' size ' 変数に受け取り、最適化されたパラメータの数をpc変数に受信し、パラメータ名 (グローバルレベルで宣言されている) を持つ配列のサイズをncに受信します。

int size=ArraySize(aOpt.Pass);
     
int pc=ArraySize(aOpt.ParameterName);

int nc=ArraySize(co_names);

グローバル配列 co_names []:

string co_names[]={"Result","Profit","Expected Payoff",
                   "Profit Factor","Recovery Factor",
                   "Sharpe Ratio","Custom","Equity DD","Trades"};

テーブルの HTML コードは、その形成時にs変数に追加されますので、変数の宣言時にテーブルの開始タグを追加します。

string s="<table>";

次に、行の先頭のタグと、 "Pass " のテキストを使用してヘッダの最初のセルを追加します。

s=s+"<tr>";
s=s+"<th>Pass</th>";

"Pass" 列の後には、パラメータを持つ列が続き、そのいずれかを使用してカラー表示を形成できます。 セルの HTML コードを確定:

for(int i=0;i<nc;i++){
   s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>";   
}

必要に応じて、HStyle() 関数は、セルの背景色を変更するコードを形成します。

string HStyle(int i,int i1,int i2,int i3){
   if(i==i1)return(" style='background-color: rgb(255,0,0);'");
   if(i==i2)return(" style='background-color: rgb(0,255,0);'");
   if(i==i3)return(" style='background-color: rgb(0,0,255);'");
   return("");
}

カラー表示ヘッダを持つセルのテキストを形成する:

s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>";

ColorCollHeader() 関数コード:

string ColorCollHeader(int i1,int i2,int i3){
   return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B");
}

次に、最適化されたパラメータの名前を含むセルの HTML コードを作成し、テーブルの行を終了します。

for(int j=0;j<pc;j++){
   s=s+"<th>"+aOpt.ParameterName[j]+"</th>";       
}
s=s+"</tr>";     

その後、3つの補助変数 ( rgb) が宣言されます。 この後に、すべてのレポート行の HTML コードが形成されるループが続きます。 RGB 成分値は、各ループの開始時に計算されます。

ComponentsToRGB(aOpt.Pass[i].ColorComponent[0],
                aOpt.Pass[i].ColorComponent[1],
                aOpt.Pass[i].ColorComponent[2],
                r,g,b);

ComponentsToRGB() 関数コード:

void ComponentsToRGB(double c1,double c2,double c3,int & r,int & g,int & b){
   r=(int)(c1*255.0);
   g=(int)(c2*255.0);
   b=(int)(c3*255.0);
}

次に、テスト結果を含むセルで行の HTML コードが形成されます。

s=s+"<tr>";
   
s=s+"<td>"+aOpt.Pass[i].Pass+"</td>";
s=s+"<td>"+aOpt.Pass[i].Result+"</td>";   
s=s+"<td>"+aOpt.Pass[i].Profit+"</td>";         
s=s+"<td>"+aOpt.Pass[i].ExpectedPayoff+"</td>";   
s=s+"<td>"+aOpt.Pass[i].ProfitFactor+"</td>";               
s=s+"<td>"+aOpt.Pass[i].RecoveryFactor+"</td>";        
s=s+"<td>"+aOpt.Pass[i].SharpeRatio+"</td>";               
s=s+"<td>"+aOpt.Pass[i].Custom+"</td>";   
s=s+"<td>"+aOpt.Pass[i].EquityDD_perc+"</td>";        
s=s+"<td>"+aOpt.Pass[i].Trades+"</td>";      

その後、カラー表示セルが来ます。 RGB コンポーネントは、最初に RGBToStr() 関数を使用して文字列に変換されます。その後、セルコードが形成されます。

string cs=RGBToStr(r,g,b);
s=s+"<td title='"+cs+"' style='background-color: "+cs+"'>&nbsp</td>"; 

RGBToStr() 関数コード:

string RGBToStr(int r,int g,int b){
   return("rgb("+(string)r+","+(string)g+","+(string)b+")");
}

最適化の下のパラメータ値を持つセルは、行の最後に表示されます。

for(int j=0;j<pc;j++){
   s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>";       
}

s=s+"</tr>"

テーブルが閉じられ、 s変数の内容が関数の最後に返されます。

s=s+"</table>";   

return(s);

最適化されたパラメータを持つ平面

平面は、2つ以上の最適化されたパラメータがある場合に描画することができます。 平面は図2に示されています。


図2. 最適化されたパラメータ平面

最初の線は、平面軸に対応するパラメータを示しています: Inp_Signal_MACD_PeriodSlow の値は X 軸 (水平方向) に沿って表示され、Inp_Signal_MACD_PeriodFast の値は Y 軸に沿って表示されます。 セル内のグラデーションは、他のパラメータが変更されたときに、X および Y パラメータのこの値のペアに対してテスト結果がどのように変化したかを示します。 最も悪い値の色が左側に表示され、最も良いのは右側になります。 ベスト、およびワーストは、抽象カラー成分の算術平均として計算され、前述のソート係数に基づいて決定されます。

平面の HTML コードは、Color2DPlanes() 関数で形成されます。 最適化された2つのパラメータのすべての可能な組み合わせがこの関数にあり、HTML プレーンコードが各ペアに対して生成されます。 Color2DPlanes() 関数コード:

string Color2DPlanes(SOptimization2 & aOpt){
   string s="";
   int pc=ArraySize(aOpt.ParameterName);
   for(int y=0;y<pc;y++){
      for(int x=y+1;x<pc;x++){
         s=s+Color2DPlane(aOpt,x,y);         
      }   
   }
   return(s);
}

Color2DPlane() 関数では、1つのプレーンの HTML コードが形成されます。

string Color2DPlane(SOptimization2 & aOpt,int xi,int yi){

   double xa[];
   double ya[];
   
   int cnt=ArraySize(aOpt.Pass);

   ArrayResize(xa,cnt);
   ArrayResize(ya,cnt);
   
   for(int i=0;i<cnt;i++){
      xa[i]=aOpt.Pass[i].dParameters[xi];
      ya[i]=aOpt.Pass[i].dParameters[yi];      
   }
   
   ArraySort(xa);
   ArraySort(ya);
   
   int xc=1;
   int yc=1;
   
   for(int i=1;i<cnt;i++){
      if(xa[i]!=xa[i-1]){
         xa[xc]=xa[i];
         xc++;
      }
      if(ya[i]!=ya[i-1]){
         ya[xc]=ya[i];
         yc++;
      }
   }   

   string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>";


   s=s+"<tr>";   
      s=s+"<td>&nbsp;</td>";
      for(int x=0;x<xc;x++){
         s=s+"<td>"+(string)xa[x]+"</td>";
      }
   s=s+"</tr>";   
   for(int y=0;y<yc;y++){
      
      s=s+"<tr>";
      
      s=s+"<td>"+(string)ya[y]+"</td>";
      for(int x=0;x<xc;x++){

         double mx=0;
         double mn=DBL_MAX;
         int mxi=0;
         int mni=0; 
         
         for(int i=0;i<cnt;i++){
            if(aOpt.Pass[i].dParameters[yi]==ya[y] && 
               aOpt.Pass[i].dParameters[xi]==xa[x]
            ){
               if(aOpt.Pass[i].SortFactor>mx){
                  mx=aOpt.Pass[i].SortFactor;
                  mxi=i;
               }
               if(aOpt.Pass[i].SortFactor<mn){
                  mn=aOpt.Pass[i].SortFactor;
                  mni=i;
               }
            }
         }
         
         int mnr,mng,mnb;
         int mxr,mxg,mxb;
         
         ComponentsToRGB(aOpt.Pass[mni].ColorComponent[0],
                         aOpt.Pass[mni].ColorComponent[1],
                         aOpt.Pass[mni].ColorComponent[2],
                         mnr,mng,mnb);
                         
         ComponentsToRGB(aOpt.Pass[mxi].ColorComponent[0],
                         aOpt.Pass[mxi].ColorComponent[1],
                         aOpt.Pass[mxi].ColorComponent[2],
                         mxr,mxg,mxb);         
        
         string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
               
         int digits[]={2,2,6,6,6,6,6,4,0};

         for(int k=0;k<ArraySize(co_names);k++){
            title=title+co_names[k]+": "+DoubleToString(aOpt.Pass[mni].factor[k],digits[k])+
            "/"+DoubleToString(aOpt.Pass[mxi].factor[k],digits[k])+"\n";
         }

         s=s+"<td title='"+title+"' style='background: linear-gradient(to right, rgb("+
         (string)mnr+","+(string)mng+","+(string)mnb+"), rgb("+
         (string)mxr+","+(string)mxg+","+(string)mxb+"));'>&nbsp;"+
         (string)mni+"-"+(string)mxi+"</td>";
      }
      s=s+"</tr>";
   }
   
   s=s+"<table>";   

   return(s);

}

より詳細に Color2DPlane() 関数を考えてみましょう。 次の関数は、最適化レポートからのすべてのデータと、平面が作成される最適化されたパラメータのペアのインデックスである2つの int 変数xiYiを含む SOptimization2 構造体にインプットされています。 最初に、パラメータの各ペアのすべての可能な値を配列に収集します。 2つの配列を宣言し、最適化パスの数に従ってサイズを変更し、可能なすべての値で埋めます:

double xa[];
double ya[];

int cnt=ArraySize(aOpt.Pass);

ArrayResize(xa,cnt);
ArrayResize(ya,cnt);

for(int i=0;i<cnt;i++){
   xa[i]=aOpt.Pass[i].dParameters[xi];
   ya[i]=aOpt.Pass[i].dParameters[yi];      
}

パラメータの一意の値のみを使用して配列を並べ替え、配列の先頭に一意の値を移動します。

ArraySort(xa);
ArraySort(ya);

int xc=1;
int yc=1;

for(int i=1;i<cnt;i++){
   if(xa[i]!=xa[i-1]){
      xa[xc]=xa[i];
      xc++;
   }
   if(ya[i]!=ya[i-1]){
      ya[xc]=ya[i];
      yc++;
   }
}   

その後、 xc変数には1つのパラメータの一意の値の数が含まれ、 ycには他のパラメータのものがあります。 プレーンの HTML コードは、その形成中にs変数に追加されます。 S変数宣言の間に、変数の名前とテーブルの開始タグについての情報を追加してみましょう。

string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>";

X パラメータ値を含む最初のテーブル行を作成してみましょう。

s=s+"<tr>";   
   s=s+"<td>&nbsp;</td>";
   for(int x=0;x<xc;x++){
      s=s+"<td>"+(string)xa[x]+"</td>";
   }
s=s+"</tr>"; 

Yパラメータのすべての亜種をループした後:

for(int y=0;y<yc;y++){

このループでは、各パスで行を開始し、 yパラメータ値を持つセルを追加します。

s=s+"<tr>";
      
s=s+"<td>"+(string)ya[y]+"</td>";

次に、グラデーション付きのセルを追加します (すべてのxパラメータ亜種をループで追加します)。

for(int x=0;x<xc;x++){

グラデーションを作成するには、最適と最低の最適化パスを見つける必要があります。

double mx=0;
double mn=DBL_MAX;
int mxi=0;
int mni=0; 

for(int i=0;i<cnt;i++){
   if(aOpt.Pass[i].dParameters[yi]==ya[y] && 
      aOpt.Pass[i].dParameters[xi]==xa[x]
   ){
      if(aOpt.Pass[i].SortFactor>mx){
         mx=aOpt.Pass[i].SortFactor;
         mxi=i;
      }
      if(aOpt.Pass[i].SortFactor<mn){
         mn=aOpt.Pass[i].SortFactor;
         mni=i;
      }
   }
}

このコード部分が実行された後、 mxi変数とmni値にはベストおよびワーストの最適化パスのインデックスが含まれることになります。 

抽象カラーコンポーネントは RGB に変換する必要があります。

ComponentsToRGB(aOpt.Pass[mni].ColorComponent[0],
                aOpt.Pass[mni].ColorComponent[1],
                aOpt.Pass[mni].ColorComponent[2],
                mnr,mng,mnb);
                         
ComponentsToRGB(aOpt.Pass[mxi].ColorComponent[0],
                aOpt.Pass[mxi].ColorComponent[1],
                aOpt.Pass[mxi].ColorComponent[2],
                mxr,mxg,mxb);  

平面をより効率的に分析するには、ヒントを追加します (HTML 属性 ' title ' を使用して追加できます)。

string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
      
int digits[]={2,2,6,6,6,6,6,4,0};

for(int k=0;k<ArraySize(co_names);k++){
   title=title+co_names[k]+": "+DoubleToString(aOpt.Pass[mni].factor[k],digits[k])+
   "/"+DoubleToString(aOpt.Pass[mxi].factor[k],digits[k])+"\n";
}

タイトルは図3に示されています。


図3. プレーンの1つのセルに対するツールチップ

ツールチップには、最悪かつ最適な最適化パス (ワースト/ベスト) に関するすべてのデータがあります。 RGB グラデーションのコンポーネント値は、ツールチップの最初の行に表示されます。 

次に、グラデーションの最も重要な部分に進みます。

s=s+"<td title='"+title+"' style='background: linear-gradient(to right, rgb("+
(string)mnr+","+(string)mng+","+(string)mnb+"), rgb("+
(string)mxr+","+(string)mxg+","+(string)mxb+"));'>&nbsp;"+
(string)mni+"-"+(string)mxi+"</td>";

グラデーション表示は、次の web ブラウザで確認されました: Opera、Google Chrome、Yandex、マイクロソフトエッジ。 これらすべてで、うまく動作します。

各行の末尾に行エンド タグを追加します。

s=s+"</tr>";

表の最後に、表の終了タグを追加し、形成された HTML コードを返します。

s=s+"<table>";   

return(s);

次に、スクリプトから関数を呼び出してみましょう。

string report=HTMLStart("Color Optimization","style2.css")+
TableContent(opt,Factor1,Factor2,Factor3)+
Color2DPlanes(opt)+HTMLEnd();
    

HTML レポートを使用してトレード結果を分析の記事から HTMLStart() と HTMLEnd() 関数を使用しました。 同じ記事からのスタイルファイルがわずかに変更され、スタイル2に名前変更しました。

準備ができているファイルは、次のとおりです: ColorOptimization.mqh と ColorOptimization スクリプト。 

カラーモデルの変更

ColorOptimization.mqh のコードは、別のカラーモデルに対して修正できるように構成されています。 CMY カラーモデルを追加してみましょう。 こには、予備的なステップを実行する必要があります。

1. ColorOptimization.mqh と ColorOptimization.mq5 をコピーし、ColorOptimization2.mqh およびColorOptimization2.mq5 として保存します。 

2. 2つのカラーモデルおよびグローバル変数のためにColorOptimization2.mqh に2つの定数を追加します。このグローバル変数はカラーモデルを決定します。

#define MODEL_RGB 0
#define MODEL_CMY 1

int co_ColorModel;

3. ユーザーがカラーモデルを選択するための列挙体と外部変数を追加します。

enum EColorModel{
   RGB=MODEL_RGB,
   CMY=MODEL_CMY
};

input EColorModel          ColorModel     =  RGB;

スクリプトの OnStart() 関数の先頭で、プロパティウィンドウで選択した値を co_ColorModel 変数に代入します。

co_ColorModel=ColorModel;

主な修正は、ColorOptimization2.mqh のファイル関数で行われます。 まず、ComponentsToRGB() を変更する必要があります。 CMY モデルにおける構成要素の値の範囲は 0 ~ 1 であり、したがってレポートデータ構造体の構成要素の値は CMY コンポーネントに対応し、RGB に再計算することができます。 ComponentsToRGB() 構造体は次のとおりです。

void ComponentsToRGB(double c1,double c2,double c3,int & r,int & g,int & b){
   if(co_ColorModel==MODEL_RGB){
      r=(int)(c1*255.0);
      g=(int)(c2*255.0);
      b=(int)(c3*255.0);
   }
   else if(co_ColorModel==MODEL_CMY){
      CMYtoRGB(c1,c2,c3,r,g,b);
   }
}

この CMY モデルの RGB への変換は、別の関数で実装されています。

void CMYtoRGB(double C,double M,double Y,int & R,int & G,int & B){
   R=(int)((1.0-C)*255.0);
   G=(int)((1.0-M)*255.0);
   B=(int)((1.0-Y)*255.0);
}

その他の変更は補助レポート要素にのみ関係します。 テーブルのヘッダ行セルの適切な色付けの HStyle() 関数のリビジョン:

string HStyle(int i,int i1,int i2,int i3){
   if(co_ColorModel==MODEL_RGB){
      if(i==i1)return(" style='background-color: rgb(255,0,0);'");
      if(i==i2)return(" style='background-color: rgb(0,255,0);'");
      if(i==i3)return(" style='background-color: rgb(0,0,255);'");
   }
   else if(co_ColorModel==MODEL_CMY){
      if(i==i1)return(" style='background-color: rgb(0,255,255);'");
      if(i==i2)return(" style='background-color: rgb(255,0,255);'");
      if(i==i3)return(" style='background-color: rgb(255,255,0);'");      
   }
   return("");
}

色表示列の正しいヘッダの ColorCollHeader() 関数のリビジョン:

string ColorCollHeader(int i1,int i2,int i3){
   if(co_ColorModel==MODEL_RGB){
      return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B");
   }
   else if(co_ColorModel==MODEL_CMY){
      return(co_names[i1]+"-C,<br>"+co_names[i2]+"-M,<br>"+co_names[i3]+"-Y");   
   }
   return "";
}

その後、少しばかり修正をメインテーブルとカラープレーンのツールチップに行う必要があります。 メインテーブルでは、TableContent() 内の ' title ' 属性の値を変更する必要があります。 以下の行:

string cs=RGBToStr(r,g,b);
s=s+"<td title='"+cs+"' style='background-color: "+cs+"'>&nbsp</td>";   

should be changed as follows:

string ts="",cs=RGBToStr(r,g,b);

if(co_ColorModel==MODEL_RGB){    
   ts=cs;
}
else if(co_ColorModel==MODEL_CMY){
   ts=CMYToStr(aOpt.Pass[i].ColorComponent[0],
               aOpt.Pass[i].ColorComponent[1],
               aOpt.Pass[i].ColorComponent[2]);
}
s=s+"<td title='"+ts+"' style='background-color: "+cs+"'>&nbsp</td>";     

Color2DPlane() 関数の ' title ' 属性を変更して、平面に適切なタイトルを設定する必要があります。 ライン:

string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";

次のように変更する必要があります。

string title="";

if(co_ColorModel==MODEL_RGB){
   title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
}
else if(co_ColorModel==MODEL_CMY){         
   title=CMYToStr(aOpt.Pass[mni].ColorComponent[0],
                  aOpt.Pass[mni].ColorComponent[1],
                  aOpt.Pass[mni].ColorComponent[2])+"/"+
         CMYToStr(aOpt.Pass[mxi].ColorComponent[0],
                  aOpt.Pass[mxi].ColorComponent[1],
                  aOpt.Pass[mxi].ColorComponent[2])+"\n";                            
}

さて、スクリプトの起動時にカラーモデルタイプを選択できるようになりました。 CMY と RGB の間の差は、CMY においてよりも、最良の値が黒色で示され、他の色も異なることになります(図4、5)。


図4. CMY カラーモデルを使用して作成されたレポートのフラグメント


図5. CMY カラーモデルのカラー平面

色の指示を解釈する方法

RGB の最良のオプションは白に近く、CMY では黒に近くなります。 他の色を適切に解釈するには、カラーモデル内の個々のコンポーネントがどのように組み合わされ、結果として得られる色がどのように形成されるかを理解する必要があります。

RGB モデルを詳しく見てみましょう。 すべてのコンポーネントの値が0に等しい場合、黒い色が得られます。 すべての構成部品が最大値に等しい場合、色は白になります。 他のすべての組み合わせは、異なる色を提供します。 いずれかのコンポーネントの値が最も高く、残りの2つが0に等しい場合、適切なコンポーネント (赤、緑、青) のクリアカラーが得られます。 2つの成分が最大値を持ち、3番目がゼロの場合、結果の色も明確になります。 赤と緑は黄色、緑と青はシアン、赤と青はマゼンタになります。 図6は RGB 成分の組み合わせを示します。


図7. RGB コンポーネントの基本的な組み合わせ

このシェードに基づいて、どのパラメータインジケータがテスト結果により積極的に寄与するかを理解することができます。 赤の場合、最初のパラメータです。色が黄色の場合は、最初と2番目。緑は3番目のパラメータなどを意味します。

RGB モデルのカラーは、色付きのライトと同様に追加されます。 CMY モデルでは、値は白から減算されるため、すべての成分の最大値は黒に対応します。 CMY モデルは、混合塗料に似ています。何のペイントがない場合、白い紙のシートです。あまりにも多くの異なる塗料を混ぜると、黒 (または実際の塗料を扱うときに、むしろ汚れた色) になります。 図8. CMY コンポーネントの基本的な組み合わせを示します。


図7. CMY コンポーネントの基本的な組み合わせ

CMY の色は RGB と比較してずれています。 ここでの解釈は次のとおりです: シアンは最初のパラメータで、青色は第1と第2、マゼンタは2番目の値に、赤は2番目と3番目に表示され、3つ目は黄色、第1と第3のパラメータには緑が使用します。

ご覧のように、RGB または CMY モデルの使用には根本的な差はありません。 

結論

色の知覚は主観的なプロセスであり、したがって、色表現の利便性と利点について明確な結論を下すことは困難です。 少なくとも1つの視覚的表示、明かりの程度 (RGB では白に近いことを意味します) は、3つのパラメータの組み合わせを評価することができます。 これより、レポート分析が簡素化されます。 この記事のように、選択が自動化されている場合、ソートされたテーブルに基づく決定は、3つの値の算術平均に従って行われます。 これは、最終値をシンプルな算術平均としてではなく、より複雑な方法で計算できるファジー理論領域への最初のステップと見なすことができます。 ただし、この方法の有効性を評価するために、より実用的な実験が必要です。

添付

スクリプトによって作成されたレポートを除くすべてのファイルは、ターミナルフォルダに配置する必要があるため、フォルダに配置されます。 ターミナルデータフォルダを開き、MQL5 フォルダをコピーします。