English Русский 中文 Español Deutsch Português
MetaEditor:支点としてのテンプレート

MetaEditor:支点としてのテンプレート

MetaTrader 4 | 15 3月 2016, 16:16
1 288 0
MetaQuotes
MetaQuotes


私に支点を与えよ。されば地球を動かしてみせよう。
アルキメデス

-はじめに

プログラム言語として、MQL4 はひとたび書いてデバッグするとコードの用途は非常に幅広いものとなります。その用途は以下のようなものです。

  • .mqh インクルードファイル。こういったファイルには必要な関数すべてと、#include 命令を使ってコードに追加する定数を格納します。
  • 標準的 MQL4 プログラムとしてコンパイルし、命令を用いてリアルタイムモードでコードに追加できる関数ライブラリ
  • 時系列配列で経済的計算を行うカスタムインディケータ。それらは関数 iCustom() によってリアルタイムで呼び出されます。

ただし、開発者がすべて知っているわけではなく、それゆえ Expert Advisor ウィザードを用いてすぐに使えるテンプレートとしての Expert Advisor を簡単に信頼性を持たせて書くための協力なメカニズムを使っているわけではありません。本稿はこのツールのメリットをいくらか明らかにしています。



テンプレートとは何でしょうか?

MetaEditor という観点からのテンプレートとは何でしょうか?テンプレートとはターミナルRoot_directory_MetaEditor_4/experts/templates/の同じ名前のフォルダに格納されている .mqt ファイルです。

上の画像ではそのようなファイルが10件あります。その中でも以下が基本的なものです。
  • Expert.mqt -Expert Advisor 作成のためのテンプレート
  • Script.mqt -スクリプト作成のためのテンプレート
  • Include.mqt -スクリプト作成のためのテンプレート
  • indicator.mqt -インディケータ作成のためのテンプレート
  • Library.mqt -ライブラリ作成のためのテンプレート

その他のテンプレート(Alligator.mqt など)はテンプレート名に与えられたインディケータ名に従ったインディケータ作成のためのものです。たとえば、MetaEditor でテンプレート MetaEditor を開きます。このために、「ファイルタイプ」フィールドで「すべてのファイル (*.*)」を指定します。

ファイル内容はそれほど大きくないことがわかります。

<expert>
type=LIBRARY_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2)
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+
この最初の3行はこのテンプレートがどのタイプのファイルに属しているかという情報を伝えます。
<expert>
type=LIBRARY_ADVISOR
</expert>

type=LIBRARY_ADVISOR は MetaEditor にこのファイルはライブラリテンプレートであることを明確に伝えています。MetaEditor はみなさんの選択: EA、カスタムインディケータなど、に応じて必要なテンプレートを使用します。


そして次にくるのは、実質的に Expert Advisorウィザードの指示に従ってご自分で選ぶ名前を置き換える置換マクロ #header# です。


たとえば、EA に My_Best_Expert_Advisor と名付けたら、マクロ#header# の代わりに以下の行が作成されます。

//+------------------------------------------------------------------+
//|                                       My_Best_Expert_Advisor.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+

コマンドのこのブロックには、EA 名、それを書いた人、ウェブサイトへのリンクがあります。これらデータはすべて、Expert Advisor ウィザードの対応フィールドに入力されています。次の行

#property copyright "#copyright#"
#property link      "#link#"

には、明らかにウィザードのフィールドに対応しているマクロ #copyright# と #link# があります。



その用法は?

一番興味があるのは、EA を作成するごとに自動で作成されるように、テンプレートに自分のコードを挿入する機能です。たとば、スクリプト作成にいくらか経験があれば、スクリプトはチャート上で一度だけ起動されることを知っています。スクリプトを起動するときこういったパラメータを変更する必要があるかもしれないため、スクリプトのアルゴリズムに対して、外部パラメータをいくつか指定する必要がある場合があります。Expert Advisor ウィザードはデフォルトではこの機能を提供しません。ただ、これはコンパイラに対する命令を使用することで行えます。

#property show_inputs

任意のスクリプトコードにこの行を追加するだけで、パラメータを持つウィンドウが表示されます。Expert Advisor ウィザードによって毎回新規に作成されるスクリプト内で、この行が自動的に追加する方法を例示します。このために EA のテンプレートを開きます。

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

外部パラメータ(#extern_variables#)を置き換えるマクロの前に1行のみ追加します。以下が取得するコードです。

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#property show_inputs
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

それから、変更を保存し、Expert Advisor ウィザードを使用してスクリプトを書き始めます。スクリプトに TestScript と名付け、「完了」を押したあと、

結果を得ます。
//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

新しいスクリプトは古いテンプレートで作成されたものと1行だけしか違いませんが、それでもひじょうに役立つものです。これで以下の行

#property show_inputs

を新規スクリプトのたびにマニュアルで追加することはなく、自動で追加されるのです。とにかく、(必要なければ)毎回入力しなおすよりはコメントを入れるのがずっと簡単です。emptyParam という名前のパラメータを持つ1行だけをマニュアルで追加します。

//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
extern int emptyParam=1;
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

それをコンパイルし、チャートに入れます。このスクリプトは今すぐ実行しませんが、それによって外部パラメータを変更することはできます。


これはもっともシンプルなテンプレートの使用法例です。考えは、新規に MQL4 プログラムを作成するたびに必要なコード行をテンプレートに追加する、というところにあります。そしてこういったコード行は、Expert Advisor ウィザードを使用するとき、自動的に追加されるのです。

重要:MetaTrader 4 クライアントターミナルをインストールするとき、テンプレートはすべて書き直されます。よって自分でテンプレートのコピーをとっておく必要があります。



それはわれわれをどこに導くのでしょうか?

既製のテンプレートを使用する機能により、最大限 EA を正確に書ける可能性が生まれます。初心者は EA をどのように書くのでしょうか?たとえば、移動平均の交点を基にした戦略を考察します。以下はこの戦略にのっとって EA を作成する場合の技術要件です。

  1. 短期と長期の移動平均値を取得します。
  2. それらの交差についてチェックします。
  3. 短期のものがボトムアップで長期に交差する場合、買いで StopLoss=N ポイントです。
  4. 短期のものが、トップダウンで長期に交差する場合、売りで StopLoss=N ポイントです。
  5. オープンしたロングポジションがあれば、売りシグナルでそれをクローズします。
  6. オープンしているショートポジションがあれば、買いシグナルでそれをクローズします。

それで、EA は準備でき、最適化されました。ここで考えるのは、新しいパラメータの追加です。たとえば、TakeProfit = S ポイントです。もしくは、トレードアラートブロックの置換です。移動平均の交点の代わりにストキャスティック値を利用します。などなど。 EA に対する置換のたびに、コードになにかしら変更を加える必要があり、それは突然あまり簡単ではないことがわかります。あるとき、新しい関数をすべて持つ新しい EA を書く必要があるのがわかるのです。もちろん、経験あるのプログラマ―の方はすでにこの道のりで賢明な成長をとげ、お好きな実践方法やアプローチを行います。本稿の冒頭にあるテクニックが助けとなってくれます。すなわち以下です(繰り返します)。

  • インクルードファイル
  • ライブラリ
  • カスタムインディケータ

ただし、それらはテンプレートを使用しないと最大の効果は発揮しません。では、テンプレートEA とはどんなものなのでしょうか、また効果的なものとするにはどういうものでなければならないのでしょうか?私の解釈では、テンプレート EA とは実 EA のユニバーサルなプロトライプですが、特定の機能は持ちません。実際には、純粋仮想関数(オブジェクト指向プログラミンの視点で)、またはインターフェース(Java 視点で)を持つ特定の SuperClass です。テンプレートはEAするのかを説明しますが、 どのようにするのかは述べません。これがテンプレート作成前に、望む機能性を分析する必要がある理由です。まずわれわれの EA のストラクチャを作成する必要があります。



Expert Advisor ストラクチャ

EA は何をすべきでしょうか?一番最初の近似化で、Ea はトレードを行います。それはトレーディング処理を行うことを意味します。すなわちトレードポジションのオープンやクローズのリクエストを出すのです。また、EA は現在オープンしているポジションのストップロス および テイクプロフィットレベルを変更し(必要に応じ)未決注文を出すまたは削除し、未決注文のストップロステイクプロフィット のレベルを変更します。シンプルなストラクチャが判ってきます。

  1. トレードシグナル受信をブロックする
  2. 成行注文や未決注文を出す
  3. オープン注文のクローズ、未決注文を削除する

これで自動トレーディングの一般的考えを3つの副課題に分けたことになります。現行シグナルの判断をする関数(ブロック1)を理解する必要があることがわかりました。売る、買う、または市場(トレードシグナル)の外で待機する、です。それから(ブロック2)、トレードシグナルを基にポジションをオープンしたり、未決注文を出すことができます。なぜ「ならない」ではなく「できる」なのでしょうか?なぜなら、ポジションオープンのブロックはトレードシグナルを考慮しますが、それに従いトレードを行う義務はないからです。たとえば、すでにオープンしているポジションがいくつかあるとします。新規ポジションをオープンすることは、トレードアカウントを過度のリスクにさらすことになるのです。EA の最後の機能性(ブロック3)となってしまいましたが、オープンしているポジションのクローズと未決注文の削除、もまた独立しています。トレードシグナルによって、またその他の考慮事項(ポジション保持時間、トレードセッションの終わりなど)からポジションをクローズすることができます。このように、EA ロジックを 3 部分に分けることは根拠がないようには見受けられません。



さらなる改良

そう考えると、前述のストラクチャでは完全ではないという結論に達します。各ブロックにさらなる改良を加えます。

まず、受信ティック(ティックは価格変動のファクトです)それぞれに対するトレードシグナルを計算する、または新規バーが開くときのみこういう計算が行われるようにします。現時点ではこれは重要な可能性がありますが、事前に提供しなければ、のちにコードを変更するのは困難になるかもしれません。

次に、こういったバーのタイムフレームを変更することも可能です。現時点ではわれわれの EA は1時間足チャートでトレードを行うように作成していても、のちに4時間足でトレードを行うように変更したいと思うかもしれません。この点も考慮する必要があります。

そしてその次には、トレードシグナルを統一する必要があります。厳格な体系化が必要です。3タイプのトレードシグナルしかないので、ブロックには厳格に表記された定数を使ってそれらを表示させます。なによりもMQL4 の観点からです。

  • OP_BUY -買いシグナル
  • OP_SELL -売りシグナル
  • OP_BALANCE -シグナルなし。マーケット外に留まる

よって第 1 ブロックは以下のようになります。

/**
      1. Trade Signals . Receiving trade signals
      a)  Every tick
      b) Every bar of the given timeframe
      OP_BUY     - to buy
      OP_SELL    - to sell
      OP_BALANCE  - no signal
*/

それから、新規オーダーをオープンする、またはすでに既存のオーダーを変更するための重要なレベル計算用ブロックが必要です。これもコードの他の部分に依存しない機能性です。

/**
      2.
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/

ポジションサイズ、ロットもパラメータですがここではその計算は示していません。とはいうものの、これまでのように「資金管理」ブロックと呼ぶ個別のブロックで関数を計算してオープンする可能性あるポジションサイズを入れたいと思うことがあるかもしれません。

次のブロックは注文レベルを変更するものです。これもまた独立している必要があります。ストップロス、テイクプロフィットレベルの両方を変更する可能性があるためです。また、ティックごと、あるいは新規バーのオープン時にそれらを変更する可能性もあります。EA にこの柔軟性を与えれば、のちの最適化がずっと簡単になります。

/**
      3.
        a) Modification of each open order for every tick (SL and TP)
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/

ブロックNo.1(トレードシグナル受信)と同様の方法で新規バーのオープン時にタイムフレームの変更指定が可能であることに留意してください。

次のブロックはオーダーのクローズを行うものです。オーダーをクローズするには理由が2つあります。それは、時間とシグナルです。この場合、ポジションをオープンする逆向きのトレードシグナル受信と、ポジション保持が必要であるとする条件変更の両方を指します。

/**
      4.
        a) Close open order by time
        b) Close open order by signal
*/

私は未決注文削除のブロックをポジションクローズのブロックとは分けました。というのも、オープンしているポジションのクローズは未決注文(一般的に未決注文は現行価格からかなり離れた位置にあるものです)を削除するよりも優先順位がずっと高いことがある(たとえば、オープンしているポジションに対して価格が激しく変動し始め、なにを置いてもそれをできるだけすぐにクローズする必要があるなど)からです。

/**
      5.
        a) Delete pending order by time
        b) Delete pending order by condition
*/

未決注文削除ブロックではすべて完全に明確です。よってこれ以上のコメントは不要です。

最後のブロックはポジションをオープンし未決注文を出すものです。

/**
      6.
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

おかしなことですが、それが最後であることがわかりました。それを考える場合、この EA ストラクチャはひじょうに理論的です。のちに問題がなにも残らないよう、まず必要な準備をすべて行います(クローズすべきものはすべてクローズし、余分な未決注文はすべて削除します)。それではじめて新規注文を出し、市場に積極的に介入できるのです。

/**
      7.
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

ただし、このブロックにもいくつかバリエーションがあります。マーケット価格(アスク価格で買い、ビッド価格で売る)でポジションをオープンし、有効期限の日時で(期限切れの未決注文はサーバーにより削除されます)、または取り消すまで(自分で削除するまで)期限なしで未決注文を出します。

上記すべてが結果として以下のテンプレートストラクチャにつながります。

<expert>
  type=EXPERT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
 
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a)  Every tick
      b) Every bar of the given timeframe
      OP_BUY     - to buy
      OP_SELL    - to sell
      OP_BALANCE  - no signal
*/
 

/**
      2.
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 

/**
      3.
        a) Modification of each open order for every tick (SL and TP)
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/
 

/**
      4.
        a) Close open order by time
        b) Close open order by signal
*/
 
/**
      5.
        a) Delete pending order by time
        b) Delete pending order by condition
*/
 

/**
      6.
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/
 
//----
   return(0);
  }
//+------------------------------------------------------------------+

これですでにそのままテンプレートを保存することができます。それ以上の関数がなくても、このテンプレートは有用です。これは EA を書くために必要なブロックすべてのテキスト表記です。Expert Advisors のプログラマ―の方は皆さんが自分バージョンのストラクチャを追加するか、全体的に変更したりされます。ただいずれにしても、EA ウィザードを使用して新しい EA を作成する際には、ご自分のコードで何をどのシーケンス内で実現するのか忘れることがないようテキスト表記を確認することでしょう。もっとずっと長いものになる可能性もあります。



実装開始

ここから EA テンプレートストラクチャに一般的な関数を書き込むことができます。こういった関数は必要な処理の最終的実行は記述しませんが、テンプレートストラクチャとユーザーの最終的関数の一般的相互作用について述べます。そのため、トレードシグナルを受信するための特別な関数は記述しません。一般的な関数内のどこでユーザーが特定の関数呼び出しを入れるべきかについて記述を行います。




定義済みタイムフレームに対する新規バー出現の関数

すでに、新規バー成立時またはユーザーによって定義済みの特定タイムフレームにおいてのどちらかでブロックNo.1 でトレードシグナルが表示されるようにすると決めたので、まずはそのイベント「タイムフレームにおいて新規バーが出現しました」、を伝える関数が必要です。この関数はシンプルです。以下がそれです。

//+----------------------------------------------------------------------+
//|  It returns the sign of a new bar appearance for the given timeframe |
//+----------------------------------------------------------------------+
bool isNewBar(int timeFrame)
   {
   bool res=false;
   
   // the array contains open time of the current (zero) bar
   // for 7 (seven) timeframes
   static datetime _sTime[7];
   int i=6;
 
   switch (timeFrame)
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
//----
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
      
//----
   return(res);
   }



タイムフレーム設定と正しさのチェック

関数 isNewBar() はパラメータとして分の分量でタイムフレームを取ります。必要なタイムフレームを指し示す外部変数 TradeSignalBarPeriod を取り入れます。

extern int  TradeSignalBarPeriod       = 0;

デフォルトではその値はゼロとなっています。EA はつねに『その』タイムフレーム、すなわちリアルタイムモードでトレードが行われるときこの EA がアタッチされるチャートのタイムフレーム、または検証されるタイムフレームに等しいもの、におけるトレーディングでの新規バーを追跡します。ユーザーはタイムフレームを選択するため定義済みの定数であればなんでも取り入れることができます。ただし、そこにはエラー発生の可能性があります。よって変数 TradeSignalBarPeriod の値を確認し、必要に応じて値が誤っている場合にエラーを修正する関数を追加します。

//+------------------------------------------------------------------+
//|  if the timeframe is specified incorrect, it returns zero        |
//+------------------------------------------------------------------+
int getCorrectTimeFrame(int period)
   {
   int res=period;
//----
   switch (period)
      {
      case PERIOD_D1:  break;  // allowed timeframe, don't do anything
      case PERIOD_H4:  break;  // allowed timeframe, don't do anything
      case PERIOD_H1:  break;  // allowed timeframe, don't do anything
      case PERIOD_M30: break;  // allowed timeframe, don't do anything
      case PERIOD_M15: break;  // allowed timeframe, don't do anything
      case PERIOD_M5:  break;  // allowed timeframe, don't do anything
      case PERIOD_M1:  break;  // allowed timeframe, don't do anything
      default: res=Period();   // incorrect timeframe, set a default one
      }
//----
   return(res);      
   }

エラーが発生しなければ、この関数は何もしません。それを使うのは EA 初期化の一番最初に一度きりです。そのためその呼び出しを関数 init() に入れます。

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   TradeSignalBarPeriod=getCorrectTimeFrame(TradeSignalBarPeriod);   
   StartMessage(); // remind the EA settings
//----
   return(0);
  }

ごらんのように、そのほかにもブロック init() 内に新しいユーザー定義関数 StartMessage() があります。それは以下で説明しています。ここではその目的を説明するに留めます。Expert Advisor は、ある程度時間が経つと人が簡単に忘れてしまう設定パラメータを非常に大量に収集します。関数StartMessage() はトレードアカウントで起動する際やバックテストの際の EA パラメータの最低限の記述をする専用の関数です。これらパラメータはのちにターミナルやテスターのログで読むことができます。私はどんな EA でもそのような関数が便利であると思います。EA テンプレートにも入れたのはそのためです。



トレードアラートの受信

ここでトレードアラートを出すブロックに目を転じます。ですが、先に進む前にいくらか状況を考察します。EA を作成したら、週の特定の曜日にそれがどのように動作するか確かめたいと思うことがあるでしょう。たとえば、水曜日だけトレードを行いたい、そのため EA は「静かに」していなければならないのです。よって EA に前もってトレードする曜日を選択する機能を備えます。そのため外部整数変数 TradeDay を作成します。その値がゼロであれば(日曜=0、月曜=1、など)、通常どおり取引を行います。値がゼロ以外であれば、週のうち指定された曜日にだけ取引を行います。

また、逆の処理をしたいと思うこともあるでしょう。そうした場合、EA に取引を禁じる曜日を指定する必要があります。そこでもう一つ論理的外部変数 ReversDay を作成します。これが「偽」であれば、ロジックは正常(TradeDay は取引曜日を指定する)です。それが「真」であれば、条件は逆で TradeDay は取引を行わない曜日を指定することとなります。たとえば、TradeDay=5 で ReversDay=真 なら金曜日(金曜日=5)には取引は行わない、ということです。

最後の細工はトレーディングシステムに逆らうものです。たとえば、将来的に戦略をまったく逆にして(買いを売りに、売りを買いに変更する)何が起こるか見たい、などです。最初からこの機能を備えていれば、いつかそれを利用することができるでしょう。これにはもう一つ論理的外部変数 ReversTradeSignal を取り入れるだけで十分です。それが「偽」(デフォルト)であれば、最初の戦略どおりです。しかし、その値が「真」であれば、システムは逆の動作をします。以下が追加された外部変数です。

// constant "off market"
#define OP_BALANCE 6
 
// trade day setting
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
 
// trading system reverse
extern bool ReversTradeSignal          = false;
 
//  trading frequency settings
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

私は利用可能なトレードシグナルなし、を示す定数 OP_BALANCE を追加しました。また、もうひとつ論理変数があります。TradeSignalEveryTick です。それが「真」であれば、ティックごとにトレードシグナルを受信する、となります。ごらんのように、トレーディングシステムのコード行はまだ一行も書いていませんが、いくらか時間が経過するとその目的をわすれがちな変数をすでにいくらか入れました。通知関数 StartMessage() も一緒に書いたのはそのためです。

//+------------------------------------------------------------------+
//| It shows a message about EA settings                             |
//+------------------------------------------------------------------+
void StartMessage()
   {
   int i=0;
   string currString="";
   string array[3];
//----
   array[0]=StringConcatenate("Expert Advisor ",WindowExpertName()," has the following settings:");
 
   if (TradeSignalEveryTick)
      array[1]="1) trade signals are considered at every tick; ";
   else
      array[1]=StringConcatenate("1)trade signals are considered at each bar with the period of ",
                        TradeSignalBarPeriod," minutes;");
 
   if (TradeDay==0)   // trade day is not specified
      array[2]="2)trading is allowed on any day of week; ";
   else
      {
      if (ReversDay) //  no trading allowed on the specified day
         array[2]=StringConcatenate("2)trading is allowed on all days but day number ",TradeDay);
      else           //  trading is allowed only on the specified day of week
         array[2]=StringConcatenate("2)trading is allowed only on day number ",TradeDay);
      }
   for ( i=0;i<3;i++) currString=StringConcatenate(currString,"\n",array[i]);
   Comment(currString);
 
   for (i=2;i>=0;i--) Print(array[i]);
   
//----
   return;
   }

この関数は EA 名およびその設定に関する最低限の情報を表示します。特定 EA を書くときは配列 'array[]' にべつの行を追加することが可能です。

これでトレードシグナルを計算する関数を作成する準備が整いました。以下の設定がこの関数に渡されます。

  • ティックごと、または指定のタイムフレームで新規バーが成立するとき、トレードシグナルを計算する。
  • トレードシグナルを逆にするかどうか
  • 曜日を考慮するかどうか
  • 取引日を取引しない日に変えるかどうか
//+------------------------------------------------------------------+
//| receive a trade signal                                           |
//+------------------------------------------------------------------+
// tradeDay - day of week, on which we trade; if it is equal to zero, then we trade all days
//
// useReversTradeDay - if it is equal to 'true', then trading days become non-trading days
//
// everyTick  - if it is equal to zero, the function will calculate a signal for every tick
//
// period - if everyTick==false, then it is calculated as soon as a new bar with this period appears
int getTradeSignal(int tradeDay,          // it is usually equal to 0
                   bool useReversTradeDay,// it is usually equal to 'false'
                   bool everyTick,        // signal is calculated at every tick
                    int period            // working period for indicators and signals
                   )
   {
   int signal=OP_BALANCE;
//----
   if (tradeDay!=0)  // we will consider days of week
      {
      // day, on which we don't trade
      if (useReversTradeDay && tradeDay==DayOfWeek()) return(signal);
      
      // we trade on all days, except the day equal to tradeDay 
      if (!useReversTradeDay && tradeDay!=DayOfWeek()) return(signal);
 
      }
 
   if (!everyTick) // if we don't take trade signals at every tick
      { // nor we have any new bar on the timeframe of 'period' minutes,
      if (!isNewBar(period)) return(signal); // then exit with an empty signal
      }
 
// Fill function yourFunction() with your code/algorithm
   signal=yourFunction(period);
 

   if (signal!=OP_BUY && signal!=OP_SELL) signal = OP_BALANCE;
   
//----
   return(signal);
   }

上記に見てのとおり、コード1行の周りに複数行のバインディングを書きました。

   signal=yourFunction();

独自の戦略を書く際には、以下の3つの値の内一つを返す自分独自の関数(yourFunction() の代わりに)を書きます。

  • OP_BUY -買い
  • OP_SELL -買い
  • OP_BALANCE -シグナルなし



「友達または敵」の特定はブロックします。

EA を書くときのもう一つのつまずきポイントは EA が処理をしようとしているオーダーのチケット検出です。問題は通常サイクル for() の方法で解決されます。そこではチケットが OrderSelect() 関数によって選択されます。ただし、たった一度のみならず「友好的」オーダーを検出する必要があることに留意する必要があります。ある場合には、ストップロスを変更する必要があります。また成行注文をクローズする必要がある場合もあります。第3番目のケースとして、未決注文をいくつか削除する必要もあります。

標準的な「友好的」オーダー処理の理由により、start() 関数の始めに毎回「友好的」オーダーの配列に必要なパラメータをすべて書き込むことにしました。そのため、グローバルレベルで配列を 2 つ取り入れます。

double Tickets[][9];// array to store information about "friendly" orders:
// Tickets[][0] - ticket number
// Tickets[][1] - order type
// Tickets[][2] - lots
// Tickets[][3] - open price
// Tickets[][4] - Stop Loss
// Tickets[][5] - TakeProfit
// Tickets[][6] - MagicNumber
// Tickets[][7] - expiration time
// Tickets[][8] - open time
// 
 
string CommentsTicket[1000][2];         //array to store symbols and comments on orders. 1000 lines would be enough
// CommentsTicket[][0] - symbol name
// CommentsTicket[][1] - comment on the order

これら配列に書き込む関数はひじょうにシンプルです。

//+------------------------------------------------------------------+
//| It prepares the array of "friendly" orders                       |
//+------------------------------------------------------------------+
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;   // filling counter
   
   // let's make the array size large not to allocate memory for it every time
   ArrayResize(arrayTickets,20);
//----
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      bool ourTicket=false;
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {// if the special function has not detected the order as "friendly" one,
            // make usual checks
 
            // check for Symbol
            if (OrderSymbol()!= Symbol()) continue;
         
            //  other checks...
            // ....
         
            // last check, that for MagicNumber
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
         
            }
         // we haven't been stopped anywhere, so this is a "friendly" order
         //  fill out the array
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
 
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         // let's increase the counter of filled "friendly" orders
         count++;
         }
      }
   
   // and now let's truncate the array sizes to the minimum essential ones 
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
 
//----
   return;
   }

例として、OrderMagicNumber() および OrderSymbol() によって一般関数で「友好的」オーダーを「敵対的」オーダーと区別することができる基準の最小記述を提供しました。サイクル内の全オーダーを検索する際、「敵」は飛ばし、「友達」のデータを配列に書き込みます。関数をより柔軟にするには、その関数内でもう一つユーザー定義関数を呼びます。そこではオーダーが「友達」か「敵」か見つける基準をもういくらか記述します。この場合、私はグローバル変数の確認を追加しました。ターミナルにわれわれの EA 名を持つグローバル変数があり、その値が特定オーダーのチケットと一致すれば、そのオーダーも「友達」とみなされます。たとえばもしポジションがマニュアルでオープンしていて、EA に処理してもらいたい場合に、これは役に立ちます。

//| It returns 'true', if a Global variable with this name is available|
//+--------------------------------------------------------------------+
bool isOurOrder(int ticket)
   {
   bool res=false;
   
   // we don't use global variables in test mode!
   if (IsTesting()) return(true);// immediately return the positive result
 
   int temp;
//----
   for (int i=0;i<5;i++)
      {
      if (GlobalVariableCheck(WindowExpertName()+"_ticket_"+i))
         {// there is such a global variable
            temp=GlobalVariableGet(WindowExpertName()+"_ticket_"+i);
            if (temp==ticket)
               { // found a GV with the value equal to 'ticket'
               res=true;  // so it is a "friendly" order
               break;
               }
         }
      }
//----
   return(res);
   }

準備の大部分はすでに終わりました。ここで、以下は start() 関数の冒頭部分です。

 
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always update the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain the arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Each bar of the preset period    (TradeSignalBarPeriod=...)
      OP_BUY     - to buy
      OP_SELL    - to sell
      OP_BALANCE  - no signal
*/
 

   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
/**
      2.
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/


既存オーダーのストップロス/テイクプロフィットレベル計算

これから次の段階に進みます。「友好的」オーダーの二次元配列を取得しました。それが Tickets[][9] です。ここでお互いの現行特性を知ったうえで SL と TP の新しいレベルを計算します。計算済みの新しい値はどこかに格納する必要があります。そこで、このためにもう一つグローバル配列を作成します。

double newSL_and_TP[][5];// array to store the new values of SL and TP
// newSL_and_TP[][0] - ticket number to be controlled
// newSL_and_TP[][1] - new values of SL
// newSL_and_TP[][2] - new values of TP
// newSL_and_TP[][3] - new Open price (for pending orders)
// newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

以下のパラメータについてのみ説明が必要かもしれません。

newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

このパラメータは別々の関数で成行注文と未決注文を変更するのに必要です。配列Ticketsp[][9] とシステムリバースの記号を取り、新しい配列newSL_and_TP[][5] に値を書き込む関数を作成します(第2ディメンションのスケーリングは括弧に入れられます)。この関数は以下のようなものです。

   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);

第1のパラメータは「真」(システムが反転)または「偽」の値を採ります。第2のパラメータは「友好的」オーダーの配列です(オーダーがなければサイズはゼロです)。第3のパラメータはこの関数によって書き込みがされる配列です。関数自体は以下に提供しています。

//+------------------------------------------------------------------+
//|  It creates an array with the new values of SL and TP            |
//+------------------------------------------------------------------+
void CalculateSL_and_TP(bool ReversTrade,       // system reverse 
                      double arrayTickets[][9], // array of "friendly" orders
                      double &amp; arraySL_TP[][5]  // new values of SL, TP and openPrice
                      )
   {
   // first of all, zeroize the obtained array !!
   ArrayResize(arraySL_TP,0);
   // if the order array is empty, then exit   
   int    i,size=ArrayRange(arrayTickets,0);
   if (size==0) return;
//----
   int    sizeSL_TP=0;
   double lots, openPrice, SL,TP;
   double newSL,newTP, newOpen;
   double oldOpenPrice, oldSL,oldTP;
 
   int type,ticket,oldType;
   for (i=0;i>size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      if (ReversTrade) //  reverse all levels considering the spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL)
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, obtain a new open price
      if (type<OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL<0 || newTP<0 || newOpen<0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL<0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP<0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen<0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type<OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }         
//----
   return;
   }

まず、SL および TP の新しい値を持つ配列にサイズゼロを設定します。そうすると「友好的」オーダーを持つ配列のサイズがわかるようになります。

   int   size=ArrayRange(arrayTickets,0);
   // if the order array is empty, then exit   
   if (size==0) return;

オーダーがない場合は、何もすることがなく、ゼロサイズの配列 arraySL_TP[][] によって表されるアウトプットを伴い出ます。ゼロサイズの配列は、その配列に何もしないことを指示するものです。それから、処理するために渡されるオーダーすべての値を閲覧するサイクルを整列します。

   for (i=0;i<size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      oldOpenPrice = openPrice;
      oldSL = SL;
      oldTP = TP;
         

      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, then receive a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

そこでは oldSL、oldTP、 oldOpenPrice を計算し(未決注文の開始レベルを変更することができるため)、SL、TP、OpenPrice の新しい値を計算するため関数にこれら値をパラメータとして渡します。

      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
     
      // if it is a pending order, obtain a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);

あとは、 SL、TP、OpenPriceレベルが一つでもゼロ以外であれば(すなわちこのオーダーは変更の必要あり)、その新しい値の配列サイズを1ずつ増やすだけです。

      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket   
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

ここからトレーディングシステムを反転する機能について考察する必要があります。反転とはどういうことでしょうか?それは、買いが売りに変換される、またその逆です。始値はスプレッド値によって変化します。というのもわれわれは「アスク」価格で買い、「ビッド」価格で売るからです。また、SL と TP のレベルはもまたスプレッドによって変わります。すべてに配慮する方法でそれをコード化します。

      if (ReversTrade) //  reverse all levels considering spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL)
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }

関数 CalculateSL_and_TP() に加えられた変更のコード全体は提供しません。この関数はひじょうに大きいので。それについては添付ファイル Template_EA.mqt で確認できます。これでストップロス、テイクプロフィット、OpenPrice に対する新しい値を計算するタスクはなんとか行うことができたと言えます。ここからこの計算ブロックから呼ばれる関数を作成する必要があります。これらは、特定の EA が作成されるときに書き込みがされる空の関数で、'form functions'と名付けます。



Form Functions

以下がその関数です。

//+------------------------------------------------------------------+
//|  It calculates the level of Stop Loss                            |
//+------------------------------------------------------------------+
double getNewSL(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Stop Loss
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the level of  Take Profit                         |
//+------------------------------------------------------------------+
double getNewTP(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Take Profit
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the new open price for a pending order            |
//+------------------------------------------------------------------+
double getNewOpenPricePending(int    type,      // type of the order, for which we calculate it
                              double lots,      // volume, it can be useful
                              double openPrice, // open price
                              double stopLoss,  // current level of Stop Loss
                              double takeProfit // current level of Take Profit
                              )
   {
   double res=-1;
//----
//  here is the code of calculations for open price 
 
//----
   return(res);   
   }

これら3つの関数はお互いひじょうに似通っています。それぞれ同じインプットセットを取得し、変数 resdouble タイプの値を返します。この変数は負の値ですぐに初期化され、自分の計算用コードを挿入しないと、返されるのはこの負の値です。これが意味するところは、計算されたレベルがないことです。というのも、価格は常に正の値であるからです。

また、getTradeSignal() 関数からのトレードシグナルを計算するブロックから呼ばれる yourFunction() に対する形式を書くこともできます。

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
//  (there must be a code producing trade signals considering timeframe workPeriod here)
//----
   return (res);   
   }

この関数はworkPeriod -カスタムインディケータまたは標準インディケータを呼ぶときに使用する期間を取得します。返り値 res は OP_BALANCE の値で初期化されます。この変数値を変える独自のコードを入れなければ、関数が返すのはこの値です。これはトレードシグナルなしとして受信されます。

このテンプレートにはこれ以上カスタム関数作成はありません。技術については用法を突き詰めたので、コードの適切な位置に必要な形式をすべて入れることができます。



新規オーダーに対するパラメータ計算

ここで、新規の成行注文や未決注文を出すのに必要な SL、TP、ボリュームその他の値を取得する必要があります。このためには、名前からそれとわかる変数を作成します。

   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL, pendingTP, pendingComment);

これら変数は2つの関数でその値を取得します。関数の1つは成行注文の値を、もう1つは未決注文の値を計算します。これが分けられていることで、コードを簡単に書くことができ、あとで変更することができます。まず、関数 CalculateNewMarketValues() について考察します。

//+------------------------------------------------------------------+
//|  It calculates the data for opening a new order                  |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots,
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 

//----
   //  insert your own code to calculate all parameters
//----
   return;
   }

この関数は現行のトレードシグナル、trSignal を受信し、それを基に全戻りパラメータを計算します。

ここ、またどこでもパラメータはすべて安全な値で初期化する必要があることにご注意ください。

独自コードを入れなければ、変数はオーダー開始の意志なしを意味する値 -1 (マイナス1)を返します。ここでトレード処理の定数は負の値ではないことを思い出します。未決注文の開始パラメータ計算用関数はほとんど同じです。

//+------------------------------------------------------------------+
//|  It calculates the data for placing a pending order              |
//+------------------------------------------------------------------+
void CalculateNewPendingValues(int    trSignal,
                               int    & pendingType,
                               double & pendingOpenPrice,
                               double & pendingLots,
                               double & pendingSL,
                               double & pendingTP,
                               string & pendingComment)
   {
   // if there is no trade signal, exit
   if (trSignal==OP_BALANCE) return;
 
   pendingType      = -1;
   pendingOpenPrice = 0;
   pendingLots      = 0;
   pendingSL        = 0;
   pendingTP        = 0;
   pendingComment   = 0;
//----
   //insert your own code to calculate all parameters
//----
   return;
   }

唯一の違いは、それがもう一つのパラメータ-未決注文の開始価格、を計算することです。



注文の変更

EA の動作における次の処理は「友好的」オーダーの変更です。これには別個の関数が2つ使用されます。

   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);

それらはひじょうに似ており、それぞれインプットとして以下のパラメータを取ります。

  • ReversTradeSignal -トレードシステムが反転するサイン
  • ModifyMarketOrderEveryTick または ModifyPendingEveryTick -ティックごとのオーダー変更サイン
  • ModifyMarketBarPeriod または ModifyPendingBarPeriod -分単位でのタイムフレーム。ティックごとに価格レベルを変更する必要がなければ、ここで変更が行われます。
  • 変更されるオーダーのチケットとSL、TP(未決注文の場合は OpenPrice も)の新しい値を持つ配列 newSL_and_TP[][5]

1番目の関数-ModifyMarkets() を考察します。

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyMarkets(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }
 
//----
   return;
   }

最初の確認はすでに標準的なものです。それは変更についての配列 newSL_and_TP[][] のゼロサイズチェックです。2番目の確認は、ティックごとの変更の必要性の第1のチェックです。それを行う必要がなければ(ModifyEveryTick=偽)、次は、 ModifyBarPeriod 分のタイムフレームにおける新規バーの出現チェックです。チェックを通過しなければ、出て何も行いません。

   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }

正常に事前チェックに通過すれば、オーダー変更が開始できます。と同時に、システム動作の2とおりの方法を忘れてはいけません。それは、方向と反転です。

   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a direct system
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a reversed system
         }
      }

両者における唯一の違いは、newSL_and_TP[i][1] および newSL_and_TP[i][2](ストップロスとテイクプロフィット) の値は関数 OrderSend() 内で変化することです。

未決注文の変更に用いられる関数はほとんど同じですが、それには未決注文開始価格の受信を追加しました。

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyPendings(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }
 
//----
   return;
   }

また、みなさんの注意を促したいのが、両関数で注文タイプが異なることです。

         type=newSL_and_TP[i][4];

そして、変数 'type'(0 または 1)の値によって、プログラムはこのチケットを処理するかスキップするかの判断をします。これがオーダー変更に使用される関数についてのすべてです。



成行注文のクローズ

ここで成行注文のクローズを行うコードを書く必要があります。このためには、オーダーをクローズする情報を持つ配列を2つと、これら配列を処理する関数を2つ使用します。

/**
      4.
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[];
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) P
        repareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);

配列 ToClose[][2] はチケットの値とクローズされる注文タイプを格納し、配列lotsToClose[] はクローズされる各ポジションに対してクローズされるロット量に関する情報を持ちます。関数 PrepareTicketsToClose() はインプットとしてオーダーの配列- Tickets[][]、と現行トレードシグナルの値を受け取ります。買いのトレードシグナルも売りオーダーをクローズする指示となります。関数 PrepareTicketsToClose() 自体はもっと小さいボリュームで書かれました。

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to close orders                     |
//+------------------------------------------------------------------+
void PrepareTicketsToClose(int signal, bool Revers, int & ticketsClose[][2], 
                                   double & lots[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
 
   int i,type,ticket,closeSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a market order, then skip it
      if (type>OP_SELL) continue;
 
      if (Revers) // reverse the order type
         {
         if (type==OP_BUY) type=OP_SELL; else type=OP_BUY;
         }
      
      // here we will decide the fate of each open order
      //  whether we retain it in the market or add to the array of orders to be closed
      if (type==OP_BUY)
         {
         //  
         // code that allows to retain Buy
         
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELL)
         {
         //  
         // code that allows to retain Sell
         
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      closeSize=ArrayRange(ticketsClose,0);
      ArrayResize(ticketsClose,closeSize+1);
      ArrayResize(lots,closeSize+1);
      ticketsClose[closeSize][0] = arrayTickets[i][0]; // ticket #
      ticketsClose[closeSize][1] = arrayTickets[i][1]; // order type
      Print("arrayTickets[i][0]=",arrayTickets[i][0],"   ticketsClose[closeSize][0]=",ticketsClose[closeSize][0]);
      
      // here we will specify the amount of lots to be closed
      lots[closeSize] = arrayTickets[i][2]; // volume to be closed
      // they can be closed partially, then you should rewrite the code line above
      }
//----
   return;
   }

クローズするオーダーリストに1オーダーまた別のオーダーを入れることについては独自の条件を追加します。インプット配列のサイズがゼロの場合(すなわち処理対象オーダーがない)、通常どおり関数を早く出ます。

関数 CloseMarketOrders() はなにもむつかしいことは表しません。

//+------------------------------------------------------------------+
//|  It closes orders with the given tickets                         |
//+------------------------------------------------------------------+
void CloseMarketOrders(int ticketsArray[][2], double lotsArray[])
   {  
//----
   int i,size=ArrayRange(ticketsArray,0);
   if (size==0) return;
   
   int ticket,type;
   double lots;
   bool res;
   
   int total=OrdersTotal();
   Print("",size," orders should be closed, orders opened:",total);
   
   for (i=0;i<size;i++)
      {
      ticket = ticketsArray[i][0];
      type   = ticketsArray[i][1];
      lots   = lotsArray[i];
      Print("Close order #",ticket," type=",type," ",lots," lots" );
      Print(" ");
      RefreshRates(); // just in case, update the data of the market environment
 
      // buy closing block
      if (type==OP_BUY)
         {
         res = OrderClose(ticket,lots,Bid,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Buy order #",ticket,"!Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      //  sell closing block
      if (type==OP_SELL)
         {
         res = OrderClose(ticket,lots,Ask,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Sell order #",ticket,"!Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      } 
//----
   return;
   }

サイクルでは、クローズするための配列が巡回し、マーケット環境が関数 RefreshRates() で更新され、プログラムは注文タイプに対応する価格でオーダーをクローズしようとします。クローズの際のエラーは最小限まで分析され、状況を処理するにはご自身のアルゴリズムを追加する必要があります。




未決注文の削除

未決注文の削除処理は成行注文のクローズとほぼ同じです。それにはボリューム配列がないだけです。というのも、削除する未決注文のチケットを知る必要があるだけだからです。

/**
      5.
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) 
        PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);

それに応じて、この関数ブロックの構文はひじょうに類似しています。よって、ここではこれいじょうの説明はしません。

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to delete pending orders            |
//+------------------------------------------------------------------+
void PrepareTicketsToDelete(int signal, bool Revers, int & ticketsDelete[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
   ArrayResize(ticketsDelete,0);
 
   int i,type,ticket,deleteSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a pending order, then skip
      if (type<=OP_SELL) continue;
      
      if (Revers) // reverse the order type
         {
         switch (type)
            {
            case OP_BUYLIMIT : type = OP_SELLSTOP; break;
            case OP_SELLLIMIT: type = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : type = OP_SELLLIMIT; break;
            case OP_SELLSTOP : type = OP_BUYLIMIT ; break;
            }
         }
 
      // here we will decide the fate of each pending order
      //  whether we retain it or add to the array of orders to be deleted
      //  here we will give an example of a buying signal retaining 
      // pending orders OP_BUYLIMIT and OP_BUYSTOP
      // the same for selling signals
      if (type==OP_BUYLIMIT || OP_BUYSTOP)
         {
         //  
         // code that allows to retain Buy
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELLLIMIT || OP_SELLSTOP)
         {
         //  
         // code that allows to retain Sell
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      deleteSize=ArraySize(ticketsDelete);
      ArrayResize(ticketsDelete,deleteSize+1);
      ticketsDelete[deleteSize] = arrayTickets[i][0];
      }
//----
   return;
   }


成行注文によるポジションオープン、未決注文発注

これで残る処理は最後の2つです。すでにトレードシグナルは受信し、オーダーリストは準備でき、必要なオーダーすべてを変更、クローズ、削除しました。ここで、それが合理的であるなら、成行注文でポジションをオープンするか、未決注文を出します。

/**
      6.
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE)
           OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);

   if (trSignal!=OP_BALANCE) 
           SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, pendingTP,pendingComment);

第1の関数 OpenMarketOrder() はシステム反転のサインを含み必要なインプットをすべて取得します。


///+------------------------------------------------------------------+
//|  It opens a position by market                                    |
//+-------------------------------------------------------------------+
void OpenMarketOrder(bool   reversTrade,// sign of system reverse
                     int    Type,       // order type - OP_BUY or OP_SELL
                     double lots,       // volume of the position to be opened
                     double SL,         // level of StopLoss
                     double TP,         // level of TakeProfit
                     string comment)    // comment on the order
   {
 
   //Print("Open order Type=",Type,"  lots=",lots,"  SL=",SL,"TP=",TP,
        " comment=",comment,"  ExpertMagicNumber=",ExpertMagicNumber);
   int openType;
   
   if (reversTrade)                       // reverse the signals
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
      if (Type==OP_BUY)
         {
         openType = OP_SELL; // Buy will become Sell
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELL)
         {
         openType = OP_BUY;  // Sell will become Buy
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP-Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp-Point*spread,digits);
            else TP=0;
         }
      }
   else
      {
      openType=Type;
      }   
//----
 
   if (lots==0) return;
   
   RefreshRates();
   double price;
   color Color;
   if (openType==OP_BUY)
      {
      price = Ask;
      Color = Blue;
      }
   if (openType==OP_SELL)
      {
      price=Bid;
      Color = Red;
      }
   bool ticket = OrderSend(Symbol(),openType,lots,price,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color);
   if (ticket>0)
      {
      Print("Failed to open a position by market");
      Print("Type=",openType,"  lots=",lots,"  SL=",SL,"TP=",TP," comment=",comment,
        "  ExpertMagicNumber=",ExpertMagicNumber);
      //  further processing of the situation, code independently
      }
//----
   return;
   }

反転処理以外、この関数にむつかしいことはなにもありません。システム反転については、SL と TP を信頼し、スプレッド値に等しいシフトを追加します。ここで重要なのは、ストップロスまたはテイクプロフィットの値はゼロ以外であることを忘れないことです。

関数 SendPendingOrder() はそれよりほんの少し複雑です。なぜかというと、未決注文には、買いに2タイプ、売りに2タイプあることを考慮する必要があるからです。それ以外は OpenMarketOrder() 同様です。

//+------------------------------------------------------------------+
//|  It places a pending order                                       |
//+------------------------------------------------------------------+
void SendPendingOrder(bool   reversTrade,// sign of system reverse
                      int    Type,
                      double OpenPrice,
                      double Lots,
                      double SL,
                      double TP,
                      string comment)
   {
   //Print("SendPendingOrder()  Type=",Type);
   
   if (Type==-1) return; 
//----
 
   int openType;
   
   if (reversTrade)    // reverse order type and levels
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
 
      if (Type==OP_BUYLIMIT || Type==OP_BUYSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice - spread*Point,digits);
         temp=SL;
         if (TP!=0)  SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELLLIMIT || Type==OP_SELLSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice + spread*Point,digits);
         temp=SL;
         if (TP!=0) SL = NormalizeDouble(TP - Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp - Point*spread,digits);
            else TP=0;
         }
 
      switch (Type)
         {
         case OP_BUYLIMIT:  openType = OP_SELLSTOP ; break;
         case OP_SELLLIMIT: openType = OP_BUYSTOP  ; break;
         case OP_BUYSTOP:   openType = OP_SELLLIMIT; break;
         case OP_SELLSTOP:  openType = OP_BUYLIMIT ; break;
         default: Print("Invalid order type Type=",Type," in function SendPendingOrder()!!!");
         
         }
      }
   else openType = Type;
      
   color Color;
   if (openType==OP_SELLLIMIT || openType==OP_SELLSTOP)  Color = Red;
   if (openType==OP_BUYLIMIT  || openType==OP_BUYSTOP)   Color = Blue;
 
   bool res = OrderSend(Symbol(),openType,Lots,OpenPrice,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color);
   if (!res)
      {
      Print("Failed placing pending order");
      //  further processing of the situation, code independently
      }
 
//----
   return;
   }

どちらの関数でも、トレードリクエストエラーは最小で、これにはご自身のコード追加が可能です。



関数 start() の最終バージョン

ひとたび必要な関数がすべて作成されると、もう一度テキストテンプレートから本格的コードに渡された関数 start() に目を向けます。

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always zeroize the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Every bar in the preset period   (TradeSignalBarPeriod=...)
      OP_BUY     - to buy
      OP_SELL    - to sell
      OP_BALANCE  - no signal
*/
 

   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
                       
/*
   if (trSignal==OP_BUY) Print("Buy signal");
   if (trSignal==OP_SELL) Print("Sell signal");
   if (trSignal!=OP_SELL && trSignal!=OP_BUY) Print("The current signal is equal to ",trSignal);
*/
 
/**
      2.
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
 

   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL,
        &nbsp;pendingTP, pendingComment);
 
/**
      3.
        a) Modification of each open order for every tick (SL and TP)
               (ModifyMarketOrderEveryTick = true)
               
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
               (ModifyPendingEveryTick = true)
        
        c) Modification of each open order on each new bar in the preset period (SL and TP)
               (ModifyMarketBarPeriod = ...)
        
        d) Modification of each pending order on each new bar in the preset period (OpenPrice, SL and TP)
               (ModifyPendingBarPeriod = ...)
        
*/
 
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);
 
/**
      4.
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[];
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);
 
/**
      5.
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);
 
/**
      6.
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE)
      OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   
   if (trSignal!=OP_BALANCE)
      SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL,
            pendingTP,pendingComment);
//----
   return(0);
  }
//+------------------------------------------------------------------+

EA の処理ロジックはすべて明らかで、実装詳細は対応する関数に隠されています。プログラムのデバッグ時、エラーをどこで検索するかわかります。また、Expert Advisor の全体的ロジックは明確に分かれています。「友好的」オーダー検索のアルゴリズムを変更したい場合、どの関数でコードを変更するか解っています。ポジションのクローズやオープンに対しどこに条件を挿入するか、必要があればどの関数にトレーリングストップを取り入れるかわかっています。やり残している唯一の事柄は、準備のできたテンプレートを自分で使用することです。



用例

取得したテンプレート―Template_EA.mqt、は本稿に添付があります。それはフォルダ C:\Program Files\MetaTrader 4\experts\templates\ にExpert.mqt として保存できます。この場合、新規に EA を作成する際、このファイルは常にそのテンプレートとして使用され、上記関数はすべて自動的に挿入されます。もう一つ別の方法があります。名前を変えずに同じフォルダに保存するのです。そうすると、新規に Expert Advisor を作成するとき、マニュアルでこのファイルをテンプレートに指定することができるようになります。


ここでは、われわれのテンプレート、Template_EA を選択し、「次へ」を押します。以下のシンプルな戦略を書くとしましょう。

  • ストキャスティックシグナルラインが売られ過ぎの領域からはみ出し、 DownLevel の設定レベルを下から上へ突き抜ける場合(デフォルトでは10)、買いシグナルが形成される。
  • ストキャスティックシグナルラインが買われ過ぎの領域からはみ出し、 UpLevelの設定レベルを上から下へ突き抜ける場合(デフォルトでは10)、売りシグナルが形成される。
  • 保護的ストップロスは始値(変更可能性あり)から100ポイントの距離に設定される。
  • テイクプロフィットは始値(変更可能性あり)から100ポイントの距離に設定される。
  • ストキャスティックのパラメータは EA の外部パラメータによって設定され、変更も可能である。

われわれの新しい EA を Osc_test と名付け、必要な外部パラメータを取り入れます。


「完了」を押し、Expert Advisor ウィザードが上記のテンプレートを基に作成されたEA にそれらパラメータを入れたことを確認します。

これで、EA 作成時必要なパラメータはすべて追加されました。

//+------------------------------------------------------------------+
//|                                                     Osc_test.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
//---- input parameters
extern int       Kperiod=18;//5;
extern int       Dperiod=14;//3;
extern int       slowPeriod=10;//3;
extern int       UpLevel=90;
extern int       DownLevel=10;
extern int       StopLoss=100;
extern int       TakeProfit=100;
extern double    Lots=0.1;
 
// constant "off market"
#define OP_BALANCE 6

ここで、yourFunction() に売買シグナルを作成するコードを挿入します。

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
 
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
 
//----
   return (res);
   }

これはわずか4行です。あとは成行でポジションをオープンするためのデータ準備を行う関数 CalculateNewMarketValue() を詳細に書き込むだけです。

//+------------------------------------------------------------------+
//|  It calculates the data to open a new order                      |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots,
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 

//----
   //  insert your own code to calculate all parameters
 
   if (trSignal==OP_BUY  && StopLoss!=0) marketSL = Bid - StopLoss*Point;
   if (trSignal==OP_SELL && StopLoss!=0) marketSL = Ask + StopLoss*Point;
 
   if (trSignal==OP_BUY  && TakeProfit!=0) marketTP = Bid + TakeProfit*Point;
   if (trSignal==OP_SELL && TakeProfit!=0) marketTP = Ask - TakeProfit*Point;
 
   marketLots = Lots;
 
   if (trSignal==OP_BUY) marketType = OP_BUY;
   if (trSignal==OP_SELL) marketType = OP_SELL;
 
   marketcomment="test";
//----
   return;
   }

ごらんのように、これも 5 行しか取りません。よって、われわれのシンプルな戦略を記述するのに必要なコードは最大限書きました。これはこの方法の第1のメリットです。

テンプレートの作成には典型的な EAを実現する詳細をすべて推敲する必要があります。ただしこれは新規戦略を作成するとき将来に返ってきます。



その他のメリット

まだ終わりではありません。取得した EA を任意のシンボルとタイムフレームで検証を始めます。たとえばデフォルト設定で EURUSD H1 とします。


この場合、利益が出るかどうかは問題ではありません。テストレポートを見てみます。


トレード総額は430 で、そのうち 241 は売りで 189 は買いです。それではシステムを反転します。売りトレードで買いを始め、買いトレードのところで売りを始めます。このためには、パラメータReversTradeSignal に値「真」を設定します。これはシステム反転のサインです。


それ以外のパラメータは変更せず、検証を開始します。以下が取得したテストレポートです。


実際、ここで買いトレードが 241 と、売りトレードが 189 あります。売りの金額と買いトレードは変化しています。収益性あるトレードの比率も反転しています。反転 EA がどのように動作するかを確認するために EA を書き直す必要はないのです。

ただ、これがすべてというわけでもありません。われわれには TradeDay というパラメータがありました。覚えていますか?デフォルトでは、それはゼロですが、EA に金曜日だけ取引してほしければどうでしょうか?その値を 5 に設定します。


それ以外のパラメータには手を触れず検証を開始します。結果を確認します。EA が反転システムで金曜日だけ取引を行ったらどうなるか表示しているテスターレポートを確認することができます。


始めの 430 からトレードが 81 件だけ残っているのが解ります。それは、それ以外の取引は別の曜日に行われることを意味しています。この場合、結果が利益を出すものであるかということにはこだわりません。金曜日以外のすべての曜日で取引を行うことにした場合、EA は履歴上でどのように取引をするのか知りたいと仮定します。これには、パラメータ ReversDay があります。それを「真」に切り替えるだけです。


検証を開始し、レポートを見ます。


取引は 430 件あります。金曜日の取引件数(81)を引くと 349 を得ます。数字をすべて足します。430-81=349 となります。これで、正確にトレード日を反転させました。しかも EA をプログラムしなおす必要はありませんでした。



おわりに

私自身、本稿に2つデメリットがあるのがわかっています。一方で、簡潔過ぎて関数の詳細説明がまったくありません。もう一方で、最初の読み物としては長すぎます。それでも、MQL4 EA を作成するこの方法を支持する意見を持つ人が最終的に現れることを願うものです。この方法はチーム作業に最適であります。継続的に1つのトレーディングアルゴリズムを改善するよりは、ブレーンストーミングで必要なテンプレートを作成する方が合理的であろうということです。

数多くのシンボルを同時にトレードするための特殊なテンプレートを作成し、トレード関数が返すエラーに対して特別なエラー処理コードを追加することも可能です。また、情報関数、ログ関数、検証結果やリアルタイムトレードの特別レポート作成用関数を追加することもできます。トレードセッションを選択した上で、曜日のみならず時間でトレード制限をいくつか追加することもできます。再定義対象関数のみ見るために、個別の *.mqh ファイルに全サービス(インターフェース)関数を集積することだって可能です。このアプローチにはほんとうに多くの可能性があります。

主なメリットは、一度作成されると、テンプレートは継続して使用可能であることです。同時に、新規に作成する EA で起こるエラーの発生確率も削減されるのです。

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

添付されたファイル |
Osc_test.mq4 (36.96 KB)
Template_EA.mqt (35.82 KB)
文字列:ASCII シンボルのテーブルとその使用 文字列:ASCII シンボルのテーブルとその使用
本稿では、ASCII シンボルのテーブルとその利用方法を分析します。また、その動作原理が ASCII テーブルの特殊性に基づく新しい関数をいくつか取り上げ、それらをインクルードする新しいライブラリを作成します。それらは別のプログラム言語ではきわめて一般的ですが、埋め込み関数には含まれていないものです。そのほかに、文字列を処理する基本を詳しく考察します。よって、みなさんはこの便利なデータタイプについて、確かに何か新しいことを学ぶこととなるのです。
金融時系列の予測 金融時系列の予測
金融時系列の予測はあらゆる投資活動に必要とされる要素です。将来利益を得るために今資金を投入する、という投資そのもののコンセプトは、将来予測のコンセプトに基づいています。そのため、金融時系列の予測は、組織化された為替やその他有価証券の取引システムといった投資業界全体に根差すものです。
効率的な Expert Advisor 操作のための仲介会社の自動選択 効率的な Expert Advisor 操作のための仲介会社の自動選択
効率的な Expert Advisor 操作のために適切な仲介会社を見つける必要がある、ということは秘密ではありません。本稿ではこの検索のシステム的アプローチを説明します。みなさんは異なるターミナルで作業しながら dll によってプログラムを作成する手順に詳しくなることでしょう。
初心者向け MQL4 言語カスタムインディケータ(パート 2) 初心者向け MQL4 言語カスタムインディケータ(パート 2)
本稿は『初心者向け MQL4 言語』シリーズの第5弾です。今日はグラフィカルオブジェクトの使用について学習します。それはインディケータを使用する機能を実質的に広げる強力な作成ツールです。また、スクリプトや Expert Advisor でも利用可能です。それからオブジェクトの作成、そのパラメータ変更、エラーチェックについても学習します。もちろん、すべてのオブジェクトの詳細を説明することはできません。ひじょうに数が多いのです。ですが、ご自身で学習できるように必要な知識は得られることでしょう。本稿には複雑なシグナルインディケータの作成の段階的なガイド例を盛り込んでいます。そこでは多くのパラメータは調整可能で、それによりインディケータの表示変更が簡単になります。