English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 クックブック:ОСО オーダー

MQL5 クックブック:ОСО オーダー

MetaTrader 5 | 25 12月 2015, 10:37
1 324 0
Denis Kirichenko
Denis Kirichenko

はじめに

本稿は OCO のような注文ペアタイプの処理に焦点を当てています。このメカニズムは MetaTrader 5 と競合するトレーディングターミナルいくつかに実装されているものです。OCO 注文を処理するためのパネルを持つ EA の作成例から2つの目的を達成します。一方で、標準ライブラリの特徴を説明したいと思います。その一方で、トレーダーのツールセットを拡張したいと思います。


1. OCO 注文の基礎

OCO 注文(one-cancels-the-other order:1件が別の注文を取り消す)は2件の未決注文のペアです。

それらはお互いのキャンセル関数で連結しています。第1の注文が実行されると、第2の注文は処理されないまま、またはその逆です。

図1 OCO 注文ペア

図1 OCO 注文ペア

図1 はシンプルな注文相互依存スキームです。基本的な定義を反映しています。両方の注文が存在する限りペアは存在する、というものです。論理面では、ペアのどちらか(ひとつ)の注文が基本ですが、ペアの存在には十分な条件ではありません。

ペアの1つは未決注文でもう1つは逆指し値注文、さらに注文の方くは1方向(売りか買いのどちらか)でなければならない、とするソースもあります。私の意見では、その制限は柔軟なトレーディング戦略作成には役立たないと思います。多様な注文がペアで分析することを提案します。またもっとも重要なことはこのペアのプログラムを試みることです。


2. 注文ペアのプログラミング

私の意見では、OCO 注文の管理に関したタスクをプログラムするのに ООP ツールセットが最高の形で適しています。

次のセクションではわれわれの目的に役立つデータタイプについてお話します。まずはCiOcoObject クラスです。


2.1. CiOcoObject クラス

相互関連の注文を管理するソフトウェアオブジェクトをいくつか考え出す必要があります。

従来どおり、抽象クラスCObject を基に新しいオブジェクトを作成します。

以下がこの新しいクラスの記述です。

//+------------------------------------------------------------------+
//| Class CiOcoObject                                                |
//| Purpose: a class for OCO orders                                  |            
//+------------------------------------------------------------------+
class CiOcoObject : public CObject
  {
   //--- === Data members === --- 
private:
   //--- tickets of pair
   ulong             m_order_tickets[2];
   //--- initialization flag
   bool              m_is_init;
   //--- id
   uint              m_id;

   //--- === Methods === --- 
public:
   //--- constructor/destructor
   void              CiOcoObject(void){m_is_init=false;};
   void             ~CiOcoObject(void){};
   //--- copy constructor
   void              CiOcoObject(const CiOcoObject &_src_oco);
   //--- assignment operator
   void              operator=(const CiOcoObject &_src_oco);

   //--- initialization/deinitialization
   bool              Init(const SOrderProperties &_orders[],const uint _bunch_cnt=1);
   bool              Deinit(void);
   //--- get id
   uint              Id(void) const {return m_id;};

private:
   //--- types of orders
   ENUM_ORDER_TYPE   BaseOrderType(const ENUM_ORDER_TYPE _ord_type);
   ENUM_BASE_PENDING_TYPE PendingType(const ENUM_PENDING_ORDER_TYPE _pend_type);
   //--- set id
   void              Id(const uint _id){m_id=_id;};
  };

OCO 注文の各ペアはそれ自身の識別子を持ちます。その値は乱数ジェネレータ(CRandom クラスのオブジェクト)という方法で設定されます。

ペアの初期化と再初期化のメソッドはインターフェースのコンテキストの関心事です。最初のものはペアを作成(初期化)し、次のものはペアを削除(再初期化)します。

CiOcoObject::Init() メソッドは SOrderPropertiesタイプのストラクチャの配列を引数として受け付けます。このストラクチャタイプはペア、すなわち OCO 注文での注文プロパティを表します。


2.2 SOrderProperties ストラクチャ

上記のストラクチャのフィールドを考察します。

//+------------------------------------------------------------------+
//| Order properties structure                                       |
//+------------------------------------------------------------------+
struct SOrderProperties
  {
   double                  volume;           // order volume   
   string                  symbol;           // symbol
   ENUM_PENDING_ORDER_TYPE order_type;       // order type   
   uint                    price_offset;     // offset for execution price, points
   uint                    limit_offset;     // offset for limit price, points
   uint                    sl;               // stop loss, points
   uint                    tp;               // take profit, points
   ENUM_ORDER_TYPE_TIME    type_time;        // expiration type
   datetime                expiration;       // expiration
   string                  comment;          // comment
  }

初期化メソッドが動作するようにするには、前もって2点の要素を考慮しつつストラクチャの配列を書き込む必要があります。簡単に言うと、どの注文を出すのかプログラムに説明する必要があるのです。

このストラクチャでは ENUM_PENDING_ORDER_TYPE タイプの列挙が使用されます。

//+------------------------------------------------------------------+
//| Pending order type                                               |
//+------------------------------------------------------------------+
enum ENUM_PENDING_ORDER_TYPE
  {
   PENDING_ORDER_TYPE_BUY_LIMIT=2,       // Buy Limit
   PENDING_ORDER_TYPE_SELL_LIMIT=3,      // Sell Limit
   PENDING_ORDER_TYPE_BUY_STOP=4,        // Buy Stop
   PENDING_ORDER_TYPE_SELL_STOP=5,       // Sell Stop
   PENDING_ORDER_TYPE_BUY_STOP_LIMIT=6,  // Buy Stop Limit
   PENDING_ORDER_TYPE_SELL_STOP_LIMIT=7, // Sell Stop Limit
  };

一般的に言うと、それは標準列挙ENUM _ORDER_TYPE と似て見えますが、それは未決注文だけ選択できるようにします。またはより正確にはその類の注文タイプです。

それは入力パラメータ内で対応する注文タイプを選択するとき、エラーから保護します(図2)。

図2 使用可能な注文タイプのドロップダウンリストを持つ『タイプ』フィールド

図2 使用可能な注文タイプのドロップダウンリストを持つ『タイプ』フィールド

それでも標準列挙 ENUM _ORDER_TYPE を使用するなら、マーケット注文タイプ(ORDER_TYPE_BUY または ORDER_TYPE_SELL)を設定することができますが、ここでは未決注文しか処理しないため、それは不要です。


2.3. ペア の初期化

上述のように、CiOcoObject::Init() メソッドは注文ペアの初期化を行います。

事実、それは注文ペア自体を出し、新規ペアの生成が正常に行われたか失敗したかを記録します。それ自体がトレーディング処理を行うため、これは能動的なメソッドであると言えます。受動的なメソッドを作成することも可能です。それは個別に出されすでにアクティブとなっている未決注文に接続します。

メソッド全体のコードは提しません。ただトレードクラスメソッド CTrade::OrderOpen() がトレード注文を処理する価格すべて(開始、ストップ、収益、限界)を計算することが重要であることをご注意申し上げたいと思います。そのために配慮することが2つあります。注文の方向(売りか買いか)と現在価格に関連する注文実行価格のポジション(上か下か)です。

このメソッドはプライベートなメソッドを呼びだします。BaseOrderType() および PendingType()です。前者は注文の方向を決め、後者は未決注文タイプを判断します。

注文が出されると、そのチケットは m_order_tickets[] 配列に記録されます。

私はこのメソッド検証のため簡単な Init_OCO.mq5 スクリプトを使用しました。

#property script_show_inputs
//---
#include "CiOcoObject.mqh"
//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
sinput string Info_order1="+===--Order 1--====+";   // +===--Order 1--====+
input ENUM_PENDING_ORDER_TYPE InpOrder1Type=PENDING_ORDER_TYPE_SELL_LIMIT; // Type
input double InpOrder1Volume=0.02;                  // Volume
input uint InpOrder1PriceOffset=125;                // Offset for execution price, points
input uint InpOrder1LimitOffset=50;                 // Offset for limit price, points
input uint InpOrder1SL=250;                         // Stop loss, points
input uint InpOrder1TP=455;                         // Profit, points
input string InpOrder1Comment="OCO Order 1";        // Comment
//---
sinput string Info_order2="+===--Order 2--====+";   // +===--Order 2--====+
input ENUM_PENDING_ORDER_TYPE InpOrder2Type=PENDING_ORDER_TYPE_SELL_STOP; // Type
input double InpOrder2Volume=0.04;                  // Volume    
input uint InpOrder2PriceOffset=125;                // Offset for execution price, points
input uint InpOrder2LimitOffset=50;                 // Offset for limit price, points
input uint InpOrder2SL=275;                         // Stop loss, points
input uint InpOrder2TP=300;                         // Profit, points
input string InpOrder2Comment="OCO Order 2";        // Comment

//--- globals
CiOcoObject myOco;
SOrderProperties gOrdersProps[2];
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- property of the 1st order
   gOrdersProps[0].order_type=InpOrder1Type;
   gOrdersProps[0].volume=InpOrder1Volume;
   gOrdersProps[0].price_offset=InpOrder1PriceOffset;
   gOrdersProps[0].limit_offset=InpOrder1LimitOffset;
   gOrdersProps[0].sl=InpOrder1SL;
   gOrdersProps[0].tp=InpOrder1TP;
   gOrdersProps[0].comment=InpOrder1Comment;

//--- property of the 2nd order
   gOrdersProps[1].order_type=InpOrder2Type;
   gOrdersProps[1].volume=InpOrder2Volume;
   gOrdersProps[1].price_offset=InpOrder2PriceOffset;
   gOrdersProps[1].limit_offset=InpOrder2LimitOffset;
   gOrdersProps[1].sl=InpOrder2SL;
   gOrdersProps[1].tp=InpOrder2TP;
   gOrdersProps[1].comment=InpOrder2Comment;

//--- initialization of pair
   if(myOco.Init(gOrdersProps))
      PrintFormat("Id of new OCO pair: %I32u",myOco.Id());
   else
      Print("Error when placing OCO pair!");
  }

ここでペアの将来の注文の多様なプロパティを設定することが可能です。MetaTrader 5 には異なる5種類の未決注文タイプがあります。

このコンテキストで、ペアのバリアント(combinations)は15です(ペアに異なる注文があると仮定して)。

C(k,N) = C(2,6) = 15

バリアントはすべてスクリプトを用いて検証されています。ペア 逆指値注文指し値つきストップ注文 を例とします。

注文タイプはスクリプトパラメータで設定します(図3)。

図3 『逆指値注文』と『指し値つきストップ注文』のペア

図3 『逆指値注文』と『指し値つきストップ注文』のペア

以下の情報が登録『エキスパート』に表示されます。

QO      0       17:17:41.020    Init_OCO (GBPUSD.e,M15) Code of request result: 10009
JD      0       17:17:41.036    Init_OCO (GBPUSD.e,M15) New order ticket: 24190813
QL      0       17:17:41.286    Init_OCO (GBPUSD.e,M15) Code of request result: 10009
JH      0       17:17:41.286    Init_OCO (GBPUSD.e,M15) New order ticket: 24190814
MM      0       17:17:41.379    Init_OCO (GBPUSD.e,M15) Id of new OCO pair: 3782950319

ただループに頼ることなくスクリプトを用いて最大まで OCO 注文を処理することはできません。


2.4. ペアの再初期化

このメソッドは注文ペアを制御します。ペアはアクティブな注文リストから任意の注文が去ると『死んで』しまいます。

このメソッドを EA のコードのハンドラ OnTrade() または OnTradeTransaction() に入れると仮定します。その方法で EA はディレイなくいかなる注文のアクティブ化処理も行うことができるのです。

//+------------------------------------------------------------------+
//| Deinitialization of pair                                         |
//+------------------------------------------------------------------+
bool CiOcoObject::Deinit(void)
  {
//--- if pair is initialized
   if(this.m_is_init)
     {
      //--- check your orders 
      for(int ord_idx=0;ord_idx<ArraySize(this.m_order_tickets);ord_idx++)
        {
         //--- current pair order
         ulong curr_ord_ticket=this.m_order_tickets[ord_idx];
         //--- another pair order
         int other_ord_idx=!ord_idx;
         ulong other_ord_ticket=this.m_order_tickets[other_ord_idx];

         //---
         COrderInfo order_obj;

         //--- if there is no current order
         if(!order_obj.Select(curr_ord_ticket))
           {
            PrintFormat("Order #%d is not found in active orders list.",curr_ord_ticket);
            //--- attempt to delete another order                 
            if(order_obj.Select(other_ord_ticket))
              {
               CTrade trade_obj;
               //---
               if(trade_obj.OrderDelete(other_ord_ticket))
                  return true;
              }
           }
        }
     }
//---
   return false;
  }

ある詳細についてお話したいと思います。ペア初期化フラグはクラスメソッドの本文でチェックされます。注文確認の試行はフラグがクリアされていると行われません。この方法により別の注文がまだ出されていないときあるアクティブな注文が削除されることはありません。

注文が数件出されるスクリプトに機能性を追加します。このため、Control_OCO_EA.mq5 検証 EA を作成します。

一般的に言えば、この EA はコードの Trade() イベント処理ブロックが異なるだけです。

//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- OCO pair deinitialization
   if(myOco.Deinit())
     {
      Print("No more order pair!");
      //--- clear  pair
      CiOcoObject new_oco;
      myOco=new_oco;
     }
  }

ビデオに MetaTrader 5 ターミナルでの両プログラムの動作が示されています。


ただしどちらの検証プログラムにも弱点があります。

前者のプログラム(スクリプト)はペアは能動的に作成しますが、その後そのペアに対しての制御は失います。

後者のプログラム(Expert Advisor)はペアの制御は行いますが、最初のペア作成後他のペアを繰り返し作成することはできません。OCO 注文プログラム(スクリプト)をフル装備とするには、そのツールセットに発注機能を加えて拡張する必要があります。それは次のセクションで行います。


3. EA の制御

ペア注文のパラメータを入れて設定するためのチャート上に OCO 注文管理パネルを作成します。

それは EA 制御の一部です(図4)。ソースコードは Panel_OCO_EA.mq5 にあります。

図4 OCO 注文作成用パネル:初期状態

図4 OCO 注文作成用パネル:初期状態

将来の注文タイプを選択し、OCO 注文のペアを入れるフィールドに書き込みます。

その後パネル上の唯一のボタンは変更されます(テキストプロパティ、図5)。

図5 OCO 注文作成用パネル:新規ペア

図5 OCO 注文作成用パネル:新規ペア

「パネル」構築には 標準ライブラリ の以下のクラスが使用されました。

  • CAppDialog -メインのアプリケーションダイアログ
  • CPanel -長方形のラベル
  • CLabel -テキストラベル
  • CComboBox -ドロップダウン付のフィールド
  • CEdit -入力フィールド
  • CButton -ボタン

もちろん親クラスメソッドは自動で呼び出されました。

それではコードに行きます。インディケーションパネルおよびダイアログ作成専用の標準ライブラリ部分はひじょうに大きいということを言う必要があります。

たとえば、ドロップダウンリストのクローズイベントが必要であれば、呼び出しのス宅を詳しく調べる必要があります(図6)。

図6 呼び出しのスタック

図6 呼び出しのスタック

開発者は特定のイベントに対し %MQL5\Include\Controls\Defines.mqh ファイルにマクロと表記法を設定します。

私は OCO ペアを作成するために ON_OCO カスタムイベントを作成しました。

#define ON_OCO (101) // OCO pair creation event 

今後の注文のパラメータが書き込まれ、OnChartEvent() ハンドラの本文にペアが生成されます。

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- handling all chart events by main dialog
   myDialog.ChartEvent(id,lparam,dparam,sparam);

//--- drop-down list handling
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE)
     {
      //--- if it is Panel list
      if(!StringCompare(StringSubstr(sparam,0,7),"myCombo"))
        {
         static ENUM_PENDING_ORDER_TYPE prev_vals[2];
         //--- list index
         int combo_idx=(int)StringToInteger(StringSubstr(sparam,7,1))-1;

         ENUM_PENDING_ORDER_TYPE curr_val=(ENUM_PENDING_ORDER_TYPE)(myCombos[combo_idx].Value()+2);
         //--- remember order type change
         if(prev_vals[combo_idx]!=curr_val)
           {
            prev_vals[combo_idx]=curr_val;
            gOrdersProps[combo_idx].order_type=curr_val;
           }
        }
     }

//--- handling input fields
   else if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- if it is Panel's input field
      if(!StringCompare(StringSubstr(sparam,0,6),"myEdit"))
        {
         //--- find object
         for(int idx=0;idx<ArraySize(myEdits);idx++)
           {
            string curr_edit_obj_name=myEdits[idx].Name();
            long curr_edit_obj_id=myEdits[idx].Id();
            //--- if names coincide
            if(!StringCompare(sparam,curr_edit_obj_name))
              {
               //--- get current value of field
               double value=StringToDouble(myEdits[idx].Text());
               //--- define gOrdersProps[] array index
               int order_num=(idx<gEditsHalfLen)?0:1;
               //--- define gOrdersProps structure field number
               int jdx=idx;
               if(order_num)
                  jdx=idx-gEditsHalfLen;
               //--- fill up gOrdersProps structure field
               switch(jdx)
                 {
                  case 0: // volume
                    {
                     gOrdersProps[order_num].volume=value;
                     break;
                    }
                  case 1: // execution
                    {
                     gOrdersProps[order_num].price_offset=(uint)value;
                     break;
                    }
                  case 2: // limit
                    {
                     gOrdersProps[order_num].limit_offset=(uint)value;
                     break;
                    }
                  case 3: // stop
                    {
                     gOrdersProps[order_num].sl=(uint)value;
                     break;
                    }
                  case 4: // profit
                    {
                     gOrdersProps[order_num].tp=(uint)value;
                     break;
                    }
                 }
              }
           }
         //--- OCO pair creation flag
         bool is_to_fire_oco=true;
         //--- check structure filling 
         for(int idx=0;idx<ArraySize(gOrdersProps);idx++)
           {
            //---  if order type is set 
            if(gOrdersProps[idx].order_type!=WRONG_VALUE)
               //---  if volume is set  
               if(gOrdersProps[idx].volume!=WRONG_VALUE)
                  //---  if offset for execution price is set
                  if(gOrdersProps[idx].price_offset!=(uint)WRONG_VALUE)
                     //---  if offset for limit price is set
                     if(gOrdersProps[idx].limit_offset!=(uint)WRONG_VALUE)
                        //---  if stop loss is set
                        if(gOrdersProps[idx].sl!=(uint)WRONG_VALUE)
                           //---  if take profit is set
                           if(gOrdersProps[idx].tp!=(uint)WRONG_VALUE)
                              continue;

            //--- clear OCO pair creation flag 
            is_to_fire_oco=false;
            break;
           }
         //--- create OCO pair?
         if(is_to_fire_oco)
           {
            //--- complete comment fields
            for(int ord_idx=0;ord_idx<ArraySize(gOrdersProps);ord_idx++)
               gOrdersProps[ord_idx].comment=StringFormat("OCO Order %d",ord_idx+1);
            //--- change button properties
            myButton.Text("New pair");
            myButton.Color(clrDarkBlue);
            myButton.ColorBackground(clrLightBlue);
            //--- respond to user actions 
            myButton.Enable();
           }
        }
     }
//--- handling click on button
   else if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- if it is OCO pair creation button
      if(!StringCompare(StringSubstr(sparam,0,6),"myFire"))
         //--- if to respond to user actions
         if(myButton.IsEnabled())
           {
            //--- generate OCO pair creation event
            EventChartCustom(0,ON_OCO,0,0.0,"OCO_fire");
            Print("Command to create new bunch has been received.");
           }
     }
//--- handling new pair initialization command 
   else if(id==CHARTEVENT_CUSTOM+ON_OCO)
     {
      //--- OCO pair initialization
      if(gOco.Init(gOrdersProps,gOcoList.Total()+1))
        {
         PrintFormat("Id of new OCO pair: %I32u",gOco.Id());
         //--- copy pair
         CiOcoObject *ptr_new_oco=new CiOcoObject(gOco);
         if(CheckPointer(ptr_new_oco)==POINTER_DYNAMIC)
           {
            //--- add to list
            int node_idx=gOcoList.Add(ptr_new_oco);
            if(node_idx>-1)
               PrintFormat("Total number of bunch: %d",gOcoList.Total());
            else
               PrintFormat("Error when adding OCO pair %I32u to list!",gOco.Id());
           }
        }
      else
         Print("OCO-orders placing error!");

      //--- clear properties
      Reset();
     }
  }

ハンドラコードは長くなります。数ブロックについて強調したいと思います。

全チャートイベントの最初の処理はメインダイアログに入ります。

次はさまざまなイベント処理のブロックです。

  • 注文タイプを定義するドロップダウンリスト変更
  • 注文のプロパティを記入する入力フィールドの編集
  • ON_OCO イベント生成用ボタンのクリック
  • ON_OCO イベント応答:注文ペア作成

EA はパネルフィールド記入の正確性は検証しません。そのため自分で値をチェックする必要があります。そうでないと EA は OCO 注文がエラーを出すのを表示します。

OnTrade() ハンドラの本文ではペアの削除、または残っている注文をクローズする必要があるか確認します。


おわりに

私は特定タスクのいくつかを遂行する標準ライブラリクラスの豊富さを実証しようとしました。

特に、OCO 注文処理の課題に対処しました。ここで紹介したOCO 注文処理用パネルを伴う EA のコードが、より複雑な注文ペア作成のスタート地点となるとよいと思います。

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

添付されたファイル |
ciocoobject.mqh (27.71 KB)
crandom.mqh (5.19 KB)
init_oco.mq5 (6.42 KB)
control_oco_ea.mq5 (7.88 KB)
panel_oco_ea.mq5 (30.25 KB)
MetaTrader 5 でRSS フィードを表示するためのインタラクティブアプリケーション構築 MetaTrader 5 でRSS フィードを表示するためのインタラクティブアプリケーション構築
本稿では RSS フィードを表示するためのアプリケーションを作成する機能を見ていきます。本稿は MetaTrader 5 用のインタラクティブプログラム作成に標準ライブラリの特徴を利用する方法を示します。
CCanvas クラスを知る透明なオブジェクトの描画方法 CCanvas クラスを知る透明なオブジェクトの描画方法
みなさんは移動平均のぎこちないグラィック以上のものが必要ですか?ターミナルにただ色がついている長方形よりも見栄えの良い何かを描画してみたいですか?ターミナルには魅力的なグラフィックを描くことができるのです。それはカスタムグラフィックを作成する CСanvas クラスで実装することができます。このクラスで透明性を取り入れ、色を混ぜ、オーバーラップと色の混ぜ合わせによって透明の錯覚を産み出すことができるのです。
マーケットでプロダクトを購入することについてのアドバイス段階的ガイド マーケットでプロダクトを購入することについてのアドバイス段階的ガイド
この段階的ガイドは希望のプロダクトをよりよく理解し検索しやすくするアドバイスと技を提供します。本稿は適切なプロダクトを検索し、不要なプロダクトをより分け、みなさんにとってのプロダクトの効果と本質を判断するための異なる方法を解き明かす試みをしています。
MQL5 クックブック: 連想配列またはクイック データアクセスのための辞書の実装 MQL5 クックブック: 連想配列またはクイック データアクセスのための辞書の実装
本稿はユニークなキーによってエレメントへのアクセスが可能となる特殊なアルゴリズムについて説明します。キーとして任意のベースデータタイプが使用可能です。たとえば文字列や整数変数として表すことが可能です。そのようなデータのコンテナは一般的に辞書または連想配列と呼ばれます。それにより課題解決に対するより簡単で効果的な方法が提供されます。