English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
トレーディングモデルに基づくマルチエキスパートアドバイザーの作成

トレーディングモデルに基づくマルチエキスパートアドバイザーの作成

MetaTrader 5テスター | 7 10月 2015, 17:20
816 0
Vasiliy Sokolov
Vasiliy Sokolov

はじめに

MetaTrader4ターミナルの技術的可能性、そして、そのストラテジーテスターは、マルチ通貨取引システムの動作やテストを規定します。MetaTrader4のにおいてのそのようなシステムを開発する複雑さは、まず、複数の取引ツールにおいて、ティックごとのシミュレーションができないことから始まりました。加えて、MQL4言語の限られた言語的資産は、複雑なデータ構造や、効率的なデータ管理を難しくさせていました。

ただ、MQL5の誕生で、状況は大きく変わりました。MQL5は、 オブジェクト指向のアプローチをサポートしており、補助関数に基づき、データの構成から、標準システム関数の作業用インターフェースにいたるユーザーの日々の業務を加速させる標準ライブラリのベースクラスを保有しています。

また、ターミナルやストラテジーテスターの技術的記述は、複数通貨用EAの使用を認めているが、複数のタイムフレーム、金融商品におけるEAの作業の並列化のためのメソッドを内蔵していません。単純な場合のEAの作業においては、金融商品名やタイムフレームを決定するシンボルのウィンドウにて稼働させる必要があります。結果として、MetaTrader4の時から受け入れられている手法は、ストラテジーテスターやMetaTrader5ターミナルの利点を最大限に活用していません。

それぞれの金融商品において計算されるポジション一つのみが許されているという事実により複雑化されており、ネット・ポジションへの移行は、正しく、タイムリーなものなのです。ネット・ポジションは、特定のマーケットにおけるトレーダーの関心をほぼ完全に表すものでもあります。

しかし、そのような取引群は、トレードのプロセスを単純化し、簡単に視覚化するものではありません。以前、EAがオープンオーダーを選択する(例えば、マジックナンバーを用いれば、オーダーが特定されます。)場合では十分でした。今では、一つの金融商品においてネットポジションがないことはEAの特定のインスタンスがマーケットにないということを指しません。

サードパーティーの開発者は、ビジュアルによる注文のマネージャーを記述することから、(A Virtual Order Manager to track orders within the position-centric MT5 environment(ポジション中心のMT5環境での注文追跡のための仮想注文マネージャーという記事をみてください。) マジックナンバーを用いて集合したポジションのインプット値を統合することまで様々なネットポジションの問題解決方法を提供しています。(The Optimal Method for Calculation of Total Position Volume by Specified Magic Number(特定のマジックナンバーによる総ポジション値の計算のための関数)The Use of ORDER_MAGIC for Trading with Different Expert Advisors on a Single Instrument(一つの証券においての異なるEAでのトレードのために、ORDER_MAGICを使用する)という記事をみてください)

しかしながら、集合したポジションにおける問題に加えて、同じEAが複数の金融商品のトレードにおいて必要とされた際のいわゆる複数通貨における問題があります。この問題への解決策は、Creating an Expert Advisor which Trades on Different Instruments(異なる金融商品にてトレードを行うエキスパートアドバイザーの作成).という記事にてご覧ください。

すべての紹介されたメソッドは、それぞれ利点を持ちます。その致命的な欠陥は、それぞれのメソッドがそれぞれの観点から問題にアプローチしており、複数のEAにおいては適しているが複数通貨のソリューシに適していないような方法を提供しているということです。

この記事は、一つのソリューションによりすべての問題を解決することを目指しています。このソリューションにより、複数通貨や異なるEA間のやりとりにおけるマルチシステムテストの問題を解決することができます。これは、難しそうに見えますが、実際はより簡単なものなのです。

想像してみてください、あなたの シングルEA が全ての金融商品やタイムフレームにていくつもの戦略のトレードを行うことができるのです。さらに、EAは、簡単にテスターにてテストされ、全ての戦略においていくつかの資産運用システムを稼働させることができます。

こちらに解決する必要のあるいくつかの主要なタスクがあります。

  1. EAは同時に複数の通貨システムに基づいたトレードを行う必要があります。加えて、複数のトレーディングシステム、また、単一のシステム上にて簡単に取引を行う必要があります:
  2. EAにおいて実行されるトレーディングシステムは、お互いに邪魔しあわないようにする必要があります。個々の取引システムは、全てのネットポジションや注文のみに対する貢献を扱う必要があります。
  3. それぞれのシステムでは、全てのタイムフレームを同時に行うのと同様、単一のタイムフレームでトレードを行うことは同等に簡単です。
  4. それぞれのシステムでは、全てのタイムフレームを同時にやるのと同様、単一のタイムフレームでトレードを行うことは同等に簡単です。

もし、扱う必要のあるタスクのリストを注意深く検査すると、3次元の配列にたどり着きます。配列の一次元は、トレーディングシステムの数、二次元は、特定のTSが作動する必要のあるタイムフレーム数、3次元は、TSにおけるトレード用金融商品数です。MACD Sampleのような簡単なEAでさえ、8つの通貨ペアにて同時に稼働する際は、152の独立したソリューションを持ることを簡単な計算が示しており、1 EA * 8 ペア * 19 タイムフレーム、という式となります。(週・月ごとのタイムフレームは含まれていません。)

もし、トレーディングシステムがより大きくなり、EAのポートフォリオがより広範んものであれば、ソリューション数は、たやすく500を超え、あるケースでは、1000を超えます!それぞれの組み合わせを個別に調整しアップロードすることは不可能であることが明白です。そのため、自動的にそれぞれの組み合わせを調整し、EAのメモリーへロードし、特定の組み合わせのルールに基づきEAがトレードしてくれるようなシステムを開発する必要があります。


条件とコンセプト

"トレーディング ストラテジー"という概念がありますが、ここではトレーディングモデルや 、より簡潔にモデルと表します。トレーディングモデルは、特別なクラスであり、トレーディングの戦略を示す特定のルールに沿って開発されています:トレードにて使用されるインジケーター、エントリーとエグジットの取引状況、資産管理のメソッドなどです。それぞれのトレーディングモデルは、抽象的であり、稼働のために複数のパラメーターを定義しません。

簡単な具体例は、二つの移動平均のクロスオーバーに基づく、取引戦術です。もし早い移動平均が 上方に遅い方を越すと、買いの取引が開きます。もし、逆に下方に越せば、売り取引を開きます。この方程式はトレーディングモデルを記載する上で十分なものです。

しかしながら、そのようなモデルが一旦記載されれば、平均期間での移動平均、データウィンドウの期間、このモデルが取引する金融商品などを決定する必要があります。一般的に、この抽象的なモデルは、特定のモデルインスタンスを作成すると、この抽象的なモデルは記入する必要のあるパラメーターを含むようになります。この手法の元では、抽象的なモデルはパラメーター内で異なるモデルの複数のインスタンスの派生元になる可能性があります。


ネットポジションの勘定の完全な拒絶

多くの開発者が集合ポジションを監視しようとしています。しかしながら、上をご覧の通り、集合ポジションのサイズや、その変動は、特定のモデルのインスタンスには関連しません。そのモデルは、集合ポジションが存在しない間の短いものになりえます。逆に、集合ポジションはモデルがロングポジションを持つ間、短くなります。

より詳しくこれらのケースを見てみましょう。一つの金融証券取引が3つの異なる取引戦略でトレードされ、それぞれが独立した資産管理システムを持るケースを想定してみましょう。また、それら3つのシステムの1つが、カバーなしで3つの契約を売る、つまり、3つの契約にてショートポジションを作成することを決定したと考えてみましょう。取引の終了後、ネットポジションは、最初のトレーディングシステムの取引から成るか、その額が3つの契約、もしくは3つのカバーのない契約から引いたものになるか、しばらくすると、二つ目のトレーディングシステムが 同じ資産の4つの契約を買う決定を行います。

結果として、ネットポジションは変化し、一つのロング契約からなるようになります。今回は、二つのトレーディングシステムによる影響を含みます。さらに、3つ目のトレーディングシステムは標準の契約額で同様の資産においてのショートポジションを作成します。ネットポジションは中立になります。なぜなら -3 ショート+ 4 ロング - 1 ショート = 0、だからです。

ネットポジションがなければ、3つのトレーディングシステム全てが市場にないと指すことになるのでしょうか?実は全く違います。二つのシステムがカバーなしの4つの契約を持ち、カバーが常に作成されるということを示します。一方、3つ目のシステムは4つのロング契約を持ち、それらは売られていないという状況です。4つのショート契約の再支払いが終了すれば、中立のポジションは、3つのシステムにおいてポジションが欠乏しているということを示します。

もちろん、それぞれのモデルに全ての一連のアクションを再構築することができ、現在のポジションのサイズへの特定の影響を決定することができますが、より簡単なメソッドが存在します。このメソッドは簡単で、すべてのサイズになりえ、(マニュアルトレーディングなど)二つの外部、内部の要素(EA内のほかのモデルの動作)に依存します。現在の集合ポジションが依存されていないのであれば、特定のモデルインスタンの行動をどのように説明すればいいでしょうか?

最も簡単で、効果的な方法は、モデルの個々のインスタンスに対して、注文用のテーブルを備え付けることです。これは、すべての未決注文や、削除された注文などのすべての注文を扱います。注文に関する広範な情報は、トレーディングサーバーに格納されています。注文のチケットを知ると、オープンの時刻からその量まで注文に関するいかなる情報も取得することができます。

あとは、チケット注文を特定のモデルインスタンスに紐付けることのみです。それぞれのモデルインスタンスは、モデルインスタンスにより提示される現在の注文リストを含む注文用テーブルを持つ特別なクラスのインスタンスを持ちます。


抽象的なトレーディングモデルを投影する

それでは、トレーディング戦略が基づいているモデルの抽象的な共通のクラスを紹介します。EAは複数の(もしくは無限の)モデルを使用する必要がありますので、このクラスは、外部の専門家がシグナルを送ることができる統一されたインターフェースを持つ必要があることは明確です。

例えば、このインターフェースはProcessing()関数になるかもしれません。簡単にいうと、 CModel クラスがProcessing()関数を持つことになります。この関数は、ティック毎、分毎、もしくは、Trade 型のイベントの発生につき呼ばれます。

こちらにこのタスクの解法の一例があります。

class CModel
{
protected:
   string            m_name;
public:
   void             CModel(){m_name="Model base";}
   bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

cmodel_macd     *macd;
cmodel_moving   *moving;

それでは、このコードがどのように動くか見てみましょう。CModelベースクラスは、 string型であり、m_nameと呼ばれる変数を持ちます。 "protected" というキーワードは、そのクラスの派生クラスにより変数を使用可能にし、派生クラスはすでにその変数を持ちます。さらに、ベースクラスは、Processing() 仮想関数を定義します。 この場合での '仮想'という単語は、これがエキスパートアドバイザーとモデルの特定のインスタンス間のインターフェースやラッパーであることを示します。

CModelから継承するクラスは、Processing() インターフェースを持つことを保証されています。この関数のコードの実行は、派生先に委任されます。モデルの内部がお互いに異なり、CModelクラスの一般的なレベルに位置付けられているという保証がないので、このような委任を行うことは明確です。

さらに、cmodel_macdやcmodel_movingの二つのクラスの記載があります。CModelクラスから生成されており、Processing()関数やm_name変数のインスタンスを持ちます。双方のモデルのprocessing()関数の内部の実行は異なることに注意してください。最初のモデルでは、Print ("It is cmodel_macd. Model name is ", m_name)を含んでおり、 二番目 には Print("It is cmodel_moving. Model name is ", m_name)があります。次に、二つのポインターが生成されます。それぞれは、特定のモデルのインスタンスを指し、一つはcmodel_macd 型、二つにcmodel_moving型のクラスを指します。

OnInit 関数では、 これらのポインターが動的に生成されたクラスのモデルを継承し、その後OnEvent()関数にてProcessing()関数が呼ばれます。その二つのポインターは、グローバルレベルにて宣言されているため、OnInit()関数終了後も、生成されたクラスは、削除されず、グローバルレベルで保持され続けます。5秒毎にOnTimer() 関数が、適切なProcessing() 関数を呼び、両方のモデルを標本化します。

作成したこの原始的なモデルのサンプリングシステムは、柔軟性や拡張性にかけています。そのようなモデルを複数扱いたい場合はどうすれば良いでしょうか?個別にそれぞれを扱うことは不便です。すべてのモデルを一つのコミュニティ、例えばProcessing()関数に集めることは比較的容易く、Processing()関数によって呼ばれた配列の要素を初期化します。

しかし、問題は配列の組織は、格納されたデータが同じ型を必要とするということです。この場合、 cmodel_macdとcmodel_movingはお互いにとても類似していますが、同一ではなく、自動的に配列にて使用することを不可能にします。

幸運にも、その配列はデータを要約する唯一の方法ではなく、より柔軟で拡張性のある一般化があります。その一つは、接続されたリストの技術です。その仕組みは簡単です。それぞれのリストに含まれているアイテムが二つのポインターを持ちます。一つのポインターがリストの一つ前のアイテムを指し、二番目が次のアイテムを指します。

また、アイテムのインデックス値を知り、いつでも参照することが可能です。アイテムを追加、もしくは削除したい場合、ポインターと、隣に位置するアイテムのポインターを再構築すれば十分であり、お互いを参照しあうようになります。そのようなコミュニティの内部組織を知ることは必要ではなく、共通の部分のみを理解するだけで十分です。

標準のMetaTrader5のインストールは、特別な補助的CList クラスを含み、接続されたリストを扱う機会を提供します。しかし、このリストの要素はCObject 型のオブジェクトのみになります。というのも、接続されたリストを扱うポインターしか持たないためです。CObjectクラスはより原始的であり、単純にCListクラスと連携するためのインターフェースになります。

以下をご覧になってください。

//+------------------------------------------------------------------+
//|                                                       Object.mqh |
//|                      Copyright © 2010, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//|                                              Revision 2010.02.22 |
//+------------------------------------------------------------------+
#include "StdLibErr.mqh"
//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Base class element storage.                             |
//+------------------------------------------------------------------+
class CObject
  {
protected:
   CObject          *m_prev;               // previous list item
   CObject          *m_next;               // next list item

public:
                     CObject();
   //--- methods of access to protected data
   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }
   //--- method of identifying the object
   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
  };
//+------------------------------------------------------------------+
//| Constructor CObject.                                             |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CObject::CObject()
  {
//--- initialize protected data
   m_prev=NULL;
   m_next=NULL;
  }
//+------------------------------------------------------------------+

ご覧の通り、このクラスの基礎は、二つのポインターにあり、典型的な機能が実装されています。

最も重要な部分:継承の仕組みのおかげで、このクラスをトレーディングモデルに組み込むことが可能であり、これはトレーディングモデルのクラスがCList 型のリストに含まれることができることを意味します。これをやってみましょう。

それでは、抽象クラスのCModel をCObject クラスの派生クラスとして作成しましょう:

class CModel : public CObject

cmodel_movingと cmodel_averageクラスは CModel クラスを継承しているため、CObjectクラスのデータやメソッドを含んでおり、CList 型のリストに含まれることができます。二つのコンディショナルトレーディングモデルを作成するソースコードがそれらをリストに格納し、各ティックをサンプリングします。以下に詳細が示されています:

//+------------------------------------------------------------------+
//|                                            ch01_simple_model.mq5 |
//|                            Copyright 2010, Vasily Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Vasily Sokolov (C-4)."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Arrays\List.mqh>

// Base model
class CModel:CObject
{
protected:
   string            m_name;
public:
        void              CModel(){m_name="Model base";}
        bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

//Create list of models
CList *list_models;

void OnInit()
{
   int rezult;
   // Great two pointer
   cmodel_macd          *m_macd;
   cmodel_moving        *m_moving;
   list_models =        new CList();
   m_macd   =           new cmodel_macd();
   m_moving =           new cmodel_moving();
   //Check valid pointer
   if(CheckPointer(m_macd)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_macd);
      if(rezult!=-1)Print("Model MACD successfully created");
      else          Print("Creation of Model MACD has failed");
   }
   //Check valid pointer
   if(CheckPointer(m_moving)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_moving);
      if(rezult!=-1)Print("Model MOVING AVERAGE successfully created");
      else          Print("Creation of Model MOVING AVERAGE has failed");
   }
}

void OnTick()
{
   CModel               *current_model;
   for(int i=0;i<list_models.Total();i++){
      current_model=list_models.GetNodeAtIndex(i);
      current_model.Processing();
   }
}

void OnDeinit(const int reason)
{
   delete list_models;
}

このプログラムが一旦コンパイルされ、稼働されると、EAの標準の動作を示す類似した記述が「エキスパート」ジャーナル上に現れます。

2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Prosessing Moving Average...
2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Processing MACD Model...
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MOVING AVERAGE was created successfully
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MACD was created successfully  

このコードがどのように動いているかを詳しく分析してみましょう。上述した通り、基礎となるトレーディングモデルCModel はCObjectクラスから継承しており、CList 型のリストに基礎モデルの派生クラスを含むことができるようにします。

rezult=list_models.Add(m_macd);
rezult=list_models.Add(m_moving);

データの構成としては、ポインターを扱うことを要請します。特定のモデルのポインターがOnInit()関数のローカルレベルにて作成され、list_modelsのグローバルリスト内に入ると、それらに対する必要性がなくなり、この関数のその他の変数と同様に安全に消去されます。

一般的に、提案されているモデルの特徴としては、(モデルクラスに加えて)グローバル変数のみがこれらのモデルの動的な接続リストであるという点です。従って、最初からプロジェクトのカプセル化の高度なサポートがあります。

もしモデルの作成がなんらかの理由(例えば、必要なパラメーターの値が間違ってリスト化されているなど)で失敗していた場合、このモデルはリストに追加されなくなります。これは、EA全体に影響しません。というのも、リストに正しく追加されたモデルのみを扱うためです。

作成されたモデルのサンプリングは、OnTick()関数にて作成されます。for loopから成り立っています。このループでは、要素の数が決定され、その後、サイクルの最初の要素から (i = 0) 最後の要素まで(i <list_models.Total();i++)のループが起こります:

CModel               *current_model;
for(int i=0;i<list_models.Total();i++){
   current_model=list_models.GetNodeAtIndex(i);
   current_model.Processing();
}

CModelへのポインターは、ユニバーサルアダプターとして使用されます。これは、このインジケーターにサポートされるすべての関数が派生モデルへ使用可能であることを保証します。 この場合、 Processing()関数のみを必要とします。それぞれのモデルが独自のProcessing()関数を持っており、他のモデルの類似する関数とは異なる内部の実行を持ちます。この関数のオーバーロードは、必要ではなく、単一のフォームで存在できます;入力パラメーターを持たず、Bool型の値を返しません。

関数の「両肩」に当たるタスクは広範囲に渡ります。

  1. その関数は、独自でトレーディングモデルに基づき現在のマーケットの状況を決定します。
  2. 市場の決定がされたのち、その関数は、取引に関する、取引量、最大の損失可能額や利益額など必要とされる情報を計算します。
  3. このモデルの行動は、以前のアクションと相関関係があるはずです。例えば、もしそのモデルのより初期化されたショートポジションがあれば、将来それを構築することは不可能かもしれません。これらの保証は、Processing() 関数を通して実行されます。
  4. これらの関数は、アカウントのステータスなど共通の変数へのアクセス権を持ちます。このデータに基づき、この関数は、モデルに組み込まれている変数を使用し資産管理を実行します。例えば、もし資産管理がfの公式にて実行されれば、値は個々のモデルとは異なるはずです。

Processing() 関数は、 タスクの大きさにより補助クラスに依存します。それは MetaTrader 5に組み込まれています。それは、このソリューションのために設計されているものと同様です。

ご覧の通り、大半の作業はそのモデルのインスタンスに委任されています。EAの外部レベルは、コントロールをそれぞれのモデルに与え、作業が終了されます。特定のモデルによってなされることは、その内部ロジックに依存しています。

一般的に、インタラクションのシステムは以下のスキームによって記述されています。

上記のコードに示されている通り、モデルのソーティングはOnTick()関数内で発生しますが、そのような形である必要はありません。ソーティングサイクルは、OnTrade() やOnTimer()など、その他の望ましい関数内に配置することができます。


仮想注文のテーブル ー モデルの基礎

すべてのトレーディングモデルをリスト内に組み合わせると、トレードの 手順を記述する頃合いとなります。CModelクラスに戻り、トレーディングプロセスにて基礎となる追加データや関数を加えていきましょう。

上述の通り、新規ネットポジションの典型は、注文や取引を扱う異なるルールを定義します。MetaTrader 4では、それぞれの取引は、注文とともになされ、「トレード」タブ上にて存在し、取引の終了や注文の消去まで表示されています。

MetaTrader 5 では、未決注文取引の終了時まで存在します。その取引の実行やマーケットの入場後、これらの注文は、トレーディングサーバーに保存される注文履歴へ移されます。この状況は不確実性を生みます。EAが実行済みの注文を発行することを想定してください。集合ポジションは変化します。その後、EAはポジションをクローズする必要があります。

MetaTrader 4ではできる、特定の注文のクローズは、行うことができません。というのも、 ポジションのクローズを行うという注文のクローズというコンセプトがないためです。では、ポジションのどの部分がクローズされるのでしょうか。注文履歴を見ることができ、EAにより発行されたものを選択肢、これらの注文を現在のマーケットの状況と関連づけることができ、 必要であれば、逆の注文をブロックすることができます。. このメソッドは多くの課題を含んでいます。

例えば、過去にその注文がすでにブロックされていないことを確認できるでしょうか?現在のポジションが現在のEAのみに所属していることを想定するという別の方法をとることもできます。この選択肢は、もし一つのEAのみと一つの戦略に基づいてトレードする場合、使用できます。これらのすべてのメソッドは、直面する課題を解決することができません。

最も明確で、シンプルなソリューションは、現在のモデルの注文に関する必要な情報をすべて(すなわち、逆の取引によりブロックされた注文)を保存することです。

例えば、もしモデルが注文を発行すれば、そのチケットはこのモデルのメモリに記録され、接続リストのシステムの助けにより整理することができます。

チケット注文を知ると、ほとんどすべての必要な情報を得ることができ、チケット注文とそのモデルを接続するのみです。チケット注文を CTableOrder という特別なクラスに保存するようにしましょう。チケットに加えて、注文の量や、インストール回数、マジックナンバーなどを収容できます。

このクラスは以下のように構築されています。

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"

#include <Trade\_OrderInfo.mqh>
#include <Trade\_HistoryOrderInfo.mqh>
#include <Arrays\List.mqh>
class CTableOrders : CObject
{
private:
   ulong             m_magic;       // Magic number of the EA that put out the order
   ulong             m_ticket;      // Ticket of the basic order
   ulong             m_ticket_sl;    // Ticket of the simulated-Stop-Loss order, assigned with the basic order
   ulong             m_ticket_tp;    // Ticket of the simulated-Take-Profit, assigned with the basic order
   ENUM_ORDER_TYPE   m_type;         // Order type
   datetime          m_time_setup;  // Order setup time
   double            m_price;       // Order price
   double            m_sl;          // Stop Loss price
   double            m_tp;          // Take Profit price
   double            m_volume_initial;  // Order Volume
public:
                     CTableOrders();
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);
   double            StopLoss(void){return(m_sl);}
   double            TakeProfit(void){return(m_tp);}
   ulong             Magic(){return(m_magic);}
   ulong             Ticket(){return(m_ticket);}
   int               Type() const;
   datetime          TimeSetup(){return(m_time_setup);}
   double            Price(){return(m_price);}
   double            VolumeInitial(){return(m_volume_initial);}
};

CTableOrders::CTableOrders(void)
{
   m_magic=0;
   m_ticket=0;
   m_type=0;
   m_time_setup=0;
   m_price=0.0;
   m_volume_initial=0.0;
}

bool CTableOrders::Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit)
{
   if(HistoryOrderSelect(history_order_info.Ticket())){
      m_magic=history_order_info.Magic();
      m_ticket=history_order_info.Ticket();
      m_type=history_order_info.Type();
      m_time_setup=history_order_info.TimeSetup();
      m_volume_initial=history_order_info.VolumeInitial();
      m_price=history_order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

bool CTableOrders::Add(COrderInfo &order_info, double stop_loss, double take_profit)
{
   if(OrderSelect(order_info.Ticket())){
      m_magic=order_info.Magic();
      m_ticket=order_info.Ticket();
      m_type=order_info.Type();
      m_time_setup=order_info.TimeSetup();
      m_volume_initial=order_info.VolumeInitial();
      m_price=order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

int   CTableOrders::Type() const
{
   return((ENUM_ORDER_TYPE)m_type);
}

CModelクラスに類似して、CTableOrdersクラスはCObjectを継承しています。モデルのクラスと同じく、 CTableOrdersのインスタンスをCList 型のListTableOrdersに格納します。

チケット注文に加えて(m_tiket) 、そのクラスはEAのマジック ナンバー (ORDER_MAGIC) についての情報を保有しており、それは型やオープニング価格、量、推定オーバーラップレベル:ストップロス (m_sl) や 利取り (m_tp)などについてです。最後の二つの変数に関しては、個別に紹介します。いかなる取引も遅かれ早かれ反対の取引によってクローズされることは明白です。反対の取引は、現在の市場の状況か事前に決定された価格のポジションの部分的クローズに基づき開始されます。

MetaTrader4では「ポジションからの無条件のエグジット」は特別な種類の エグジットです。: ストップロス と 利取りの二つがあります。MetaTrader 4の特徴は、これらのレベルが特定の注文に適用されるという点です。例えば、もしストップがアクティブな注文の内の一つに発生すれば、その証券におけるその他のオープンな注文には影響しません。

MetaTrader 5では、これが少し異なります。設定された注文ごとにストップロス や利取りの額を明記することができますが、これらのレベルは特定の注文に反して動作せず、この証券における全てのポジションに関して、これらの額は設定されます。

オープンのBUYポジションが EURUSD の標準ロットにて ストップロス や利取りのレベルなしにあることを想定してみてください。その後、他の注文が0.1ロットにてEURUSDの購入のため発行され、ストップロス や利取りのレベルが設定されています。そして、価格がストップロスか利取りのレベルに達します。これが起こると、EURUSDの1.1ロットのサイズの全てのポジションがクローズします。

言い換えると、ストップロス や利取りは、固有の注文と対するのではなく、集合ポジションにのみ関連し設定されるということです。これに基づくと、マルチシステムのEAでは、これらの注文を使用することが不可能となります。これは、ストップロス や利取りが一つのシステムにより設定されるなら、証券の集合ポジションに含まれている利率がその他全てのシステムに適用されるため、明確なことです。

結果的に、トレーディングEAのサブシステムは、内部のストップロスや利取り をそれぞれで使用するべきなのです。また、このコンセプトは、同じトレーディングシステムにおいても、異なる注文は異なるストップロス と 利取りのレベルを持っており、MetaTrader 5では、これらのアウトプットは個々の注文に指示されていないという事実に由来します。

もし、仮想注文にて統合的なストップロス と利取りのレベルを設定すれば、価格に達すればEAは独自で既存の注文をブロックすることができるようになります。これらの注文をブロックしたのち、安全にアクティブな注文のリストから除去されます。これがなされる方法は以下に紹介しています。

CTableOrdersクラスは、 とても重要なAdd() 関数を持っています。この関数は、テーブルに記録される必要のある注文チケットを取得します。注文チケットに加えて、この関数は仮想ストップロス と利取り.のレベルを取得します。まずAdd()関数 は、サーバーに保存されている注文履歴の中で注文を割り当てようとします。もしそれが可能であれば、チケットに関する情報を history_order_infoクラスのインスタンスに入力します。そして、情報を新しいTableOrdersの要素に入力し始めます。さらに、この要素は注文リストに追加されます。もし注文の選択が終了しなければ、おそらく未決注文を扱っており、そのため、この注文を現在の注文からOrderSelect() 関数を経由して割り当てる必要があります。この注文の成功した選択は、履歴注文と同じようなアクションが取られています。

現在、Tradeイベントの紹を行うストラクチャーの導入の前に、マルチシステムEAにおいて未決注文を扱うことは難しい。もちろん、このストラクチャーの導入後は、未決注文に基づきEAを設計することができるようになります。さらに、もし注文テーブルがあれば、未決注文における仮想的にいかなるトレーディング戦略は市場にて実行に移されることができます。これらの理由により、この記事にて示される全てのトレーディングモデルは市場での実行 (ORDER_TYPE_BUYかORDER_TYPE_SELL)を持っています。


CModel - トレーディングモデルの ベースクラス

そして、注文のテーブルが十分に設計されれば、CModelの全てを紹介できるようになります:

class CModel : public CObject
{
protected:
   long              m_magic;
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;
   string            m_model_name;
   double            m_delta;
   CTableOrders      *table;
   CList             *ListTableOrders;
   CAccountInfo      m_account_info;
   CTrade            m_trade;
   CSymbolInfo       m_symbol_info;
   COrderInfo        m_order_info;
   CHistoryOrderInfo m_history_order_info;
   CPositionInfo     m_position_info;
   CDealInfo         m_deal_info;
   t_period          m_timing;
public:
                     CModel()  { Init();   }
                     ~CModel() { Deinit(); }
   string            Name(){return(m_model_name);}
   void              Name(string name){m_model_name=name;}
   ENUM_TIMEFRAMES    Timeframe(void){return(m_timeframe);}
   string            Symbol(void){return(m_symbol);}
   void              Symbol(string set_symbol){m_symbol=set_symbol;}
   bool virtual      Init();
   void virtual      Deinit(){delete ListTableOrders;}
   bool virtual      Processing(){return (true);}
   double            GetMyPosition();
   bool              Delete(ENUM_TYPE_DELETED_ORDER);
   bool              Delete(ulong Ticket);
   void              CloseAllPosition();
   //bool virtual      Trade();
protected:
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);

   void              GetNumberOrders(n_orders &orders);
   bool              SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, double lot,
                              double price, double stop_loss, double take_profit, string comment);
};

このクラスから得たデータは、全てのトレーディングモデルの基礎となる定数を含んでいます。

これはマジック ナンバー (m_magic)、モデルが開始されるシンボル (m_symbol) 、タイムフレーム (m_timeframe)、最も取引が行われているモデルの名前(m_name)などです。

さらに、そのモデルはすでに私たちに馴染みのある、注文テーブルのクラス(CTableOrders * table) 、そして、このテーブルのインスタンスが保存されているリスト(CList*ListTableOrders)などもあります。必要があれば全てのデータが動的に作成されるため、このデータの処理はポインターを通して実行されます。

これは、m_delta変数に従います。資産管理においての公式にて現在のロットを計算するための関数をこの変数は保持している必要があります。例えば、この変数は口座の株に関する情報を保持し、口座のリスクが2パーセントに達する際、この変数は0.02に等しくなります。より活発なメソッドにおいては。例えば、最適なメソッドではfの関数はより大きくなります。

この変数において重要なことは、シングルEAの部分であるそれぞれのモデルにおいてのリスクの個々の選択を許すという点です。もし資本化の公式が使用されていなければ、記入することは必要ではありません。標準として、0.0に等しいものです。

次に、補助的なトレーディングクラスの包括があります。それは、口座情報 からポジションについての情報に至るまでの全ての必要な上の右側の処理と受信を促進するよう意図されています。特定のトレードモデルの派生クラスは、OrderSelectOrderSendなどの一般的な機能ではなく、これらの補助クラスを使用する必要があると理解されています。

m_timing変数は、個別に記述される必要があります。EAの作業プロセス中に、特定のタイムインターバル間にて特定のイベントを呼ぶ必要があります。異なるモデルが異なるタイムインターバルに存在するため、OnTimer()関数は、これに適していません。

例えば、いくつかのイベントは個々の新しいバーにて呼ばれる必要があります。1時間ごとのグラフにおけるトレーディングを行うモデルにおいては、そのようなイベントは、新しい日ごとのバーを持つグラフでのトレーディングを行うモデルのため各時間ごとに呼ばれる必要があります。これらのモデルは、異なる時間設定を持っており、それぞれが独自のモデルにて保存される必要があることは明確です。t_periodストラクチャーは、CModelクラスに含まれており、モデル内にこれらの設定の個別での保存を可能とします。

こちらがそのストラクチャーの様相となります。

struct t_period
{
   datetime m1;
   datetime m2;
   datetime m3;
   datetime m4;
   datetime m5;
   datetime m6;
   datetime m10;
   datetime m12;
   datetime m15;
   datetime m20;
   datetime m30;
   datetime h1;
   datetime h2;
   datetime h3;
   datetime h4;
   datetime h6;
   datetime h8;
   datetime h12;
   datetime d1;
   datetime w1;
   datetime mn1;  
   datetime current; 
};

ご覧の通り、基本的なタイムフレームのリストを含んでいます。新規バーが発生したかを確かめるためには、t_periodストラクチャーに記録された時間と、最後のバーの時間を比較する必要があります。もし双方の時間が合わなければ、新規バーが発生したこととなり、ストラクチャー内の時間は、現在のバーの時間に更新され、結果(true)を返す必要があります。もし最後のバーの時間とストラクチャーが同一であれば、新規バーは発生していないこととなり、 そのような結果(false)を返す必要があります。

こちらは、紹介したアルゴリズムに基づき動作する関数になります。

bool timing(string symbol, ENUM_TIMEFRAMES tf, t_period &timeframes)
{
   int rez;
   MqlRates raters[1];
   rez=CopyRates(symbol, tf, 0, 1, raters);
   if(rez==0)
   {
      Print("Error timing");
      return(false);
   }
   switch(tf){
      case PERIOD_M1:
         if(raters[0].time==timeframes.m1)return(false);
         else{timeframes.m1=raters[0].time; return(true);}
      case PERIOD_M2:
         if(raters[0].time==timeframes.m2)return(false);
         else{timeframes.m2=raters[0].time; return(true);}
      case PERIOD_M3:
         if(raters[0].time==timeframes.m3)return(false);
         else{timeframes.m3=raters[0].time; return(true);}
      case PERIOD_M4:
         if(raters[0].time==timeframes.m4)return(false);
         else{timeframes.m4=raters[0].time; return(true);}
     case PERIOD_M5:
         if(raters[0].time==timeframes.m5)return(false);
         else{timeframes.m5=raters[0].time; return(true);}
     case PERIOD_M6:
         if(raters[0].time==timeframes.m6)return(false);
         else{timeframes.m6=raters[0].time; return(true);}
     case PERIOD_M10:
         if(raters[0].time==timeframes.m10)return(false);
         else{timeframes.m10=raters[0].time; return(true);}
     case PERIOD_M12:
         if(raters[0].time==timeframes.m12)return(false);
         else{timeframes.m12=raters[0].time; return(true);}
     case PERIOD_M15:
         if(raters[0].time==timeframes.m15)return(false);
         else{timeframes.m15=raters[0].time; return(true);}
     case PERIOD_M20:
         if(raters[0].time==timeframes.m20)return(false);
         else{timeframes.m20=raters[0].time; return(true);}
     case PERIOD_M30:
         if(raters[0].time==timeframes.m30)return(false);
         else{timeframes.m30=raters[0].time; return(true);}
     case PERIOD_H1:
         if(raters[0].time==timeframes.h1)return(false);
         else{timeframes.h1=raters[0].time; return(true);}
     case PERIOD_H2:
         if(raters[0].time==timeframes.h2)return(false);
         else{timeframes.h2=raters[0].time; return(true);}
     case PERIOD_H3:
         if(raters[0].time==timeframes.h3)return(false);
         else{timeframes.h3=raters[0].time; return(true);}
     case PERIOD_H4:
         if(raters[0].time==timeframes.h4)return(false);
         else{timeframes.h4=raters[0].time; return(true);}
     case PERIOD_H6:
         if(raters[0].time==timeframes.h6)return(false);
         else{timeframes.h6=raters[0].time; return(true);}
     case PERIOD_H8:
         if(raters[0].time==timeframes.h8)return(false);
         else{timeframes.h8=raters[0].time; return(true);}
     case PERIOD_H12:
         if(raters[0].time==timeframes.h12)return(false);
         else{timeframes.h12=raters[0].time; return(true);}
     case PERIOD_D1:
         if(raters[0].time==timeframes.d1)return(false);
         else{timeframes.d1=raters[0].time; return(true);}
     case PERIOD_W1:
         if(raters[0].time==timeframes.w1)return(false);
         else{timeframes.w1=raters[0].time; return(true);}
     case PERIOD_MN1:
         if(raters[0].time==timeframes.mn1)return(false);
         else{timeframes.mn1=raters[0].time; return(true);}
     case PERIOD_CURRENT:
         if(raters[0].time==timeframes.current)return(false);
         else{timeframes.current=raters[0].time; return(true);}
     default:
         return(false);
   }
}

現在、ストラクチャーの連続的なソートの可能性はありません。そのようなソートは、異なるタイムフレーム上で同じトレーディングモデルのサイクル内にて複数のインスタンスを作成しなければならない時に必要とされます。従って、t_periodのストラクチャーに関して、特別なソート用関数を記述する必要がありました。

こちらは、 この 関数のソースコードです:

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

これら全ての関数は利便性のためにIncludeフォルダーの一つのファイル内に組み込まれます。 Time.mqhと名前をつけておきましょう。

これは、ベースクラスのCModelに含まれる予定です:

#incude <Time.mqh>

get/set の Name()のシンプルな関数に加えて、 Timeframe()や Symbol(), CModelクラス は複雑な関数の Init()関数や GetMyPosition()Delete(), CloseAllPosition() и Processing()関数などを含んでいます。最後の関数の指示はすでに親しみのあるもののはずです。内部のストラクチャーを後ほどより詳細に紹介しますが、今は主要な関数であるベースクラスCModelの紹介から始めます。

そのCModel::Add() 関数は動的にクラスCTableOrdersのインスタンスを作成し、適切なCTabeOrders::Add() 関数を用い、それを記入します。処理の原則は上記に紹介されています。記入されたのち、このアイテムは現在のモデルの注文リストに組み込まれます。(ListTableOrders.Add (t))

そのCModel::Delete() 関数は一方でCTableOrders 型の要素 をアクティブな注文のリストから削除します。このために、削除されるべき注文のチケットを明記する必要があります。そのための規則はシンプルです。その関数は連続的に正しいチケットの注文を検索し、注文テーブルを全てソートします。その注文が見つかれば、削除します。

CModel::GetNumberOrders() 関数はアクティブな注文の数を数えます。 特別な ストラクチャー n_ordersを埋めます:

struct n_orders
{
   int all_orders;
   int long_orders;
   int short_orders;
   int buy_sell_orders;
   int delayed_orders;
   int buy_orders;
   int sell_orders;
   int buy_stop_orders;
   int sell_stop_orders;
   int buy_limit_orders;
   int sell_limit_orders;
   int buy_stop_limit_orders;
   int sell_stop_limit_orders;
};

ご覧の通り、呼ばれた後はいくつの特別なタイプの注文がセットされているかわかります。例えば、ショートな注文の数を取得し、 short_ordersの全ての値を読み込みます(n_orders.にインスタンスにおける)

そのCModel::SendOrder() 関数は、トレーディングサーバーへの注文の送信のための基礎となる関数です。それぞれの固有のモデルがサーバーへ注文を送信するアルゴリズムを持つ代わりに、 SendOrder() 関数はこれらの送信における一般的な手続きを定義しています。そのモデルに関わらず、注文を発行するプロセスは、同じchecksと関連付けられており、効率的に中央にある位置にて実行されます。

この関数のソースコードを見ていきましょう。:

bool CModel::SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, 
                          double lot, double price, double stop_loss, double take_profit, string comment)
{
   ulong code_return=0;
   CSymbolInfo symbol_info;
   CTrade      trade;
   symbol_info.Name(symbol);
   symbol_info.RefreshRates();
   mm send_order_mm;
   
   double lot_current;
   double lot_send=lot;
   double lot_max=m_symbol_info.LotsMax();
   //double lot_max=5.0;
   bool rez=false;
   int floor_lot=(int)MathFloor(lot/lot_max);
   if(MathMod(lot,lot_max)==0)floor_lot=floor_lot-1;
   int itteration=(int)MathCeil(lot/lot_max);
   if(itteration>1)
      Print("The order volume exceeds the maximum allowed volume. いかに分けられます:", itteration, " deals");
   for(int i=1;i<=itteration;i++)
   {
      if(i==itteration)lot_send=lot-(floor_lot*lot_max);
      else lot_send=lot_max;
      for(int i=0;i<3;i++)
      {
         //Print("Send Order: TRADE_RETCODE_DONE");
         symbol_info.RefreshRates();
         if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask();
         if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid();
         m_trade.SetDeviationInPoints(ulong(0.0003/(double)symbol_info.Point()));
         m_trade.SetExpertMagicNumber(m_magic);
         rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, 0.0, 0.0, comment); 
         // Sleeping is not to be deleted or moved!さもなければ、注文はm_history_order_info内に記録される時間を持ちません。
         Sleep(3000);
         if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
         {
               //Print(m_trade.ResultComment());
               //rez=m_history_order_info.Ticket(m_trade.ResultOrder());
               if(op_mode==ORDER_ADD){
                  rez=Add(m_trade.ResultOrder(), stop_loss, take_profit);
               }
               if(op_mode==ORDER_DELETE){
                  rez=Delete(ticket);
               }
               code_return=m_trade.ResultRetcode();
               break;
         }
         else
         {
            Print(m_trade.ResultComment());
         }
         if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED||
            m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED||
            m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY||
            m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS||
            m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME)
         {
            break;
         }
      }
   }
   return(rez);
}

この関数が実行する最初の内容としては、トレーディングサーバーの記述された量の実行可能性を確かめるということです。これは、CheckLot() 関数によって実行されます。ポジションのサイズにおいて、トレーディングの制限があるかもしれません。それらは考慮に入れる必要があります。

以下のケースを考えてみましょう:両方向にて15の標準スタンダードのサイズに関する制限がある場合です。現在のポジションは3ロットに等しいです。資金管理システムに基づくトレーディングモデルは18.6ロットにてロングポジションを開くことを欲しています。 CheckLot() 関数 は正しい取引の量を返します。この場合、12ロットに等しくなります(15ロット中の3ロットはその他の取引によって占められているためです。)もし現在のオープンポジションがロングではなく、ショートであれば、その関数は18.6の代わりに15ロットを返します。これは可能なポジションの最大の量です。

15ロットの 買い注文を行ったのち、ネットポジションはこの場合12ロットになります (3 - sell, 15 - buy). 別のモデルが最初の3ロットの買い注文のショートポジションを再定義した際、集合ポジションは最大値-15ロットとなります。他の買いシグナルは、 もでるが15ロットの buyのうちいくつかを再定義するまで再定義しません。必要な取引の量は越えられ、その関数は定数EMPTY_VALUEを返します。そのようなシグナルが渡される必要があります。

もし設定した量の可否のチェックが成功すれば、必要なマージンの量に関しての計算がなされます。十分な資金ではないかもしれません。これらの目的のために、 CheckMargin() 関数があります。もしマージンが十分でなければ、現在のマージンがオープンを許可するような注文量に正そうとします。もしマージンが最小の口座を開くほど十分でなければ、 Margin-Callの状況にあります。

もしポジションがなく、マージンが使用されないのであれば、technical margin-call - という取引をオープン不可能な状況ということを指します。口座へ資金を追加せずには続けることは不可能です。もしマージンが使える状態であれば、マージンを使用する取引がクローズされるまで待つしかありません。いかなる場合においても、マージンの欠乏は定数EMPTY_VALUE.を返します。

この関数の特徴は、現在の注文をいくつかの独立とした取引に分けることができるという点です。もしトレーディングモデルが口座の資金システムを使用すれば、必要な量が制限を簡単に超えることができます。(例えば、資金システムは数千、数百の量の取引をオープンする必要があります。)一つの取引においてその量を確認することは不可能です。典型的に、トレーディング状況は100ロットの最大取引サイズをお決定しますが、いくつかのトレーディングサーバーはその他の制限を持ち、MetaQuotesChampionship 2010 サーバーでは、この制限は5ロットでした。そのような制限は考慮にいれられる必要があり、これに基づき実際の取引量
を計算する必要があります。

設定された量を実行するために必要な注文数がまず計算されます。もし設定された量が取引の最大数を越えなければ、この注文を発行するために一回の送信のみを必要とします。もし望ましい取引量が最大値を越えれば、この量はいくつかの部分に分けれられます。例えばEURUSD11.3ロットを買いたいとします。この金融商品における取引の最大値は5.0ロットです。それから、OrderSend 関数は、この量を3つの注文に分けます:5.0ロット、5.0ロット、1.3ロットに分けます。

従って、一つの注文の代わりに、3つもあります。それぞれは、注文テーブルにリストとして組み込まれ、マジックナンバーや、ストップロス、利取りの値などの設定を持ちます。そのような注文を処理する中で、トレーディングモデルがいかなる数の注文も扱えるように設計されているため、特に難しい点はないはずです。

実際、全ての注文は同じ 利取りや ストップロス値を持っています。それぞれは順番にLongClose や ShortClose 関数によってソートされています。クローズのための正しい状況が発生するか、 SLや TPに達すれば、それらはクローズされます。

全ての注文は CTradeクラス内のOrderSend 関数を使用し、サーバーへ送られます。この最も面白い点は、以下に隠されています。

実際は、注文の割り当ては二重なのかもしれません。買い・売り注文は、シグナルの発生時に送られるか、以前の注文をブロックするための注文かもしれません。OrderSend 関数は送られた注文の型を知る必要が有ります。といのも、これは全ての注文を注文テーブルに格納するか、特定のイベント発生時にテーブルから除去する関数です。

もし追加したい注文の種類が ADD_ORDERすなわち、注文テーブルに格納されるべき独立した注文であれば、その関数はこの注文に関する情報をテーブルに追加します。もしその注文が以前保存された注文を再定義するためであれば、(例えば、仮想) ストップロスの発生時) DELETE_ORDERの種類を持つ必要が有ります。その後OrderSend 関数が手動で注文リストに接続するために必要な注文に関する情報を除去します。このため、その関数は注文の種類に加えて、注文チケットを継承します。もしこれがADD_ORDERであれば、チケットはシンプルな0で埋められています。


移動平均のクロスオーバーに基づく最初のトレーディングモデル

すでに全ての主要な CModel ベースクラスの要素を紹介しています。今からは 特定のトレーディングクラスについて考えていきます。

これらの目的のために、まずシンプルなインジケーターMACDに基づく、トレーディングモデルを作成します。 .

このモデルは常にロング・ショートポジションを持ちます。早い方の線分が遅い方の線を下方に超え次第、ロングポジションがクローズになる間、ショートポジションをオープンにします。上方のクロスオーバーの場合、ロングポジションをショートポジションがクローズの際に、オープンにします。このモデルでは、保護的なストップ、利益レベルを使用しません。

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model uses MACD indicator.                                      |
//| Buy when it crosses the zero line downward                           |
//| Sell when it crosses the zero line upward                            |
//+----------------------------------------------------------------------+  
struct cmodel_macd_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               fast_ema;
   int               slow_ema;
   int               signal_ema;
};
   
class cmodel_macd : public CModel
{
private:
   int               m_slow_ema;
   int               m_fast_ema;
   int               m_signal_ema;
   int               m_handle_macd;
   double            m_macd_buff_main[];
   double            m_macd_current;
   double            m_macd_previous;
public:
                     cmodel_macd();
   bool              Init();
   bool              Init(cmodel_macd_param &m_param);
   bool              Init(string symbol, ENUM_TIMEFRAMES timeframes, int slow_ma, int fast_ma, int smothed_ma);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_macd_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
};

cmodel_macd::cmodel_macd()
{
   m_handle_macd=INVALID_HANDLE;
   ArraySetAsSeries(m_macd_buff_main,true);
   m_macd_current=0.0;
   m_macd_previous=0.0;
}
//this default loader
bool cmodel_macd::Init()
{
   m_magic      = 148394;
   m_model_name =  "MACD MODEL";
   m_symbol     = _Symbol;
   m_timeframe  = _Period;
   m_slow_ema   = 26;
   m_fast_ema   = 12;
   m_signal_ema = 9;
   m_delta      = 50;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_macd::Init(cmodel_macd_param &m_param)
{
   m_magic      = 148394;
   m_model_name = "MACD MODEL";
   m_symbol     = m_param.symbol;
   m_timeframe  = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_fast_ema   = m_param.fast_ema;
   m_slow_ema   = m_param.slow_ema;
   m_signal_ema = m_param.signal_ema;
   if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}


bool cmodel_macd::CheckParam(cmodel_macd_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
   {
      Print("Symbol ", m_symbol, " selection has failed. Check symbol name");
      return(false);
   }
   if(m_fast_ema == 0)
   {
      Print("Fast EMA must be greater than 0");
      return(false);
   }
   if(m_slow_ema == 0)
   {
      Print("Slow EMA must be greater than 0");
      return(false);
   }
   if(m_signal_ema == 0)
   {
      Print("Signal EMA must be greater than 0");
      return(false);
   }
   return(true);
}

bool cmodel_macd::InitIndicators()
{
   if(m_handle_macd==INVALID_HANDLE)
   {
      Print("Load indicators...");
      if((m_handle_macd=iMACD(m_symbol,m_timeframe,m_fast_ema,m_slow_ema,m_signal_ema,PRICE_CLOSE))==INVALID_HANDLE)
      {
         printf("Error creating MACD indicator");
         return(false);
      }
   }
   return(true);
}

bool cmodel_macd::Processing()
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   return(true);
}

bool cmodel_macd::LongOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   //Print("LongOpened");
   if(m_macd_current>0&&m_macd_previous<=0&&m_orders.buy_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, 0, lot, m_symbol_info.Ask(), 0, 0, "MACD Buy");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::ShortOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   if(m_macd_current<=0&&m_macd_previous>=0&&m_orders.sell_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, 0, lot, m_symbol_info.Bid(), 0, 0, "MACD Sell");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close buy stop-loss");
      }
      if(m_macd_current<0&&m_macd_previous>=0)
      {
         //Print("Long position closed by Order Send");
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close by signal");
      }
   }
   return(rez);
}

bool cmodel_macd::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close buy stop-loss");
      }
      if(m_macd_current>0&&m_macd_previous<=0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close by signal");
      }
   }
   return(rez);
}

CModelベースクラス は、派生クラスの内部コンテンツに対して制限を敷きません。義務的なものとしてある唯一のものはProcessing() インターフェース関数です。この関数の内部組織における問題は、特定のモデルのクラスです。Processing()関数内に位置する普遍アルゴリズムはなく、それゆえ、固有のモデルがどのように調整されるかのメソッドを派生クラスに押し付ける理由がありません。しかしながら、仮想的にモデルの内部ストラクチャーは標準化されることができます。そのような標準化は大いに外部の、もしくはあなたのコードへの理解を促進し、 そのモデルをより「公式的」にすることができます。

それぞれのモデルは個々のイニシャライザーを持たなければなりません。そのモデルのイニシャライザーは、処理に必要な正しいパラメータに対して責任を持っており、例えば、モデルが動作するために、MACDインジケーター値を選ぶ必要があり、一致するバッファーのハンドラを取得し、そのモデルのための 金融商品とタイムフレームを決定しますこれは全てイニシャライザーで行われます。

モデルのイニシャライザーは、クラスのメソッドです。これらのメソッドは共通してInitという名前を持っています。実は MQL5はコンストラクターをサポートしておらず、コンストラクター内にモデルのイニシャライザーを作成できません。なぜなら オーバーロードは 入力パラメーターのために必要となるためです。そのモデルのコンストラクター内にてその 基礎的なパラメーターを示すことは制限されていないですが、

すべてのモデルは3つのイニシャライザーを持っています。第一に、標準のイニシャライザーです。パラメーターを要求せずに標準的にモデルを設定し、ロードしなければなりません。これは、「as is」モードでのテストにてとても便利です。例えば、私たちのモデルに関しては、標準のイニシャライザーは 金融商品用ツールとしてまたモデルのタイムフレームとして現在のタイムフレームとグラフを選択します。

MACD インジケーター の設定は標準化されます: EMA = 12, slow EMA = 26, signal MA = 9; もしそのモデルがある方法で設定される必要があるなら、そのようなイニシャライザーは適していません。パラメーター付きのイニシャライザーが必要です。二種類を作ることが望ましいです。最初のイニシャライザーは古典的な関数としてパラメーターを取得します:Init (type param1, type param2, ..., type paramN)二番目のイニシャライザーはモデルのパラメーターを、それらを保存する特別なストラクチャーを使用し発見します。このオプションは、パラメーター数が大きくなり、ストラクチャーにて移しやすいため、より望ましい選択肢です。

それぞれのモデルはパラメーターのストラクチャーを持っています。このストラクチャーはどのような名前でも持つことができますが、パターンmodelname_paramによって呼ぶことが良いです。モデルの設定は、マルチタイムフレーム・マルチシステム・マルチ通貨トレーディングシステムを使用する上でとても重要なステップです。このステージでは、どの金融商品でモデルがトレードを行うか決定します。

私たちのトレーディングモデルは、4つトレーディング関数を持っています。ロングポジションをオープンにする関数:LongOpen、ショート ポジションをオープンにする関数:ShortOpen、ロングポジションをクローズする関数: LongClosed、そして、ショート ポジションを閉じる関数ShortClosedです関数LongOpen и ShortOpen の動作はささいなものです。両方ともMACDインジケーターの値を取得します。それは、その前の二つのバーの値と比較されます。「再描画」を防ぐため0バーは使用されません。

もし下方へのクロスオーバーがあれば ShortOpenは関数ヘッダー mm.mqhに含まれている関数を用い計算します。その後、必要なロットはコマンドをOrderSend 関数へ送信します。LongCloseは逆にすべてのロングポジションをクローズします。これは、関数が連続的にモデルの注文テーブルにおけるすべてのオープンオーダーをソートするために発生します。もしロング注文があれば、その関数は反対の注文によりクローズします。逆の方向で正確に同じものが ShortClose() 関数により注文されます。これらの関数が上記にて提供されているリスト内にてご覧になれます。

より詳しく現在のロットがトレーディングモデル内にてどのように計算されているかを分析しましょう。

上記の通り、これらの目的のために、口座の資金に関する特別な関数を使用します。資金化の公式に加えて、これらの関数は使用されたマージンに基づき、トレーディングポジションのサイズの制限によるロットの計算の確認を行います。ポジションのサイズにおいていくつかのトレーレディングの制限があるかもしれません。それらは考慮に入れる必要があります。

以下のケースを考えてみましょう:両方向にて15の標準スタンダードのサイズに関する制限がある場合です。現在のポジションは3ロットに等しいです。そのトレーディングモデルは、資金管理のシステムに基づき、18.6路とにてロングポジションを開こうとしています。 その関数 CheckLot() は、正しい注文の量を返します。この場合、12ロットに等しくなります(15ロット中の3ロットはその他の取引によって占められているためです。)もし現在のオープンポジションがショートポジションである場合、その関数は15ロットを18.6の代わりに返します。これはポジションの取りうる最大の量です。

15ロットまで 買い注文を積み上げたのち、ネットポジションは12ロットになります。 (3 - sell, 15 - buy). 別のモデルが最初の3ロットの買い注文のショートポジションを再定義した際、集合ポジションは最大値-15ロットとなります。他の買い注文のシグナルは15ロットのいくつかをモデルが再定義するまで 処理されません。もし必要な取引においての使用可能な量が使い果たされた時にEMPTY_VALUE 定数を返します。このシグナルは渡される必要があります。

もし設定した量の可否のチェックが成功すれば、必要なマージンの量に関しての計算がなされます。口座資金は設定された量に関しては不十分かもしれません。これらの目的のためには、 CheckMargin()関数があります。もしマージンが十分でなければ、記述された取引量を正し、現在のマージンがオープンになるように設定します。もしマージンが最小の量をオープンするほど十分でなければ、Margin-Callの状態にあるということです。

もしポジションがなく、マージンが使用されないのであれば、technical margin-call - という取引をオープン不可能な状況ということを指します。口座へ資金を追加せずには続けることは不可能です。もしマージンが依然使用されるなら、このマージンを使用するポジションのクロージャー を待つしかありません。どの場合でも、マージンがなければ、 EMPTY_VALUE.定数を返します。

ロットのサイズやマージンを管理する関数は、普通直接は使用されません。これらは資金管理のための特別な関数です。これらの関数は口座の資金化の公式を実行します。その mm.mqhファイルは二つの資金管理における関数を含み、一つは口座の分数を基礎とし計算され、一方は固定比率で知られるRyan Jonesというメソッドに基づき計算されます。

最初のメソッドの目的は、リスクとされる口座の固定部分を定義します。例えば、もし許容されるリスクが口座の2%で、その口座が10,000ドルに等しければ、最大のリスク量は、200ドルになります。どのロットが200ドルのストップのために使用されるべきかを計算するため、どれほど最大の距離がオープンポジションに対する価格により達せられるのかについて正確に知る必要があります。それゆえ、この公式を通してロットを計算するためには、正確にストップロスと取引が行われる価格レベルを決定する必要があります。

Ryan Jonesにより提案されたメソッドは、以前のものとは異なります。その本質は資金化が二次方程式により定義される関数によって行われます。

こちらが詳細です:

x=((1.0+MathSqrt(1+4.0*d))/2)*Step;

where: x - the lower boundary of the transition to the next level d = (Profit / delta) * 2.0 Step- a step of the delta, such as 0.1 lots.

デルタのサイズが小さければ、より積極的に関数がポジション数を増やそうとします。より詳しくこの関数がどのように構築されているか知りたい場合は、Ryan Jonesの本 The Trading Game: Playing by the Numbers to Make Millionsをご覧ください。

もし資金管理の関数が使用されることを計画されていない場合、直接ロットとマージンを管理する関数を呼ぶ必要があります。

なので、すべての基礎となるEAを検証しました。すべての作業の果実を刈り取る良いタイミングです。

まずは、4つのモデルを作成しましょう。一つ目のモデルはEURUSD 標準パラメータによりトレードさせ、二つ目もまた EURUSDにて、ただ15分間のタイムフレームでトレードさせます。三つ目は、標準パラメーター付きのGBPUSDグラフにて開始させます。4つ目は、USDCHFにより以下のパラメーターを用いたに時間のグラフに基づきます: SlowEMA= 6 FastEMA = 12 SignalEMA = 9. testing - H1のテストモード期間は、すべてのティック01.01.2010から01.09.2010.までです。

しかし、このモデルを4つの異なるモードにて稼動させる前に、まずはそれぞれの証券とタイムフレームを個別に試してみましょう。

こちらは、テストにおける主要なインジケーターが作成されるテーブルになります。

システム 取引数 利益, $
MACD(9,12,26)H1 EURUSD 123 1092
MACD (9,12,26) EURUSD M15 598 -696
MACD(9,6,12) USDCHF H2 153 -1150
MACD(9,12,26) GBPUSD H1 139 -282
全システム 1013 -1032

全モデルの総取引数は、1013であるべきであり、全利益は$ -1032であるとその図表は示しています。

結果的に、これらは同時に全システムをテストすれば取得できる同じ値です。結果は、いくつかの逸脱は起こりますが、異なりません。

それでは、こちらが最後のテストです:

ご覧の通り、一つの取引のみ残っており、利益は、10ドルの差しかありません。これは、0.1ロットにおける10ポイントの違いに一致します。資金管理のシステムを使用した場合、組み合わせテストの結果は、根本的にそれぞれのモデルのテスト結果の量とは異なることに注意してください。これは、バランスの変動は、システムに影響を与え、ロットの計算された値は変化するためです。

結果は、利益を示さないにも関わらず、複雑であるが、柔軟で管理しやすいEAのストラクチャーを作成しました。ストラクチャーをもう一度見てみましょう。

このために、以下のスキームを見てみます:

クラスリファレンス

そのスキーマは、モデルのインスタンスの基礎的なストラクチャーを示します。

一旦、トレーディングモデルのクラスインスタンスが作成されると、高負荷のInit()関数を呼び出します。必要なパラメーターの初期化を行い、データを準備し、もし使用されていればIndicatorsのハンドラをロードします。これら全てはEAの初期化中、すなわち、OnInit()関数内にて発生します。そのデータは、トレードを促進するベースクラスのインスタンスを含んでいることに注意してください。そのトレーディングモデルは、MQL5の標準の関数の代わりに、これらクラスを活発に使用する必要があると想定します。そのモデルクラスが作成され、初期化されたのち CListのリストに含まれます。それとのさらなるコミュニケーションは、CObjectユニバーサルアダプターを通して実行されます。

OnTrade()OnTick()イベントの発生後、リスト内のモデルのインスタンスのソートが行われます。それらとのコミュニケーションは、Processing()関数を呼び出すことで実行されます。さらに、自身のモデルのトレーディング関数を呼び出します。(青の関数グループ)そのリストや名前は厳しく定義されておりませんが、LongOpened()やShortClosed()など標準の名前を使用することが便利です。組み込まれたロジックに基づくこれらの関数は取引を完了する時間を選択し、SendOrder()関数の取引のオープンやクローズのためのリクエストを投げます。

後者は、必要なチェックを行い、市場に注文を投げます。そのトレーディング関数は、モデルの補助的な関数(緑のグループ)に依存しており、基礎となる補助的なクラス(紫のグループ)を使用します。すべての補助的なクラスは、データセクション(ピンクのグループ)のクラスのインスタンスとして表されています。グループ間の連携は、ダークブルーの矢印により示されています。


Bollinger Bandsのインジケーターに基づくトレーディングモデル

一般的なデータ構造やメソッドは明確になったので、Bollinger Bandsのトレンドインジケーターに基づくトレーディングモデルを作成します。このトレーディングモデルの基礎として、Andrei Kornishkinによる簡単なEA Bollinger Bandsに基づくエキスパートアドバイザーを使用しました。Bollinger Bands - シンプルな移動平均からの標準的逸脱のサイズに等しいレベルです。このインジケーターがいかに構築されているのかについては、技術的な分析において、MetaTrader5に付いているヘルプセクションにて見ることができます。

そのトレーディングアイディアの本質はシンプルです:価格は、返却のプロパティを持ちます。すなわち、もし価格があるレベルに達すれば、おそらく、逆の方向これは、すべての実際の金融証券の配分についてのテストにより証明されています:標準の配分カーブは、細長く伸びています。Bollinger bandsは、最も起こりうる価格レベルの点に達していることを決定します。一旦、届いてしまえば、(Bollinger bandsの上方か下方に)価格はおそらく反対の方向へ行くでしょう。

トレーディング用戦術をシンプル化し、補助的なインジケーターを使用しませんー指数の移動平均二つ(Double Exponential Moving AverageかDEMA). しかし、厳しい保護的なストップ ー 仮想ストップロスを使用します。トレーディング手順を安定させ、同時に具体例を理解するための手助けとなり、それぞれのトレーディングが保護的な注文レベルを使用します。

保護的注文のレベルにおいては、インジケーター値の揮発性ATRを使用します。例えば、現在のATR値が68ポイントに等しく、,1.25720の価格で売るシグナルがあれば、仮想ストップロスは1.25720 + 0.0068 = 1.26400に等しくなります。同様に、反対の方向で買いシグナルでは1.25720 - 0.0068 = 1.25040に等しくなります。

このモデルのソースコードは以下になります。

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model use Bollinger bands.
//| Buy when price is lower than lower band
//| Sell when price is higher than upper band
//+----------------------------------------------------------------------+  
struct cmodel_bollinger_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               period_bollinger;
   double            deviation;
   int               shift_bands;
   int               period_ATR;
   double            k_ATR;
   double            delta;
};
   
class cmodel_bollinger : public CModel
{
private:
   int               m_bollinger_period;
   double            m_deviation;
   int               m_bands_shift;
   int               m_ATR_period;
   double            m_k_ATR;
   //------------Indicators Data:-------------
   int               m_bollinger_handle;
   int               m_ATR_handle;
   double            m_bollinger_buff_main[];
   double            m_ATR_buff_main[];
   //-----------------------------------------
   MqlRates          m_raters[];
   double            m_current_price;
public:
                     cmodel_bollinger();
   bool              Init();
   bool              Init(cmodel_bollinger_param &m_param);
   bool              Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES TimeFrame, double delta,
                          uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_bollinger_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
   bool              CloseByStopSignal();
};

cmodel_bollinger::cmodel_bollinger()
{
   m_bollinger_handle   = INVALID_HANDLE;
   m_ATR_handle         = INVALID_HANDLE;
   ArraySetAsSeries(m_bollinger_buff_main,true);
   ArraySetAsSeries(m_ATR_buff_main,true);
   ArraySetAsSeries(m_raters, true);
   m_current_price=0.0;
}
//this default loader
bool cmodel_bollinger::Init()
{
   m_magic              = 322311;
   m_model_name         =  "Bollinger Bands Model";
   m_symbol             = _Symbol;
   m_timeframe          = _Period;
   m_bollinger_period   = 20;
   m_deviation          = 2.0;
   m_bands_shift        = 0;
   m_ATR_period         = 20;
   m_k_ATR              = 2.0;
   m_delta              = 0;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param)
{
   m_magic              = 322311;
   m_model_name         = "Bollinger Model";
   m_symbol             = m_param.symbol;
   m_timeframe          = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_bollinger_period   = m_param.period_bollinger;
   m_deviation          = m_param.deviation;
   m_bands_shift        = m_param.shift_bands;
   m_ATR_period        = m_param.period_ATR;
   m_k_ATR              = m_param.k_ATR;
   m_delta              = m_param.delta;
   //if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES timeframe, double delta,
                           uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR)
{
   m_magic           = magic;
   m_model_name      = name;
   m_symbol          = symbol;
   m_timeframe       = timeframe;
   m_delta           = delta;
   m_bollinger_period= bollinger_period;
   m_deviation       = deviation;
   m_bands_shift     = bands_shift;
   m_ATR_period      = ATR_period;
   m_k_ATR           = k_ATR;
   if(!InitIndicators())return(false);
   return(true);
}


/*bool cmodel_bollinger::CheckParam(cmodel_bollinger_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)){
      Print("Symbol ", m_symbol, " select failed. Check valid name symbol");
      return(false);
   }
   if(m_ma == 0){
      Print("Fast EMA must be bigest 0. Set MA = 12 (default)");
      m_ma=12;
   }
   return(true);
}*/

bool cmodel_bollinger::InitIndicators()
{
   m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE);
   if(m_bollinger_handle==INVALID_HANDLE){
      Print("Error in creation of Bollinger indicator. Restart the Expert Advisor.");
      return(false);
   }
   m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period);
   if(m_ATR_handle==INVALID_HANDLE){
      Print("Error in creation of ATR indicator. Restart the Expert Advisor.");
      return(false);
   }
   return(true);
}

bool cmodel_bollinger::Processing()
{
   //if(timing(m_symbol,m_timeframe, m_timing)==false)return(false);
   
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   //m_symbol_info.Name(m_symbol);
   //m_symbol_info.RefreshRates();
   //Copy last data of moving average
 
   GetNumberOrders(m_orders);
   
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   if(m_orders.all_orders!=0)CloseByStopSignal();
   return(true);
}

bool cmodel_bollinger::LongOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   //Print("Model Bollinger: ", m_orders.buy_orders);
   bool rezult, time_buy=true;
   double lot=0.1;
   double sl=0.0;
   double tp=0.0;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   //lot=open_mm.optimal_f(m_symbol,OP_BUY,m_symbol_info.Ask(),sl,delta);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::ShortOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, time_sell=true;
   double lot=0.1;
   double sl=0.0;
   double tp;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {   
      sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_BUY)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_SELL)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::CloseByStopSignal(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   bool rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyRates(m_symbol,m_timeframe,0,3,m_raters);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by stop");
         continue;
      }
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by stop");
         continue;
      }
   }
   return(rez);
}

ご覧の通り、トレーディングモデルのコードは、以前のトレーディング戦術のソースコードに類似しています。仮想ストップ注文の発生や関数cmodel_bollinger:: CloseByStopSignal()が主な変化としてあります。

実際、保護ストップの使用の場合、それらの値は単純にSendOrder()関数に渡されなければなりません。そして、この関数はこれらのレベルを注文テーブルに記入します。価格がこれらのレベルを超えるか、達すると、CloseByStopSignal()関数が反対の注文での取引をクローズし、アクティブな注文リストから削除します。


トレーディングモデル、金融商品、タイムフレームの統合

今や二つのトレーディングモデルがあるので、同時にテストしていきます。モデルのレイアウトを作成する前に、最も効果的なパラメーターを決定すると便利です。このために、それぞれのモデルの最適化を図らなければなりません。

最適化はどの市場やタイムフレームでそのモデルが最も効果を発揮するかについて示します。それぞれのモデルのために、最も良いタイムフレーム二つと3つの証券を選択します。結果として、のちにテストを行う12のソリューション(2 models * 3 instruments * 2 time-frames),を得ることができます。もちろん、選択された最適化メソッドはいわゆる結果の「調整」を行う必要がありますが、これは重要ではありません。

以下のグラフはサンプルの最良な結果を示します。

1.1 MACD EURUSD M30

MACD EURUSD M30

1.2 . MACD EURUSD H3


MACD EURUSD H3

1.3 MACD AUDUSD H4

MACD AUDUSD H4

1.4 . MACD AUDUSD H1


MACD AUDUSD H1

1.5 MACD GBPUSD H12


MACD GBPUSD H12

1.6 MACD GBPUSD H6


MACD GBPUSD H6

2.1 Bollinger GBPUSD M15


Bollinger GBPUSD M15

2.2 Bollinger GBPUSD H1


Bollinger GBPUSD H1

2.3 Bollinger EURUSD M30

Bollinger EURUSD M30

2.4 Bollinger EURUSD H4


Bollinger EURUSD H4

2.5 Bollinger USDCAD M15


Bollinger USDCAD M15

2.6 Bollinger USDCAD H2

Bollinger USDCAD H2

最適化の結果はすでに知られているので、残りのやることはあと少しになりました - 結果を集め、統合することです。

以下にloader関数のソースコードがあります。これは、12のトレーディングモデルを作成し、その後、それらに基づきトレードを開始します。

bool macd_default=true;
bool macd_best=false;
bool bollinger_default=false;
bool bollinger_best=false;

void InitModels()
{
   list_model = new CList;             // Initialized pointer of the model list
   cmodel_macd *model_macd;            // Create the pointer to a model MACD
   cmodel_bollinger *model_bollinger;  // Create the pointer to a model Bollinger
   
//----------------------------------------MACD DEFAULT----------------------------------------
   if(macd_default==true&&macd_best==false)
    {
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      // Loading of the parameters was completed successfully
      if(model_macd.Init(129475, "Model macd M15", _Symbol, _Period, 0.0, Fast_MA,Slow_MA,Signal_MA))
      { 
      
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Загружаем модель в список моделей
      }
      else
      {
                                 // The loading of parameters was completed successfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//-------------------------------------------------------------------------------------------
//----------------------------------------MACD BEST------------------------------------------
   if(macd_best==true&&macd_default==false)
   {
      // 1.1 EURUSD H30; FMA=20; SMA=24; 
      model_macd = new cmodel_macd; // Initialize the pointer to the model MACD
      if(model_macd.Init(129475, "Model macd H30", "EURUSD", PERIOD_M30, 0.0, 20,24,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " created successfully");
         list_model.Add(model_macd);// load the model into the list of models
      }
      else
      {// Loading parameters was completed unsuccessfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.2 EURUSD H3; FMA=8; SMA=12; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H3", "EURUSD", PERIOD_H3, 0.0, 8,12,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
       {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.3 AUDUSD H1; FMA=10; SMA=18; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd M15", "AUDUSD", PERIOD_H1, 0.0, 10,18,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// The loading of parameters was unsuccessful                       
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.4 AUDUSD H4; FMA=14; SMA=15; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H4", "AUDUSD", PERIOD_H4, 0.0, 14,15,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else{// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.5 GBPUSD H6; FMA=20; SMA=33; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H6, 0.0, 20,33,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was  unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.6 GBPUSD H12; FMA=12; SMA=30; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H12, 0.0, 12,30,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//----------------------------------------------------------------------------------------------
//-------------------------------------BOLLINGER DEFAULT----------------------------------------
   if(bollinger_default==true&&bollinger_best==false)
   {
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger",_Symbol,PERIOD_CURRENT,0,
                             period_bollinger,dev_bollinger,0,14,k_ATR))
      {
         Print("Model ", model_bollinger.Name(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
//--------------------------------------BOLLLINGER BEST-----------------------------------------
   if(bollinger_best==true&&bollinger_default==false)
   {
      //2.1 Symbol: EURUSD M30; period: 15; deviation: 2,75; k_ATR=2,75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,15,2.75,0,14,2.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
              ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.2 Symbol: EURUSD H4; period: 30; deviation: 2.0; k_ATR=2.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_H4,0,30,2.00,0,14,2.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.3 Symbol: GBPUSD M15; period: 18; deviation: 2.25; k_ATR=3.0;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_M15,0,18,2.25,0,14,3.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.4 Symbol: GBPUSD H1; period: 27; deviation: 2.25; k_ATR=3.75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_H1,0,27,2.25,0,14,3.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.5 Symbol: USDCAD M15; period: 18; deviation: 2.5; k_ATR=2.00;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_M15,0,18,2.50,0,14,2.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.6 Symbol: USDCAD M15; period: 21; deviation: 2.5; k_ATR=3.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_H2,0,21,2.50,0,14,3.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
}

それでは、12のモデルを同時にテストしましょう:


結果の資本化

結果のグラフはとても印象的なものです。しかしながら、重要な点は結果ではなく、それぞれの保護的なストップを使用し、全モデルが同時にトレードを行うという事実です。それでは結果のグラフを資本化してみましょう。このために、Ryan Jonesにより提供されているメソッドや固定比率型のメソッドなど資本化の標準的関数を使用します。

いわゆるoptimal f - 固定比率型のメソッドの特別な形です。このメソッドの本質としては、それぞれの取引が損失の制限を与えられており、口座の数パーセントに等しいです。保守的な資本化戦略は普通2%の損失制限を適用します。すなわち、$10,000のポジションサイズが計算され、ストップロスが渡されたのちの損失は$200を超えないようになります。しかし、リスクの上昇からの最終的なバランスの向上における関数があります。その関数はベルのような形です。つまり、リスクの上昇に伴って、利益が上昇するというものです。しかしながら、それぞれの取引にリスクがあり、総利益のバランスは減少を始めます。これはいわゆるoptimal f と呼ばれています。

この記事は、資金管理に関しては扱っていません。固定比率型メソッドの使うために知る必要があることは、保護的ストップのレベルとリスクにかけれる口座の率のみです。Ryan Jonesの公式は異なった形で構築されています。このために固定の保護ストップの使用は必要とされていません。紹介されたモデルの一つ目 (MACD モデル)はむしろ原始的であり、保護ストップを持ちませんので、このモデルの資本化のためにこのメソッドを使用します。Bollinger bandsに基づくモデルのため、固定比率型のメソッドを使用します。

資本化の公式を使用し始めるために、変数m_deltaを記入する必要があります。

その公式を使用する際は、それぞれの取引ごとにリスク率が等しくなければなりません。Ryan Jonesのメソッドを使用する際は、それはいわゆるデルタ増殖に等しく、つまり、より多くのポジション量に辿りつくための資金の量に等しいです。

以下が資本化のグラフです:

ご覧の通り、すべてのモデルはそれぞれの資本化の公式を持っています(固定比率型のメソッドやRyan Jonesのメソッドなど)

紹介した例では、同じ最大リスク値やデルタを使用しています。しかし、それぞれのモデルに資本化のための個別のパラメーターを選択することができます。それぞれのモデルへの調整はこの記事では取り扱っていません。


未決注文の使用

示されているトレーディングモデルはいわゆる未決注文を使用していません。これらは、特定の条件がなければ実行されません。

実際、未決注文を使用するいかなるトレーディング戦略も市場における注文の使用に調整されます。むしろ、未決注文は、より信頼できるシステムオペレーションのために必要であり、システムの急停止の際など、未決注文は以前保護ストップを実行し、また、決定された価格にて市場に参入するようにします。

紹介したトレーディングモデルは、未決注文を扱うことができますが、それらの管理プロセスはより複雑なものです。これらの注文を扱うために、高負荷なCTableOrdersクラスのAdd (COrderInfo & order_info, double stop_loss, double take_profit)メソッドを使用します。この場合、このクラスの変数m_typeは、適切な未決注文の種類を含んでいます。それは、ORDER_TYPE_BUY_STOPORDER_TYPE_SELL_LIMITです。

未決注文が設定された際、実行される瞬間か機嫌が切れる瞬間を捉えなければなりません。Tradeイベントをコントロースするのみではないため、これはそこまで簡単ではありません。何がきっかけになりイベントが起こるかも知る必要があります。

MQL5言語は、成長しており、Tradeイベントを説明する特別なサービス構造を含めるという問題は現在解決されている最中にあります。しかし今のところ、履歴の注文リストを見る必要があります。もし未決注文と同じチケットの注文が履歴内に見つかれば、テーブル内で反映される必要のあるいくつかのイベントが発生したということです。

基本的なモデルのコードはCModel:: ReplaceDelayedOrdersメソッドを含みます。このメソッドは以下のアルゴリズムにて作動します。まず、すべてのアクティブな注文がチェックされます。これらの注文のチケットは履歴内の注文のチケット比較されます。 (HistoryOrderGetTicket()). もし履歴内の注文のチケットが注文テーブル内の未決注文と合致するが注文ステータスが実行されている状態(ORDER_STATE_PARTIALORDER_STATE_FILLED)であれば、その未決注文は実行された状態に変化しています。

さらに、ストップロスや利取りの動作を模倣し、この注文が未決注文と接続していなければ、そのような注文はすでに発行されており、それらのチケットは適切なテーブル内の値(TicketSL, TicketTP)に挿入されています。そして、保護ストップや利取りを模倣した未決注文は、m_siやm_tpなどの変数の助けで前もって設定された価格で実行されています。すなわち、これらの価格はReplaceDelayedOrdersメソッドを呼び出す際には知られている必要があります。

そのメソッドは注文が市場に投げられた際の状況を扱うために調整されているということに注意してください。

一般的にそのメソッドの動作は重要で、使用方法についてすこしばかりの理解が必要です。

bool CModel::ReplaceDelayedOrders(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int history_orders=HistoryOrdersTotal();
   ulong ticket;
   bool rez=false;
   long request;
   total_elements=ListTableOrders.Total();
   int try=0;
   if(total_elements==0)return(false);
   // View every order in the table
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      switch(t.Type())
      {
         case ORDER_TYPE_BUY:
         case ORDER_TYPE_SELL:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to one of the tickets 
               // Stop Loss or Take Profit, then the order was blocked, and it needs to be
               // deleted from the table of orders
               if(ticket==t.TicketSL()||ticket==t.TicketTP())
               {
                  ListTableOrders.DeleteCurrent();
               }
            }
            // If the orders, imitating the Stop Loss and Take Profit are not found in the history,
            // then perhaps they are not yet set. Therefore, they need to be inputted,
            // using the process for pending orders below:
            // the cycle  keeps going, the exit 'break' does not exist!!!
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to the ticket of the pending order 
               // then the pending order has worked and needs to be put out
               // the pending orders, imitating the work of Stop Loss and Take Profit.
               // It is also necessary to change the pending status of the order in the table
               // of orders for the executed (ORDER_TYPE_BUY или ORDER_TYPE_SELL)
               m_order_info.InfoInteger(ORDER_STATE,request);
               if(t.Ticket()==ticket&&
                  (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED))
                  {
                  // Change the status order in the table of orders:
                  m_order_info.InfoInteger(ORDER_TYPE,request);
                  if(t.Type()!=request)t.Type(request);
                  //------------------------------------------------------------------
                  // Put out the pending orders, imitating Stop Loss an Take Profit:
                  // The level of pending orders, imitating Stop Loss and Take Profit
                  // should be determined earlier. It is also necessary to make sure that
                  // the current order is not already linked with the pending order, imitating Stop Loss
                  // and Take Profit:
                  if(t.StopLoss()!=0.0&&t.TicketSL()==0)
                    {
                     // Try to put out the pending order:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put up a pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
                  if(t.TakeProfit()!=0.0&&t.TicketTP()==0){
                     // Attempt to put out the pending order, imitating Take Profit:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                           break;
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
               }
            }
            break;
         
      }
   }
   return(true);
}

このメソッドを使用し、未決注文に基づくモデルを作成することができます。


結論

不幸にもすべての利点や課題などを一つの記事で紹介できませんでした。データのシリアライズ化 - データファイルからモデルの現在の状況についての必要なすべての情報を取得し記録するメソッド - を紹介していません。想像を超えて、統合的な拡散に関連するトレードに基づくモデルが残っていました。それはとても興味深いトピックであり、 紹介されたコンセプトにとっても効果的なソリューションを持っています。

やるべきことは、完全に動的な管理可能データストラクチャーを開発することです。接続されたリストのコンセプトは効果的にそれらを管理し、トレーディング戦術をよりカスタマイズ可能なものにします。その他のこのアプローチの利点は普遍的な点です。

例えば、二つのEAを作成し、同じ証券内に設定するだけで十分です。両方とも自動的にそれぞれの注文や資金管理システムを扱います。従って、互換性におけるサポートがあります。一つのEAで同時に行えることはいくつかのシステムに配分することができます。この特性はネットポジションを扱う際にとても重要になります。

そのモデルはただ単純な理論でありません。資本管理などの補助的な関数の先進的な一部を含みます。注文を送信するシステムはいわゆる孫引きや、実際のトレーディング中に見られるスリッページに対応しています。

トレーディングエンジンは、ポジションや取引の最大量を決定します。特別なアルゴリズムは、トレード要求をいくつかの取引に細分し、それぞれが個別に処理されます。さらに、紹介されたモデルAutomated Trading Championship of 2010にて効果を示し、そのチャンピオンシップでは、すべての取引が正確にトレーディングプランやモデルに沿って、大きなリスクにて管理され、異なる資金管理システムのもと実行されました。

そのアプローチは、チャンピオンシップに参加するにはほとんど完璧なものでした。このアプローチに馴染む唯一の困難は複雑さにあります。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/217

添付されたファイル |
files-en.zip (18.6 KB)
ミクロ、ミドル、メイントレンドのインディケータ ミクロ、ミドル、メイントレンドのインディケータ
本稿は James Hyerczyk著 "Pattern, Price &amp; Time: Using Gann Theory in Trading Systems" から得たいくらかの考え方を基にインディケータやExpert Advisor形式でトレードの自動化可能性調査と分析を目的とします。完全にとは言わず、ここではモデル、すなわちギャン理論の最初の部分だけを対象とします。
チャネルの描画 : 内見および外観 チャネルの描画 : 内見および外観
マーケット分析および移動平均後トレードを判断するのにチャネルはもっとも人気あるツールだと言っても過言ではないでしょう。チャネルとそのコンポーネントを使用するトレード戦略の多くを深く掘り下げず、数学的基本とインディケータの実用的実装について語っていきたいと思います。それはクライアント端末画面の3つの極値によって判断されるチャネルを描くものです。
チャート分析の良計経済学的アプローチ チャート分析の良計経済学的アプローチ
本稿では、分析の計量経済学的手法、自己相関分析、とりわけ条件付き分散の分析について述べていきます。ここに挙げた手法のメリットは何でしょうか?非線形 GARCH モデルの使用により、数学的観点から正式に分析した系列を表現することができ、また特定の段階数に関して予測をすることができます。
MetaTrader 5での並列計算 MetaTrader 5での並列計算
人類の歴史において時間は高価値であり、われわれはそれを無駄に費やさないよう努力しています。本稿では、マルチ コア プロセッサのコンピュータをご使用の場合、お手元のExpert Advisor の動作スピードを上げる方法について述べていきます。また、提案のメソッド実装には MQL5以外の言語知識は必要とされません。