モスクワ証券取引所で取引をする際の、EAの安全性について

9 3月 2016, 08:33
Vasiliy Sokolov
0
669

コンテンツ一覧


イントロダクション

相場でトレードする人は誰でも、様々な損失のリスクに直面しています。このリスクの性質は異なりますが、結果は同じです。資金を失い、時間を浪費し、不満が続きます。このような芳しくないことを避けるには、単純なルールに従わなければなりません。: リスク管理 (資金管理)及び、信頼できるトレードアルゴリズムの開発、そして、利益の出るトレードシステムの利用このルールは異なる取引領域に関係し、信用できるトレード結果のために、それらを統合する必要があります。

現在では、日々の取引でのトレードシステム同様に、この資金管理の問題に対する書籍や記事が山ほどあります。不幸にも、資金管理の原則はトレードの原則と一致しません。

この記事の目的は、トレード時に守るべき上記の安全原則を使って、問題を解決することです。このトレード原則に基づけば、価格の急変化、流動性の欠如、その他の不可抗力に伴う大きな損失を避けることができます。この記事では、トレードロジックの開発とそのリスク管理は脇に置いて、技術的なリスクに焦点を当てています。

この方法は、"モスクワ証券取引所の例によるトレードの原則"による、実践的なアプローチとなります。この記事は取引価格の理論について扱っていますが、現在の記事ではEAを突発的なアクシデントから守るメカニズムについて記述しています。


チャプター1. プライスフローの不連続性とその対処


1.1. プライスフローの不連続性プライスギャップ

流動性は株式市場のメインコンセプトの一つです。終値で商品をあなたから買ったり売ったりするのが相場の機能です。流動性が高ければ高いほど、相場価格が近くでそれぞれのオーダーが通ります。価格は非連続なプロセスです。それはつまり、我々が使う価格というのは高速で行われる取引の総体から成るということです。取引の流れはクオート、ティックチャートによって構成され、それらは足やバーチャートに変換されます。トレーダー側から見ると、チャートは連続しているように見えます。どの時間においても、足やバーは特定の値を持っています。これは下記のようになります。


図1. 価格バーと連続価格

価格のどのポイントを取ろうとも、赤線で表示されている価格を持っています。これは厳密には、メタトレーダーのストラテジーテスターの"全ティック"モードで、どのように足が表示されているかです。このモードでは、価格は連続するように生成されます。例えば、ステップが1で価格が10から15に動いたとき、価格11,12,13,14は価格が動いている間でも利用可能です。現実には、価格は非連続で、小さなジャンプをしながら変化します。しかも、これらの価格は必ずしも存在するとは限りません。ときとして、価格は一瞬で飛ぶことがあります。より現実的な価格推移を使って検証してみましょう。:

図2. 価格バーと非連続価格

見てもわかるように、実際には連続価格のようなものはありません。(赤線で表示)これはつまり、成行注文(及びストップ)は大きく滑った場所で約定するかもしれないということです。これは成行注文のとても危険な特徴です。買いストップ予約注文がどのように約定するか検証してみましょう。価格が64203に達したときリクエストが送信されると仮定します。 (価格バーにクロスした青いライン). しかし、この価格はバーの中に単純には存在しません。この場合、我々のオーダーは64203よりも高い、次の価格で有効になります。:

図3. 非連続な価格での予約注文の有効化

この例では、実際のオーダーの実行は64220で起こります。このポイントは、リクエストよりも13ポイント悪い場所です。この差がスリッページです。相場に十分に流動性がある場合、非連続の価格はスムーズに推移します。しかし、価格が急激に変化する場合、高い流動性がある相場でも価格ジャンプがあります。チャートからギャップを観察することは不可能ですが、その存在を意識することは可能です。


1.2. 価格の急上昇

流動性の欠如によって、価格ジャンプは高い水準に達し、プライススパイク(取引値からかけ離れた約定)となります。 これはマニュアルトレーダーにとっても自動売買にとってもとても危険なものです。そのようなスパイクは予約注文に意図しない価格で約定させます。

単純な例を考えてみましょう。: RUB/USDをBuy Stopで64200で取引するとします。このストップロスは64100になります。価格が上がると予想しますが、もしそうならなかった場合、64100のストップロスが100ポイントの損失になります。リスクは制限されているように見えますが、実際にはそうではありません。ストップロス付近で価格の暴騰暴落が起こった場合を考えてみましょう。:


図4. スパイクのティックと買いのストップオーダー

ティックチャート上では、ティックの一部がスパイクを形成しながら遠くに配置されているのがわかります。このティックが買いのストップ64440で起こります。次のティックでは、価格はストップロス64100の現在の範囲に戻ります。1秒以内の間に、予約注文が実行されストップロスで決済し、巨大な損失を残してしまいます。計算上の損失は100ポイントですが、実際のロスは340ポイントです。

実際には、スパイクがこれよりも大きなこともあります。よって、たとえどんな大きな資産があろうとも、単一の大きなスパイクで我々の口座の残高をなくす可能性は十分あると言えます。このような災難を回避するためには、次のようなルールに則らなければなりません。

ストラテジーテスターの"全ティックモード"では、実際の相場よりもスパイクは生易しいものです。図にあるような価格ジャンプをテストしたら、予約注文は(あるとしても)最小のスリッページになります。ご存じのように、ストラテジーテスター内の価格推移は均等に平均化されているので、スリッページなしで我々のオーダーが通るように実行されます。実際、そのようなケースはストラテジーテスターでは適切に考える必要があります。そのためには、特別なテストモードを選択します。これはチャプター3で扱います。 


1.3. リミットオーダーを使った最大スリッページの管理

相場ではストップオーダーがスリッページに対して何の抵抗力もないことが分かりました。リクエストを実行するほどの十分な流動性がないか、短時間でのスパイクでは流動性を失うことが原因です。さらに言えば、そのようなスパイクはFORTSデリバティブ市場などの流動性の低い相場によく見られます。しかし、ストップオーダーの代わりにリミットオーダーを利用することによりそのような事態をさけることができます。

リミットオーダーは特定の値から悪い位置で約定することはありません。リミットオーダーの面白い特徴として、指定された注文価格を上回っている場合でも下回っている場合でも、現在の価格で実行される特徴があります。

例えば、現在のRUB/USDの値が64 200だった場合、買いのリミットオーダーを64 220でセットすることができます。これはつまり、64220以上の場合は買わないことになります。現在の価格64 200 が、オーダーの値よりも大きいので、オーダーは即座に実行されます。よって、最大スリッページを制御することができます。もし何らかの理由で 64 220付近に十分な流動性がない場合、オーダーは実行されません。

つまり、リミットオーダーを使うとスリッページを管理することができます。一般的なオーダーでは最大スリッページをセットすることができません。そのため、リミットオーダーは低流動性相場で安全性を確保する唯一の手段となります。

エントリーと決済でリミットオーダーを使うことが賢明です。現在の相場価格でエントリー・決済するロジックだとしてもリミットオーダーに書き換えた方が良いでしょう。買い・売りの成行注文を指値注文に変更しましょう。例えば、現在値でロングする場合、現在の価格よりもほんの少し高い価格でリミットオーダーを出します。売りの場合も同様です。この場合、現在値よりもわずかに下にリミットオーダーを出します。現在値でのオーダーとリミットオーダーの違いは、最大許容スリッページです。

次の例を考えましょう。ED-3.15 EUR/USDを 1.1356で買いエントリーするとします。現在の流動性は低い状態です。このようなときに、リミットオーダーを使って相場にエントリーするメリットを示します。エントリーした瞬間にM1チャートでスパイクが起こります。:

図5. スパイクの瞬間にエントリー ED-3.15

エントリーポイントがずれることは明白です。この瞬間のティックチャートを分析してみましょう。:


図6. ティックチャートとリミットオーダーの実行

リミットオーダーの実行は白い大きな円です。(ティック): . ティックは青いドットです。1.1356で買った場合、1.1356から1.1398のトランザクション中に我々のリクエストは実行されます。これでは大きなスリップが発生し、平均的に1.1356よりもはるかに悪い場所でエントリーすることになります。リクエストを実行するのに必要なトランザクションが多ければ多いほど、エントリーポイントの位置は悪くなります。

この場合、巨大なギャップは低い流動性の状態で引き起こされますが、リミットオーダーなら種々の理由で消滅します。*しかし、リミットオーダーには保護する機能が内包されています。それは単純に価格が1.1356を超えた場合、オーダーが実行されないためです。例えば、リミットオーダーがチャート上の白い円の中で実行されたとします。他の価格帯もありますが、それらはすべて 1.1356よりも悪い値です。よって、その価格帯は無視されます。しばらくした後、価格が落ち着き、オーダーが実行されます。


1.4. 最大スリッページを管理するリミットオーダーのマニュアル設定

リミットオーダーの実際を扱ってきましたが、今度は実際の相場に対して適応させてみましょう。アカウントがモスクワ取引所に接続しているとします。リミットオーダーを現在値よりも少しだけ悪いところに配置しましょう。また、EUR/USD (ED-6.15)に最も近いものを選択しましょう。買いリミットオーダーをAskよりもほんの少し高い位置にセットします。

図7. 実行モードで、手動によるリミットオーダーのセット

図7. 実行モードで、手動によるリミットオーダーのセット

スクリーンショットでもわかるように、現在のAskは1.1242、予約注文1.1245です。我々のオーダーと最善の値の差は 0.0003 ポイントです。 (1.1245 - 1.1242 = 0.0003). これが我々が想定している最大スリッページです。実行モードでは、このようなリミットオーダーは最大スリッページを設定した成行注文と同等です。:

図8. スリッページ付の成行注文の実行

図8. スリッページ付の成行注文の実行

最大スリッページは利用できないので、図7の方法でリミットオーダーによるスリッページの設定が唯一の方法です。


1.5. 取引実行モードでEAを使った最大スリッページの設定

プログラムを使ってリミットオーダーに書き換えてみましょう。このためには、次の要素によるシンプルなパネルをコーディングする必要があります。:

  • BUY ボタン – リミットオーダーによる買い;
  • SELL ボタン – リミットオーダーによる売り;
  • 最大スリッページフィールド(ポイント)は後で付け加えます。;
  • 買いと売りのボリュームもあとで付け加えます。

このスクリーンショットはパネルの第一弾です。:

図9. DeviationPanelで最大スリッページの設定

このパネルはCDevPanel クラスで作ります。ソースコードは下記です。:

//+------------------------------------------------------------------+
//|                                                       Panel.mqh  |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Trade\Trade.mqh>
#define OP_BUY 0
#define OP_SELL 1
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CDevPanel
  {
private:
   CTrade            Trade;
   string            m_descr_dev;
   string            m_buy_button_name;
   string            m_sell_button_name;
   string            m_deviation_name;
   string            m_volume_name;
   string            m_bg_fon;
   int               m_deviation;
   void              OnObjClick(string sparam);
   void              OnEndEdit(string sparam);
   double            CalcCurrentPrice(int op_type);

public:
                     CDevPanel();
                    ~CDevPanel();
   void              OnChartEvent(const int id,
                                  const long &lparam,
                                  const double &dparam,
                                  const string &sparam);
  };
//+------------------------------------------------------------------+
//| CDevPanel class                                                  |
//+------------------------------------------------------------------+
CDevPanel::CDevPanel(): m_buy_button_name("buy_button"),
                        m_sell_button_name("sell_button"),
                        m_deviation_name("deviation"),
                        m_volume_name("volume"),
                        m_bg_fon("bg_fon"),
                        m_descr_dev("descr_dev"),
                        m_deviation(3)
  {
//--- background
   ObjectCreate(0,m_bg_fon,OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(0,m_bg_fon,OBJPROP_YSIZE,80);
   ObjectSetInteger(0,m_bg_fon,OBJPROP_XSIZE,190);
   ObjectSetInteger(0,m_bg_fon,OBJPROP_BGCOLOR,clrWhiteSmoke);

//--- buy button
   ObjectCreate(0,m_buy_button_name,OBJ_BUTTON,0,0,0);
   ObjectSetInteger(0,m_buy_button_name,OBJPROP_XDISTANCE,100);
   ObjectSetInteger(0,m_buy_button_name,OBJPROP_YDISTANCE,50);
   ObjectSetInteger(0,m_buy_button_name,OBJPROP_XSIZE,80);
   ObjectSetInteger(0,m_buy_button_name,OBJPROP_BGCOLOR,clrAliceBlue);
   ObjectSetString(0,m_buy_button_name,OBJPROP_TEXT,"BUY");

//--- sell button
   ObjectCreate(0,m_sell_button_name,OBJ_BUTTON,0,0,0);
   ObjectSetInteger(0,m_sell_button_name,OBJPROP_XDISTANCE,10);
   ObjectSetInteger(0,m_sell_button_name,OBJPROP_YDISTANCE,50);
   ObjectSetInteger(0,m_sell_button_name,OBJPROP_XSIZE,80);
   ObjectSetInteger(0,m_sell_button_name,OBJPROP_BGCOLOR,clrPink);
   ObjectSetString(0,m_sell_button_name,OBJPROP_TEXT,"SELL");

//--- deviation
   ObjectCreate(0,m_deviation_name,OBJ_EDIT,0,0,0);
   ObjectSetInteger(0,m_deviation_name,OBJPROP_XDISTANCE,120);
   ObjectSetInteger(0,m_deviation_name,OBJPROP_YDISTANCE,20);
   ObjectSetInteger(0,m_deviation_name,OBJPROP_XSIZE,60);
   ObjectSetInteger(0,m_deviation_name,OBJPROP_BGCOLOR,clrWhite);
   ObjectSetInteger(0,m_deviation_name,OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(0,m_deviation_name,OBJPROP_ALIGN,ALIGN_RIGHT);
   ObjectSetString(0,m_deviation_name,OBJPROP_TEXT,(string)m_deviation);

//--- description
   ObjectCreate(0,m_descr_dev,OBJ_LABEL,0,0,0);
   ObjectSetInteger(0,m_descr_dev,OBJPROP_XDISTANCE,12);
   ObjectSetInteger(0,m_descr_dev,OBJPROP_YDISTANCE,20);
   ObjectSetInteger(0,m_descr_dev,OBJPROP_XSIZE,80);
   ObjectSetInteger(0,m_descr_dev,OBJPROP_BGCOLOR,clrWhite);
   ObjectSetString(0,m_descr_dev,OBJPROP_TEXT,"Deviation (pips):");
   ObjectSetInteger(0,m_descr_dev,OBJPROP_COLOR,clrBlack);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDevPanel::~CDevPanel(void)
  {
   ObjectDelete(0,m_buy_button_name);
   ObjectDelete(0,m_sell_button_name);
   ObjectDelete(0,m_bg_fon);
   ObjectDelete(0,m_deviation_name);
   ObjectDelete(0,m_descr_dev);
  }
//+------------------------------------------------------------------+
//| Event function                                                   |
//+------------------------------------------------------------------+
void CDevPanel::OnChartEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam)
  {
   switch(id)
     {
      case CHARTEVENT_OBJECT_CLICK:
         OnObjClick(sparam);
         break;
      case CHARTEVENT_OBJECT_ENDEDIT:
         OnEndEdit(sparam);
     }
  }
//+------------------------------------------------------------------+
//| End edit detect                                                  |
//+------------------------------------------------------------------+
void CDevPanel::OnEndEdit(string sparam)
  {
   if(sparam != m_deviation_name)return;
   int value = (int)ObjectGetString(0, m_deviation_name, OBJPROP_TEXT);
   if(value <= 0)
      ObjectSetString(0,m_deviation_name,OBJPROP_TEXT,(string)m_deviation);
   else
      m_deviation=value;
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| End obj click                                                    |
//+------------------------------------------------------------------+
void CDevPanel::OnObjClick(string sparam)
  {
   if(sparam==m_buy_button_name)
      Trade.BuyLimit(1,CalcCurrentPrice(OP_BUY));
   if(sparam==m_sell_button_name)
      Trade.SellLimit(1,CalcCurrentPrice(OP_SELL));
   ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
   Sleep(100);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Calc level price                                                 |
//+------------------------------------------------------------------+
double CDevPanel::CalcCurrentPrice(int op_type)
  {
   if(op_type==OP_BUY)
     {
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      return ask + (m_deviation * Point());
     }
   else if(op_type==OP_SELL)
     {
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
      return bid - (m_deviation * Point());
     }
   return 0.0;
  }
//+------------------------------------------------------------------+

このパネルを使えば、最大スリッページをポイント単位で設定することができます。実際のエントリー・決済はリミットオーダーで実行されます。

このパネルはブローカーがオーダー方式に対応している場合にのみ有効です。それ以外の場合、リミットオーダーをしようとするとエラーが発生します。

2015.04.15 14:08:39.709 Trades  '58406864': failed buy limit 0.10 EURUSD at 1.05927 [Invalid price]


1.6. 買いストップと売りストップの代替としての、買いのストップ・リミットと売りのストップ・リミット

リミットオーダーはスリッページに対して自然な予防策となります。しかし、ときとして、予約注文を使う必要があります。ストップロスオーダーがその例です。また、特定の手法では特定のチャネルを残して価格に反応するようにするべきです。エントリーするにはストップオーダーもまた必要です。しかし、先でもわかるように、ストップオーダーはスリッページの影響を受け、流動性の問題に対して対処する方法がありません。さらに言えば、ストップオーダーに対して最大スリッページを設定することはできません。

この場合、Buy Stop LimitSell Stop Limit を使うべきです。これはMetaTrader 5のアルゴリズムオーダーです。これは相場ではなく、メタトレーダーサーバーで実装されています。公式のドキュメントを見てみましょう。:

  • Buy Stop Limit — これは2つのタイプの組み合わせです。 [Buy Limit and Buy Stop] リミットオーダーに代わるストップオーダーです。Askがストップレベルに達するとすぐに、リミットオーダーがストップオーダーの位置で実行されます。
  • Sell Stop Limit — これはリミットオーダーに代わるストップオーダーです。Bidがストップレベルに達するとすぐに、リミットオーダーがストップの代わりとして実行されます。

MetaTrader 5のオーダーの原則(図. 10)でも説明されています。黄色いフレームは、現在議論している2つのオーダータイプです。:


図10. MetaTrader 5のオーダータイプ

よって、リミットオーダーは、価格がストップの値に達したときに、実行されます。買いのストップリミットオーダーでは、ストップの値は現在のAskよりも高い場所に置きます。売りのストップリミットオーダーでは、現在のBidよりも低い位置に置きます。実行モードでのリミットオーダーの位置は、ストップの上でも下でも構いません。この性質により、スリッページを管理した特殊なストップオーダーを実行できます。下の画像はどのように機能するかを示したものです。:

図11. 買いストップリミットオーダーによる最大スリッページの設定

ストップの価格を超えてリミットの価格を持つストップリミットオーダーを設定することができます。リミット価格が現在のストップ価格よりも悪いので、ストップの値に達するやいなや、買いリミットオーダーが送信され、即座に実行されます。ストップ価格とリミット価格の差は最大スリッページとして機能します。売りのストップリミットオーダーも同じように働きます。リミット価格はストップ価格よりも下になるように設定してください。

さて、実践に移ってみましょう。買いのストップリミットオーダーをマニュアルで実行します。


1.7. ストップロスの代わりとしての、買いストップ・リミットと売りストップ・リミットのマニュアル設定

ストップオーダーを使ってエントリーを保護しようとするとします。しかし、流動性が低い市場は危険すぎ、かつ、ストップオーダーや成行注文を使うには予測不可能です。ストップオーダー(例えば、ストップロス)は、スリッページの上限がありません。そのため、大きな価格ジャンプやスパイクがあった場合、資産を大きく減らしてしまいます。これを避けるためには、ストップオーダーに際し、ストップリミットオーダーを実行します。

次の例を考えましょう。買いポジションをSi-6.15で建てるとします。ストップロスは、56 960です。ストップリミット価格が56 960 - 5 = 56 955 となるように、最大スリッページを5ポイントに設定します。:

図12. 買いポジションのストップ値としてのストップリミットの実行

図12. 買いポジションのストップ値としてのストップリミットの実行

図からもわかるように、売りのストップリミットオーダーは実行モードでは可能です。価格が 56 960 に達したとき、56 955 のリミットオーダーが実行されます。現在値56 960 は、リミットオーダーの値よりも良いので、オーダーは56 960ですぐに実行されます。その周辺で十分な流動性がない場合、オーダーの実行は56 955よりも下で行われます。最大スリッページが保障されているので、リミットオーダーは56 955よりも悪い値で実行されません。: 56 960 - 56 955 = 5.

同じようにして売りポジションも保護してみましょう。ショートポジションをストップロスで決済するには、逆のことをします。つまり、買いのストップリミットオーダーを使って買います。現在のショートポジションのストップロスが 56 920 だとします。その場合、買いのストップリミットオーダーを使い、サイダイスリッページが5ポイントになるようにします。:

図13. 買いストップリミットをショートポジションのストップ値として設定

図13. 買いストップリミットをショートポジションのストップ値として設定

このとき、ストップリミットは5ポイントの範囲内で実行され、56 925を含みます。


1.8. EA内で、買いストップ・リミットと売りのストップ・リミットを使ったストップロスの書き換え

セクション1.5のパネルに戻ってみましょう。買いのストップリミットと売りのストップリミットを使って、ストップの設定を修正する必要があります。そのためには、新しいフィールド Stop-Lossを追加します。これでパネルは下記のようになります。:

図14. DevaitionPanelでのストップロス値の設定

コード上に2つの重要な変化があります。: CDevPanelクラスが買いのストップリミットと売りのストップリミットの新しいメソッドを内包しています。エントリーのOnObjClickメソッドが修正されています。このメソッドのコードは次の通りです。:

//+------------------------------------------------------------------+
//| End obj click                                                    |
//+------------------------------------------------------------------+
void CDevPanel::OnObjClick(string sparam)
  {
   if(sparam==m_buy_button_name)
     {
      if(Trade.BuyLimit(1,CalcCurrentPrice(OP_BUY)))
         SendStopLoss(OP_BUY);
     }
   if(sparam==m_sell_button_name)
     {
      if(Trade.SellLimit(1,CalcCurrentPrice(OP_SELL)))
         SendStopLoss(OP_SELL);
     }
   ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
   Sleep(100);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Send SL order                                                    |
//+------------------------------------------------------------------+
bool CDevPanel::SendStopLoss(int op_type)
  {
   if(op_type==OP_BUY)
     {
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
      if(m_sl_level>=0.0 && m_sl_level<bid)
        {
         MqlTradeRequest request={0};
         request.action = TRADE_ACTION_PENDING;
         request.symbol = Symbol();
         request.volume = 1.0;
         request.price=m_sl_level;
         request.stoplimit=m_sl_level -(m_deviation*Point());
         request.type=ORDER_TYPE_SELL_STOP_LIMIT;
         request.type_filling=ORDER_FILLING_RETURN;
         request.type_time=ORDER_TIME_DAY;
         MqlTradeResult result;
         bool res=OrderSend(request,result);
         if(!res)
            Print("Error set S/L. Reason: "+(string)GetLastError());
         return res;
        }
     }
   else if(op_type==OP_SELL)
     {
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      if(m_sl_level>=0.0 && m_sl_level>ask)
        {
         MqlTradeRequest request={0};
         request.action = TRADE_ACTION_PENDING;
         request.symbol = Symbol();
         request.volume = 1.0;
         request.price=m_sl_level;
         request.stoplimit=m_sl_level+(m_deviation*Point());
         request.type=ORDER_TYPE_BUY_STOP_LIMIT;
         request.type_filling=ORDER_FILLING_RETURN;
         request.type_time=ORDER_TIME_DAY;
         MqlTradeResult result;
         bool res=OrderSend(request,result);
         if(!res)
            Print("Error set S/L. Reason: "+(string)GetLastError());
         return res;
        }
      if(CharToStr(StringGetChar(data,strlen-1))=='.')
         StringSetChar(data,strlen-1,'');
     }
   return false;
  }

これらのメソッドとは異なり、パネルクラスのコードは初期関数とストップロスのフィールドを含んでいます。さて、BUY/SELLをクリックする前にストップロスを入れる場合、新規の成行注文が、買いのストップリミットか売りのストップリミットにより、実行されます。(ポジションの方向によって変わります。)


チャプター2. 相場の流動性の分析


2.1市場に参加する前のスリッページ計算

株式市場は一極集中型の市場です。よって、すべてのリミット注文は相場の深さを観察することにより利用可能となります。"モスクワ証券取引所の例によるトレードの原則"にある定義に戻ると,市場の厚みに位置しているリミットオーダーが相場の流動性 (直近の取引付近の特定のボリュームでの買い・売りの能力)を形作っていることが分かります。

現在の値に関連した値から離れるようにリクイディティプロバイダーをひきつける必要があるので、売買のボリュームが大きければ大きいほど、市場の厚みからのオーダーはスリッページを大きくして実行されます。*スリッページがどのように機能するかは、上記の"交換価格の原則"で記されています。この問題をはっきりさせるために、簡単な例を考えてみましょう。

任意の時間で相場の厚みの買い・売りのボリュームがあります。今のSi-6.15 USD/RUBの相場の厚みを見ます。:


図15. Si-6.15 の相場の厚み

2回買いエントリーすれば、Ask: 51 931で取引が実行され、スリッページの問題は解決します。しかし、もし4回買いエントリーをすれば、その平均価格は51 931から離れてしまいます。: (2*51 931+2*51 932)/4 = 51 931.5. 51 931で2回買えば、51 932で2つになります。51 931.5は、エントリー価格の加重平均です。最善のAskとの差がスリッページとなります。.

これで、取引ボリュームに応じてスリッページを決める流動性の表を調整することができます。1,2取引のボリュームでは、我々の取引は、スリッページなしで最も良いAsk値で実行されます。(51 931)4つの取引の場合は、スリッページは0.5ポイントになります。 (51 931.5 - 51 931.0). この式はシンプルです。: 最も良いAsk/Bidは加重エントリー価格から取り出される。

流動性の表は下記のとおりです。:

ボリューム 価格 取引
ボリューム
加重中央
エントリー価格
スリッページ
2  51 938 25 51 934.5 3.5
9  51 936  23 51 934.2 3.2
3  51 935  14 51 933.0 2.0
7  51 933  11 51 932.5 1.5
2  51 932  4 51 931.5 0.5
2  51 931  2 51 931.0 0.0

テーブル1. 加重中央エントリー価格の計算と最適なスリッページ

この表は、相場の深さと同様に、下から上に向かって見ます。見てもわかるように、2つの取引のボリュームにはスリッページがありません。4つの取引のボリュームには0.5ポイントのスリッページがあります。25取引の場合には、3.5ポイントのスリッページが発生し、加重中央価格は 51934.5となります。

中央市場と市場の深さを使えば、次の結論に達します。:

相場の厚みの状態を知ること取引をする前にスリッページの計算をする

そうすると、リスクを管理することができます。裁量、自動売買に関わらず、相場の厚みを事前に測ることが可能です。こういう意味では、トレーダーはダイバーに例えられます。水に飛び込む前に、ダイバーはプールの深さを知らなければなりません。ダイバーが大きければ大きいほど、プールは深くなければなりません。同様に、取引量が大きければ大きいほど、相場には流動性が必要です。もちろん、相場の厚みはエントリーする瞬間に変化する可能性はあります。しかし、しっかりとした計算は十分に精確な取引を実現します。


2.2. リアルタイムでの潜在スリッページ計算

さて、この理論を実践に移してみましょう。相場の厚みは即座に変化し、計算量もあるので、潜在的なスリッページを手動で計算することは不可能です。よって、自動化する必要があります。計算を簡単にするために、CMarketBookを使います。このようなクラスの開発は困難であり、1記事分に値します。ここでは、中身の詳細については触れません。代わりとして、メソッドを使いましょう。: GetDeviationByVol. どのように動くか見てみましょう。:

//+------------------------------------------------------------------+
//| ボリュームから偏差を取得Retun -1.0 if deviation is        |
//| infinity (insufficient liquidity)                                |
//+------------------------------------------------------------------+
double CMarketBook::GetDeviationByVol(long vol,ENUM_MBOOK_SIDE side)
  {
   int best_ask = InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int last_ask = InfoGetInteger(MBOOK_LAST_ASK_INDEX);
   int best_bid = InfoGetInteger(MBOOK_BEST_BID_INDEX);
   int last_bid = InfoGetInteger(MBOOK_LAST_BID_INDEX);
   double avrg_price=0.0;
   long volume_exe=vol;
   if(side==MBOOK_ASK)
     {
      for(int i=best_ask; i>=last_ask; i--)
        {
         long currVol=MarketBook[i].volume<volume_exe ?
                      MarketBook[i].volume : volume_exe;
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe<=0)break;
        }
     }
   else
     {
      for(int i=best_bid; i<=last_bid; i++)
        {
         long currVol=MarketBook[i].volume<volume_exe ?
                      MarketBook[i].volume : volume_exe;
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe<=0)break;
        }
     }
   if(volume_exe>0)
      return -1.0;
   avrg_price/=(double)vol;
   double deviation=0.0;
   if(side==MBOOK_ASK)
      deviation=avrg_price-MarketBook[best_ask].price;
   else
      deviation=MarketBook[best_bid].price-avrg_price;
   return deviation;
  }

このメソッドを呼び出すと、相場の厚みを参照します。最良の価格から相場の厚みを経て、利用可能なボリュームを計算します。利用可能なボリュームが要求されたものと同じか超過している場合、このメソッドは所定の量に応じた加重中央価格を計算します。計算した加重中央価格と最良のBid/Askの差がスリッページになります。

何らかの理由で相場の厚みが十分でない場合、このメソッドは-1.0を返します。

潜在スリッページの計算メソッドを使って、得られた結果を表示させます。明らかに、スリッページは買い・売りのボリュームと相関します。ボリュームが大きければ大きいほど、スリッページも大きくなります。よって、Volumeを呼ぶ新しいラインとフィールドの追加が必要になります。 :

図16. ボリュームフィールドのパネル

さて、これでパネルが任意のボリュームで買いや売りをできるようになりました。例えば、相場価格で5取引量を買いたい場合、ボリュームフィールドに5と入力し、BUYをクリックします。これだけではありません。すでに述べたように、GetDeviationVolメソッドの恩恵でエントリーするときに、スリッページを管理することができます。

UI向上のため、直接的にBUY/SELLボタンに計算された値を表示させてみましょう。ポイント単位でスリッページを指定します。この値は、相場の厚みの変化とともに、新しく計算されます。流動性が増加すれば、スリッページは逆に減少します。1取引量だけ取引したい場合には、1取引量が最良のBid/Askを超えることがないので、スリッページはありません。

リアルタイムでアップデートされたパネルを見てみることを推奨します。下のビデオは RTS-6.15 でのリアルタイムでのスリッページの計算の様子です。

 

初めの内は、ボリュームフィールドに1取引を入れています。予想の通り、BUY/SELLボタンは0を表しています。これはスリッページなしでエントリーするということを表します。100取引量までボリュームが増えると、平均スリッページは10-20ポイントまで増加します。500取引量までボリュームが増えると、平均スリッページは60-80まで増加します。最終的に、1500取引量までボリュームが増えると、不十分な流動性のために、-1.0が表示されます。 (スリッページ定義不能). 多大な取引量の売りは100-130ポイントまでスリッページが増加しますが、流動性は十分です。

相場の厚みの稼働に使うクラスは、下のコードで利用可能です。


2.3. スプレッドレコードインジケーターを使ったエントリーフィルター

エントリーする前に現在の流動性の分析することは大切です。丁寧に開発されたロボットは複雑な計算を人に代わって計算し、危険なスリッページを知らせてくれます。しかし、これで常に十分という訳ではありません。

他の問題もトレーダーは対処しなければなりません。スプレッドです。スプレッドはAskとBidの差です。スプレッドは、それ自身よりも相場の厚みの影響を受け、取引量が大きくなると大きくなります。しかし、トレーダーはいつも相場の厚みの履歴を見るわけではありません。過去の流動性にアクセスすることは困難です。一方、スプレッドはシンボルの流動性に相関があります。スプレッドが狭くなればなるほど、流動性が大きくなります。逆も然りです。

この特徴を頭の片隅に入れると、過去のスプレッドを表示するスプレッドインジケーターを開発することができます。過去の流動性とスプレッドの幅を確認することができるので、このインジケーターはトレードでは非常に有効です。平均のスプレッドの値を知ることにより、スプレッドが広がったとき、トレードを制限することができます。

さて、そのようなインジケーターを作成しましょう。下のチャートウィンドウにバーとして値を表示します。平均スプレッドは緑色のドットで表示されます。このインジケーターはスプレッドを下記のように計算します。:

  • 始値のタイミングのスプレッド;
  • 足の中での最大スプレッド;
  • 足の中での最小スプレッド;
  • 現在のスプレッド;
  • 足の中での平均スプレッド

このインジケーターはスプレッド値を保存しないので、ターミナルがリセットされたからのスプレッドを表示します。下記はインジケーターのソースコードです。

//+------------------------------------------------------------------+
//|                                                Spread Record.mq4 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "https://www.mql5.com/ru/users/c-4"
#property version   "1.00"
#property description "Recording spread and show it."
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   5
#property indicator_type1   DRAW_BARS
#property indicator_type2   DRAW_ARROW
#property indicator_color1   clrBlack
#property indicator_color2   clrBlack
double spread_open[];
double spread_high[];
double spread_low[];
double spread_close[];
double spread_avrg[];
int elements;
double avrg_current;
int count;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- インジケーターバッファマッピング
   SetIndexBuffer(0,spread_open,INDICATOR_DATA);
   SetIndexBuffer(1,spread_high,INDICATOR_DATA);
   SetIndexBuffer(2,spread_low,INDICATOR_DATA);
   SetIndexBuffer(3,spread_close,INDICATOR_DATA);
   SetIndexBuffer(4,spread_avrg,INDICATOR_DATA);
   IndicatorSetInteger(INDICATOR_DIGITS,1);
   PlotIndexSetInteger(1,PLOT_ARROW,0x9f);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,clrRed);
   PlotIndexSetInteger(1,PLOT_LINE_COLOR,clrGreen);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   printf("DEINIT");
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   if(prev_calculated==0)
     {
      printf("INITIALIZE INDICATORS "+TimeToString(TimeCurrent()));
      double init_value=EMPTY_VALUE;
      ArrayInitialize(spread_high,init_value);
      ArrayInitialize(spread_low,init_value);
      ArrayInitialize(spread_open,init_value);
      ArrayInitialize(spread_close,init_value);
      ArrayInitialize(spread_avrg,init_value);
      elements=ArraySize(spread_high);
      InitNewBar(elements-1);
     }
//---新しい足の初期化
   for(; elements<ArraySize(spread_high); elements++)
      InitNewBar(elements);
   double d=GetSpread();
   for(int i=rates_total-1; i<rates_total; i++)
     {
      if(d>spread_high[i])
         spread_high[i]=d;
      if(d<spread_low[i])
         spread_low[i]= d;
      spread_close[i] = d;
      avrg_current+=d;
      count++;
      spread_avrg[i]=avrg_current/count;
     }
//--- 次のコールのprev_calculated の返り値
   return(rates_total-1);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetSpread()
  {
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   return NormalizeDouble((ask-bid)/Point(), 0);
  }
//+------------------------------------------------------------------+
//| 新しいバー                                                     |
//+------------------------------------------------------------------+
void InitNewBar(int index)
  {
   spread_open[index] = GetSpread();
   spread_high[index] = 0.0;
   spread_low[index]=DBL_MAX;
   avrg_current=0.0;
   count=0;
  }

Si-6.15の分チャートでインジケーターを稼働させてみましょう。始まってすぐに、下記の結果を表示します。:

図17. Si-6.15分チャートでのSpreadRecordインジケーター

Si-6.15のスプレッドを1から21ポイントで確認することができます。各分で、少なくとも一回スプレッドが最小の1ポイントに達していることが分かります。平均は3ポイントです。上の通り、緑のドットでインジケーターに表示されます。


2.4. スプレッド拡大時の手動・自動トレード制限

このインジケーターの利用法について考えてみましょう。最もシンプルな方法は、インジケーターの値が大きいときにはトレードを制限することです。選択した期間では、このインジケーターは1-9ポイントの間にあります。この領域は"緑"で、取引可能エリアです。もしスプレッドが9ポイントよりも大きくなれば、赤い領域になり、トレードを控えます。これは下記のようになります。


図18. インジケーターによるトレード可能な領域と不可能な領域

裁量トレードでの制限に加えて、自動売買の場合はスプレッドが大きいときにEAにインジケーターの値を教える必要があります。EAからiCustom関数でインジケーターを呼び出すことによって可能です。この関数はEA内でインジケーターを呼び出します。下記はEAのテンプレートで、スプレッドを管理しています。:

//+------------------------------------------------------------------+
//|                                          SpreadRecordControl.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#define OPEN  0
#define HIGH  1
#define LOW   2
#define CLOSE 3
#define AVRG  4

input int MaxSpread=9;

int h_spread_record=INVALID_HANDLE;       // Handle of SpreadRecord indicator
bool print_disable = false;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   h_spread_record=iCustom(Symbol(),Period(),"Spread Record");
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinit function                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(h_spread_record);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(IsTradeDisable(MaxSpread))return;
   //
   // トレードロジック
   //
  }
//+------------------------------------------------------------------+
//| トレードが利用不可能な場合falseを返します             |
//+------------------------------------------------------------------+
bool IsTradeDisable(int max_spread)
  {
   if(h_spread_record==INVALID_HANDLE)
      return false;
   double close[];
   if(CopyBuffer(h_spread_record, CLOSE, 0, 1, close) < 1)return false;
   if(close[0]>MaxSpread)
     {
      if(!print_disable)
         printf("trade disable");
      print_disable=true;
      return true;
     }
   if(print_disable)
      printf("trade enable");
   print_disable=false;
   return false;
  }

IsTradeDisable関数は、トレードが許可されているかどうかに答えます。スプレッドが高すぎる場合、かつ、トレードが許可されていない場合、trueを返します。スプレッドが通常の場合、falseを返します。この関数は、CopyBuffer関数を使って現在値をコピーすることによって、SpreadRecordインジケーターの呼び出しに基づいています。EAでは、MaxSpreadパラメーターを提供しています。*もし値が超過した場合、EAはトレードをブロックします。スプレッドが指定したボーダーを下回った場合、EAは活動を再開います。IsTradeDisable 関数は、対応するメッセージ: "trade enable" と "trade disable"によって状態の変化を示します。:

2015.05.27 16:57:08.238 SpreadRecordControl (Si-6.15,H1)        trade enable
2015.05.27 16:57:08.218 SpreadRecordControl (Si-6.15,H1)        trade disable
2015.05.27 16:56:49.411 SpreadRecordControl (Si-6.15,H1)        trade enable
2015.05.27 16:56:49.401 SpreadRecordControl (Si-6.15,H1)        trade disable
2015.05.27 16:56:36.478 SpreadRecordControl (Si-6.15,H1)        trade enable
2015.05.27 16:56:36.452 SpreadRecordControl (Si-6.15,H1)        trade disable

このEAのプロトタイプをトレードに使い、流動性が低い場合やスリッページが大きい場合にエントリーするのを避けます。

EAと SpreadRecord インジケーターのソースコードは下記に添付してあります。


チャプター3. テストモードとEAと安全なトレード


3.1. ティック毎の管理の代替手法としての"Sleep Mode"の使用

セクション1.1 "Discrete Nature of a Price Flow. Price Gaps"で述べたように、相場は価格の連続した流れと比較されます。よって、株価が$10から$15に変化する場合、それは11, 12, 13, 14の瞬間があったことを意味します。しかし、必ずしもそうならないことは、すでにお分かりかと思います。

トレードロジックは価格が段階的に推移する想定で作られていますが、価格はたまに急激に移動します。ストップロスを設定すると、それ以上の損失は発生せずにその値で決済されることを期待してしまいます。しかし、ストップロスの基本はその値を超えた後の利用可能な価格でもって決済されます。不連続な価格の場合、そのようなストップロスは予期しない損失を発生させます。現在の価格がストップロスで設定したものよりも悪い場合、ストップロスはかなり大きな損失を出します。

一方、EAが相場状況を確認している場合でも、好ましくない価格で決済される可能性があります。: 流動性が低い場合、価格のジャンプを伴って、ティックの生成がまばらになります。

よって、ティック毎の代わりに、ストラテジーを鈍感にすることが必要です。つまり、EAのトレードロジックを特定の時間間隔毎に行います。(例えば、1分毎). ストップオーダーを使うことは問題外です。代わりに、特定の時間間隔毎に状況を確認するストップのアルゴリズムを使うことが賢明です。

トレードロジックを鈍感にするとトレード結果をゆがめるように思うかもしれませんが、そんなことはありません。もちろん、一分の間に、相場の潜在的な決済やエントリーポイントが動く可能性はあります。しかし、価格が好ましい方向に動く可能性もまたあります。

Si-6.15 RUB/USD 5月28日の実際の相場のケースを考えてみましょう。10:03 (モスクワ時間)に価格のスパイクがあります。その時間、買いポジションを 53 040に、ストップロスを52 740 (300 ポイント)に持っていたとします。この場合、ストップロスは指定した値よりもかなり下で行われます。

例のように、価格のジャンプ中ではストップロスは一般的に悪い位置で執行されます。今回は、 52 493で行われ、 53 040 - 52 493 = 547 ルーブルの損失になるでしょう。(想定では300 ルーブル). これは下のチャートAの通りです。一分ごとにストップロスを確認した場合、価格スパイクはストラテジー上、無視されます。ストップロスは実行されず、最後には利益を確定して終わります。(チャートB):

図19. この戦略は、リアル/バーチャルのストップオーダーの利用によって動作します。

図19. この戦略は、リアル/バーチャルのストップオーダーの利用によって動作します。

下記の価格スパイクは比較的小さいです。しかし、時々、リミットに届いてしまうことがあります。通常、リミットは現在価格の5%の位置に配置します。そのため、レバレッジが1:1の場合、ストップロスのリスクは5%です。レバレッジが1:10の場合、ストップロスのリスクは50%です。


3.2. 移動平均線に基づくサンプルEAと期間ごとの確認

サンプルのEAでは2対の移動平均線のクロスを使っています。これはEAの確認と相場状況の確認には非常にいい例です。移動平均線(MA)の最新の値は終値の値と共に常に変化します。

2つの移動平均線による古典的な方法は多くのトレーダーが知っています。短期MAが長期MAを上抜きしたときに買い、下抜きしたときに売ります。下記の画像は買いと売りのシグナルです。:

図20. 移動平均線による買いと売りのシグナル

図20. 移動平均線による買いと売りのシグナル

先に述べたように、移動平均線の最新の値は変化します。今回の場合、短期MAは長期MAを単一の足の中で何度もクロスします。価格はほとんど変わらないのに、EAがおかしなシグナルをだしてします。最新の足では相場の状況を判断しない方が良いというのは既知の事実です。よって、一分ごとにトレード状況を確認するEAを作ります。この場合、EAは確定したひとつ前の足をチェックするので、MAのリペイントはEAに影響を与えません。

移動平均線のEAのコードは下記です。:

//+------------------------------------------------------------------+
//|                                                MovingAverage.mq5 |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#property version   "1.00"
#include <Trade\Trade.mqh>

input int FastMAPeriod = 10;     // Fast MA period
input int SlowMAPeriod = 20;     // Slow MA period
input double Volume = 1.0;       // Volume for Trade
int FastMA = INVALID_HANDLE;     // Handle of fast MA indicator.
int SlowMA = INVALID_HANDLE;     // Handle of slow MA indicator.
datetime TimeLastBar;
CTrade Trade;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   FastMA = iMA(Symbol(), Period(), FastMAPeriod, MODE_SMA, 1, PRICE_CLOSE);
   SlowMA = iMA(Symbol(), Period(), SlowMAPeriod, MODE_SMA, 1, PRICE_CLOSE);
   if(FastMA==POINTER_INVALID || SlowMA==POINTER_INVALID)
     {
      printf("handle of indicator has not been created");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(FastMA);
   IndicatorRelease(SlowMA);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(!NewBarDetect())return;
   if(CrossOver())
      Trade.Buy(GetVolume());
   else if(CrossUnder())
      Trade.Sell(GetVolume());
  }
//+------------------------------------------------------------------+
//| Return true if fast ma cross slow ma over. Otherwise return      |
//| false.                                                           |
//+------------------------------------------------------------------+
bool CrossOver()
  {
   double fast_ma[];
   double slow_ma[];
   if(CopyBuffer(FastMA, 0, 1, 2, fast_ma) < 1)return false;
   if(CopyBuffer(SlowMA, 0, 1, 2, slow_ma) < 1)return false;
   bool is_over=fast_ma[1]>slow_ma[1] && fast_ma[0]<slow_ma[0];
   return is_over;
  }
//+------------------------------------------------------------------+
//| 短期MAが長期MAをクロスした場合それ以外の場合false     |
//|                                                            |
//+------------------------------------------------------------------+
bool CrossUnder()
  {
   double fast_ma[];
   double slow_ma[];
   if(CopyBuffer(FastMA, 0, 1, 2, fast_ma) < 1)return false;
   if(CopyBuffer(SlowMA, 0, 1, 2, slow_ma) < 1)return false;
   bool is_under=fast_ma[0]>slow_ma[0] && fast_ma[1]<slow_ma[1];
   return is_under;
  }
//+------------------------------------------------------------------+
//| トレードのカウント値を返す/                                   |
//+------------------------------------------------------------------+
double GetVolume()
  {
   if(PositionSelect(Symbol()))return Volume*2.0;
   return Volume;
  }
//+------------------------------------------------------------------+
//| 新しい足が形成されたらtrue,それ以外の場合false           |
//+------------------------------------------------------------------+
bool NewBarDetect()
  {
   datetime times[];
   if(CopyTime(Symbol(),Period(),0,1,times)<1)
      return false;
   if(times[0] == TimeLastBar)return false;
   TimeLastBar = times[0];
   return true;
  }
//+------------------------------------------------------------------+

 主となる EAの特徴は、新しい足の形成の確認です。

void OnTick()
{
   if(!NewBarDetect())return;
   ...
}

現在の EA のバージョンではストップロスはありません。しかし、利用する場合、ストップロスによる決済の確認は新しい足の形成の後に行います。これにより、ストップロスは新しい足の形成後に実行されます。

こうすることにより、相場の状況を新しい足の形成のタイミングでのみ確認し、価格スパイクを避けることができます。もちろん、新しい足の形成時に価格スパイクが起きる可能性もありますが、ティック毎の確認と比較すれば、万に一回もあるかないかです。


3.3. ティック毎のEAテストに代わる確定足モードの使用

最後に、MetaTrader5のストラテジーテスターで、面白いEAとインジケーターのモデルを考察してみましょう。モードは"始値のみ"です。ストラテジーテスターを起動し(View --> Strategy Tester)、テスターウィンドウのセクションを実行します。

図21. "始値のみ"モードの選択

図21. "始値のみ"モードの選択

トレーダーの方々はこのモードが正確でなさすぎるという理由で軽視しています。また、このモードを十分に使えるEAというのも多くはありません。しかし、実際のところ、"全ティック"と比較して最も正確で速いモードであることを知る人は知っています。

始値のみを使うことによって高い精確性になります。すべての新しい足の価格は決定されたときのみ利用可能です。つまり、履歴の確定した足となります。

対照的に、"全ティック"モードでは最も小さい時間枠から特別な方法でデータを収集し、ティックを生成します。MetaTrader 5ではティックヒストリーを保存しないので、一分足未満の価格推移については再現することはできません。したがって、テスター内で驚異的な結果をもたらす"Grail"を開発することは可能ですが、リアル相場では失敗します。

ティックテストモードでは、細かいティック内での非再現性が脅威となります。さらに言えば、予約注文も現実とはかけ離れています。買いのストップオーダーを使い、強い上昇トレンドを待つ戦略について考えてみましょう。upward movement. 2015年5月25日19:00 (午後開けの直後) SBRF-6.15は、7 473 から 7 530 に一分以内に動きました。もし 7 485に予約注文を入れていたとしたら、ストラテジーテスターではその価格で実行されます。:

図22. 予約注文の動き

しかし、現実は全く異なります。その分の間に価格が存在しないことが分かっています。言い換えれば、その注文はもっと悪いところで約定します。下記のビデオは"全ティック"モードでどのようにオーダーが実行されるかを示したものです。:

 

見てもわかるように、ストラテジーテスターでは我々が設定した価格で何の問題もありません。しかし、一分足のチャートのティックを見てみましょう。:

図23. 一分足のティックチャート

この価格は一分足の中で急激な変化を伴っています。ティックチャートは大きな価格ギャップと滑らかな動きの特徴が見て取れます。よって、我々のストップオーダーは本当のマーケットではまず実行されないということになるでしょう。実際には、7 510 - 7 520の内部で実行されるでしょう。


各ティックを分析し、予約注文の代わりに成行価格を使っても何も変わりません。ストラテジーテスターのティック生成はティックを連続的に生成するので、我々のオーダーはAskが指定した価格に達するとすぐに実行されます。実際には、指定された価格で注文を実行することは不可能です。

よって、"全ティック"モードを使うには注意が必要です。価格スパイクに対して敏感かどうかに気を使う必要があります。

確定した足でのテストモードはより安全です。予約注文をこのモードで使うべきではありません。我々の手法で7 495にエントリーした場合、毎足の始値をチェックし、必要な値になるまで待つ必要があります。確定足モードの場合、19:00の足の始値は7495ルーブルよりも低いので、新しい足が19:01に形成されたときに、価格が想定の値の上にあることに気付くでしょう。よって、確定足モードでは下記のように見えることになります。:

 

図24. 確定足モードでの実際のトレード

最終的な結果は悪いものですが、大きな利点があります。:

確定足でのテストは、実際の価格でのトレード結果ということになります。つまり、このモードでの手法は流動性が低い相場でも使うことができます。

M1よりも大きな時間枠で動く手法で、かつ、足ごとに条件を確認する余裕がない場合、"1分足のOHLC"テストモードを試してみてください。このモードでは、それぞれの足はM1チャートの価格だけに基づいて生成されます。すべてのM1の価格はヒストリーに基づいているので、このモードは絶対的に精確で、中期的な手法に有効です。

流動性が低い相場では、"全ティック"モードを使うことは推奨しません。また、その手法ではストップオーダーを使うべきでもありません。

エントリーの細かさはトレード手法にとってとても重要で、ほんの少しの違いが結果に大きな影響をもたらすものだと思うかもしれません。しかし、代数の法則によると、計算結果と実際のエントリーの差は単なるノイズ成分にしかなりません。時として、同じ足の中で価格推移が戻ることはありますが、多くのケースでは、実際の相場のエントリーは計算によるものよりもかなり悪くなります。*

"全ティック"モードを使用した場合、そのような瞬間に損失を出してしまいます。しかし、確定足を使えば、そのような状況でエントリーすることはなくなります。言い換えれば、悪い価格でのエントリーは他の良い要因によって埋め合わされます。一般的には、その違いは完璧に消滅し、結果はエントリーレベルよりもトレード手法に依存する形tとなります。


結論

さて、今回のアイディアを要約しましょう。:

  • 相場価格は非連続である。非連続性を隠された状態でチャートは形成されている。足が形成されている最中に何が起こっているかを信頼性をもって決定づけることはできません。最初の近似方法では、各価格が取引に反映されるように、足の中の流動性が無限であり足中の価格は均等に配置されていると想定します。

  • 流動性が低い相場では、相場価格のバラツキ・非連続性が顕著になります。そのため、エントリーと決済にリミット注文を使うことが推奨されます。リミットオーダーにより、相場の非連続性と多大なスリッページを克服することができます。

  • MetaTrader 5 では、通常のストップロスに代わるBuy Stop Limit と Sell Stop Limit オーダーがあります。ストップリミットオーダーは安全で、たとえターミナルが稼働していなくても、最大スリッページを管理することができます。

  • 非連続性に加え、相場には顧慮しなければならない特定の流動性があります。現在の流動性はスリッページに影響します。相場の厚みの状態を知ることにより、潜在的なスリッページを計算することが可能です。

  • 現在のスプレッドは、相場の厚みを使わずに、同じような方法で管理することができます。スプレッドは通常、シンボルの流動性に依存しています。スプレッドが極端に大きい場合、取引までしばらく待つのが賢明です。

  • "Sleep"モードは、価格スパイクに対抗する、信頼できるプロテクト方法です。このモードでは、新しい足が形成されるときにだけトレードロジックの確認を行います。加えて、このモードによって開発されたEAは、確定足のテストモードで利用可能です。

  • 確定足モードは、実際のヒストリーに基づいて稼働するので、最も信頼に足るテストモードと言えます。しかも、このモードのテストスピードは早いです。確定足と"1分OHLC"は、流動性が低い相場でも通用する唯一のテストモードです。

MetaQuotes Software Corp.によりロシア語から翻訳された
元の記事: https://www.mql5.com/ru/articles/1683

添付されたファイル |
marketbook.mqh (20.26 KB)
panelsl.mqh (24.82 KB)
deviationpanel.mq5 (3.18 KB)
movingaverage.mq5 (7.55 KB)
spread_record.mq5 (8.59 KB)
グラフィカルインタフェース I: 種々のプログラム及びメタトレーダー4ターミナルでのライブラリのテスト(チャプター 5) グラフィカルインタフェース I: 種々のプログラム及びメタトレーダー4ターミナルでのライブラリのテスト(チャプター 5)

このグラフィカルインターフェイスに関するシリーズの第一部の前章では、フォームクラスは、そのコントロールを押すしてフォームの管理を許可するメソッドによって改善されました。本稿では、インディケータやスクリプトなどの異なるMQLプログラムでのテストが行われます。ライブラリはすべてのMetaTraderプラットフォームで使用できるクロスプラットフォーム対応として設計されたので、MetaTrader 4でもテストを行います。

グラフィカルインタフェース I: フォームボタンとインターフェイス要素削除のための関数(チャプター 4) グラフィカルインタフェース I: フォームボタンとインターフェイス要素削除のための関数(チャプター 4)

本稿では、コントロールのクリックによるフォーム管理のメソッドを追加してWindowクラスの開発を続けていこうと思います。フォームのボタンによってのプログラムの終了を有効にするだけでなく、フォームの最小化と最大化機能も実装します。

CCanvas Classを使ったメーターの描写 CCanvas Classを使ったメーターの描写

メーターは車や飛行機などの産業や日常生活で見ることができます。これには管理する値を即座に反映する半円を使います。この記事では、 MetaTrader 5用のメーターのライブラリについて説明します。

MQL5でのレジスタンス・サポートレベルの描写 MQL5でのレジスタンス・サポートレベルの描写

この記事では、サポートとレジスタンスのレベルを描画するための4つの極点を求める方法を説明します。通貨ペアのチャートの極値を見つけるために、RSIインジケータを使用します。例として、サポートとレジスタンスのレベルを表示するインジケータコードを掲載しています。