上位100件の最適化パス(その1)最適化分析器の開発

Andrey Azatskiy | 21 12月, 2018

はじめに

現代の技術は金融取引の分野に深く浸透しており、技術なしの金融取引を想像することはほとんど不可能です。それにもかかわらず、取引が手作業で行われ、売買する資産の量を示す複雑な手サイン(あっという間に忘れ去られようとしている)が存在したのは、つい最近までのことです。

パーソナルコンピュータは、オンライン取引を文字通り家庭にもたらすことによって、従来の取引方法に急速に取って代わりました。今では相場をリアルタイムで見て適切な決定を下すことができます。さらに、市場技術におけるオンライン技術の出現により、手作業トレーダーのランクの低下が加速しています。現在、取引の半分以上は取引アルゴリズムによって行われており、MetaTrader 5はこのために最も便利な端末のうち、一位の座を占めています。

このプラットフォームには多数の利点がありますが、欠点もあります。ここで説明するアプリケーションではその軽減をを試みます。本稿では、取引アルゴリズム最適化パラメータの選択を改善するために設計されたEasyAndFastGUIライブラリを使用してMQL5で完全に書かれたプログラムの開発について説明します。また、遡及的取引分析および一般的なEA評価の分析に新しい機能を追加しています。



まず、EAの最適化にはかなりの時間がかかります。もちろん、これは、テスターでより高品質の方法でティックを生成されるためです(OHLCを選択しても、ローソク足ごとに4つのティックが生成されます)。また、EAの評価を向上するためのその他の追加機能のためでもあります。しかし、一般家庭のPCでは馬力が足りず、最適化には数日から数週間かかります。EAパラメータを選択した後に間違っていることに気づき、最適化パスの統計といくつかの評価比率のほかに手元には何もないことがよくあります。

各最適化パスごとに本格的な統計情報があって、複数のパラメータでそれぞれを絞り込む能力(条件付きフィルタを含む)があったらよいでしょう。また、取引統計を持ち切り戦略と比較し、すべての統計をお互いに課すことも良いでしょう。さらに、各取引の結果の後続処理のために、すべての取引履歴データをファイルにアップロードする必要があることがあります。

一部の戦略は市場の種類に依存するため、アルゴリズムがどのような種類のスリッページに耐えることができ、特定の時間間隔でアルゴリズムがどのように動作するかを見たい場合もあります。フラットベースの戦略が例として役立ちます。この戦略では、トレンド期間中は負け、フラットな期間中は利益が得られます。また、一般的なPLグラフとは別に、特定の期間(日付別)を比率およびその他の追加(単純に価格表ではなく)として表示するとよいでしょう。

フォワードテストにも注意を払う必要があります。 それらは非常に有益ですが、グラフは、ストラテジーテスターの標準レポートでは前のものの続きとして表示されます。 初心者のトレーダーは、ロボットが急激にすべての利益を失った後、回復し始めた(または悪化した)と簡単に結論づけるかもしれません。 ここで説明するプログラムでは、すべてのデータが最適化タイプ(フォワードまたは履歴)の観点から見直されます。

また、Grailsの多くのEA開発者が探し求めていることについて言及することも重要です。ロボットによっては、月に1000%以上の利益を得るものがあります。これらは市場を凌駕しているように見えるかもしれませんが(持ち切り戦略)、実際にはすべてが全く異なって見えます。記述されたプログラムが示すように、これらのロボットは実際に1000%の利益をあげることができますが、市場を凌駕するものではありません。

このプログラムでは、フルロット(増減など)のロボットを使用した取引と1つのロット(取引可能な最小ロット)を使用したロボットの取引の模倣との間の分析の分離が特徴です。持ち切り取引グラフを構築する場合、説明されているプログラムは、ロボットによるロット管理を考慮します(ロットが増加するとより多くの資産を買い、ロットが減少するとより少なくの資産を買う)。この2つのグラフを比較すると、最良の最適化パスの1つで非現実的な結果を示した私のテストロボットが市場を凌駕できないことが判明しました。したがって、取引戦略をより客観的に評価するためには、1ロット取引グラフを見るべきです。ここでは、ロボットと持ち切り戦略の両方の最小許容取引量でのPL(利益/損失 - 時間に伴う利益のグラフ)が表示されます。

さて、プログラムの開発方法をより詳細に見てみましょう。


最適化分析器の構造

プログラム構造は、次のように図式化することができます。


生成された最適化分析器は、特定のロボットと結びついておらず、その一部でもありません。<しかし、MQL5のグラフィカルインターフェイスの構築の詳細により、MQL5 EA開発テンプレートがプログラムの基礎として使用されました。プログラムが非常に大きくなった(数千行のコード)ため、より具体性と一貫性を持たせるために、上の図に表示されているようにブロックに分割されてからクラスに分けられました。ロボットテンプレートは、アプリケーションを起動するための出発点にすぎません。ブロックのそれぞれについて、以下でより詳細に考察します。ここでは、それらの関係について説明します。アプリケーションを使用するには、次のものが必要です。

ロボット自体は好きな用に開発することができます(OOPを使用、ロボットテンプレート内の関数、DLLからインポート...)。最も重要なのは、MQL5ウィザードが提供するロボット開発テンプレートを適用することです。これは、各最適化パスが配置された後に、クラスが必要なデータをデータベースにアップロードするデータベースブロックの1つのファイルを接続します。ストラテジーテスターでロボットを起動するときにデータベースが形成されるため、この部分は独立しており、アプリケーション自体に依存しません。

計算ブロックは前の記事「トレード履歴のカスタム表示とレポート図の作成」を改良したものです。

データベースと計算ブロックは、解析されたロボットと記述されたアプリケーションの両方で使用されます。したがって、それらはIncludeディレクトリに配置されます。これらのブロックは、大部分の作業を実行し、プレゼンタクラスを介してグラフィカルインターフェイスに接続されます。

プレゼンタクラスはプログラムブロック間を接続します。ブロックの各々は、グラフィカルインターフェイスにおいてそれぞれの機能を有します。他の論理ブロックへのリダイレクトだけでなく、ボタン押下などのイベントも処理します。それらから得られたデータはプレゼンタに返されます。それらはここで処理され、適切なグラフがプロットされ、表が記入されるなどの、グラフィック部分とのやりとりが行われます。

プログラムのグラフィカルな部分は概念論理を実行しません。代わりに、必要なインターフェイスを持つウィンドウを作成し、ボタンを押している間に適切なプレゼンタ関数を呼び出します。

プログラム自体MQL5プロジェクトとして書かれているので、より構造化された方法で開発し、必要なすべてのファイルを1つの場所にまとめることができます。プロジェクトにはさらに別のクラスがあり、これは計算ブロックで説明されます。このクラスは、このプログラム専用に書かれたクラスで、私が開発した方法を使って最適化パスをソートします。実際には、「最適化の選択」タブ全体が特定の基準によるデータのサンプリングを減らします。

普遍的並び替えクラスは、プログラムに単独で追加されています。それはいずれのブロックにもはまりませんが、プログラムの重要な部分のままです。したがって、この記事のこの部分で簡単に検討します。

名前が示すように、クラスはデータの並び替えを扱います。そのアルゴリズムはサードパーティウェブサイトの選択ソート(ロシア語)からとられました。

//+------------------------------------------------------------------+
//| 並び変えスタイルの列挙体                                            |
//+------------------------------------------------------------------+
enum SortMethod
  {
   Sort_Ascending,// 昇順
   Sort_Descendingly// 降順
  };
//+------------------------------------------------------------------+
//| 渡されたデータ型を並び替えるクラス                                   |
//+------------------------------------------------------------------+
class CGenericSorter
  {
public:
   // デフォルトのコンストラクタ
                     CGenericSorter(){method=Sort_Descendingly;}
   // 並び替えメソッド
   template<typename T>
   void              Sort(T &out[],ICustomComparer<T>*comparer);
   // 並び替えの種類を選択する
   void Method(SortMethod _method){method=_method;}
   // 並び替えメソッドを取得する
   SortMethod Method(){return method;}
private:
   // 並び替えメソッド
   SortMethod        method;
  };

クラスにはテンプレートSortメソッドが含まれています。このメソッドはデータを並び変えます。テンプレートメソッドでは、クラスや構造体を含む渡されたデータを並び変えることができます。データ比較メソッドは、IСustomComparer<T>インターフェイスを実装する別のクラスで記述する必要があります。 Compareメソッドの従来のIСomparerインターフェイスでは参照データは参照によっては渡されません。参照渡しは構造体をMQL5言語のメソッドに渡す条件の1つであるため、独自の IСomparer 型のインターフェイスを開発する必要がありました。

CGenericSorter::Methodクラスメソッドは、戻り値を受け取り、データ並び変えタイプ(昇順または降順)をオーバーロードします。このクラスは、データが並び変えられるプログラムのすべてのブロックで使用されます。


グラフィック

警告


グラフィカルインターフェイスの開発中に、適用されたライブラリ(EasyAndFastGUI)でバグが検出されました。これは、ComboBoxのグラフィック要素が、補充中に不完全に一部の変数をクリアするということです。ライブラリ開発者の推奨(ロシア語)によれば、これを修正するために

m_item_index_focus =WRONG_VALUE;
m_prev_selected_item =WRONG_VALUE;
m_prev_item_index_focus =WRONG_VALUE;

をCListView::Clear(const bool redraw=false)メソッドに追加します。

このメソッドは、ListView.mqhファイルの600行目にあります。このファイルのパスは下記です。
Include\EasyAndFastGUI\Controls.

これらの編集を追加しないと、ComboBoxを開いているときに "Array out of range"エラーがポップアップして、アプリケーションが異常終了することがあります。


EasyAndFastGUIライブラリに基づいてMQL5でウィンドウを作成するには、後続のすべてのウィンドウを満たすためのコンテナとして機能するクラスが必要です。 クラスは、CwindEventsクラスから派生する必要があります。 クラス内でメソッドを再定義する必要があります。

 //--- 初期化/初期化会場
   void              OnDeinitEvent(const int reason){CWndEvents::Destroy();};
   //--- チャートイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);//

ウィンドウを作成するための空白は、次のようになります。

class CWindowManager : public CWndEvents
  {
public:
                     CWindowManager(void){presenter = NULL;};
                    ~CWindowManager(void){};
   //===============================================================================   
   // メソッドの呼び出しとイベント
   //===============================================================================
   //--- 初期化/初期化会場
   void              OnDeinitEvent(const int reason){CWndEvents::Destroy();};
   //--- チャートイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

//--- プログラムのグラフィカルインターフェイスを作成する
   bool              CreateGUI(void);


private:
 //--- メインウィンドウ
   CWindow           m_window;
  }

ウィンドウ自体はクラス内にCwindow型で作成されます。ただし、ウィンドウを表示する前に、いくつかのウィンドウプロパティを定義する必要があります。この特定の場合、ウィンドウの作成方法は次のようになります。

bool CWindowManager::CreateWindow(const string text)
  {
//--- ウィンドウポインタをウィンドウ配列に追加する
   CWndContainer::AddWindow(m_window);
//--- 座標
   int x=(m_window.X()>0) ?m_window.X() : 1;
   int y=(m_window.Y()>0) ?m_window.Y() : 1;
//--- プロパティ
   m_window.XSize(WINDOW_X_SIZE+25);
   m_window.YSize(WINDOW_Y_SIZE);
   m_window.Alpha(200);
   m_window.IconXGap(3);
   m_window.IconYGap(2);
   m_window.IsMovable(true);
   m_window.ResizeMode(false);
   m_window.CloseButtonIsUsed(true);
   m_window.FullscreenButtonIsUsed(false);
   m_window.CollapseButtonIsUsed(true);
   m_window.TooltipsButtonIsUsed(false);
   m_window.RollUpSubwindowMode(true,true);
   m_window.TransparentOnlyCaption(true);

//--- ツールヒントを設定する
   m_window.GetCloseButtonPointer().Tooltip("Close");
   m_window.GetFullscreenButtonPointer().Tooltip("Fullscreen/Minimize");
   m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
   m_window.GetTooltipButtonPointer().Tooltip("Tooltips");
//--- フォームを作成する
   if(!m_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//---
   return(true);
  }

このメソッドの前提条件は、ウィンドウをアプリケーションウィンドウの配列に追加してフォームを作成する文字列です。その後、アプリケーションが実行され、OnEventイベントがトリガされると、ライブラリメソッドの1つが、ウィンドウの配列にリストされているすべてのウィンドウにわたってループで実行されます。次に、ウィンドウ内のすべての要素を調べ、管理インターフェイスがクリックされる、表の行が強調表示されるなどのイベントを探します。したがって、新しいアプリケーションウインドウを作成する際には、参照配列にウインドウへの参照を追加する必要があります。

開発されたアプリケーションは、インターフェイスをタブで分割したもので4つのタブコンテナがあります。

//--- タブ
   CTabs             main_tab; // メインタブ
   CTabs             tab_up_1; // 設定と結果表のタブ
   CTabs             tab_up_2; // 統計、パラメータの選択、共通グラフのタブ
   CTabs             tab_down; // 統計とファイルへのアップロードのタブ

これらはフォームでは次のように表示されます(スクリーンショットでは赤)。

タブを作成するプロセスは似ています。唯一の違いはコンテンツです。例として、メインタブの作成方法を示します。

//+------------------------------------------------------------------+
//| メインタブ                                                         |
//+------------------------------------------------------------------+
bool CWindowManager::CreateTab_main(const int x_gap,const int y_gap)
  {
//--- メイン要素へのポインタを保存する
   main_tab.MainPointer(m_window);

//--- タブ幅の配列
   int tabs_width[TAB_MAIN_TOTAL];
   ::ArrayInitialize(tabs_width,45);
   tabs_width[0]=120;
   tabs_width[1]=120;
//---
   string tabs_names[TAB_UP_1_TOTAL]={"Analysis","Optimisation Data"};
//--- プロパティ
   main_tab.XSize(WINDOW_X_SIZE-23);
   main_tab.YSize(WINDOW_Y_SIZE);
   main_tab.TabsYSize(TABS_Y_SIZE);
   main_tab.IsCenterText(true);
   main_tab.PositionMode(TABS_LEFT);
   main_tab.AutoXResizeMode(true);
   main_tab.AutoYResizeMode(true);
   main_tab.AutoXResizeRightOffset(3);
   main_tab.AutoYResizeBottomOffset(3);
//---
   main_tab.SelectedTab((main_tab.SelectedTab()==WRONG_VALUE)?0 : main_tab.SelectedTab());
//--- 指定されたプロパティを持つタブを追加する
   for(int i=0; i<TAB_MAIN_TOTAL; i++)
      main_tab.AddTab((tabs_names[i]!="")?tabs_names[i]: "Tab "+string(i+1),tabs_width[i]);
//--- コントロール要素を作成する
   if(!main_tab.CreateTabs(x_gap,y_gap))
      return(false);
//--- オブジェクトグループの共通配列にオブジェクトを追加する
   CWndContainer::AddToElementsArray(0,main_tab);
   return(true);
  }

コンテンツが異なることに加えて、コードでの主な文字列は次のとおりです。

  1. メイン要素へのポインタの追加 - タブコンテナは、それが割り当てられる要素を知っている必要がある
  2. コントロール要素を作成する文字列
  3. コントロールの共通リストへの要素の追加

階層的に、コントロール要素が次です。アプリケーションでは11種類のコントロール要素が使用されました。それらはすべて同様の方法で作成されます。したがって、コントロール要素を作成するためにはそれらを追加するメソッドが記述されています。そのうちの1つのみの実装を考えてみましょう。

bool CWindowManager::CreateLable(const string text,
                                 const int x_gap,
                                 const int y_gap,
                                 CTabs &tab_link,
                                 CTextLabel &lable_link,
                                 int tabIndex,
                                 int lable_x_size)
  {
//--- メイン要素へのポインタを保存する
   lable_link.MainPointer(tab_link);
//--- タブに割り当てる
   tab_link.AddToElementsArray(tabIndex,lable_link);

//--- 設定
   lable_link.XSize(lable_x_size);

//--- 作成
   if(!lable_link.CreateTextLabel(text,x_gap,y_gap))
      return false;

//--- オブジェクトを一般オブジェクトグループ配列に追加する
   CWndContainer::AddToElementsArray(0,lable_link);
   return true;
  }

渡されたコントロール要素(CTextLabel)は、タブと共にコンテナとして割り当てられた要素を覚えておく必要があります。次に、タブコンテナは、要素が配置されているタブを覚えています。その後、要素に必要な設定と初期データが入力されます。最終的に、オブジェクトはオブジェクトの一般的な配列に追加されます。

ラベルと同様に、クラスコンテナの内部でフィールドとして定義されている他の要素が追加されます。私はいくつかの要素を分離し、それらのいくつかを '保護された'クラス領域に配置しました。これらはプレゼンター経由でアクセスする必要のない要素です。他のいくつかの要素は'public'に置かれました。これらは、いくつかの条件やラジオボタンを定義する要素です。その状態は、プレゼンタからチェックする必要があります。言い換えれば、アクセスが望ましくないすべての要素とメソッドは、プレゼンターへの参照とともに、クラスの「保護された」部分または「私的な」部分にヘッダーを持っています。プレゼンター参照の追加は、既に追加されたプレゼンターの存在が最初にチェックされ、それに対する参照がまだ追加されていない場合はプレゼンターが保存されるパブリックメソッドの形式で行われます。これは、プログラムの実行中に動的なプレゼンターの置換を避けるために行われます。

ウィンドウ自体は、CreateGUIメソッドで作成されます。

bool CWindowManager::CreateGUI(void)
  {
//--- ウィンドウを作成する
   if(!CreateWindow("Optimisation Selection"))
      return(false);

//--- タブを作成する
   if(!CreateTab_main(120,20))
      return false;
   if(!CreateTab_up_1(3,44))
      return(false);
   int indent=WINDOW_Y_SIZE-(TAB_UP_1_BOTTOM_OFFSET+TABS_Y_SIZE-TABS_Y_SIZE);
   if(!CreateTab_up_2(3,indent))
      return(false);
   if(!CreateTab_down(3,33))
      return false;

//--- コントロールを作成する 
   if(!Create_all_lables())
      return false;
   if(!Create_all_buttons())
      return false;
   if(!Create_all_comboBoxies())
      return false;
   if(!Create_all_dropCalendars())
      return false;
   if(!Create_all_textEdits())
      return false;
   if(!Create_all_textBoxies())
      return false;
   if(!Create_all_tables())
      return false;
   if(!Create_all_radioButtons())
      return false;
   if(!Create_all_SepLines())
      return false;
   if(!Create_all_Charts())
      return false;
   if(!Create_all_CheckBoxies())
      return false;

// ウィンドウを見せる
   CWndEvents::CompletedGUI();

   return(true);
  }

その実装からわかるように、コントロール要素自体を直接作成するのではなく、これらの要素を作成するための他のメソッドを呼び出すだけです。このメソッドで最終的にコード文字列に含める必要があるのは「CWndEvents::CompletedGUI();」です。

これはグラフィックスの作成を完了してユーザの画面にプロットします。各コントロール要素(分離線、ラベル、ボタンなど)の作成は同様のメソッドで実装されており、グラフィカルコントロール要素を作成するための上記のアプローチを適用しています。メソッドヘッダはクラスの「プライベート」部分にあります。

//===============================================================================   
// コントロールの作成:
//===============================================================================
//--- すべてのラベル
   bool              Create_all_lables();
   bool              Create_all_buttons();
   bool              Create_all_comboBoxies();
   bool              Create_all_dropCalendars();
   bool              Create_all_textEdits();
   bool              Create_all_textBoxies();
   bool              Create_all_tables();
   bool              Create_all_radioButtons();
   bool              Create_all_SepLines();
   bool              Create_all_Charts();
   bool              Create_all_CheckBoxies();

グラフィックについてお話しするときにイベントモデルの部分をスキップすることは不可能です。EasyAndFastGUIを使用して開発されたグラフィックアプリケーションでの正しい処理のためには、以下の手順を実行する必要があります。

イベントハンドラメソッドを作成します(たとえば、ボタンの押下)。このメソッドは、パラメータとして「id」と「lparam」を受け入れる必要があります。最初のパラメータはグラフィカルイベントのタイプを示し、2番目のパラメータはインタラクションが発生したオブジェクトのIDを示します。メソッドの実装は、すべての場合で同様です。

//+------------------------------------------------------------------+
//| Btn_Update_Click                                                 |
//+------------------------------------------------------------------+
void CWindowManager::Btn_Update_Click(const int id,const long &lparam)
  {
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==Btn_update.Id())
     {
      presenter.Btn_Update_Click();
     }
  }

まず、条件を確認します(ボタンが押されたか、リスト要素が選択されたか...)。次に、lparamを確認して、メソッドに渡されたIDと必須のリスト要素のIDを比較します。

すべてのボタン押下イベントの宣言は、クラスの「プライベート」部分にあります。イベントは、その応答を取得するために呼び出されます。宣言されたイベントは、オーバーロードされたOnEventメソッドで呼び出されます。

//+------------------------------------------------------------------+
//| OnEvent                                                          |
//+------------------------------------------------------------------+
void CWindowManager::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   Btn_Update_Click(id,lparam);
   Btn_Load_Click(id,lparam);
   OptimisationData_inMainTable_selected(id,lparam);
   OptimisationData_inResults_selected(id,lparam);
   Update_PLByDays(id,lparam);
   RealPL_pressed(id,lparam);
   OneLotPL_pressed(id,lparam);
   CoverPL_pressed(id,lparam);
   RealPL_pressed_2(id,lparam);
   OneLotPL_pressed_2(id,lparam);
   RealPL_pressed_4(id,lparam);
   OneLotPL_pressed_4(id,lparam);
   SelectHistogrameType(id,lparam);
   SaveToFile_Click(id,lparam);
   Deals_passed(id,lparam);
   BuyAndHold_passed(id,lparam);
   Optimisation_passed(id,lparam);
   OptimisationParam_selected(id,lparam);
   isCover_clicked(id,lparam);
   ChartFlag(id,lparam);
   show_FriquencyChart(id,lparam);
   FriquencyChart_click(id,lparam);
   Filtre_click(id,lparam);
   Reset_click(id,lparam);
   RealPL_pressed_3(id,lparam);
   OneLotPL_pressed_3(id,lparam);
   ShowAll_Click(id,lparam);
   DaySelect(id,lparam);
  }

このメソッドは、ロボットテンプレートから呼び出されます。したがって、イベントモデルは、ロボットテンプレート(以下に提供)からグラフィカルインターフェイスまでを対象とします。GUIは、その後のプレゼンタによる処理に向けて、すべての処理、並べ替え、リダイレクションを実行します。ロボットテンプレート自体は、プログラムの出発点です。これは次のようになります。

#include "Presenter.mqh"

CWindowManager _window;
CPresenter Presenter(&_window);
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!_window.CreateGUI())
     {
      Print(__FUNCTION__," > Failed to create the graphical interface!");
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   _window.OnDeinitEvent(reason);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   _window.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

データベースの使用

このプロジェクトのかなり広範な部分を検討する前に、選択過程について言及する価値があります。プロジェクト初期の目標の1つは、最適化自体を完了した後に最適化結果を処理する能力を提供することと、いつでもこれらの結果を利用できるようにすることでした。データをファイルに保存することは、直ちに不適当とみなされました。複数の表(実際には1つの大きな表を形成するが、行数は異なる)またはファイルを作成する必要があるからです。

どちらもあまり便利ではありません。さらに、この方法は実装がより困難です。2番目の方法は、最適化フレームを作成することです。ツールキット自体は良いですが、最適化プロセス中に最適化を行うつもりはありません。さらに、フレーム機能はデータベースの機能ほど良くはありません。さらに、フレームはMetaTrader用に設計されていますが、データベースは必要に応じて任意の第三者の分析プログラムで使用できます。

正しいデータベースを選択するのは簡単でした。簡単にリンクできて追加のソフトウェアを必要としない、高速かつ普及したデータベースが必要でした。Sqliteデータベースはすべての基準を満たしています。上記の特性のため、これは非常に人気があるデータベースです。使用するには、プロバイダーが提供するデータベースをDLLプロジェクトに接続します。DLLデータはC言語で書かれており、MQL5アプリケーションと簡単にリンクすることができます。第三者の言語でコードを一行も書く必要がないため、プロジェクトは複雑になりません。これは、すばらしいことです。このアプローチの欠点の1つは、DLL Sqliteがデータベースの連携に便利なAPIを提供していないため、少なくともデータベースを操作するための最小限のラッパーを記述する必要があることです。この機能を記述した例はSQL と MQL5:SQLITE データベースとの連携稿で効率的に説明されています。このプロジェクトでは、WinApiとのやりとりとDLLからMQL5へのいくつかの関数のインポートに関連するコードの一部を上記の記事から借用しました。ラッパーについては、自分で書くことに決めました。

その結果、データベース処理ブロックは、データベースと連携するための便利なラッパーを持つSqlite3フォルダと、開発されたプログラム用に特別に作成されたOptimisationSelectorフォルダで構成されています。両方のフォルダは、MQL5/Includeディレクトリに位置します。前述したように、Windows標準ライブラリの多くの関数がデータベースとの連携に使用されます。アプリケーションのこの部分のすべての関数は、WinApiディレクトリにあります。上記に加えて、共有リソース(Mutex)を作成するためのコードもコードベースから借用しました。2つのソースからデータベースと連携する場合(つまり、最適化アナライザが最適化中に使用されたデータベースを開く場合)、プログラムによって得られたデータは常に完全でなければなりません。これが、共有リソースが必要な理由です。一方(最適化プロセスまたはアナライザ)がデータベースをアクティブにすると、その操作が完了されるまでもう一方が待機することになります。Sqliteデータベースは複数のスレッドから読み取ることができます。本稿の主題からそれるため、MQL5からsqlite3データベースに連携するためのラッパーの詳細は検討しません。代わりに、実装方法と適用メソッドのいくつかの点についてのみ説明します。すでに説明したように、データベースに連携するためラッパーはSqlite3フォルダーにあります。そこには3つのファイルがあります。順番に見て行きましょう。

#import "Sqlite3_32.dll"
int sqlite3_open(const uchar &filename[],sqlite3_p32 &paDb);// データベースを開く
int sqlite3_close(sqlite3_p32 aDb); // データベースを閉じる
int sqlite3_finalize(sqlite3_stmt_p32 pStmt);// 文を完了する
int sqlite3_reset(sqlite3_stmt_p32 pStmt); // 文をリセットする
int sqlite3_step(sqlite3_stmt_p32 pStmt); // 文を読むときに次の行に移動する
int sqlite3_column_count(sqlite3_stmt_p32 pStmt); // 列の数を計算する
int sqlite3_column_type(sqlite3_stmt_p32 pStmt,int iCol); // 選択された列の型を取得する
int sqlite3_column_int(sqlite3_stmt_p32 pStmt,int iCol);// 値をintに変換する
long sqlite3_column_int64(sqlite3_stmt_p32 pStmt,int iCol); // 値をint64に変換する
double sqlite3_column_double(sqlite3_stmt_p32 pStmt,int iCol); // 値をdoubleに変換する
const PTR32 sqlite3_column_text(sqlite3_stmt_p32 pStmt,int iCol);//  テキストの値を取得する
int sqlite3_column_bytes(sqlite3_stmt_p32 apstmt,int iCol); // 渡されたセルからラインが占めるバイト数を取得する
int sqlite3_bind_int64(sqlite3_stmt_p32 apstmt,int icol,long a);// (int64型の)値を持つリクエストを結合する
int sqlite3_bind_double(sqlite3_stmt_p32 apstmt,int icol,double a);// (double型の)値を持つリクエストを結合する
int sqlite3_bind_text(sqlite3_stmt_p32 apstmt,int icol,char &a[],int len,PTRPTR32 destr);// (string型(C++のchar* ))の)値を持つリクエストを結合する
int sqlite3_prepare_v2(sqlite3_p32 db,const uchar &zSql[],int nByte,PTRPTR32 &ppStmt,PTRPTR32 &pzTail);// リクエストを準備する
int sqlite3_exec(sqlite3_p32 aDb,const char &sql[],PTR32 acallback,PTR32 avoid,PTRPTR32 &errmsg);// Sqlの実行
int sqlite3_open_v2(const uchar &filename[],sqlite3_p32 &ppDb,int flags,const char &zVfs[]); // データベースをパラメータで開く
#import

開発者によって提供されたデータベースは、Librariesフォルダに配置され、DLLデータベースラッパーが動作するビット数に応じてSqlite3_32.dllまたはSqlite3_64.dllと名付けられます。本稿添付のファイルからDLLデータを取り出し、Sqlite Amalgmationから自分でコンパイルする、または、Sqlite開発者のWebサイトからDLLデータを取り出すことができます。その存在はプログラムの前提条件です。EAにDLLのインポートを許可することも必要です。  

//+------------------------------------------------------------------+
//| データベースの接続および管理クラス                                   |
//+------------------------------------------------------------------+
class CSqliteManager
  {
public:
                     CSqliteManager(){db=NULL;} // 空のコンストラクタ
                     CSqliteManager(string dbName); // 名前を渡す
                     CSqliteManager(string dbName,int flags,string zVfs); // 名前と接続フラグを渡す
                     CSqliteManager(CSqliteManager  &other) { db=other.db; } // コピーコンストラクタ
                    ~CSqliteManager(){Disconnect();};// デストラクタ

   void              Disconnect(); // データベースの接続を切断する
   bool              Connect(string dbName,int flags,string zVfs); // パラメータによるデータベースエージェントへの接続
   bool              Connect(string dbName); // 名前によるデータベースへの接続

   void operator=(CSqliteManager  &other){db=other.db;}// 割り当て演算子

   sqlite3_p64 DB() { return db; }; // データベースのポインタを取得する

   sqlite3_stmt_p64  Create_statement(const string sql); // 文を作成する
   bool              Execute(string sql); // 命令を実行する
   void              Execute(string  sql,int &result_code,string &errMsg); //  命令を実行し、エラーコードとメッセージを提供する

   void              BeginTransaction(); // トランザクション開始
   void              RollbackTransaction(); // トランザクションロールバック
   void              CommitTransaction(); // トランザクションを確認する

private:
   sqlite3_p64       db; // データベース

   void stringToUtf8(const string strToConvert,// UTF-8エンコーディングで配列に変換される文字列
                     uchar &utf8[],// 変換されたstrToConvert文字列が配置されるUTF-8エンコーディングの配列
                     const bool untilTerminator=true)
     {    // UTF-8エンコーディングに変換され、UTF-8配列にコピーされる銘柄の数
      //---
      int count=untilTerminator ?-1 : StringLen(strToConvert);
      StringToCharArray(strToConvert,utf8,0,count,CP_UTF8);
     }
  };

コードから分かるように、結果のクラスには、データベースに2種類の接続を作成する機能があります(テキストとパラメータの指定)。Create_sttementメソッドはデータベースへのリクエストを作成し、そのポインタを返します。Exequteメソッドのオーバーロードは単純な文字列クエリを実行し、トランザクションメソッドはトランザクションを作成して受け入れ/キャンセルします。データベース自体への接続は、db変数に格納されます。Disconnectメソッドを適用した場合、またはデフォルトのコンストラクタを使用してクラスを作成した場合(データベースに接続する時間がなかった場合)、変数はNULLです。Connectメソッドを繰り返し呼び出すと、以前に接続したデータベースから切断して新しいデータベースに接続します。データベースに接続するには、UTF-8形式の文字列を渡す必要があるため、クラスには、文字列を必要なデータ形式に変換する特殊な「プライベート」メソッドがあります。

typedef bool(*statement_callback)(sqlite3_stmt_p64); // クエリ実行時のコールバック(正常の場合「true」を実行)
//+------------------------------------------------------------------+
//| データベースへのクエリのクラス                                       |
//+------------------------------------------------------------------+
class CStatement
  {
public:
                     CStatement(){stmt=NULL;} // 空のコンストラクタ
                     CStatement(sqlite3_stmt_p64 _stmt){this.stmt=_stmt;} // 文へのポインタをパラメータとして持つコンストラクタ
                    ~CStatement(void){if(stmt!=NULL)Sqlite3_finalize(stmt);} // デストラクタ
   sqlite3_stmt_p64 get(){return stmt;} // 文へのポインタを取得する
   void              set(sqlite3_stmt_p64 _stmt); // 文へのポインタを設定する

   bool              Execute(statement_callback callback=NULL); // 文を実行する
   bool              Parameter(int index,const long value); // パラメータを追加する
   bool              Parameter(int index,const double value); // パラメータを追加する
   bool              Parameter(int index,const string value); // パラメータを追加する

private:
   sqlite3_stmt_p64  stmt;
  };

パラメータメソッドのオーバーロードがリクエストパラメータを記入します。「set」メソッドは、渡された文を「stmt」変数に保存します。新しいリクエストを保存する前に古いリクエストがクラスに保存されていることがわかった場合、以前に保存したリクエストに対してSqlite3_finalizeメソッドが呼び出されます。

//+------------------------------------------------------------------+
//| データベースからの応答を読み取るクラス                                |
//+------------------------------------------------------------------+
class CSqliteReader
  {
public:
                     CSqliteReader(){statement=NULL;} // 空のコンストラクタ
                     CSqliteReader(sqlite3_stmt_p64 _statement) { this.statement=_statement; }; // 文へのポインタを受け取るコンストラクタ
                     CSqliteReader(CSqliteReader  &other) : statement(other.statement) {} // コピーコンストラクタ
                    ~CSqliteReader() { Sqlite3_reset(statement); } // デストラクタ

   void              set(sqlite3_stmt_p64 _statement); // 文への参照を追加する
   void operator=(CSqliteReader  &other){statement=other.statement;}// リーダー割り当て演算子
   void operator=(sqlite3_stmt_p64 _statement) {set(_statement);}// 文の割り当て演算子

   bool              Read(); // 文字列を読む
   int               FieldsCount(); // 列の数を数える
   int               ColumnType(int col); // 列のタイプを取得する

   bool              IsNull(int col); // value == SQLITE_NULLかどうかを確認する
   long              GetInt64(int col); // 'int'に変換する
   double            GetDouble(int col);// 'double'に変換する
   string            GetText(int col);// 'string'に変換する

private:
   sqlite3_stmt_p64  statement; // 文へのポインタ
  };

Sqlite3.dllからアップロードされたデータベースに連携するための関数を使用して記述されたクラスを実装したので、記述されているプログラムからデータベースで動作するクラスを記述する必要があります。

作成されたデータベースの構造は次のとおりです。

持ち切りの表:

  1. Time — X軸(時間間隔ラベル)
  2. PL_total — ロボットに比例してロットを増矢した場合の利益/損失
  3. PL_oneLot — 単一のロットを常に取引した場合の利益/損失
  4. DD_total — EAと同じ方法で多く取引擦る場合のドローダウン
  5. DD_oneLot — 単一のロットを取引した場合のドローダウン
  6. isForvard — 前方グラフのプロパティ

OptimisationParamsの表:

  1. ID — データベース内の一意の自動入力エントリインデックス
  2. HistoryBorder — 履歴最適化完了日
  3. TF — 時間枠
  4. Param_1...Param_n — パラメータ
  5. InitalBalance — 初期入金額

ParamsCoefitientsの表:

  1. ID — 外部キー、OptimisationParams(ID)への参照
  2. isForvard — 前方最適化プロパティ
  3. isOneLot — 比率が基づいたチャートのプロパティ
  4. DD — ドローダウン
  5. averagePL — PLグラフによる平均損益
  6. averageDD — 平均ドローダウン
  7. averageProfit — 平均利益
  8. profitFactor — 利益率
  9. recoveryFactor — 回復率
  10. sharpRatio — シャープレシオ
  11. altman_Z_Score — Altman Zスコア
  12. VaR_absolute_90 — VaR 90
  13. VaR_absolute_95 — VaR 95
  14. VaR_absolute_99 — VaR 99
  15. VaR_growth_90 — VaR 90
  16. VaR_growth_95 — VaR 95
  17. VaR_growth_99 — VaR 99
  18. winCoef — 勝率
  19. customCoef — カスタム比率

ParamTypeの表:

  1. ParamName — ロボットパラメータ名
  2. ParamType — ロボットパラメータ型(int/double/string)

TradingHistoryの表:

  1. ID — OptimisationParams(ID)への外部キー参照
  2. isForvard — 前方最適化フラグ
  3. Symbol — 銘柄
  4. DT_open — 開始日付け
  5. Day_open — 開始日
  6. DT_close — 終了日付
  7. Day_close — 終了日
  8. Volume — ロット数
  9. isLong — long/shortプロパティ
  10. Price_in — エントリ価格
  11. Price_out — エグジット価格
  12. PL_oneLot — 単一のロットを常に取引した場合の利益
  13. PL_forDeal — 以前と同じ方法で取引した場合の利益
  14. OpenComment — エントリコメント
  15. CloseComment — エグジットコメント

上記のデータベース構造から、表がEAパラメータを格納するOptimisationParams表を参照するためにいくつかの外部キーを使用することがわかります。入力パラメータの各列には、その名前が付いています(たとえば、Fast/Slow — 高速/低速移動平均)。また、各列には特定のデータ形式が必要です。多くのSqliteデータベースは、表の列のデータ形式を定義せずに作成されます。この場合、すべてのデータは行として格納されます。しかし、特定のプロパティによって比率を並び替える必要があるため、正確なデータ形式を知る必要があります。これは、データベースからアップロードされたデータを元の形式に変換することを意味します。

これには、データをデータベースに入力する前にその形式を知っておく必要があります。テンプレートメソッドを作成してそれへのコンバータを送る、またはEA変数の名前と組み合わされたいくつかのデータ型(すべてのデータ型を変換可能)の汎用ストレージであるクラスを作成するなどのいくつかのオプションがあります。ここでは、2番目のオプションを選択し、CDataKeeperクラスを作成しました。記述されたクラスは、3つのデータ型[int、double、string]を格納できます。EA入力形式として使用できる他のすべてのデータ型はこれらと相互に変換できます。

//+------------------------------------------------------------------+
//| EAパラメータの入力データの型                                        |
//+------------------------------------------------------------------+
enum DataTypes
  {
   Type_INTEGER,// int
   Type_REAL,// double。float
   Type_Text // string
  };
//+------------------------------------------------------------------+
//| 2つのCDataKeeperを比較した結果                                     |
//+------------------------------------------------------------------+
enum CoefCompareResult
  {
   Coef_Different,// 異なるデータ型または変数名
   Coef_Equal,// 変数は等しい
   Coef_Less, // 現在の変数が渡された物より小さい
   Coef_More // 現在の変数が渡された物より大きい
  };
//+---------------------------------------------------------------------+
//| 1つの特定のロボットインプットを格納するクラス                             |
//| [int, double, string]型のデータを格納できる                            |
//+---------------------------------------------------------------------+
class CDataKeeper
  {
public:
                     CDataKeeper(); // コンストラクタ
                     CDataKeeper(const CDataKeeper&other); // コピーコンストラクタ
                     CDataKeeper(string _variable_name,int _value); // パラメトリックコンストラクタ
                     CDataKeeper(string _variable_name,double _value); // パラメトリックコンストラクタ
                     CDataKeeper(string _variable_name,string _value); // パラメトリックコンストラクタ

   CoefCompareResult Compare(CDataKeeper &data); // 比較メソッド

   DataTypes         getType(){return variable_type;}; // データ型を取得する
   string            getName(){return variable_name;}; // パラメータ名を取得する
   string            valueString(){return value_string;}; // パラメータを取得する
   int               valueInteger(){return value_int;}; // パラメータを取得する
   double            valueDouble(){return value_double;}; // パラメータを取得する
   string            ToString(); // パラメータを文字列に変換する。これが文字列パラメータの場合は、両側から文字列に一重引用符が追加される <<'>>

private:
   string            variable_name,value_string; // 変数名と文字列変数
   int               value_int; // Int変数
   double            value_double; // Double変数
   DataTypes         variable_type; // 変数型

   int compareDouble(double x,double y) // 小数点以下10桁までのDouble型の比較
     {
      double diff=NormalizeDouble(x-y,10);
      if(diff>0) return 1;
      else if(diff<0) return -1;
      else return 0;
     }
  };

3つのコンストラクタのオーバーロードでは変数名を最初のパラメータ、言及された型の1つに変換された値を2番目のパラメータとして受け取ります。これらの値は、クラスのグローバル変数に保存され、「value_」の後に型の指定が続きます。getType()メソッドは上記の列挙型として型を返し、getName()メソッドは変数名を返します。「value」で始まるメソッドは必要な型の変数を返しますが、valueDouble()メソッドが呼び出されてそのクラスに格納されている変数の型が「int」の場合はNULLが返されます。ToString()メソッドは、任意の変数の値を文字列形式に変換します。ただし、変数が最初に文字列であった場合は、単一引用符が追加されます。これは、より便利にSQLリクエストを形成するためです。Compare(CDataKeeper &ther)メソッドは、下記を比較しながらCDataKeeper型の2つのオブジェクトを比較します。

  1. EA変数名
  2. 変数型
  3. 変数値

最初の2つが一致しない場合は、2つの異なるパラメータを比較している(たとえば、高速移動平均の期間と低速期間の比較)ため、比較の必要がありません。 比較されるデータの型は同じでなければいけないからです。この場合、CoefCompareResult型のCoef_Different値が返されます。それ以外の場合は、比較が行われ、必要な結果が返されます。比較メソッド自体は次のように実装されています。

//+------------------------------------------------------------------+
//| 現在のパラメータと渡されたパラメータを比較する                         |
//+------------------------------------------------------------------+
CoefCompareResult CDataKeeper::Compare(CDataKeeper &data)
  {
   CoefCompareResult ans=Coef_Different;

   if(StringCompare(this. variable_name,data.getName())==0 && 
      this.variable_type==data.getType()) // 名前と型を比較する
     {
      switch(this.variable_type) // 値を比較する
        {
         case Type_INTEGER :
            ans=(this.value_int==data.valueInteger() ?Coef_Equal :(this.value_int>data.valueInteger() ?Coef_More : Coef_Less));
            break;
         case Type_REAL :
            ans=(compareDouble(this.value_double,data.valueDouble())==0 ?Coef_Equal :(compareDouble(this.value_double,data.valueDouble())>0 ?Coef_More : Coef_Less));
            break;
         case Type_Text :
            ans=(StringCompare(this.value_string,data.valueString())==0 ?Coef_Equal :(StringCompare(this.value_string,data.valueString())>0 ?Coef_More : Coef_Less));
            break;
        }
     }
   return ans;
  }

変数の型に依存しない表現によって、名前、変数のデータ型、およびその値の両方を考慮に入れて、より便利な形式で変数を使用することができます。

次のタスクは、上記のデータベースを作成することです。これにはCDatabaseWriterクラスを使用します。

//+---------------------------------------------------------------------------------+
//| ユーザ比率を計算するコールバック                                                    |
//| 比率計算を必要とする履歴型の履歴データとフラグが                                      |
//| 入力に渡される                                                                    |
//+---------------------------------------------------------------------------------+
typedef double(*customScoring_1)(const DealDetales &history[],bool isOneLot);
//+---------------------------------------------------------------------------------+
//| ユーザ比率を計算するコールバック                                                    |
//| データベースへの接続(読み取り専用)、履歴、リクエストされた比率のタイプフラグが            |
//| 入力に渡される                                                                    |
//+---------------------------------------------------------------------------------+
typedef double(*customScoring_2)(CSqliteManager *dbManager,const DealDetales &history[],bool isOneLot);
//+---------------------------------------------------------------------------------+
//| データをデータベースに保存し、その前にデータベースを作成するクラス                      |
//+---------------------------------------------------------------------------------+
class CDBWriter
  {
public:
   // OnInitのリセットのいずれかを呼び出す
   void              OnInitEvent(const string DBPath,const CDataKeeper &inputData_array[],customScoring_1 scoringFunction,double r,ENUM_TIMEFRAMES TF=PERIOD_CURRENT); // コールバック1
   void              OnInitEvent(const string DBPath,const CDataKeeper &inputData_array[],customScoring_2 scoringFunction,double r,ENUM_TIMEFRAMES TF=PERIOD_CURRENT); // コールバック2
   void              OnInitEvent(const string DBPath,const CDataKeeper &inputData_array[],double r,ENUM_TIMEFRAMES TF=PERIOD_CURRENT);// コールバックやユーザ比率なし(ゼロに等しい)
   double            OnTesterEvent();// OnTesterで呼び出す
   void              OnTickEvent();// OnTickで呼び出す

private:
   CSqliteManager    dbManager; // データベースのコネクタ
   CDataKeeper       coef_array[]; // 入力パラメータ
   datetime          DT_Border; // 最終ローソク足の日付(OnTickEventで計算)
   double            r; // リスクなしの比率

   customScoring_1   scoring_1; // コールバック
   customScoring_2   scoring_2; // コールバック
   int               scoring_type; // コールバックの種類 [1,2]
   string            DBPath; // データベースへのパス
   double            balance; // 残高
   ENUM_TIMEFRAMES   TF; // 時間枠

   void              CreateDB(const string DBPath,const CDataKeeper &inputData_array[],double r,ENUM_TIMEFRAMES TF);// データベースと補助オブジェクトを作成する
   bool              isForvard();// 現在の最適化の種類を定義する(履歴/フォワード)
   void              WriteLog(string s,string where);// ファイルログエントリ

   int               setParams(bool IsForvard,CReportCreator *reportCreator,DealDetales &history[],double &customCoef);// 表に入力する
   void              setBuyAndHold(bool IsForvard,CReportCreator *reportCreator);// 持ち切り履歴を記入する
   bool              setTraidingHistory(bool IsForvard,DealDetales &history[],int ID);// 取引履歴を埋める
   bool              setTotalResult(TotalResult &coefData,bool isOneLot,long ID,bool IsForvard,double customCoef);// 表に比率を記入する
   bool              isHistoryItem(bool IsForvard,DealDetales &item,int ID); // パラメータが取引履歴の表に既存するか確認する
  };

クラスはカスタムロボット自体のみで使用されます。その目的は、記述されたプログラム、つまり必要な構造と内容を持つデータベースの入力パラメータを作成することです。ご覧のように、3つのパブリックメソッドがあります(オーバーロードされたメソッドは1つのメソッドとみなされます)。

それぞれは、必要なパラメータが渡される、対応するロボットテンプレートのコールバックで呼び出されます。OnInitEventメソッドは、データベースに連携するクラスを準備するために設計されています。オーバーロードは次のように実装されます。

//+------------------------------------------------------------------+
//| データベースと接続を作成する                                         |
//+------------------------------------------------------------------+
void CDBWriter::OnInitEvent(const string _DBPath,const CDataKeeper &inputData_array[],customScoring_2 scoringFunction,double _r,ENUM_TIMEFRAMES _TF)
  {
   CreateDB(_DBPath,inputData_array,_r,_TF);
   scoring_2=scoringFunction;
   scoring_type=2;
  }
//+------------------------------------------------------------------+
//| データベースと接続を作成する                                         |
//+------------------------------------------------------------------+
void CDBWriter::OnInitEvent(const string _DBPath,const CDataKeeper &inputData_array[],customScoring_1 scoringFunction,double _r,ENUM_TIMEFRAMES _TF)
  {
   CreateDB(_DBPath,inputData_array,_r,_TF);
   scoring_1=scoringFunction;
   scoring_type=1;
  }
//+------------------------------------------------------------------+
//| データベースと接続を作成する                                         |
//+------------------------------------------------------------------+
void CDBWriter::OnInitEvent(const string _DBPath,const CDataKeeper &inputData_array[],double _r,ENUM_TIMEFRAMES _TF)
  {
   CreateDB(_DBPath,inputData_array,_r,_TF);
   scoring_type=0;
  }

実装でわかるように、メソッドはクラスフィールドに必要な値を割り当ててデータベースを作成します。コールバックメソッドは、ユーザが個人的に(カスタム比率を計算する必要がある場合)実装する必要があります。コールバックを使用せずにオーバーロードを使用する必要があります。この場合、カスタム比率はゼロに等しくなります。ユーザの比率は、EA最適化パスを評価するカスタムメソッドです。これを実装するために、必要な2種類のデータを持つ2つの関数へのポインタが作成されました。

CreateDBメソッドは、主なクラスメソッドの1つで、作業の準備をすべて行います。

OnTickEvent パブリックティックは、各ティックで分足ローソク足の日時を保存します。戦略をテストするとき、現在のパスがフォワードであるかどうかを定義することは不可能ですが、データベースにも同様のパラメータがあります。しかし、テスターが履歴テストの後にフォワードテストを実行することはわかっています。したがって、各ティックで日付を変数に上書きしながら、最適化プロセスの最後で最終の日付を見つけます。OptimisationParamsの表にはHistoryBorderパラメータがあります。これは保存された日時と同じです。この行は、履歴最適化中にのみこの表に追加されます。このパラメータを使用した最初のパス(履歴最適化パスと同じ)では、データベース内の必須フィールドに日付が追加されます。次のパスの1つの間に、これらのパラメータを持つエントリがすでにデータベースに存在することがわかっている場合、2つのオプションがあります。

  1. ユーザが何らかの理由で履歴の最適化を停止して再開した
  2. フォワード最適化

これらを区別するためには、現在のパスに格納されている最後の日付とデータベースの日付を比較します。現在の日付がデータベース内の日付より大きい場合はフォワードパスで、それ以外の場合は履歴パスです。最適化を同じ比率で2回実行する必要があることを考慮して、新しいデータのみをデータベースに入力するか、現在のパスで行われたすべての変更をキャンセルします。メソッドは、データをデータベースに保存します。これは次のように実装されています。

//+------------------------------------------------------------------+
//| データをすべてデータベースに保存して                                  |
//| カスタム比を返す                                                   |
//+------------------------------------------------------------------+
double CDBWriter::OnTesterEvent()
  {

   DealDetales history[];

   CDealHistoryGetter historyGetter;
   historyGetter.getDealsDetales(history,0,TimeCurrent()); // 取引履歴を取得する

   CMutexSync sync; // 同期オブジェクト 
   if(!sync.Create(getMutexName(DBPath))) { Print(Symbol()+" MutexSync create ERROR!"); return 0; }
   CMutexLock lock(sync,(DWORD)INFINITE); // セグメントをブラケット内にロックする

   bool IsForvard=isForvard(); // 現在のテスター反復が前のテスター反復であるかどうかを調べる
   CReportCreator rc;
   string Symb[];
   rc.Get_Symb(history,Symb); // 銘柄リストを取得する
   rc.Create(history,Symb,balance,r); // レポートを作成する(持ち切りレポートは自動的に作成される)。

   double ans=0;
   dbManager.BeginTransaction(); // トランザクション開始

   CStatement stmt(dbManager.Create_statement("INSERT OR IGNORE INTO ParamsType VALUES(@ParamName,@ParamType);")); // EAパラメータ型リストの保存リクエスト
   if(stmt.get()!=NULL)
     {
      for(int i=0;i<ArraySize(coef_array);i++)
        {
         stmt.Parameter(1,coef_array[i].getName());
         stmt.Parameter(2,(int)coef_array[i].getType());
         stmt.Execute(); // パラメータ型と名前を保存する
        }
     }

   int ID=setParams(IsForvard,&rc,history,ans); // EAのパラメータと評価率を保存してIDを取得する
   if(ID>0)// ID > 0の場合、パラメータは正常に保存された
     {
      if(setTraidingHistory(IsForvard,history,ID)) // 取引履歴を保存して保存されたことを確認する
        {
         setBuyAndHold(IsForvard,&rc); // 持ち切り履歴を保存する(最初の保存中に1回のみ保存)
         dbManager.CommitTransaction(); // トランザクション終了を確認する
        }
      else dbManager.RollbackTransaction(); // さもなければ、トランザクションをキャンセルする
     }
   else dbManager.RollbackTransaction(); // さもなければ、トランザクションをキャンセルする

   return ans;
  }

メソッドは最初に、前の記事で説明したクラスを使用して取引履歴を作成します。次に、共有リソース(Mutex)を受け取り、データを保存します。これを達成するには、まず、現在の最適化パスが(前述の方法に従って)順方向であるかどうかを定義してから、銘柄リスト(取引されたすべての銘柄)を取得します。

したがって、例えばスプレッド取引EAがテストされた場合、取引された両方の銘柄の取引履歴がアップロードされます。その後、レポートが生成され(後述のクラスを使用)、データベースに書き込まれます。正しいレコードのトランザクションが作成されます。表に記入するときにエラーが発生したり、不正なデータが取得された場合、トランザクションはキャンセルされます。 最初に、比率が保存され、うまくいけば、取引履歴とそれに続く持ち切り履歴が保存されます。後者は、最初のデータ入力中に1回だけ保存されます。データ保存エラーの場合は、Common/FilesフォルダのLog Fileが生成されます。

データベースを作成した後、それを読み取る必要があります。記述されたプログラムではデータベース読み込みクラスが既に使用されています。それはより簡単で次のようになります。

//+------------------------------------------------------------------+
//| データベースからデータを読むクラス                                   |
//+------------------------------------------------------------------+
class CDBReader
  {
public:
   void              Connect(string DBPath);// データベースに接続するメソッド

   bool              getBuyAndHold(BuyAndHoldChart_item &data[],bool isForvard);// 持ち切り履歴を計算するメソッド
   bool              getTraidingHistory(DealDetales &data[],long ID,bool isForvard);// EAによる取引履歴を計算するメソッド 
   bool              getRobotParams(CoefData_item &data[],bool isForvard);// EAパラメータと比を計算するメソッド

private:
   CSqliteManager    dbManager; // データベースマネージャ
   string            DBPath; // データベースへのパス

   bool              getParamTypes(ParamType_item &data[]);// 入力型と名前を計算する
  };

ここで関心のある4つの表を読み、3つのパブリックメソッドを実装し、これらの表のデータで構造体配列を作成します。

したがって、記述されたクラスでは、データベースに直接連携することはできませんが、必要なデータを提供するクラスを使用して、データベース連携アルゴリズム全体を隠すことができます。これらのクラスは、書かれたデータベースのラッパーと連携して作業を簡素化します。上記のラッパーは、データベース開発者が提供するDLLを介してデータベースと連携します。データベースそのものは、すべての必要条件を満たすものであり、実際には、このプログラムと他の分析アプリケーションの両方で輸送と処理に便利なファイルです。このアプローチのもう1つの利点は、1つのアルゴリズムの長期的な操作によって、各最適化からデータベースを収集し、履歴とトラッキングパラメータの変更パターンを蓄積できることです。


計算

ブロックは2つのクラスで構成されています。最初のブロックは取引レポートを生成するためのもので、前の記事で説明した取引レポートを生成するクラスを改良したものです。

2番目はフィルタクラスです。最適化サンプルを渡された範囲で並び替え、個々の最適化率の値ごとに収益性の高い損失のある取引の頻度を表示するグラフを作成することができます。このクラスの別の目的は、最適化の終了時(すなわち、最適化期間全体にわたったPL)に実際に取引されたPLの正規分布グラフを作成することです。つまり、最適化パスが1,000ある場合、1,000の最適化結果が得られます(最適化終了時のPL)。ここで興味のある分布はそれらに基づいています。

この分布は、得られた値の非対称性がどの方向にシフトしているかを示します。長い尾と分布のセンターが利益ゾーンにある場合、ロボットはほとんど有益な最適化パスを生成し、それに応じて良好です。それ以外の場合は、ほとんど利益の出ないパスが生成されます。定義の非対称性が損失ゾーンにシフトすると、これは選択されたパラメータが主に利益ではなく損失を引き起こすことも意味します。

取引レポートを生成するクラスから始めてこのブロックを見てみましょう。記述されたクラスは、「History manager」フォルダのインクルードディレクトリにあり、次のヘッダを持ちます。

//+------------------------------------------------------------------+
//| 取引履歴統計を生成するクラス                                         |
//+------------------------------------------------------------------+
class CReportCreator
  {
public:

   //=============================================================================================================================================
   // 計算/再計算
   //=============================================================================================================================================

   void              Create(DealDetales &history[],DealDetales &BH_history[],const double balance,const string &Symb[],double r);
   void              Create(DealDetales &history[],DealDetales &BH_history[],const string &Symb[],double r);
   void              Create(DealDetales &history[],const string &Symb[],const double balance,double r);
   void              Create(DealDetales &history[],double r);
   void              Create(const string &Symb[],double r);
   void              Create(double r=0);

   //=============================================================================================================================================
   // ゲッター
   //=============================================================================================================================================

   bool              GetChart(ChartType chart_type,CalcType calc_type,PLChart_item &out[]); // PLグラフを取得
   bool              GetDistributionChart(bool isOneLot,DistributionChart &out); // 分散グラフを取得
   bool              GetCoefChart(bool isOneLot,CoefChartType type,CoefChart_item &out[]); // 比率グラフを取得
   bool              GetDailyPL(DailyPL_calcBy calcBy,DailyPL_calcType calcType,DailyPL &out); // 日ごとのPLグラフを取得
   bool              GetRatioTable(bool isOneLot,ProfitDrawdownType type,ProfitDrawdown &out); // 極値の表を取得
   bool              GetTotalResult(TotalResult &out); // TotalResult表を取得
   bool              GetPL_detales(PL_detales &out); // PL_detales表を取得
   void              Get_Symb(const DealDetales &history[],string &Symb[]); // 取引された銘柄の配列を取得
   void              Clear(); // 統計をクリアする

private:
   //=============================================================================================================================================
   // プライベートデータ型
   //=============================================================================================================================================
   // PLグラフ型の構造体
   struct PL_keeper
     {
      PLChart_item      PL_total[];
      PLChart_item      PL_oneLot[];
      PLChart_item      PL_Indicative[];
     };
   // 日ごとのP/Lグラフ型の構造体
   struct DailyPL_keeper
     {
      DailyPL           avarage_open,avarage_close,absolute_open,absolute_close;
     };
   // 極点の表の構造体
   struct RatioTable_keeper
     {
      ProfitDrawdown    Total_max,Total_absolute,Total_percent;
      ProfitDrawdown    OneLot_max,OneLot_absolute,OneLot_percent;
     };
   // 行内の利益と損失の量を計算するための構造体
   struct S_dealsCounter
     {
      int               Profit,DD;
     };
   struct S_dealsInARow : public S_dealsCounter
     {
      S_dealsCounter    Counter;
     };
   // 補助的データの計算に使用される構造体
   struct CalculationData_item
     {
      S_dealsInARow     dealsCounter;
      int               R_arr[];
      double            DD_percent;
      double            Accomulated_DD,Accomulated_Profit;
      double            PL;
      double            Max_DD_forDeal,Max_Profit_forDeal;
      double            Max_DD_byPL,Max_Profit_byPL;
      datetime          DT_Max_DD_byPL,DT_Max_Profit_byPL;
      datetime          DT_Max_DD_forDeal,DT_Max_Profit_forDeal;
      int               Total_DD_numDeals,Total_Profit_numDeals;
     };
   struct CalculationData
     {
      CalculationData_item total,oneLot;
      int               num_deals;
      bool              isNot_firstDeal;
     };
   // 比率グラフの作成に使用される構造体
   struct CoefChart_keeper
     {
      CoefChart_item    OneLot_ShartRatio_chart[],Total_ShartRatio_chart[];
      CoefChart_item    OneLot_WinCoef_chart[],Total_WinCoef_chart[];
      CoefChart_item    OneLot_RecoveryFactor_chart[],Total_RecoveryFactor_chart[];
      CoefChart_item    OneLot_ProfitFactor_chart[],Total_ProfitFactor_chart[];
      CoefChart_item    OneLot_AltmanZScore_chart[],Total_AltmanZScore_chart[];
     };
   // 終了日によって取引履歴を並び替えるクラス
   class CHistoryComparer : public ICustomComparer<DealDetales>
     {
   public:
      int               Compare(DealDetales &x,DealDetales &y);
     };
   //=============================================================================================================================================
   // キーパー:
   //=============================================================================================================================================
   CHistoryComparer  historyComparer; // 比較クラス
   CChartComparer    chartComparer; // 比較クラス

                                    // 補助的構造体
   PL_keeper         PL,PL_hist,BH,BH_hist;
   DailyPL_keeper    DailyPL_data;
   RatioTable_keeper RatioTable_data;
   TotalResult       TotalResult_data;
   PL_detales        PL_detales_data;
   DistributionChart OneLot_PDF_chart,Total_PDF_chart;
   CoefChart_keeper  CoefChart_data;

   double            balance,r; // 初期入金とリスクフリー比率
                                // 並び変えクラス
   CGenericSorter    sorter;

   //=============================================================================================================================================
   // 計算
   //=============================================================================================================================================
   // PLを計算する
   void              CalcPL(const DealDetales &deal,CalculationData &data,PLChart_item &pl_out[],CalcType type);
   // PLヒストグラムを計算する
   void              CalcPLHist(const DealDetales &deal,CalculationData &data,PLChart_item &pl_out[],CalcType type);
   // プロットに使用する補助的構造体を計算する
   void              CalcData(const DealDetales &deal,CalculationData &out,bool isBH);
   void              CalcData_item(const DealDetales &deal,CalculationData_item &out,bool isOneLot);
   // 1日のP/Lを計算する
   void              CalcDailyPL(DailyPL &out,DailyPL_calcBy calcBy,const DealDetales &deal);
   void              cmpDay(const DealDetales &deal,ENUM_DAY_OF_WEEK etalone,PLDrawdown &ans,DailyPL_calcBy calcBy);
   void              avarageDay(PLDrawdown &day);
   // 銘柄を比較する
   bool              isSymb(const string &Symb[],string symbol);
   // 利益率を計算する
   void              ProfitFactor_chart_calc(CoefChart_item &out[],CalculationData &data,const DealDetales &deal,bool isOneLot);
   // 回復率を計算する
   void              RecoveryFactor_chart_calc(CoefChart_item &out[],CalculationData &data,const DealDetales &deal,bool isOneLot);
   // 勝率を計算する
   void              WinCoef_chart_calc(CoefChart_item &out[],CalculationData &data,const DealDetales &deal,bool isOneLot);
   // シャープレシオを計算する
   double            ShartRatio_calc(PLChart_item &data[]);
   void              ShartRatio_chart_calc(CoefChart_item &out[],PLChart_item &data[],const DealDetales &deal);
   // 分布を計算する
   void              NormalPDF_chart_calc(DistributionChart &out,PLChart_item &data[]);
   double            PDF_calc(double Mx,double Std,double x);
   // VaRを計算する
   double            VaR(double quantile,double Mx,double Std);
   // Zスコアを計算する 
   void              AltmanZScore_chart_calc(CoefChart_item &out[],double N,double R,double W,double L,const DealDetales &deal);
   // TotalResult_item構造体を計算する
   void              CalcTotalResult(CalculationData &data,bool isOneLot,TotalResult_item &out);
   // PL_detales_item構造体を計算する
   void              CalcPL_detales(CalculationData_item &data,int deals_num,PL_detales_item &out);
   // 日時から日を取得する
   ENUM_DAY_OF_WEEK  getDay(datetime DT);
   // データをクリアする
   void              Clear_PL_keeper(PL_keeper &data);
   void              Clear_DailyPL(DailyPL &data);
   void              Clear_RatioTable(RatioTable_keeper &data);
   void              Clear_TotalResult_item(TotalResult_item &data);
   void              Clear_PL_detales(PL_detales &data);
   void              Clear_DistributionChart(DistributionChart &data);
   void              Clear_CoefChart_keeper(CoefChart_keeper &data);

   //=============================================================================================================================================
   // コピー:
   //=============================================================================================================================================
   void              CopyPL(const PLChart_item &src[],PLChart_item &out[]); // PLグラフをコピーする
   void              CopyCoefChart(const CoefChart_item &src[],CoefChart_item &out[]); // 比率グラフをコピーする

  };

このクラスは、以前のバージョンとは異なり、以前の2倍のデータを計算し、より多くのタイプのグラフを作成します。「Create」メソッドのオーバーロードもレポートを計算します。

実際、レポートはCreateメソッド呼び出し時に1回だけ生成されます。その後、「Get」で始まるメソッドで以前に計算されたデータのみが取得されます。一度入力パラメータを反復処理するメインループは、最も引数の多いCreateメソッドにあります。このメソッドは引数を繰り返し処理し、一連のデータを即時に計算します。これに基づいて、必要なすべてのデータが同じ繰り返しで構築されます。

これにより、1回のパスで興味のあるものすべてを構築することができます。グラフを取得するためのこのクラスの以前のバージョンは、初期データを再度繰り返します。その結果、すべての比率の計算はミリ秒で終了しますが、必要なデータを取得するにはさらに時間がかかります。 クラスの「プライベート」部分には、より便利なデータコンテナとしてそのクラス内でのみ使用される一連の構造があります。取引履歴の並び替えは、前述の汎用並び替えメソッドを使用して実行されます。

getterのそれぞれを呼び出すときに取得されるデータについて説明します。

メソッド パラメータ チャートの種類
GetChart chart_type = _PL, calc_type = _Total PLグラフ — 実際の取引履歴による
GetChart chart_type = _PL, calc_type = _OneLot PLグラフ — 単一ロットを取引した場合
GetChart chart_type = _PL, calc_type = _Indicative PLグラフ — 指標
GetChart chart_type = _BH, calc_type = _Total BHグラフ — ロボットとしてロットを管理する場合
GetChart chart_type = _BH, calc_type = _OneLot BHグラフ — 単一ロットを取引した場合
GetChart chart_type = _BH, calc_type = _Indicative BHグラフ — 指標
GetChart chart_type = _Hist_PL, calc_type = _Total PLヒストグラム — 実際の取引履歴による
GetChart chart_type = _Hist_PL, calc_type = _OneLot PLヒストグラム — 単一ロットの取引
GetChart chart_type = _Hist_PL, calc_type = _Indicative PLヒストグラム — 指標
GetChart chart_type = _Hist_BH, calc_type = _Total BHヒストグラム — ロボットとしてロットを管理する場合
GetChart chart_type = _Hist_BH, calc_type = _OneLot BHヒストグラム — 単一ロットを取引した場合
GetChart chart_type = _Hist_BH, calc_type = _Indicative BHヒストグラム — 指標
GetDistributionChart isOneLot = true 単一ロットを取引した場合の分布とVaR
GetDistributionChart isOneLot = false 以前と同じ方法で取引した場合の分布とVaR
GetCoefChart isOneLot = true, type=_ShartRatio_chart 単一ロットを取引した場合のシャープレシオ
GetCoefChart isOneLot = true, type=_WinCoef_chart 単一ロットを取引した場合の勝率
GetCoefChart isOneLot = true, type=_RecoveryFactor_chart 単一ロットを取引した場合の時間による回復率
GetCoefChart isOneLot = true, type=_ProfitFactor_chart 単一のロットを取引した場合の時間による利益率
GetCoefChart isOneLot = true, type=_AltmanZScore_chart Z — 単一のロットを取引した場合の時間によるAltmanスコア
GetCoefChart isOneLot = false, type=_ShartRatio_chart 以前と同じ方法で取引した場合のシャープレシオ
GetCoefChart isOneLot = false, type=_WinCoef_chart 以前と同じ方法で取引した場合の勝率
GetCoefChart isOneLot = false, type=_RecoveryFactor_chart 以前と同じ方法で取引した場合の回復率
GetCoefChart isOneLot = false, type=_ProfitFactor_chart 以前と同じ方法で取引した場合の利益率
GetCoefChart isOneLot = false, type=_AltmanZScore_chart Z — 以前と同じ方法で取引した場合のAltmanスコア
GetDailyPL calcBy=CALC_FOR_CLOSE, calcType=AVERAGE_DATA 終了時点での日の平均PL
GetDailyPL calcBy=CALC_FOR_CLOSE, calcType=ABSOLUTE_DATA 終了時点での日の合計PL
GetDailyPL calcBy=CALC_FOR_OPEN, calcType=AVERAGE_DATA 開始時点での日の平均PL
GetDailyPL calcBy=CALC_FOR_OPEN, calcType=ABSOLUTE_DATA 開始時点での日の合計PL
GetRatioTable isOneLot = true, type = _Max 単一ロットを取引した場合 - 取引ごとの最大のP/L
GetRatioTable isOneLot = true, type = _Absolute 単一ロットを取引した場合 - 総P/L
GetRatioTable isOneLot = true, type = _Percent 単一ロットを取引した場合 - 損益%
GetRatioTable isOneLot = false, type = _Max 単一ロットを取引した場合 - 取引ごとの最大のP/L
GetRatioTable isOneLot = false, type = _Absolute 以前と同じ方法で取引した場合 — total profit/loss
GetRatioTable isOneLot = false, type = _Percent 以前と同じ方法で取引した場合 — 損益%
GetTotalResult
比率表
GetPL_detales
PL曲線の簡単な要約
Get_Symb
取引履歴に存在する銘柄の配列

PLグラフ — 実際の取引履歴による:

グラフは通常のPLグラフに等しいです。すべてのテスターパスの後で、端末でこれを見ることができます.

PLグラフ — 単一ロットを取引した場合:

このグラフは、前述の取引量が異なるグラフと似ており、あたかも常に単一ロットを取引しているかのように計算されます。エントリ価格とエグジット価格は、EA市場の出入口の合計数で平均価格として計算されます。また、EAによって取引された利益に基づいて取引利益も計算されますが、比率を使用して1つのロットを取引するように得られた利益に変換されます。

PLグラフ — 指標:

正規化されたPLグラフです。PL> 0の場合、PLはこの時点までに達成された最大損失配分で除算されます。さもなければ、PLは今までに達成された最大利益取引で除算されます。

ヒストグラムグラフも同様の方法で構築されます。

分布とVaR:

パラメトリックVaRは絶対データと成長の両方を使用して構築されています。

分布グラフについても同様です。

比率グラフ:

各ループ反復において、この特定の反復で利用可能な履歴全体にわたる適切な方程式に基づいて構築されます。

1日の利益のグラフ:

表に記載されている4つの可能な利益の組み合わせによって構築されています。ヒストグラムのように見えます。

上記のデータを作成する方法は、次のようになります。

//+------------------------------------------------------------------+
//| 比率の計算/再計算                                                  |
//+------------------------------------------------------------------+
void CReportCreator::Create(DealDetales &history[],DealDetales &BH_history[],const double _balance,const string &Symb[],double _r)
  {
   Clear(); // データをクリアする
            // 残高を保存する
   this.balance=_balance;
   if(this.balance<=0)
     {
      CDealHistoryGetter dealGetter;
      this.balance=dealGetter.getBalance(history[ArraySize(history)-1].DT_open);
     }
   if(this.balance<0)
      this.balance=0;
// リスクなしの比率を保存する
   if(_r<0) _r=0;
   this.r=r;

// 補助的構造体
   CalculationData data_H,data_BH;
   ZeroMemory(data_H);
   ZeroMemory(data_BH);
// 取引履歴の並び替え
   sorter.Method(Sort_Ascending);
   sorter.Sort<DealDetales>(history,&historyComparer);
// 取引履歴のループ
   for(int i=0;i<ArraySize(history);i++)
     {
      if(isSymb(Symb,history[i].symbol))
         CalcData(history[i],data_H,false);
     }
// 持ち切りの履歴と適切なループのソート
   sorter.Sort<DealDetales>(BH_history,&historyComparer);
   for(int i=0;i<ArraySize(BH_history);i++)
     {
      if(isSymb(Symb,BH_history[i].symbol))
         CalcData(BH_history[i],data_BH,true);
     }

// 1日の平均PL (平均されたタイプ)
   avarageDay(DailyPL_data.avarage_close.Mn);
   avarageDay(DailyPL_data.avarage_close.Tu);
   avarageDay(DailyPL_data.avarage_close.We);
   avarageDay(DailyPL_data.avarage_close.Th);
   avarageDay(DailyPL_data.avarage_close.Fr);

   avarageDay(DailyPL_data.avarage_open.Mn);
   avarageDay(DailyPL_data.avarage_open.Tu);
   avarageDay(DailyPL_data.avarage_open.We);
   avarageDay(DailyPL_data.avarage_open.Th);
   avarageDay(DailyPL_data.avarage_open.Fr);

// 損益率の表に記入する
   RatioTable_data.data_H.oneLot.Accomulated_Profit;
   RatioTable_data.data_H.oneLot.Accomulated_DD;
   RatioTable_data.data_H.oneLot.Max_Profit_forDeal;
   RatioTable_data.data_H.oneLot.Max_DD_forDeal;
   RatioTable_data.data_H.oneLot.Total_Profit_numDeals/data_H.num_deals;
   RatioTable_data.data_H.oneLot.Total_DD_numDeals/data_H.num_deals;

   RatioTable_data.Total_absolute.Profit=data_H.total.Accomulated_Profit;
   RatioTable_data.Total_absolute.Drawdown=data_H.total.Accomulated_DD;
   RatioTable_data.Total_max.Profit=data_H.total.Max_Profit_forDeal;
   RatioTable_data.Total_max.Drawdown=data_H.total.Max_DD_forDeal;
   RatioTable_data.Total_percent.Profit=data_H.total.Total_Profit_numDeals/data_H.num_deals;
   RatioTable_data.Total_percent.Drawdown=data_H.total.Total_DD_numDeals/data_H.num_deals;

// 正規分布を計算する
   NormalPDF_chart_calc(OneLot_PDF_chart,PL.PL_oneLot);
   NormalPDF_chart_calc(Total_PDF_chart,PL.PL_total);

// TotalResult
   CalcTotalResult(data_H,true,TotalResult_data.oneLot);
   CalcTotalResult(data_H,false,TotalResult_data.total);

// PL_detales
   CalcPL_detales(data_H.oneLot,data_H.num_deals,PL_detales_data.oneLot);
   CalcPL_detales(data_H.total,data_H.num_deals,PL_detales_data.total);
  }

実装からわかるように、データの一部はループが履歴を通過するときに計算され、一部のデータは構造体のデータ(CalculationData data_H、data_BH\)に基づいてすべてのループを通過した後に計算されます。

CalcDataメソッドは、Createメソッドと同様の方法で実装されています。これは、各反復で計算を実行するはずのメソッドを呼び出す唯一のメソッドです。最終データを計算するすべてのメソッドは、上記の構造に含まれる情報に基づいて計算されます。記載された構造体への記入/再記入は、以下のメソッドによって行われます。

//+------------------------------------------------------------------+
//| 補助的データを計算する                                              |
//+------------------------------------------------------------------+
void CReportCreator::CalcData_item(const DealDetales &deal,CalculationData_item &out,
                                   bool isOneLot)
  {
   double pl=(isOneLot ?deal.pl_oneLot : deal.pl_forDeal); // PL
   int n=0;
// 損益額
   if(pl>=0)
     {
      out.Total_Profit_numDeals++;
      n=1;
      out.dealsCounter.Counter.DD=0;
      out.dealsCounter.Counter.Profit++;
     }
   else
     {
      out.Total_DD_numDeals++;
      out.dealsCounter.Counter.DD++;
      out.dealsCounter.Counter.Profit=0;
     }
   out.dealsCounter.DD=MathMax(out.dealsCounter.DD,out.dealsCounter.Counter.DD);
   out.dealsCounter.Profit=MathMax(out.dealsCounter.Profit,out.dealsCounter.Counter.Profit);

// 損益のシリーズ
   int s=ArraySize(out.R_arr);
   if(!(s>0 && out.R_arr[s-1]==n))
     {
      ArrayResize(out.R_arr,s+1,s+1);
      out.R_arr[s]=n;
     }

   out.PL+=pl; // 総合的PL
               // 最大利益 / DD
   if(out.Max_DD_forDeal>pl)
     {
      out.Max_DD_forDeal=pl;
      out.DT_Max_DD_forDeal=deal.DT_close;
     }
   if(out.Max_Profit_forDeal<pl)
     {
      out.Max_Profit_forDeal=pl;
      out.DT_Max_Profit_forDeal=deal.DT_close;
     }
// 累積利益/DD
   out.Accomulated_DD+=(pl>0 ?0 : pl);
   out.Accomulated_Profit+=(pl>0 ?pl : 0);
// 利益による極値
   double maxPL=MathMax(out.Max_Profit_byPL,out.PL);
   if(compareDouble(maxPL,out.Max_Profit_byPL)==1/* || !isNot_firstDeal*/)// データの保存に確認があと1つ必要
     {
      out.DT_Max_Profit_byPL=deal.DT_close;
      out.Max_Profit_byPL=maxPL;
     }
   double maxDD=out.Max_DD_byPL;
   double DD=0;
   if(out.PL>0)DD=out.PL-maxPL;
   else DD=-(MathAbs(out.PL)+maxPL);
   maxDD=MathMin(maxDD,DD);
   if(compareDouble(maxDD,out.Max_DD_byPL)==-1/* || !isNot_firstDeal*/)// データの保存に確認があと1つ必要
     {
      out.Max_DD_byPL=maxDD;
      out.DT_Max_DD_byPL=deal.DT_close;
     }
   out.DD_percent=(balance>0 ?(MathAbs(DD)/(maxPL>0 ?maxPL : balance)) :(maxPL>0 ?(MathAbs(DD)/maxPL) : 0));
  }

これは、各計算メソッドのすべての入力データを計算する基本的なメソッドです。このアプローチ(入力データのこのメソッドへの計算の移動)は、クラスの以前のバージョンで発生した履歴ループでの過度のパスを回避し、トレーディングレポートを作成します。このメソッドは、CalcDataメソッドの内部で呼び出されます。

最適化パス結果フィルタのクラスには、次のヘッダがあります。

//+--------------------------------------------------------------------------+
//| 最適化パスをデータベースからアンロードした後に並び替えするクラス                 |
//+--------------------------------------------------------------------------+
class CParamsFiltre
  {
public:
                     CParamsFiltre(){sorter.Method(Sort_Ascending);} // デフォルトのコンストラクタ
   int               Total(){return ArraySize(arr_main);}; // アンロードされたパラメータの総数(最適化データ表による)
   void              Clear(){ArrayFree(arr_main);ArrayFree(arr_result);}; // すべての配列をクリアする
   void              Add(LotDependency_item &customCoef,CDataKeeper &params[],long ID,double total_PL,bool addToResult); // 配列に新しい値を追加する
   double            GetCustomCoef(long ID,bool isOneLot);// IDによってカスタム比を取得する
   void              GetParamNames(CArrayString &out);// EAパラメータ名を取得する
   void              Get_UniqueCoef(UniqCoefData_item &data[],string paramName,CArrayString &coefValue); // 一意の比率を取得する
   void              Filtre(string Name,string from,string till,long &ID_Arr[]);// arr_result配列を並び変える
   void              ResetFiltre(long &ID_arr[]);// 絞り込みをリセットする

   bool              Get_Distribution(Chart_item &out[],bool isMainTable);// 両方の配列で分布を作成する
   bool              Get_Distribution(Chart_item &out[],string Name,string value);// 選択されたデータで分布を作成する

private:
   CGenericSorter    sorter; // ソーター
   CCoefComparer     cmp_coef;// 比率を比較する
   CChartComparer    cmp_chart;// グラフを比較する

   bool              selectCoefByName(CDataKeeper &_input[],CDataKeeper &out,string Name);// 名前で比率を選択する
   double            Mx(CoefStruct &_arr[]);// 算術平均
   double            Std(CoefStruct &_arr[],double _Mx);// 標準偏差

   CoefStruct        arr_main[]; // 最適化データ表の同等
   CoefStruct        arr_result[];// 結果表の同等
  };

クラスの構造を分析し、いくつかのメソッドについてより詳しく説明します。ご覧のように、クラスには2つのグローバル配列arr_mainとarr_resultがあります。配列は、最適化のデータを格納します。最適化を使用してデータベースから表をアンロードした後、データベースは2つの表に分割されます。

記述された配列は、EAのIDとパラメータ、および配列名に従って上記の表の他のデータを格納します。本質的に、このクラスは2つの機能を実行します。1つ目は表を使用した操作のための便利なデータストレージであること、2つ目は選択された最適化パスの結果の表を並び替えることです。並び替えクラスと2つの比較クラスは、上記の配列の並び替えプロセスと、記述された表に従って構築された分布の並び替えに関係しています。

このクラスはEA比、つまりCdataKeeperクラスの形式での表現で動作するため、プライベートメソッドselectCoefByNameが作成されます。1つの必要な比率を選択し、1つの特定の最適化パスの渡されたEA比率の配列から参照によって結果を返します。

Addメソッドは、addToResult == trueの場合はデータベースにアップロードされた行を両方の配列に追加、または、addToResult == falseの場合はarr_main配列のみに追加します。IDは各最適化パスの一意のパラメータであるため、特定の選択されたパスの定義に関するすべての作業は、それに基づいています。提供された配列のうち、このパラメータのユーザ計算された比率が得られます。評価はプログラム参加なしでEA最適化の間に計算されるので、プログラム自体はカスタム評価を計算するための方程式を知りません。このため、これらの配列にカスタム評価を保存する必要があります。リクエストされたら、渡されたIDでGetCustomCoefメソッドを使用して取得します。

最も重要なクラスメソッドは次のとおりです。


プレゼンター

プレゼンターはコネクタとして機能します。これは、アプリケーションのグラフィックレイヤと前述のロジックとの間のリンクの一種です。このアプリケーションでは、プレゼンターは抽象(IPresenterインターフェイス)を使用して実装されます。このインターフェイスには、必要なコールバックメソッドの名前が含まれています。 それらは、必要なインターフェイスを継承するプレゼンタクラスで実装されます。この区別は、アプリケーションを完成させるために作成されました。プレゼンターブロックを書き換える必要がある場合は、グラフィックブロックまたはアプリケーションロジックに影響を与えずに簡単に実行できます。記載されたインターフェイスは、以下のように表されます。

//+------------------------------------------------------------------+
//| プレゼンターインターフェイス                                        |
//+------------------------------------------------------------------+
interface IPresenter
  {
   void Btn_Update_Click(); // データをダウンロードしてフォーム全体を構築する
   void Btn_Load_Click(); // レポートを作成する
   void OptimisationData(bool isMainTable);// 表の最適化行を選択する
   void Update_PLByDays(); // 日ごとの損益をアップロードする
   void DaySelect();// PL tableから1日を選択する
   void PL_pressed(PLSelected_type type);// 選択した履歴のPLグラフを構築する 
   void PL_pressed_2(bool isRealPL);// 「他のチャート」グラフを構築する
   void SaveToFile_Click();// データファイルを(サンドボックスに)保存する
   void SaveParam_passed(SaveParam_type type);// ファイルに書き出す日付を選択する
   void OptimisationParam_selected(); // 最適化パラメータを選択して「Optimisation selection」表に記入する
   void CompareTables(bool isChecked);// 結果表で分布を構築する(共通(メイン)表との相関)
   void show_FriquencyChart(bool isChecked);// 損益の頻度グラフを表示する
   void FriquencyChart_click();// 比率表の行を選択して分布を作成する
   void Filtre_click();// 選択した条件で並び替える
   void Reset_click();// 絞り込みをリセットする
   void PL_pressed_3(bool isRealPL);// 結果表のすべてのデータによって損益グラフを構築する
   void PL_pressed_4(bool isRealPL);// 統計表を構築する
   void setChartFlag(bool isPlot);// PL_pressed_3(bool isRealPL)メソッドからグラフを構築する(または構築しない)条件
  };

プレゼンタークラスは、必要なインターフェイスを実装しており、次のようになります。

class CPresenter : public IPresenter
  {
public:
                     CPresenter(CWindowManager *_windowManager); // コンストラクタ

   void              Btn_Update_Click();// データをダウンロードしてフォーム全体を構築する
   void              Btn_Load_Click();// レポートを作成する
   void              OptimisationData(bool isMainTable);// 表の最適化行を選択する
   void              Update_PLByDays();// 日ごとの損益をアップロードする
   void              PL_pressed(PLSelected_type type);// 選択した履歴のPLグラフを構築する 
   void              PL_pressed_2(bool isRealPL);// 「他のチャート」グラフを構築する
   void              SaveToFile_Click();// データファイルを(サンドボックスに)保存する
   void              SaveParam_passed(SaveParam_type type);// ファイルに書くデータを選択する
   void              OptimisationParam_selected();// 最適化パラメータを選択して「Optimisation selection」表に記入する
   void              CompareTables(bool isChecked);// 結果表で分布を構築する(共通(メイン)表との相関)
   void              show_FriquencyChart(bool isChecked);// 損益の頻度グラフを表示する
   void              FriquencyChart_click();// 比率表の行を選択して分布を作成する
   void              Filtre_click();// 選択した条件で並び替える
   void              PL_pressed_3(bool isRealPL);// 結果表のすべてのデータによって損益グラフを構築する
   void              PL_pressed_4(bool isRealPL);// 統計表を構築する
   void              DaySelect();// PL表の日を選ぶ 
   void              Reset_click();// フィルタをリセットする
   void              setChartFlag(bool isPlot);// PL_pressed_3(bool isRealPL) メソッドからグラフを構築する(または構築しない)条件

private:
   CWindowManager   *windowManager;// ウィンドウクラスへの参照
   CDBReader         dbReader;// データベース連携クラス
   CReportCreator    reportCreator; // データ処理クラス

   CGenericSorter    sorter; // 並び変えクラス
   CoefData_comparer coefComparer; // データ比較クラス

   void              loadData();// データベースからデータをアップロードして表を記入する

   void              insertDataTo_main_Table(bool isResult,const CoefData_item &data[]); // 結果表と「メイン」表(最適化パス比を持つ表)にデータを挿入する
   void              insertRowTo_main_Table(CTable *tb,int n,const CoefData_item &data); // 最適化パス表への直接データ挿入
   void              selectChartByID(long ID,bool recalc=true);//  IDによってグラフを選択する
   void              createReport();// レポートを作成する
   string            getCorrectPath(string path,string name);// ファイルへの正しいパスを取得する
   bool              getPLChart(PLChart_item &data[],bool isOneLot,long ID);

   bool              curveAdd(CGraphic *chart_ptr,const PLChart_item &data[],bool isHist);// グラフを他のチャートに追加する
   bool              curveAdd(CGraphic *chart_ptr,const CoefChart_item &data[],double borderPoint);// グラフを他のチャートに追加する
   bool              curveAdd(CGraphic *chart_ptr,const Distribution_item &data);// グラフを他のチャートに追加する
   void              setCombobox(CComboBox *cb_ptr,CArrayString &arr,bool isFirstIndex=true);// コンボボックスパラメータを設定する
   void              addPDF_line(CGraphic *chart_ptr,double &x[],color clr,int width,string _name=NULL);// 分布グラフの滑らかな線を追加する
   void              plotMainPDF();// 「メイン」表(最適化データ)によって分布を構築する)
   void              updateDT(CDropCalendar *dt_ptr,datetime DT);// ドロップダウンカレンダーを更新する

   CParamsFiltre     coefKeeper;// 最適化パスを分布別に並び替える
   CArrayString      headder; // 比率表のヘッダ

   bool              _isUpbateClick; // 更新ボタンの押下とデータベースからのデータアップロードのフラグ
   long              _selectedID; // 選択されたすべてのPLグラフのID(損失の場合は赤、収益の場合は緑)
   long              _ID,_ID_Arr[];// データをアップロードした後に「Result」表に選択されたIDの配列
   bool              _IsForvard_inTables,_IsForvard_inReport; // 最適化パスの表の最適化データ型のフラグ
   datetime          _DT_from,_DT_till;
   double            _Gap; // 以前に選択した最適化グラフの追加されたギャップの保存された種類(スプレッド拡張/スリップシミュレーション...)
  };

それぞれのコールバックは非常によくコメントされているので、ここでは説明しません。これは、アプリケーションのすべてのフォーム動作が実装されている部分だと言うことが必要なだけです。それは、グラフの構築、コンボボックスの記入、データベースからのデータのアップロードおよび処理方法の呼び出し、また、さまざまなクラスを接続する他の操作を含みます。


終わりに

テスターを通過する可能性のあるすべての最適化パラメータを使用して表を処理するアプリケーションを開発しました。また、すべての最適化パスをデータベースに保存する機能をEAに追加しました。関心のあるパラメータを選択すると詳細な取引レポートが得られるのに加えて、最適化履歴全体から選択した一定期間を徹底的に見たり、同期間のすべての比率を見ることができます。Gapパラメータを増加させてスリッページをシミュレートし、グラフや比率の動作への影響を確認することもできます。もう1つの追加は、特定の間隔の比率値によって最適化結果を並び変えるる機能です。

上位100件の最適化パスを取得する最も簡単な方法は、CDBWriterクラスをサンプルEAと同じように(添付ファイル内)接続し、条件付きフィルタを設定(たとえば、Profit Factor> = 1で直ちに負けトレードの組み合わせを除外)し、「Update」をクリックして「Show n params」パラメータを100のままにすることです。この場合、(フィルターに応じて)上位100件の最適化パスが結果表に表示されます。結果として得られるアプリケーションの各オプションと、より洗練された比率の選択方法については、次の記事で詳しく説明します。


本稿には以下のファイルが添付されています。

Experts/2MA_Martin — テストEAプロジェクト

Experts/OptimisationSelector — 説明されたアプリケーションプロジェクト

Include/CustomGeneric

Include/History manager

Include/OptimisationSelector

Include/Sqlite3

Include/WinApi

ライブラリ

テストデータベース