トレードロボットのプロトタイプ

--- | 18 9月, 2015

はじめに

どのトレーディングシステムのライフサイクルもポジションの開始と終了に集約される。これは疑いのない事実である。しかし、ことアルゴリズムの実現になると、プログラマーの数だけの見解があると言っても過言ではない。誰もが同じ問題を彼自身の方法で解くことができるが、しかし同じ最終結果に至る。

何年にもわたるプログラムつくりの実践を通じて、いくつかのエキスパートの論理と構造を構築するアプローチが試みられてきた。今では全てのコードで使われている明快なパターンのテンプレートを確立したと主張できる。

このアプローチは100%普遍的とはいえない、しかしあなたのエキスパート論理の設計の方法を変えるかもしれない。このケースはあなたがエキスパートを使いたいオーダーに使う能力のレベルにはない。全ての論点はトレーディングモデルの作成の原理である。


1. トレーディングシステム設計の原理とイベントソースのタイプ

主として使われているアルゴリズム設計への基本的アプローチは1つのポジションの開始から終了までをたどることである。これは線形なアプローチである。そしてコードに変更を加えたいと望むなら、非常に多くの条件が沸き起こりコードに新しい解析の枝が蓄積するので、それはしばしば大変な混乱に陥る。

トレーディングロボットのモデルへの最良の 解決は"条件に対応すること"である。基本的原理は、このエキスパートの条件とそのポジションとオーダーがいかにして起こったかを解析することではなく、それらを今処理するべき内容である。この基本原理はトレードの管理を根本的に変えつつありコードの開発を簡単化する。

そのことをさらに詳細に考察する。

1.1. 「条件に対応」の原理

既に述べたように、エキスパートは現在の状況がいかにして形成されたかを知る必要はない。それをどうするべきかをその環境 (パラメーターの値、保存された オーダーのプロパティなど) に従って知る必要がある。

この原理はエキスパートがどのループ (特にチック(目盛)からチック) にも存在すると言う事実に直接関連し、前のチックでオーダーに何が起こったかについて気にするべきではない。 従って、マージンのオーダーにイベント駆動型のアプローチを採るべきである。 すなわち、エキスパートがその状態を保存している現在のチック上でということで、それは次のチックの判断の開始点である。

例えば、エキスパートの全ての 保留オーダーを除去すべきであり、そしてインジケーター の解析と新しいオーダーを続けるのみである。我々が見てきたコード例は "(true)の間はループを {除去するようにし}" あるいは少し和らげて " (k < 1000)ならば { k++のループを取り除く}"のである。 変形もスキップし、除去コマンドをエラー解析もなしで呼ぶ。

この方法は線形であり、無期限の時間エキスパートを非動作に置く。

従って、エキスパートをループさせないことがより正しく、オーダーを除去するためにオーダーを保存し、新しいチック毎にこのオーダーがチェックされ、一方保留のオーダーは除去を試みる。このケースでは、エキスパートは、状態パラメーターを読む間、その瞬間にオーダーを消去すべきであることを知っている。そして、それはそれらを除去しようと試みる。もしトレーディングにエラーが起こると、エキスパートは単純にそれ以降の解析を止め次のループに備えて働く。

1.2. 設計の第2の原則は、考慮される ポジションの方向 (買い/売り) 、通貨とチャートからの可能な限りの抽象化である。エキスパートファンクションの全てはそのような方法で組まれるべきであり、方向やシンボルが解析されるのはまれなケースで、本当に避けられない場合 (例えば、ポジションをオープンするのに特定することを避ける別の選択があるにも関わらず、価格の望ましい伸びを考慮するような場合)だけである。 常にそのような「低レベルの」設計を避けるようにする。これでコードそしてファンクションを書くプロセスを最低2回減らす。そしてそれらを「取引-独立」にする。

この原則の実装は、オーダーのタイプ、シンボルのパラメーターとそれに絡んでマクロファンクションで計算されるパラメーターの明示的な解析を置き換えることである。次の論説でこの実装の詳細をカバーする。
1.3. 第3の原則は アルゴリズムを論理的な単位 (独立なモジュール) に分割すること)である。

実用的には、最良のアプローチはエキスパート操作を個別のファンクションに分離することである。エキスパートの全アルゴリズムを1つのファンクションに書くことは難しいことに同意するだろう。またそれは後に続く解析と編集を複雑にしてしまう。 そこで、MQL5ではそうはしない、これは環境をほぼ完全に制御できるようになっている。

そこで、論理部分 (例えば、オーダーの開始、追跡、終了) は環境パラメーターの解析とイベントについてもお互いに分離されて実装されるべきである。このアプローチで、エキスパートは設計が柔軟になる。既存のものに触れることなく新しい独立したモジュールを追加することができ、メインのコードを変えることなく既存のモジュールを無効にすることができる。

これら3つの原則は全ての エキスパートに対して1つのプロトタイプを作成することを可能にし、どの与えられたタスクに対しても簡単に修正し適応させることができる。

エキスパートシステムのイベントソースは次のものである:


1. インジケーター。 1つの例はインジケーターラインの値の解析、それらの交差、結合、など。また、インジケーターは:現在時刻、インターネットから得られたデータ、などであるかもしれない。多くの場合、インジケーターイベントはオーダーの開始と終了のシグナルに使われる。それらの調整は多くはないが (通常はストップロスの追跡あるいはインジケーターの保留のオーダー)。

例えば、インジケーターの実用的な実装はエキスパートを呼び,速い、もしくは、遅いMAの交差を解析しさらに交差の向きによってポジションの更なる開始とすることがある。

2. 既存のオーダー、ポジションとその状態例えば、現在の損益の額。ポジションの有り無し、あるいは保留のオーダー、終了したポジションの利得、など。これらのイベントの実用的な実装はずっと広くさらに多様であり、インジケーターのイベントに対するよりもっと多くの関連するオプションがある。

トレードイベントにのみ基づくエキスパートの最も簡単な例は、既存のポジションの平均を埋めそしてそれを希望する利益に出力するものである。すなわち、可能なポジション上の損失の存在は新しい平均化のオーダーをするイベントとなる。

あるいは、例えば、ストップロスの追跡。このファンクションは前のストップロスから特定の数のポイントの間価格が利得に動くイベントをチェックする。その結果、エキスパートは価格によって3つのストップロスを引く。

3. 外部イベント このようなイベントは純粋なエキスパートシステムでは起こらないが、一般に、判断をするために考慮されるべきである。これにはオーダー、ポジション、トレードエラーの処理、チャートイベントの処理 (オブジェクトの移動/作成/消去、ボタンを押す、など) の調整を含む。一般に、これらは来歴で検証できず、エキスパートが働いたときにのみ起こるイベントである。

このようなエキスパートの目立った例はグラフィカルなトレード制御を持ったトレード情報システムである。

エキスパートの多様性はこれら3つのイベントのソースの組合せに基づいている。


2. CExpertAdvisor を基にしたクラス - エキスパートコンストラクター

トレードエキスパートの働きは何か?MQL-プログラムの一般的なやり取りの方式は下のダイヤグラムに示されている。

図 1. MQL-プログラム要素のやり取りの一般的な方式

図 1. MQL-プログラム要素のやり取りの一般的な方式

この方式に見るように、まず作業ループ (これはチックあるいはタイマー信号) への入り口がある。この最初のブロックの段階で、このチックは処理なしでフィルターされることがある。これは、 エキスパートがチックごとに働く必要がない(新しいバーでのみでよい)、あるいはエキスパートが単に働くことを許されない時に行われる。

そしてプログラムは第2ブロックに進む、オーダーとポジションを扱うモジュール、そしてそれからやっとイベント処理ブロックがモジュールから呼び出される。各モジュールは関連するイベントのみ問い合わせることができる。

この方式は直接論理方式と呼ばれ、それはまずをエキスパートがするのか (何のイベント処理モジュールが使われるのか) を決め、そしてその後でのみいかにそしてなぜそれを行うのか (イベントシグナルを得る) を実装するからである。

直接論理は我々の世界の認識と普遍的な論理と整合性がある。結局、人は最初に概念を固め、そしてそれを総括し、それからそれらの関係を分類し同定する。

エキスパートの設計もこの観点で例外ではない。まず、何をエキスパートはするべきなのか (ポジションを開けそして閉め、保護的ストップをかける) を宣言し、そしてそれからそれはどのイベントでいかにそれを行うべきかと特定する。しかし、とにかく逆はない:それはシグナルを受け取りそしてどこでいかにそれを処理するか考えること。これは逆の論理であり、用いないほうが良い、結果として大変な数の条件分枝を持つ厄介なコードを得ることになる。

ここに逆と直接論理の例をしめす開始/終了をRSIシグナルで起す。

エキスパートを複雑にしたければ、第2の変形を用いれば第1のものより容易である。新しいイベントモジュールを作成するのはもうたくさんである。

そして第1の変形では信号処理の構造を更新しなければならない、あるいは別のファンクションとして貼り付けなければならない。

勧告:‘‘トレーディングシステムを記述するときは、1のような語で始めてはいけない。シグナルを得て...オーダーを開く、しかし直ちに複数の部分 "a) オーダーを開く条件、b) オーダーを保持する条件、など"に分割し、各々で要求されるシグナルを解析することになる。

このアプローチをよりよく理解するために、ここに異なった作業方式が4つの異なったエキスパートの文脈としてある。

図 2. エキスパート実装の例

図 2. エキスパート実装の例

a). エキスパート、あるインジケーターのシグナルにのみ基づく。それはシグナルが変化したときに、ポジションを開始と終了を行うことができる。例 - MAエキスパート.
b). グラフィカルなトレード制御のあるエキスパート
c). インジケーターに基づくエキスパート、しかしストップロスと操作時間の追跡を追加する。例 - MAインジケーターによるトレンドでポジションを開始する。
d). インジケーターなしのエキスパート、ポジションの平均化付き新しいバーを開けたときに一度だけポジションのパラメーターを検証する。例 - 平均化エキスパート

方式から分かるように、どのトレーディングシステムも直接論理を用いて簡単に記述できる。


3. エキスパートクラスの実装

上記に述べた全ての規則と用件を用いて、今後全てのエキスパートの基礎となるであろうクラスを作成する。

CExpertAdvisorクラスに入れるべき最小限の機能は次のようである:

1. 初期化

2. シグナルを得るファンクション

3. サービスファンクション

4. トレードモジュール

5. 初期化戻し

このクラスの全ての関数は3つのグループに分割される。入れ子になった一般方式とその説明が以下に示される。

図 3. エキスパートの入れ子ファンクションの方式

図 3. エキスパートの入れ子ファンクションの方式

1. マクロファンクション

この小さな関数グループはオーダーのタイプ、シンボルのパラメーターとオーダーに設定される価格の値を取り扱うための基礎となる。これらのマクロファンクションは設計の第2原則 - 抽象化 - を提供する。これらはエキスパートで使われるシンボルの文脈で働く。

タイプを変換するマクロ ファンクションは市場の方向 - 買いか売りか - に応じて働く。従って、自身の定数を作成しないために、既存のもの ORDER_TYPE_BUYORDER_TYPE_SELLを用いる。ここにマクロ使用の例とその働きの結果の例を示す。

//--- Type conversion macro
   long       BaseType(long dir);        // returns the base type of order for specified direction
   long       ReversType(long dir);      // returns the reverse type of order for specified direction
   long       StopType(long dir);        // returns the stop-order type for specified direction
   long       LimitType(long dir);       // returns the limit-order type for specified direction

   //--- Normalization macro
   double     BasePrice(long dir);       // returns Bid/Ask price for specified direction
   double     ReversPrice(long dir);     // returns Bid/Ask price for reverse direction

   long dir,newdir;
   dir=ORDER_TYPE_BUY;
   newdir=ReversType(dir);               // newdir=ORDER_TYPE_SELL
   newdir=StopType(dir);                 // newdir=ORDER_TYPE_BUY_STOP
   newdir=LimitType(dir);                // newdir=ORDER_TYPE_BUY_LIMIT
   newdir=BaseType(newdir);              // newdir=ORDER_TYPE_BUY

   double price;
   price=BasePrice(dir);                 // price=Ask
   price=ReversPrice(dir);               // price=Bid

エキスパートを開発するときにマクロは処理の方向を指定せずそしてより簡潔なコードの作成を助ける。

2. サービスファンクション

これらの関数はオーダーとポジションを扱うよう設計されている。マクロファンクションのようにこれらも低レベルである。便宜上、これらは2つのカテゴリーに分割される;ファンクションと実行ファンクション。これらは1種類の行動のみ行い、いかなるイベントの解析も行わない。これらは上位エキスパートハンドラーからの命令を実行する。

情報ファンクションの例: 現在の保留オーダーの最高開始価格を見つける;ポジションが如何に終了されたか調べる - 利得あるいは損失;エキスパートのオーダーの数とチケットのリストを得る、など。

実行ファンクションの例: 特定のオーダーを終了する;ストップロスを特定のポジションで修正する、など。

このグループは最大。これはある種の機能性であり、エキスパートの全ルーチンワークがこれらに基づいている。非常に多数のこれらファンクションの例を https://www.mql5.com/ru/forum/107476のフォーラムで見つけることができる。このMQL5 標準ライブラリ は既にオーダーとポジションを発する仕事の部分 を引き受けるクラス、特にCTradeを含んでいる。

しかしあなたのどのタスクも新しい実装を作成するか既存のものを少し修正する必要がある。

3. イベント処理モジュール

このファンクションのグループは最初の2つのグループの上の高レベルの上位構造である。上に述べたように、これらはあなたのエキスパートを構築するためのそのまますぐ使えるブロックである。一般に、それらはMQLのイベント処理 ファンクションに含まれている: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent()。このグループはそれほど多くなく、その内容はいろいろなタスクに調節できる。しかし基本的に何も変化しない。

モジュールでは、同じモジュールが買いでも売りでも呼び出されるように、全てが抽象的であるべきである (第2設計原則)。 これはもちろんマクロの助けで達成される。.

それでは実装に進もう

1. 初期化と、定義n

class CExpertAdvisor
  {
protected:
   bool              m_bInit;       // flag of correct initialization
   ulong             m_magic;       // magic number of expert
   string              m_smb;       // symbol, on which expert works
   ENUM_TIMEFRAMES      m_tf;       // working timeframe
   CSymbolInfo      m_smbinf;       // symbol parameters
   int               m_timer;       // time for timer

public:
   double              m_pnt;       // consider 5/3 digit quotes for stops
   CTrade            m_trade;       // object to execute trade orders
   string              m_inf;       // comment string for information about expert's work

これがエキスパートファンクションが働くために最低限要求されるパラメーターのセットである。

m_smb と m_tfパラメーターは特にエキスパートプロパティに置かれ、エキスパートにどの通貨、そしてどの期間とするかを伝える。 例えば、もしm_smb = "USDJPY"と割り当てると、エキスパートはそのシンボルで走っているかに関わらず、そのシンボルで働く。もしtf = PERIOD_H1とセットすると、インジケーターの全てのシグナルと解析はチャートで行われる。

さらにクラスメソッドがある。最初のメソッドは初期化とエキスパートの初期化外し(ディイニシャライゼーション)である。

public:
   //--- Initialization
   void              CExpertAdvisor();                               // constructor
   void             ~CExpertAdvisor();                               // destructor
   virtual bool      Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization

基本クラスのコンストラクターとデストラクタは何も行わない。

Init() メソッドはエキスパートパラメータの初期化をシンボル、時間枠とマジック番号で行う。

//------------------------------------------------------------------ CExpertAdvisor
void CExpertAdvisor::CExpertAdvisor()
  {
   m_bInit=false;
  }
//------------------------------------------------------------------ ~CExpertAdvisor
void CExpertAdvisor::~CExpertAdvisor()
  {
  }
//------------------------------------------------------------------ Init
bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf)
  {
   m_magic=magic; m_smb=smb; m_tf=tf;         // set initializing parameters
   m_smbinf.Name(m_smb);                      // initialize symbol
   m_pnt=m_smbinf.Point();                    // calculate multiplier for 5/3 digit quote
   if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10;  
   m_trade.SetExpertMagicNumber(m_magic);     // set magic number for expert

   m_bInit=true; return(true);                // trade allowed
  }

2. シグナルを得るファンクション

これらのファンクションは市場とインジケーターを解析する。

bool              CheckNewBar();                          // check for new bar
   bool              CheckTime(datetime start,datetime end); // check allowed trade time
   virtual long      CheckSignal(bool bEntry);               // check signal
   virtual bool      CheckFilter(long dir);                  // check filter for direction

最初の2つのファンクションは特定の場合に使用され、そしてこのクラスのさらに子クラスに使われる。

//------------------------------------------------------------------ CheckNewBar
bool CExpertAdvisor::CheckNewBar()          // function of checking new bar
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)      // copy bar
     { Print("CopyRates of ",m_smb," failed, no history"); return(false); }
   if(rt[1].tick_volume>1) return(false);   // check volume 
   return(true);
  }
//---------------------------------------------------------------   CheckTime
bool CExpertAdvisor::CheckTime(datetime start,datetime end)
  {
   datetime dt=TimeCurrent();                          // current time
   if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range
   if(start>=end) if(dt>=start|| dt<end) return(true);
   return(false);
  }

後の2者は常にそれらのインジケーターに依存する。全てのケースでこれらのファンクションを設定することは単に不可能である。

重要な点として、CheckSignal() と CheckFilter() のシグナルファンクションはどのインジケーター、そして、いかなる組み合わせでも完全に解析できることを理解することは重要である。すなわち、これらのシグナルが引き続き含まれるトレードモジュールはソースから独立している。

これによって一旦エキスパートを書けばそれをテンプレートとして同様な原理で働くほかのエキスパートに使うことができる。 解析されるインジケーターを変更するか、あるいは新しいフィルタリング条件を加えるとよい。

3. サービスファンクション

既に述べたように、このグループのファンクションは最も数が多い。この論説で説明する実用的なタスクのためには4つのファンクションを実装すれば十分である。

double         CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk
   ulong          DealOpen(long dir,double lot,int SL,int TP);     // execute deal with specified parameter
   ulong          GetDealByOrder(ulong order);                     // get deal ticket by order ticket
   double         CountProfitByDeal(ulong ticket);                 // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk
double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk
  {
   if(dist==0 || risk==0) return(lot);
   m_smbinf.Refresh();
   return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue())));
  }
//------------------------------------------------------------------ DealOpen
ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP)
  {
   double op,sl,tp,apr,StopLvl;
   // determine price parameters
   m_smbinf.RefreshRates(); m_smbinf.Refresh();
   StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level
   apr     = ReversPrice(dir); 
   op      = BasePrice(dir);                         // open price
   sl      = NormalSL(dir, op, apr, SL, StopLvl);    // stop loss
   tp      = NormalTP(dir, op, apr, TP, StopLvl);    // take profit

   // open position
   m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp);
   ulong order = m_trade.ResultOrder(); 
   if(order<=0) return(0);                           // order ticket
   return(GetDealByOrder(order));                    // return deal ticket
  }
//------------------------------------------------------------------ GetDealByOrder
ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket
  {
   PositionSelect(m_smb);
   HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER));
   uint total=HistoryDealsTotal();
   for(uint i=0; i<total; i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(order==HistoryDealGetInteger(deal,DEAL_ORDER))
         return(deal);                            // remember deal ticket
     }
   return(0);
  }
//------------------------------------------------------------------ CountProfit
double CExpertAdvisor::CountProfitByDeal(ulong ticket)  // position profit by deal ticket
  {
   CDealInfo deal; deal.Ticket(ticket);                 // deal ticket
   HistorySelect(deal.Time(),TimeCurrent());            // select all deals after this
   uint total  = HistoryDealsTotal();
   long pos_id = deal.PositionId();                     // get position id
   double prof = 0;
   for(uint i=0; i<total; i++)                          // find all deals with this id
     {
      ticket = HistoryDealGetTicket(i);
         if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue;
      prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit
     }
   return(prof);                                        // return profit
  }

4. トレードモジュール

最後に、このグループのファンクションはサービス ファンクションとマクロを用いてトレーディングの全プロセスを結びつけ、シグナルとイベントを処理する。トレード操作の論理的要素は少なく、それらは特定の目的に依存している。しかし、ほとんど全てのエキスパートに存在する共通の概念を認めることができる。

virtual bool      Main();                            // main module controlling trade process
   virtual void      OpenPosition(long dir);            // module of opening position
   virtual void      CheckPosition(long dir);           // check position and open additional ones
   virtual void      ClosePosition(long dir);           // close position
   virtual void      BEPosition(long dir,int BE);       // moving Stop Loss to break-even
   virtual void      TrailingPosition(long dir,int TS); // trailing position of Stop Loss
   virtual void      OpenPending(long dir);             // module of opening pending orders
   virtual void      CheckPending(long dir);            // work with current orders and open additional ones
   virtual void      TrailingPending(long dir);         // move pending orders
   virtual void      DeletePending(long dir);           // delete pending orders

これらのファンクションの特定の実装を以下の例で考察する。

新しいファンクションを加えることは難しいことではなく、それは正しいアプローチを選択し、エキスパートの構造を構成したからである。 この方式をきちんと使えば、設計は最小の努力と時間で済み、コードは1年後でも読みやすいはずである。

もちろん、エキスパートはこれらに限定されない。CExpertAdvisor クラスには、もっとも必要なメソッドのみを宣言している。 子クラスに新しいハンドラーを加え、既存のものを改造し、自身のモジュールを拡張し、1つのライブラリを作成できる。このようなライブラリをもっていれば、すぐ使える「ターンキー」 エキスパートの開発はは30分から2日でできる。


4. CExpertAdvisor クラスを用いた例

4.1. インジケーターシグナルに基づいた仕事の例

最初の例として、最も簡単な仕事から始めよう - CExpertAdvisor クラスを用いたMovingAverage エキスパートアドバイザー (MetaTrader 5の基本的な例) 。ほんの少し複雑にしよう。

アルゴリズム:

a) ポジション開始の条件

b) 閉鎖ポジションの条件

c) 限界

d) ポジションのサポート

このエキスパートのために CExpertAdvisor クラスの7つのファンクションを必要とする。

この仕事を特別に解くには CheckSignal() ファンクションとモジュールは子クラスで定義される必要がある。また、インジケーターの初期化を加える必要がある。

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"

input double Risk      = 0.1; // Risk
input int    SL        = 100; // Stop Loss distance
input int    TP        = 100; // Take Profit distance
input int    TS        =  30; // Trailing Stop distance
input int    pMA       =  12; // Moving Average period
input int    HourStart =   7; // Hour of trade start
input int    HourEnd   =  20// Hour of trade end
//---
class CMyEA : public CExpertAdvisor
  {
protected:
   double            m_risk;          // size of risk
   int               m_sl;            // Stop Loss
   int               m_tp;            // Take Profit
   int               m_ts;            // Trailing Stop
   int               m_pMA;           // MA period
   int               m_hourStart;     // Hour of trade start
   int               m_hourEnd;       // Hour of trade end
   int               m_hma;           // MA indicator
public:
   void              CMyEA();
   void             ~CMyEA();
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual bool      Main();                              // main function
   virtual void      OpenPosition(long dir);              // open position on signal
   virtual void      ClosePosition(long dir);             // close position on signal
   virtual long      CheckSignal(bool bEntry);            // check signal
  };
//------------------------------------------------------------------ CMyEA
void CMyEA::CMyEA() { }
//----------------------------------------------------------------- ~CMyEA
void CMyEA::~CMyEA()
  {
   IndicatorRelease(m_hma); // delete MA indicator
  }
//------------------------------------------------------------------ Init
bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false);  // initialize parent class

   m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA;  // copy parameters
   m_hourStart=HourStart; m_hourEnd=HourEnd;

   m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator
   if(m_hma==INVALID_HANDLE) return(false);            // if there is an error, then exit
   m_bInit=true; return(true);                         // trade allowed
  }
//------------------------------------------------------------------ Main
bool CMyEA::Main()                            // main function
  {
   if(!CExpertAdvisor::Main()) return(false); // call function of parent class

   if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars
   
   if(!CheckNewBar()) return(true);           // check new bar

   // check each direction
   long dir;
   dir=ORDER_TYPE_BUY;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);
   dir=ORDER_TYPE_SELL;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);

   return(true);
  }
//------------------------------------------------------------------ OpenPos
void CMyEA::OpenPosition(long dir)
  {
   if(PositionSelect(m_smb)) return;     // if there is an order, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00"))) return;
   if(dir!=CheckSignal(true)) return;    // if there is no signal for current direction
   double lot=CountLotByRisk(m_sl,m_risk,0);
   if(lot<=0) return;                    // if lot is not defined then exit
   DealOpen(dir,lot,m_sl,m_tp);          // open position
  }
//------------------------------------------------------------------ ClosePos
void CMyEA::ClosePosition(long dir)
  {
   if(!PositionSelect(m_smb)) return;                 // if there is no position, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00")))
     { m_trade.PositionClose(m_smb); return; }        // if it's not time for trade, then close orders
   if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction
   if(dir!=CheckSignal(false)) return;                // if the close signal didn't match the current position
   m_trade.PositionClose(m_smb,1);                    // close position
  }
//------------------------------------------------------------------ CheckSignal
long CMyEA::CheckSignal(bool bEntry)
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)
     { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); }

   double ma[1];
   if(CopyBuffer(m_hma,0,0,1,ma)!=1)
     { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); }

   if(rt[0].open<ma[0] && rt[0].close>ma[0])
      return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell

   return(WRONG_VALUE);                                // if there is no signal
  }

CMyEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.Main();                  // process incoming tick
  }

Main() ファンクションの構造を構文解析しよう。 便宜上これを2つの部分に分割する。

最初の部分では親のファンクションが呼び出される。このファンクションはエキスパートの仕事にグローバルに影響する可能性あるパラメーターを処理する。これらはエキスパートのためにトレードの余裕をチェックし来歴データを検証するのを含む。

第2の部分では市場のイベントが直接的に処理される。

CheckNewBar() フィルターが試される - 新しいバーをチェックする。トレードの2つの方向のためのモジュールが一つづつ呼び出される。

モジュールにおいて全てがかなり抽象的に組織されている (第2設計原則)。シンボルのプロパティに直接のアドレスはない。そして3つのモジュール - OpenPosition(), ClosePosition()TrailingPosition() - は外部からそれらに来るパラメーターにのみ頼る。これはこれらのモジュールを買いのためと、売りのための両方のオーダーの検証のために呼ぶことを可能にする。

4.2. CExpertAdvisorを使用する例 - インジケーターなしのエキスパート、ポジションの状態と結果を解析する。

デモのためロス後のロット増加でのポジション逆転のみでトレードするシステムを使おう。

a) 初期オーダーをする

b) 引き続きポジションを開始する

このエキスパートのために CExpertAdvisor クラスの3つのファンクションを必要とする:

エキスパートはどのインジケーターも解析しないが、ディールの結果だけがあり、生産性を最適化するためにOnTrade() イベントを用いる。すなわち、最初の初期オーダーを買いのオーダーをしたエキスパートでは、引き続く全てのオーダーをこのポジションの終了後にのみ行う。従って初期オーダーを OnTick() でそして引き続く全ての仕事は OnTrade()で行う。.

Init() ファンクションが、通常通り、単にクラスのパラメーターをエキスパートの外部パラメーターで初期化する。

OpenPosition() モジュールが初期ポジションを開始しそして m_first フラグによってブロックされる。

CheckPosition()モジュールがさらにポジションの反転を制御する。

これらのモジュールはエキスパートのそれぞれのファンクション(OnTick() と OnTrade())から呼び出される。

//+------------------------------------------------------------------+
//|                                                       eMarti.mq5 |
//|              Copyright Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"
#include <Trade\DealInfo.mqh>

input double Lots    = 0.1; // Lot
input double LotKoef = 2;   // lot multiplier for loss
input int    Dist    = 60;  // distance to Stop Loss and Take Profit
//---
class CMartiEA : public CExpertAdvisor
  {
protected:
   double            m_lots;       // Lot
   double            m_lotkoef;    // lot multiplier for loss
   int               m_dist;       // distance to Stop Loss and Take Profit
   CDealInfo         m_deal;       // last deal
   bool              m_first;      // flag of opening the first position
public:
   void              CMartiEA() { }
   void             ~CMartiEA() { }
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual void      OpenPosition();
   virtual void      CheckPosition();
  };
//------------------------------------------------------------------ Init
bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class
   m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist;       // copy parameters
   m_deal.Ticket(0); m_first=true;
   m_bInit=true; return(true);                        // trade allowed
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::OpenPosition()
  {
   if(!CExpertAdvisor::Main()) return;                       // call parent function
   if(!m_first) return;                                      // if already opened initial position
   ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position
   if(deal>0) { m_deal.Ticket(deal); m_first=false; }        // if position exists
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::CheckPosition()
  {
   if(!CExpertAdvisor::Main()) return;           // call parent function
   if(m_first) return;                           // if not yet placed initial position 
   if(PositionSelect(m_smb)) return;             // if position exists

   // check profit of previous position
   double lot=m_lots;                            // initial lot
   long dir=m_deal.Type();                       // previous direction
   if(CountProfitByDeal(m_deal.Ticket())<0)      // if there was loss
     {
      lot=NormalLot(m_lotkoef*m_deal.Volume());  // increase lot
      dir=ReversType(m_deal.Type());             // reverse position
     }
   ulong deal=DealOpen(dir,lot,m_dist,m_dist);   // open position
   if(deal>0) m_deal.Ticket(deal);               // remember ticket
  }

CMartiEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.OpenPosition();          // process tick - open first order
  }
//------------------------------------------------------------------ OnTrade
void OnTrade()
  {
   ea.CheckPosition();         // process trade event
  }


5. イベントの処理

この論説では2つのイベントを処理する例を紹介した - NewTickTrade: それらはそれぞれ OnTick() と OnTrade() ファンクション, で演じられている。たいていの場合、これら2つのイベントは継続的に使われる。

エキスパートには、イベントを処理する4つのファンクションがある。

どのイベントもその組み合わせも常に使用者の特定のタスクの解決に使うのに妥当なものであることを忘れてはいけない。


最後に

これで分かるように、エキスパートを書くことは、正しい方式をもっていれば、そう時間のかかるものではない。MQL5のイベントを処理する新しい可能性によって、トレーディング処理を管理するより柔軟な構造を使える。しかしこれらの材料はトレーディングのアルゴリズムを正しく準備したときにのみ真に強力なツールとなるのである。

この論説はその作成における3つの主な原則 - イベント対応、抽象性、モジュラリティについて説明した。エキスパートをこの「3つの枕」に基づいて作成すれば、トレードを簡単にすることができる。