Sở hữu ngay máy chủ VPS Robot Forex khi giao dịch tại HotForex

Nội dung

Trong bài viết đầu tiên, chúng ta đã bắt đầu tạo một thư viện đa nền tảng lớn, đơn giản hóa việc phát triển các chương trình cho MetaTrader 5 và MetaTrader 4. Trong các bài viết tiếp theo, chúng ta tiếp tục phát triển thư viện và hoàn thành đối tượng cơ sở của thư viện Engine và bộ sưu tập lệnh thị trường và vị trí. Trong bài viết này, chúng ta sẽ tiếp tục phát triển đối tượng cơ sở và dạy nó xác định các sự kiện giao dịch trên tài khoản.

Các sự kiện giao dịch trong MQL

Nếu chúng ta quay lại thử nghiệm EA được tạo ở cuối bài viết thứ ba, chúng ta có thể thấy rằng thư viện có thể xác định các sự kiện giao dịch xảy ra trên tài khoản. Tuy nhiên, chúng ta nên phân chia chính xác tất cả các sự kiện xảy ra theo loại của chúng để gửi chúng đến một chương trình sử dụng thư viện cho công việc. 
Để làm điều này, chúng ta nên viết phương thức xác định các sự kiện và một loại xác định các loại sự kiện.

Hãy suy nghĩ về những sự kiện giao dịch mà chúng ta cần xác định:

  • một lệnh chờ có thể được đặt,
  • một lệnh chờ có thể được gỡ bỏ,
  • một lệnh chờ có thể được kích hoạt tạo ra một vị trí,
  • một lệnh chờ có thể được kích hoạt một phần tạo ra một vị trí,
  • một vị trí có thể được mở,
  • một vị trí có thể được đóng lại,
  • một vị trí có thể được mở một phần,
  • một vị trí có thể được đóng một phần,
  • một vị trí có thể được đóng bởi một vị trí đối diện,
  • một vị trí có thể được đóng một phần bởi một vị trí đối diện,
  • một tài khoản có thể được bổ sung,
  • tiền có thể được rút từ một tài khoản,
  • hoạt động số dư có thể xảy ra trên một sự kiện tài khoản 
    chưa được theo dõi:
  • một lệnh chờ có thể được sửa đổi (thay đổi giá kích hoạt, thêm / xóa / thay đổi các mức StopLoss và TakeProfit)
  • một vị trí có thể được sửa đổi (thêm / xóa / thay đổi cấp độ StopLoss và TakeProfit)

Dựa trên những điều trên, bạn cần quyết định cách xác định rõ ràng một sự kiện. Tốt hơn là nên chia ngay giải pháp theo các loại tài khoản:

Sự kiện có thể:

  1. tăng số lượng đơn đặt hàng đang chờ xử lý có nghĩa là thêm một lệnh chờ (sự kiện trong môi trường thị trường)
  2. giảm số lượng đơn đặt hàng đang chờ xử lý:
    • tăng số lượng vị trí có nghĩa là kích hoạt đơn đặt hàng đang chờ xử lý (sự kiện trong thị trường và môi trường lịch sử)
    • số lượng vị trí không tăng có nghĩa là loại bỏ một lệnh chờ (sự kiện trong môi trường thị trường)
  3. số lượng đơn đặt hàng đang chờ xử lý không giảm:
    • tăng số lượng vị trí có nghĩa là mở một vị trí mới (sự kiện trong thị trường và môi trường lịch sử)
    • giảm số lượng vị trí có nghĩa là đóng một vị trí (sự kiện trong thị trường và môi trường lịch sử)
    • số lượng vị trí không thay đổi nhưng kèm theo một khối lượng giảm có nghĩa là đóng cửa một phần của một vị trí (sự kiện trong môi trường lịch sử)

Netting:

  1. tăng số lượng đơn đặt hàng đang chờ xử lý thêm một lệnh chờ
  2. giảm số lượng đơn đặt hàng đang chờ xử lý:
    • tăng số lượng vị trí có nghĩa là kích hoạt đơn đặt hàng đang chờ xử lý
    • số lượng vị trí không thay đổi nhưng kèm theo thời gian sửa đổi vị trí và khối lượng không thay đổi có nghĩa là kích hoạt đơn đặt hàng đang chờ xử lý và tăng âm lượng vị trí
    • giảm số lượng vị trí có nghĩa là đóng một vị trí
  3. số lượng đơn đặt hàng đang chờ xử lý không giảm:
    • tăng số lượng vị trí có nghĩa là mở một vị trí mới
    • giảm số lượng vị trí có nghĩa là đóng một vị trí
    • số lượng vị trí không thay đổi nhưng kèm theo thời gian sửa đổi vị trí và khối lượng tăng có nghĩa là thêm một âm lượng vào một vị trí
    • số lượng vị trí không thay đổi nhưng kèm theo thời gian sửa đổi vị trí và khối lượng giảm có nghĩa là đóng một phần vị trí

Để xác định các sự kiện giao dịch, chúng ta cần biết tài khoản mà chương trình hoạt động. Thêm cờ của loại tài khoản hedge vào phần riêng của lớp CEngine, xác định loại tài khoản trong hàm tạo của lớp và ghi kết quả vào biến cờ này:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine :public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
//--- Return the first launch flag
   bool                 IsFirstStart(void);
public:
//--- Create the timer counter
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() :m_first_start(true)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

Trong lần khởi chạy đầu tiên, trong khi xây dựng đối tượng lớp, loại tài khoản mà chương trình được khởi chạy được xác định trong hàm tạo của nó. Theo đó, các phương pháp xác định các sự kiện giao dịch ngay lập tức được quy cho một tài khoản bảo hiểm rủi ro hoặc tài khoản lưới.

Sau khi xác định một sự kiện giao dịch đến, chúng ta cần lưu trữ mã của nó. Nó là không đổi cho đến sự kiện tiếp theo. Do đó, chương trình luôn có thể xác định sự kiện cuối cùng trên tài khoản. Mã sự kiện sẽ bao gồm một bộ cờ. Mỗi lá cờ sẽ mô tả một sự kiện cụ thể. Ví dụ:một sự kiện đóng vị trí có thể được chia thành các tập hợp con nhất định mô tả chính xác hơn:

  1. đóng cửa đầy đủ
  2. đóng một phần
  3. đóng bởi một đối diện
  4. đóng cửa bởi một điểm dừng lỗ
  5. đóng cửa bởi một lợi nhuận
  6. Vân vân.

Tất cả các thuộc tính này vốn có của một sự kiện “đóng vị trí”, có nghĩa là mã sự kiện sẽ chứa tất cả các dữ liệu này. Để tạo sự kiện bằng cờ, hãy tạo hai bảng liệt kê mới trong tệp Defines.mqh từ thư mục gốc của thư viện ( cờ sự kiện giao dịch và các sự kiện giao dịch có thể ) trên tài khoản mà chúng ta sẽ theo dõi:

An toàn & bảo mật vốn đầu tư tại HotForex
//+------------------------------------------------------------------+
//| List of trading event flags on the account                       |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT_FLAGS
  {
   TRADE_EVENT_FLAG_NO_EVENT        =  0,                   // No event
   TRADE_EVENT_FLAG_ORDER_PLASED    =  1,                   // Pending order placed
   TRADE_EVENT_FLAG_ORDER_REMOVED   =  2,                   // Pending order removed
   TRADE_EVENT_FLAG_ORDER_ACTIVATED =  4,                   // Pending order activated by price
   TRADE_EVENT_FLAG_POSITION_OPENED =  8,                   // Position opened
   TRADE_EVENT_FLAG_POSITION_CLOSED =  16,                  // Position closed
   TRADE_EVENT_FLAG_ACCOUNT_BALANCE =  32,                  // Balance operation (clarified by a deal type)
   TRADE_EVENT_FLAG_PARTIAL         =  64,                  // Partial execution
   TRADE_EVENT_FLAG_BY_POS          =  128,                 // Executed by opposite position
   TRADE_EVENT_FLAG_SL              =  256,                 // Executed by StopLoss
   TRADE_EVENT_FLAG_TP              =  512                  // Executed by TakeProfit
  };
//+------------------------------------------------------------------+
//| List of possible trading events on the account                   |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT,                                    // No trading event
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Pending order placed
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Pending order removed
//--- enumeration members matching the ENUM_DEAL_TYPE enumeration members
   TRADE_EVENT_ACCOUNT_CREDIT,                              // Charging credit
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Additional charges
   TRADE_EVENT_ACCOUNT_CORRECTION,                          // Correcting entry
   TRADE_EVENT_ACCOUNT_BONUS,                               // Charging bonuses
   TRADE_EVENT_ACCOUNT_COMISSION,                           // Additional commissions
   TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                     // Commission charged at the end of a day
   TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                   // Commission charged at the end of a month
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,               // Agent commission charged at the end of a trading day
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,             // Agent commission charged at the end of a month
   TRADE_EVENT_ACCOUNT_INTEREST,                            // Accrual of interest on free funds
   TRADE_EVENT_BUY_CANCELLED,                               // Canceled buy deal
   TRADE_EVENT_SELL_CANCELLED,                              // Canceled sell deal
   TRADE_EVENT_DIVIDENT,                                    // Accrual of dividends
   TRADE_EVENT_DIVIDENT_FRANKED,                            // Accrual of franked dividend
   TRADE_EVENT_TAX,                                         // Tax accrual
//--- members of enumeration related to the DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration
   TRADE_EVENT_ACCOUNT_BALANCE_REFILL,                      // Replenishing account balance
   TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL,                  // Withdrawing funds from an account
//---
   TRADE_EVENT_PENDING_ORDER_ACTIVATED,                     // Pending order activated by price
   TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             // Pending order partially activated by price
   TRADE_EVENT_POSITION_OPENED,                             // Position opened
   TRADE_EVENT_POSITION_OPENED_PARTIAL,                     // Position opened partially
   TRADE_EVENT_POSITION_CLOSED,                             // Position closed
   TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     // Position closed partially
   TRADE_EVENT_POSITION_CLOSED_BY_POS,                      // Position closed by an opposite one
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              // Position partially closed by an opposite one
   TRADE_EVENT_POSITION_CLOSED_BY_SL,                       // Position closed by StopLoss
   TRADE_EVENT_POSITION_CLOSED_BY_TP,                       // Position closed by TakeProfit
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               // Position closed partially by StopLoss
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               // Position closed partially by TakeProfit
   TRADE_EVENT_POSITION_REVERSED,                           // Position reversal (netting)
   TRADE_EVENT_POSITION_VOLUME_ADD                          // Added volume to position (netting)
  };
//+------------------------------------------------------------------+

Ở đây chúng ta nên làm rõ một số điều khoản liên quan đến phép liệt kê ENUM_TRADE_EVENT .
Vì một số sự kiện giao dịch không yêu cầu sự can thiệp của chương trình hoặc con người (tính phí hoa hồng, phí, tiền thưởng, v.v.), chúng ta sẽ lấy dữ liệu đó từ loại giao dịch ( bảng liệt kê ENUM_DEAL_TYPE ) trong MQL5. Để đơn giản hóa việc theo dõi sự kiện sau này, chúng ta cần làm cho nó để các sự kiện của chúng ta khớp với giá trị của phép liệt kê ENUM_DEAL_TYPE.
Chúng ta sẽ chia hoạt động số dư thành hai sự kiện: bổ sung số dư tài khoản và rút tiền . Các sự kiện khác từ bảng liệt kê loại giao dịch, bắt đầu từ DEAL_TYPE_CREDIT, có cùng các giá trị như trong bảng liệt kê ENUM_DEAL_TYPE ngoại trừ việc mua và bán (DEAL_TYPE_BUY và DEAL_TYPE_SELL) không liên quan đến hoạt động cân bằng.

Hãy cải thiện lớp đơn đặt hàng thị trường và bộ sưu tập vị trí.

Chúng ta sẽ thêm một đơn đặt hàng mẫu vào phần riêng của lớp CMarketCollection để thực hiện tìm kiếm theo các thuộc tính đơn hàng được chỉ định, trong khi phần công khai của lớp sẽ nhận được các phương thức để lấy danh sách đầy đủ các đơn hàng và vị trí, danh sách các đơn hàng và vị trí đã chọn theo một phạm vi thời gian được chỉ định và liệt kê các đơn hàng và vị trí trả về được chọn theo một tiêu chí được chỉ định từ các thuộc tính số nguyên, thực và chuỗi của một đơn hàng hoặc một vị trí. Điều này sẽ cho phép chúng ta nhận được danh sách cần thiết của các lệnh và vị trí thị trường từ bộ sưu tập (như đã được thực hiện cho việc thu thập các đơn đặt hàng và giao dịch lịch sử trong phần 2 và phần 3 của mô tả thư viện).

//+------------------------------------------------------------------+
//| Collection of market orders and positions                        |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Hash sum of all orders and positions on the account
      int            total_pending;          // Number of pending orders on the account
      int            total_positions;        // Number of positions on the account
      double         total_volumes;          // Total volume of orders and positions on the account
     };
   MqlDataCollection m_struct_curr_market;   // Current data on market orders and positions on the account
   MqlDataCollection m_struct_prev_market;   // Previous data on market orders and positions on the account
   CArrayObj         m_list_all_orders;      // List of pending orders and positions on the account
   COrder            m_order_instance;       // Order object for searching by property
   bool              m_is_trade_event;       // Trading event flag
   bool              m_is_change_volume;     // Total volume change flag
   double            m_change_volume_value;  // Total volume change value
   int               m_new_positions;        // Number of new positions
   int               m_new_pendings;         // Number of new pending orders
   //--- Save the current values of the account data status as previous ones
   void              SavePrevValues(void)                                                                { this.m_struct_prev_market=this.m_struct_curr_market;                  }
public:
   //--- Return the list of all pending orders and open positions
   CArrayObj*        GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- Return the list of orders and positions with an open time from begin_time to end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
   //--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   //--- Return the number of (1) new pending orders, (2) new positions, (3) occurred trading event flag, (4) changed volume
   int               NewOrders(void)                                                            const    { return this.m_new_pendings;                                           }
   int               NewPosition(void)                                                          const    { return this.m_new_positions;                                          }
   bool              IsTradeEvent(void)                                                         const    { return this.m_is_trade_event;                                         }
   double            ChangedVolumeValue(void)                                                   const    { return this.m_change_volume_value;                                    }
   //--- Constructor
                     CMarketCollection(void);
   //--- Update the list of pending orders and positions
   void              Refresh(void);
  };
//+------------

Thực hiện phương thức chọn thứ tự và vị trí theo thời gian ngoài thân lớp:

//+------------------------------------------------------------------------+
//| Select market orders or positions from the collection with the time    |
//| within the range from begin_time to end_time                           |
//+------------------------------------------------------------------------+
CArrayObj* CMarketCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
  {
   CArrayObj* list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN,TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME :end_time);
   list.FreeMode(false);
   ListStorage.Add(list);
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,begin);
   int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,end);
   int index_end=m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------

Phương thức này gần giống với phương pháp chọn đơn hàng và giao dịch lịch sử theo thời gian chúng ta đã mô tả trong Phần 3. Đọc lại mô tả trong phần thích hợp của bài viết thứ ba nếu cần thiết. Sự khác biệt giữa phương thức này và một trong các lớp bộ sưu tập lịch sử là chúng ta không chọn thứ tự thời gian sẽ được chọn theo. Lệnh thị trường và vị trí chỉ có thời gian mở.

Ngoài ra, thay đổi thứ tự lịch sử và xây dựng lớp của bộ sưu tập thỏa thuận. Hệ thống đặt hàng MQL5 không có khái niệm thời gian đóng – tất cả các đơn đặt hàng và giao dịch được sắp xếp trong danh sách theo thời gian vị trí của chúng (hoặc thời gian mở theo hệ thống đặt hàng MQL4). Để làm điều này, thay đổi chuỗi xác định hướng sắp xếp trong danh sách tập hợp các đơn đặt hàng và giao dịch lịch sử trong hàm tạo của lớp CHistoryCollection:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHistoryCollection::CHistoryCollection(void) :m_index_deal(0),m_delta_deal(0),m_index_order(0),m_delta_order(0),m_is_trade_event(false)
  {
   this.m_list_all_orders.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN #else SORT_BY_ORDER_TIME_CLOSE #endif );
   this.m_list_all_orders.Clear();
  }
//+------------------------------------------------

Bây giờ trong MQL5 , tất cả các đơn đặt hàng và giao dịch trong bộ sưu tập đơn hàng và giao dịch lịch sử sẽ được sắp xếp theo thời gian vị trí theo mặc định, trong khi trong MQL4 , chúng sẽ được sắp xếp theo thời gian đóng được xác định trong thuộc tính đơn hàng.

Bây giờ hãy chuyển sang một tính năng khác của hệ thống đặt hàng MQL5. Khi đóng một vị trí bằng một vị trí đối diện, một lệnh đóng đặc biệt của loại ORDER_TYPE_CLOSE_BY được đặt, trong khi khi đóng một vị trí theo lệnh dừng, thay vào đó, một lệnh thị trường để đóng một vị trí.
Để xem xét các đơn đặt hàng thị trường, chúng ta đã phải thêm một thuộc tính khác vào các phương thức nhận và trả lại các thuộc tính số nguyên của đơn hàng cơ sở (sử dụng nhận và trả lại số ma thuật đơn hàng làm ví dụ):

//+------------------------------------------------------------------+
//| Return the magic number                                          |
//+------------------------------------------------------------------+
long COrder::OrderMagicNumber() const
  {
#ifdef __MQL4__
   return ::OrderMagicNumber();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   :res=::PositionGetInteger(POSITION_MAGIC);           break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    :res=::OrderGetInteger(ORDER_MAGIC);                 break;
      case ORDER_STATUS_DEAL              :res=::HistoryDealGetInteger(m_ticket,DEAL_MAGIC);   break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     :res=::HistoryOrderGetInteger(m_ticket,ORDER_MAGIC); break;
      default                             :res=0;                                              break;
     }
   return res;
#endif
  }
//+--------------------------------------------------------------

Tôi đã thực hiện các thay đổi như vậy (hoặc các thay đổi tương ứng một cách hợp lý với phương thức) trong tất cả các phương thức nhận và trả về các thuộc tính số nguyên của thứ tự cơ sở nơi trạng thái này thực sự cần phải được tính đến. Vì trạng thái như vậy tồn tại, hãy tạo một lớp mới của thứ tự thị trường CMarketOrder trong thư mục Đối tượng của thư viện để lưu trữ các loại đơn đặt hàng như vậy. Lớp này hoàn toàn giống với phần còn lại được tạo trước đây và các đối tượng giao dịch và trật tự lịch sử, vì vậy ở đây tôi sẽ chỉ cung cấp danh sách:

//+------------------------------------------------------------------+
//|                                                  MarketOrder.mqh |
//|                                   Copyright 2019, Forex365 Corp. |
//|                                              https://forex365.vn |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Forex365 Corp"
#property link      "https://forex365.vn"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Market order                                                     |
//+------------------------------------------------------------------+
class CMarketOrder :public COrder
  {
public:
   //--- Constructor
                     CMarketOrder(const ulong ticket=0) :COrder(ORDER_STATUS_MARKET_ORDER,ticket) {}
   //--- Supported order properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed                   |
//| integer property, otherwise, return 'false'                      |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP          || 
      property==ORDER_PROP_DEAL_ENTRY        || 
      property==ORDER_PROP_TIME_UPDATE       || 
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_TIME_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed                   |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PROFIT            || 
      property==ORDER_PROP_PROFIT_FULL       || 
      property==ORDER_PROP_SWAP              || 
      property==ORDER_PROP_COMMISSION        ||
      property==ORDER_PROP_PRICE_CLOSE       ||
      property==ORDER_PROP_SL                ||
      property==ORDER_PROP_TP                ||
      property==ORDER_PROP_PRICE_STOP_LIMIT
     ) return false;
   return true;
  }
//+------------------------------

Trong tệp Defines.mqh của thư viện, hãy viết trạng thái mới – thứ tự thị trường:

//+------------------------------------------------------------------+
//| Abstract order type (status)                                     |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATUS
  {
   ORDER_STATUS_MARKET_PENDING,                             // Market pending order
   ORDER_STATUS_MARKET_ORDER,                               // Market order
   ORDER_STATUS_MARKET_POSITION,                            // Market position
   ORDER_STATUS_HISTORY_ORDER,                              // Historical market order
   ORDER_STATUS_HISTORY_PENDING,                            // Removed pending order
   ORDER_STATUS_BALANCE,                                    // Balance operation
   ORDER_STATUS_CREDIT,                                     // Credit operation
   ORDER_STATUS_DEAL,                                       // Deal
   ORDER_STATUS_UNKNOWN                                     // Unknown status
  };
//+------------------------

Bây giờ, trong khối để thêm đơn đặt hàng vào danh sách (phương thức Refresh() của lớp CMarketCollection để cập nhật danh sách các đơn hàng và vị trí thị trường), hãy thực hiện kiểm tra loại đơn đặt hàng . Tùy thuộc vào loại, thêm đối tượng thứ tự thị trường hoặc đối tượng đơn hàng đang chờ xử lý vào danh sách bộ sưu tập:

//+------------------------------------------------------------------+
//| Update the order list                                            |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)
  {
   ::ZeroMemory(this.m_struct_curr_market);
   this.m_is_trade_event=false;
   this.m_is_change_volume=false;
   this.m_new_pendings=0;
   this.m_new_positions=0;
   this.m_change_volume_value=0;
   m_list_all_orders.Clear();
#ifdef __MQL4__
   int total=::OrdersTotal();
   for(int i=0; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS)) continue;
      long ticket=::OrderTicket();
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketPosition *position=new CMarketPosition(ticket);
         if(position==NULL) continue;
         if(this.m_list_all_orders.InsertSort(position))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_positions++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
            delete position;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- MQ5
#else 
//--- Positions
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      if(this.m_list_all_orders.InsertSort(position))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)::PositionGetInteger(POSITION_TIME_UPDATE_MSC);
         this.m_struct_curr_market.total_volumes+=::PositionGetDouble(POSITION_VOLUME);
         this.m_struct_curr_market.total_positions++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
         delete position;
        }
     }
//--- Orders
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketOrder *order=new CMarketOrder(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
            this.m_struct_curr_market.total_market++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить маркет-ордер в список","Failed to add market order to list"));
            delete order;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
            this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL);
            this.m_struct_curr_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить отложенный ордер в список","Failed to add pending order to list"));
            delete order;
           }
        }
     }
#endif 
//--- First launch
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();
     }
//--- If the hash sum of all orders and positions changed
   if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc)
     {
      this.m_new_market=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market;
      this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending;
      this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions;
      this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4);
      this.m_is_change_volume=(this.m_change_volume_value!=0 ? true :false);
      this.m_is_trade_event=true;
      this.SavePrevValues();
     }
  }
//+---------------------------------------

Để có thể xem xét các đơn đặt hàng của loại ORDER_TYPE_CLOSE_BY , hãy thêm loại đơn đặt hàng này vào khối định nghĩa loại đơn đặt hàng trong phương thức cập nhật danh sách lịch sử và giao dịch của lớp CHistoryCollection để các đơn hàng đó được đưa vào bộ sưu tập. Không có điều đó, đối tượng cơ sở của thư viện CEngine không thể xác định rằng một vị trí được đóng bởi một vị trí đối diện:

//+------------------------------------------------------------------+
//| Update the list of orders and deals                              |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- Closed positions and balance/credit operations
      if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         //--- Removed pending orders
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ? true :false);
//--- __MQL5__
#else 
   if(!::HistorySelect(0,END_TIME)) return;
//--- Orders
   int total_orders=::HistoryOrdersTotal(),i=m_index_order;
   for(; i<total_orders; i++)
     {
      ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
        {
         CHistoryOrder *order=new CHistoryOrder(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         CHistoryPending *order=new CHistoryPending(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- save the index of the last added order and the difference as compared to the previous check
   int delta_order=i-this.m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;

//--- Deals
   int total_deals=::HistoryDealsTotal(),j=m_index_deal;
   for(; j<total_deals; j++)
     {
      ulong deal_ticket=::HistoryDealGetTicket(j);
      if(deal_ticket==0) continue;
      CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
      if(deal==NULL) continue;
      this.m_list_all_orders.InsertSort(deal);
     }
//---save the index of the last added deal and the difference as compared to the previous check
   int delta_deal=j-this.m_index_deal;
   this.m_index_deal=j;
   this.m_delta_deal=delta_deal;
//--- Set the new event flag in history
   this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif 
  }
//+--------------------------------

Khi kiểm tra lớp CEngine để xác định các sự kiện tài khoản xảy ra, tôi đã phát hiện và sửa một số lỗi nhỏ trong các phương thức dịch vụ. Không có điểm nào để mô tả chúng ở đây vì chúng không ảnh hưởng đến hiệu suất, trong khi mô tả của chúng chuyển hướng sự chú ý khỏi sự phát triển của một chức năng thư viện quan trọng. Tất cả các thay đổi đã được thực hiện cho danh sách lớp. Bạn có thể nhìn thấy chúng trong các tệp thư viện đính kèm bên dưới.

Hãy tiếp tục công việc của chúng ta về định nghĩa sự kiện.

Sau khi gỡ lỗi định nghĩa sự kiện giao dịch, tất cả các sự kiện đã xảy ra sẽ được đóng gói vào một biến thành viên lớp duy nhất được thực hiện dưới dạng một tập hợp các cờ. Phương thức đọc dữ liệu từ biến để phân tách giá trị của nó thành các thành phần đặc trưng cho một sự kiện cụ thể sẽ được tạo sau đó.

Thêm biến thành viên lớp để lưu trữ mã sự kiện giao dịch, các phương thức xác minh sự kiện giao dịch để bảo hiểm rủi ro và tạo lưới cho các tài khoản và phương thức trả lại các đối tượng đặt hàng cần thiết cho phần riêng của lớp CEngine.

Trong phần công khai, hãy khai báo các phương thức trả về danh sách các vị trí thị trường và các lệnh chờ, lệnh và giao dịch thị trường lịch sử, phương thức trả về mã sự kiện giao dịch từ biến m_trade_event_code và phương thức trả lại cờ tài khoản hedge.

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine :public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   int                  m_trade_event_code;              // Account trading event status code
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
//--- Return the first launch flag
   bool                 IsFirstStart(void);
//--- Working with (1) hedging and (2) netting collections
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(void);
//--- Return the last (1) market pending order, (2) market order, (3) last position, (4) position by ticket
   COrder*              GetLastMarketPending(void);                    
   COrder*              GetLastMarketOrder(void);                      
   COrder*              GetLastPosition(void);                         
   COrder*              GetPosition(const ulong ticket);               
//--- Return the last (1) removed pending order, (2) historical market order, (3) historical market order by its ticket
   COrder*              GetLastHistoryPending(void);                   
   COrder*              GetLastHistoryOrder(void);                     
   COrder*              GetHistoryOrder(const ulong ticket);           
//--- Return the (1) first and the (2) last historical market orders from the list of all position orders, (3) the last deal
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id); 
   COrder*              GetLastDeal(void);                             
public:
   //--- Return the list of market (1) positions, (2) pending orders and (3) market orders
   CArrayObj*           GetListMarketPosition(void);                     
   CArrayObj*           GetListMarketPendings(void);                     
   CArrayObj*           GetListMarketOrders(void);                       
   //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id
   CArrayObj*           GetListHistoryOrders(void);                      
   CArrayObj*           GetListHistoryPendings(void);                    
   CArrayObj*           GetListHistoryDeals(void);                       
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Return the (1) trading event code and (2) hedge account flag
   int                  TradeEventCode(void)             const { return this.m_trade_event_code;   }
   bool                 IsHedge(void)                    const { return this.m_is_hedge;           }
//--- Create the timer account
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Khởi tạo mã sự kiện giao dịch trong danh sách khởi tạo của hàm tạo của lớp.

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() :m_first_start(true),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

Thực hiện các phương thức khai báo bên ngoài thân lớp:

//+------------------------------------------------------------------+
//| Return the list of market positions                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of market pending orders                         |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPendings(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of market orders                                 |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketOrders(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of historical orders                             |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryOrders(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of removed pending orders                        |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryPendings(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of deals                                         |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryDeals(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//|  Return the list of all position orders                          |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the last position                                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return position by ticket                                        |
//+------------------------------------------------------------------+
COrder* CEngine::GetPosition(const ulong ticket)
  {
   CArrayObj* list=this.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the last deal                                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastDeal(void)
  {
   CArrayObj* list=this.GetListHistoryDeals();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the last market pending order                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketPending(void)
  {
   CArrayObj* list=this.GetListMarketPendings();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the last historical pending order                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryPending(void)
  {
   CArrayObj* list=this.GetListHistoryPendings();
   if(list==NULL) return NULL;
   list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the last market order                                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketOrder(void)
  {
   CArrayObj* list=this.GetListMarketOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the last historical market order                          |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryOrder(void)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return historical market order by its ticket                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the first historical market order                         |
//| from the list of all position orders                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(0);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+
//| Return the last historical market order                          |
//| from the list of all position orders                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+

Hãy xem cách các danh sách thu được bằng cách sử dụng ví dụ sau:

//+------------------------------------------------------------------+
//| Return the list of market positions                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Tất cả đều khá đơn giản và thuận tiện:nhận danh sách đầy đủ các vị trí từ bộ sưu tập các lệnh và vị trí thị trường bằng phương thức thu thập GetList(). Sau đó, chọn một đơn hàng có trạng thái ‘vị trí’ từ nó bằng cách sử dụng phương thức chọn đơn hàng theo một thuộc tính được chỉ định từ lớp CSelect. Phương pháp được mô tả trong bài viết thứ ba của mô tả thư viện. Trả về danh sách thu được.
Danh sách có thể trống (NULL), do đó, kết quả được trả về bởi phương thức này phải được kiểm tra trong chương trình gọi.

Hãy xem cách nhận đơn đặt hàng cần thiết bằng ví dụ sau:

//+------------------------------------------------------------------+
//| Return the last position                                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order :NULL);
  }
//+------------------------------------------------------------------+

Đầu tiên, nhận danh sách các vị trí bằng phương thức GetListMarketP vị trí() được mô tả ở trên. Nếu danh sách trống, trả về NULL. Tiếp theo, sắp xếp danh sách theo thời gian mở tính bằng mili giây (vì chúng ta sẽ nhận được vị trí mở cuối cùng, danh sách sẽ được sắp xếp theo thời gian) và chọn thứ tự cuối cùng trong đó. Kết quả là, trả lại một đơn đặt hàng thu được từ danh sách cho chương trình gọi.

Kết quả tìm kiếm một đơn hàng được trả về bởi phương thức có thể trống (không tìm thấy thứ tự) và bằng NULL. Do đó, hãy kiểm tra kết quả thu được cho NULL trước khi truy cập nó.

Như bạn có thể thấy, mọi thứ đều nhanh chóng và dễ dàng. Chúng ta có thể xây dựng bất kỳ phương pháp nào để nhận bất kỳ dữ liệu nào từ bất kỳ danh sách bộ sưu tập hiện có nào, cũng như các phương pháp chúng ta sẽ tạo trong tương lai. Điều này cung cấp cho chúng ta sự linh hoạt hơn trong việc sử dụng các danh sách như vậy.

Các phương thức nhận danh sách được đặt trong phần lớp công khai cho phép chúng ta nhận bất kỳ dữ liệu nào từ danh sách trong các chương trình tùy chỉnh như đã thực hiện với các phương thức được xem xét ở trên.

Các phương thức nhận đơn đặt hàng cần thiết được ẩn trong phần riêng tư. Chúng chỉ cần trong lớp CEngine cho các nhu cầu nội bộ – đặc biệt, để có được dữ liệu về các đơn đặt hàng, giao dịch và vị trí cuối cùng. Trong các chương trình tùy chỉnh, chúng ta có thể thực hiện các chức năng tùy chỉnh để nhận đơn đặt hàng cụ thể theo các thuộc tính được chỉ định (các ví dụ được hiển thị ở trên).

Để dễ dàng có được bất kỳ dữ liệu nào từ các bộ sưu tập, cuối cùng chúng ta sẽ tạo ra một loạt các chức năng cho người dùng cuối.

Điều này sẽ được thực hiện trong các bài viết cuối cùng dành cho làm việc với các bộ sưu tập đơn hàng.

Bây giờ hãy thực hiện phương pháp kiểm tra các sự kiện giao dịch.

Hiện tại, nó chỉ hoạt động trên các tài khoản bảo hiểm rủi ro cho MQL5. Sau đó, tôi sẽ phát triển các phương pháp kiểm tra các sự kiện giao dịch cho các tài khoản lưới MQL5 và cho MQL4. Để kiểm tra hoạt động của phương thức, nó có tính năng hiển thị kết quả kiểm tra và sự kiện. Khả năng này sẽ bị loại bỏ sau đó, vì chức năng này sẽ được thực hiện bằng một phương thức khác sẽ được tạo sau khi gỡ lỗi phương thức kiểm tra các sự kiện giao dịch trên tài khoản.

//+------------------------------------------------------------------+
//| Check trading events (hedging)                                   |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- Initialize the trading events code and flag
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Actions during the first launch
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the market state dynamics and account history
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- If an event is only in market orders and positions
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
      //--- If the number of pending orders increased
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- Add the flag for installing a pending order
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
         string text=TextByLanguage("Установлен отложенный ордер:","Pending order placed:");
         //--- Take the last market pending order
         COrder* order=this.GetLastMarketPending();
         if(order!=NULL)
           {
            //--- add the order ticket to the message
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Add the message to the journal
         Print(DFUN,text);
        }
      //--- If the number of market orders increased
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- do not add the event flag
         //--- ...
         string text=TextByLanguage("Выставлен маркет-ордер:","Market order placed:");
         //--- Take the last market order
         COrder* order=this.GetLastMarketOrder();
         if(order!=NULL)
           {
            //--- add the order ticket to the message
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Add the message to the journal
         Print(DFUN,text);
        }
     }
   
//--- If an event is only in historical orders and deals
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
      //--- If a new deal appeared
      if(this.m_history.NewDeals()>0)
        {
         //--- Add the account balance event flag
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         string text=TextByLanguage("Новая сделка:","New deal:");
         //--- Take the last deal
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- add its description to the text
            text+=deal.TypeDescription();
            //--- if the deal is a balance operation
            if((ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE)==DEAL_TYPE_BALANCE)
              {
              //--- check the deal profit and add the event (adding or withdrawing funds) to the message
               text+=(deal.Profit()>0 ? TextByLanguage(":Пополнение счёта:",":Account Recharge:") :TextByLanguage(":Вывод средств:",":Withdrawal:"))+::DoubleToString(deal.Profit(),(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS));
              }
           }
         //--- Display the message in the journal
         Print(DFUN,text);
        }
     }
   
//--- If the events are in market and historical orders and positions
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новые торговые события на счёте и в истории счёта","New trading events on account and in account history"));
      
      //--- If the number of pending orders decreased and no new deals appeared
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- Add the flag for removing a pending order
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
         string text=TextByLanguage("Удалён отложенный ордер:","Removed pending order:");
         //--- Take the last historical pending order
         COrder* order=this.GetLastHistoryPending();
         if(order!=NULL)
           {
            //--- add the ticket to the appropriate message
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Display the message in the journal
         Print(DFUN,text);
        }
      
      //--- If there is a new deal and a new historical order
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- Take the last deal
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- In case of a market entry deal
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- Add the position opening flag
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               string text=TextByLanguage("Открыта позиция:","Position opened:");
               //--- If the number of pending orders decreased
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- Add the pending order activation flag
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  text=TextByLanguage("Сработал отложенный ордер:","Pending order activated:");
                 }
               //--- Take the order's ticket from the order
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- If the current order volume exceeds zero
                  if(order.VolumeCurrent()>0)
                    {
                     //--- Add the partial execution flag
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично открыта позиция:","Position partially open:");
                    }
                  //--- add the order direction to the message
                  text+=order.DirectionDescription();
                 }
               //--- add the position ticket from the deal to the message and display the message in the journal
               text+=" #"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- In case of a market exit deal
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- Add the position closing flag
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               string text=TextByLanguage("Закрыта позиция:","Position closed:");
               //--- Take the deal's order ticket from the deal
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- If the deal's position is still present in the market
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Add the partial execution flag
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично закрыта позиция:","Partially closed position:");
                    }
                  //--- Otherwise, if the position is fully closed
                  else
                    {
                     //--- If the order has the flag for closing by StopLoss
                     if(order.IsCloseByStopLoss())
                       {
                        //--- Add the flag for closing by StopLoss
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                        text=TextByLanguage("Позиция закрыта по StopLoss:","Position closed by StopLoss:");
                       }
                     //--- If the order has the flag for closing by TakeProfit
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- Add the the flag for closing by TakeProfit
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                        text=TextByLanguage("Позиция закрыта по TakeProfit:","Position closed by TakeProfit:");
                       }
                    }
                  //--- add reverse order direction to the message:
                  //--- closing Buy order for Sell position and closing Sell order for Buy position,
                  //--- therefore, reverse the order direction for correct description of a closed position
                  text+=(order.DirectionDescription()=="Sell" ? "Buy " :"Sell ");
                 }
               //--- add the ticket of the deal's position and display the message in the journal
               text+="#"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- When closing by an opposite position
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- Add the position closing flag
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Add the flag for closing by an opposite position
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               string text=TextByLanguage("Позиция закрыта встречной:","Position closed by opposite position:");
               //--- Take the deal order
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- add reverse order direction to the message:
                  //--- closing Buy order for Sell position and closing Sell order for Buy position,
                  //--- therefore, reverse the order direction for correct description of a closed position
                  text+=(order.DirectionDescription()=="Sell" ? "Buy" :"Sell");
                  text+=" #"+(string)order.PositionID()+TextByLanguage(" закрыта позицией #"," closed by position #")+(string)order.PositionByID();
                  //--- If the order position is still present in the market
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Add the partial execution flag
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text+=TextByLanguage(" частично"," partially");
                    }
                 }
               //--- Display the message in the journal
               Print(DFUN,text);
              }
           //--- end of the last deal processing block
           }
        }
     }
#endif 
  }
//+------------------------------------------------------------------+

Phương pháp đơn giản nhưng khá lớn. Do đó, mã có tất cả các kiểm tra và hành động tương ứng cho phép chúng ta thấy những gì xảy ra bên trong phương thức thuận tiện hơn. Hiện tại, phương thức này thực hiện kiểm tra tài khoản bảo hiểm rủi ro MQL5. Trên thực tế, mọi thứ bắt nguồn từ việc kiểm tra số lượng đơn đặt hàng và giao dịch mới xuất hiện trong lịch sử tài khoản hoặc trên thị trường. Để hiển thị trên tạp chí, dữ liệu được lấy từ vị trí cuối cùng, giao dịch cuối cùng, đơn hàng cuối cùng hoặc đơn hàng cuối cùng – tất cả điều này được thực hiện để hiển thị dữ liệu trong tạp chí để kiểm tra thực thi mã. Chức năng này sau đó sẽ bị xóa khỏi mã và được thay thế bằng một phương thức duy nhất được sử dụng trong các chương trình làm việc với thư viện.

Kiểm tra xử lý các sự kiện giao dịch

Hãy tạo một EA thử nghiệm để kiểm tra phương thức xác định các sự kiện giao dịch trên tài khoản.

Hãy thực hiện tập hợp các nút để quản lý các sự kiện mới.

Tập hợp các hành động cần thiết và các nút tương ứng như sau:

  • Open Buy position
  • Place a pending BuyLimit order
  • Place a pending BuyStop order
  • Place a pending BuyStopLimit order
  • Close Buy position
  • Close half of Buy position
  • Close Buy position by opposite Sell position
  • Open Sell position
  • Place a pending SellLimit order
  • Place a pending SellStop order
  • Place a pending SellStopLimit order
  • Close Sell position
  • Close half of Sell position
  • Close Sell position by opposite Buy position
  • Close all positions
  • Withdraw funds from the account

Tập hợp các đầu vào như sau:

  • Magic number – magic number
  • Lots – volume of opened positions
  • StopLoss in points
  • TakeProfit in points
  • Pending orders distance (points)
  • StopLimit orders distance (points)
    Lệnh StopLimit được đặt dưới dạng lệnh dừng ở khoảng cách so với giá được đặt bằng giá trị khoảng cách đặt hàng đang chờ xử lý.
    Ngay khi giá đạt đến lệnh được chỉ định và kích hoạt nó, mức giá đó được sử dụng để đặt lệnh giới hạn ở khoảng cách so với giá được đặt bởi giá trị khoảng cách của đơn đặt hàng StopLimit.
  • Slippage in points
  • Withdrawal funds (in tester) – Tiền được rút từ tài khoản trong chế độ tester

Chúng ta sẽ cần các hàm để tính toán các giá trị chính xác để xác định giá vị trí đặt hàng so với StopLevel, dừng đơn hàng và khối lượng vị trí. Ở giai đoạn này, hãy thêm các hàm vào thư viện các hàm dịch vụ trong tệp DELib.mqh miễn là chúng ta chưa có các lớp giao dịch và các lớp ký hiệu:

//+------------------------------------------------------------------+
//| Return the minimum symbol lot                                    |
//+------------------------------------------------------------------+
double MinimumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
  }
//+------------------------------------------------------------------+
//| Return the maximum symbol lot                                    |
//+------------------------------------------------------------------+
double MaximumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
  }
//+------------------------------------------------------------------+
//| Return the symbol lot change step                                |
//+------------------------------------------------------------------+
double StepLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP);
  }
//+------------------------------------------------------------------+
//| Return the normalized lot                                        |
//+------------------------------------------------------------------+
double NormalizeLot(const string symbol_name, double order_lots) 
  {
   double ml=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
   double mx=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
   double ln=NormalizeDouble(order_lots,int(ceil(fabs(log(ml)/log(10)))));
   return(ln<ml ? ml :ln>mx ? mx :ln);
  }
//+------------------------------------------------------------------+
//| Return correct StopLoss relative to StopLevel                    |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ? 
      NormalizeDouble(fmin(price-lv*pt,stop_loss),dg) :
      NormalizeDouble(fmax(price+lv*pt,stop_loss),dg)
     );
  }
//+------------------------------------------------------------------+
//| Return correct StopLoss relative to StopLevel                    |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) :
      NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| Return correct TakeProfit relative to StopLevel                  |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmax(price+lv*pt,take_profit),dg) :
      NormalizeDouble(fmin(price-lv*pt,take_profit),dg)
     );
  }
//+------------------------------------------------------------------+
//| Return correct TakeProfit relative to StopLevel                  |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      ::NormalizeDouble(::fmax(price+lv*pt,price+take_profit*pt),dg) :
      ::NormalizeDouble(::fmin(price-lv*pt,price-take_profit*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         : 
      case ORDER_TYPE_BUY_STOP_LIMIT   : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        : 
      case ORDER_TYPE_SELL_STOP_LIMIT  : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          : Print(DFUN,TextByLanguage("Не правильный тип ордера:","Invalid order type:"),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         : 
      case ORDER_TYPE_BUY_STOP_LIMIT   : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) :price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        : 
      case ORDER_TYPE_SELL_STOP_LIMIT  : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) :price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          : Print(DFUN,TextByLanguage("Не правильный тип ордера:","Invalid order type:"),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Check the stop level in points relative to StopLevel             |
//+------------------------------------------------------------------+
bool CheckStopLevel(const string symbol_name,const int stop_in_points,const int spread_multiplier)
  {
   return(stop_in_points>=StopLevel(symbol_name,spread_multiplier));
  }
//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int StopLevel(const string symbol_name,const int spread_multiplier)
  {
   int spread=(int)SymbolInfoInteger(symbol_name,SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread*spread_multiplier :stop_level);
  }
//+------------------------------------------------------------------+ 

Các hàm chỉ cần tính toán các giá trị chính xác để chúng không vi phạm các giới hạn được đặt trên máy chủ. Ngoài biểu tượng, hàm tính toán StopLevel còn nhận được hệ số nhân. Điều này được thực hiện bởi vì nếu StopLevel trên máy chủ được đặt thành 0, điều này có nghĩa là mức nổi và để tính toán StopLevel, chúng ta nên sử dụng giá trị trải rộng nhân với một số nhất định (thường là 2, nhưng cũng có thể 3). Hệ số nhân này được chuyển đến chức năng cho phép chúng ta mã hóa nó trong cài đặt EA hoặc tính toán nó.

Để tiết kiệm thời gian, chúng ta sẽ không viết các chức năng giao dịch tùy chỉnh. Thay vào đó, chúng ta sẽ sử dụng các lớp giao dịch được tạo sẵn của thư viện chuẩn, cụ thể là lớp CTrade để thực hiện các hoạt động giao dịch.
Để tạo các nút biểu đồ làm việc, chúng ta sẽ thực hiện một phép liệt kê với các thành viên, cài đặt tên nút, nhãn và giá trị để kiểm tra một nút nào đó.

Trong MQL5\Experts\TestDoEasy\Part04\ , tạo một EA mới có tên TestDo EAS04.mqh (kiểm tra trình xử lý sự kiện OnTimer và OnChartEvent trong Trình hướng dẫn MQL khi tạo EA):

Sau khi tạo mẫu EA bằng Trình hướng dẫn MQL, hãy bao gồm thư viện tùy chỉnh và lớp giao dịch của thư viện chuẩn vào đó. Ngoài ra, thêm các tham số đầu vào:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.mq5 |
//|                                   Copyright 2019, Forex365 Corp. |
//|                                              https://forex365.vn |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Forex365 Corp"
#property link      "https://forex365.vn"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>  
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input ulong    InpMagic       =  123;  // Magic number
input double   InpLots        =  0.1;  // Lots
input uint     InpStopLoss    =  50;   // StopLoss in points
input uint     InpTakeProfit  =  50;   // TakeProfit in points
input uint     InpDistance    =  50;   // Pending orders distance (points)
input uint     InpDistanceSL  =  50;   // StopLimit orders distance (points)
input uint     InpSlippage    =  0;    // Slippage in points
input double   InpWithdrawal  =  10;   // Withdrawal funds (in tester)
//--- global variables
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 :InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+

Ở đây chúng ta bao gồm đối tượng chính của thư viện CEngine và lớp giao dịch CTrade.
Tiếp theo, tạo bảng liệt kê chỉ định tất cả các nút cần thiết.

Trình tự các phương pháp liệt kê rất quan trọng vì nó đặt thứ tự tạo nút và vị trí của chúng trên biểu đồ.

Sau đó, khai báo cấu trúc để lưu trữ tên của một đối tượng đồ họa nút và một văn bản sẽ được ghi trên một nút.

Trong khối đầu vào, đặt tất cả các biến tham số EA được liệt kê ở trên. Trong khối biến toàn cục của EA, khai báo đối tượng thư viện, đối tượng lớp giao dịch, mảng cấu trúc nút và các biến mà các giá trị đầu vào trong trình xử lý OnInit() sẽ được gán cho:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Check account type
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- set global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- create buttons
   if(!CreateButtons())  
      return INIT_FAILED;
//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Trong trình xử lý OnInit(), kiểm tra loại tài khoản và, nếu nó không được bảo hiểm, hãy thông báo về điều đó và thoát khỏi chương trình có lỗi.
Tiếp theo, đặt tiền tố của tên đối tượng (để EA có thể nhận ra các đối tượng của nó) và điền vào mảng cấu trúc với dữ liệu nút trong một vòng lặp bằng số lượng nút.
Tên đối tượng nút được đặt làm tiền tố + biểu diễn chuỗi của phép liệt kê ENUM_BUTTONS tương ứng với chỉ số vòng lặp, trong khi văn bản nút được biên dịch bằng cách chuyển đổi biểu diễn chuỗi của phép liệt kê tương ứng với chỉ số vòng lặp bằng hàm EnumToButtText().

Rất nhiều vị trí mở và đơn đặt hàng được tính toán thêm. Vì một nửa vị trí đã đóng, nên lô của vị trí mở phải ít nhất gấp đôi số lượng tối thiểu. Do đó, lô tối đa được lấy ra từ hai:
1) được nhập vào các đầu vào, 2) lô tối thiểu nhân với hai trong chuỗi fmax (InpLots, MinimalLots (Ký hiệu()) * 2.0), giá trị của lô thu được được chuẩn hóa và được gán cho biến toàn cục của lô. Kết quả là, nếu lô được người dùng nhập vào đầu vào nhỏ hơn lô tối thiểu gấp đôi, lô tối thiểu kép được sử dụng. Mặt khác, lô được nhập bởi người dùng được áp dụng.

Các đầu vào còn lại được gán cho các biến toàn cục thích hợp và hàm CreatButtons() được gọi để tạo các nút từ mảng cấu trúc đã được điền với dữ liệu nút trong bước trước đó. Nếu việc tạo nút bằng chức năng ButtonCreate() không thành công, thông báo lỗi được hiển thị và hoạt động của chương trình kết thúc với lỗi khởi tạo.

Cuối cùng, lớp CTrade được khởi tạo:

//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
  • Đặt độ trượt theo điểm,
  • Đặt số ma thuật,
  • Đặt loại thực hiện đơn hàng theo các cài đặt của biểu tượng hiện tại,
  • Đặt chế độ tính toán ký quỹ theo cài đặt tài khoản hiện tại và
  • Đặt mức ghi nhật ký tin nhắn để chỉ hiển thị các thông báo lỗi trong tạp chí
  • (chế độ ghi nhật ký đầy đủ được tự động kích hoạt trong trình kiểm tra).

Trong trình xử lý OnDeinit(), thực hiện loại bỏ tất cả các nút bằng tiền tố tên đối tượng:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete objects
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+

Bây giờ hãy quyết định về thời gian khởi động thư viện và thứ tự khởi động xử lý sự kiện EA.

Nếu EA được khởi chạy không trong trình kiểm tra, bộ hẹn giờ thư viện sẽ được khởi chạy từ bộ hẹn giờ EA, trong khi bộ xử lý sự kiện hoạt động ở chế độ bình thường.
Nếu EA được khởi chạy trong trình kiểm tra, bộ định thời thư viện sẽ được khởi chạy từ trình xử lý OnTick() của EA. Các sự kiện nhấn nút cũng được theo dõi trong OnTick().

Trình xử lý OnTick(), OnTimer() và OnChartEvent() của EA:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }  
  }
//+------------------------------------------------------------------+

Trong OnTick()

  • Kiểm tra nơi EA được khởi chạy. Nếu nó được khởi chạy trong trình kiểm tra, hãy gọi trình xử lý OnTimer() của thư viện.
  • Tiếp theo, kiểm tra tên đối tượng bởi tất cả các đối tượng biểu đồ hiện tại trong vòng lặp. Nếu nó khớp với tên của một nút, trình xử lý nhấn nút được gọi.

Trong OnTimer()

  • Kiểm tra nơi EA được khởi chạy. Nếu nó không được khởi chạy trong trình kiểm tra, hãy gọi trình xử lý OnTimer() của thư viện.

Trong OnChartEvent()

  • Kiểm tra nơi EA được khởi chạy. Nếu nó được khởi chạy trong trình kiểm tra, thoát khỏi trình xử lý.
  • Tiếp theo, ID sự kiện được chọn và nếu đây là sự kiện nhấp vào một đối tượng đồ họa và tên đối tượng chứa văn bản thuộc về các nút, trình xử lý nhấn nút thích hợp sẽ được gọi.

CreateButtons() function:

//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 :0);
      if(i==TOTAL_BUTT-2) x=cx;
      y=(cy-(i-(i>6 ? 7 :0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? w :w*2+2),h,butt_data[i].text,(i<4 ? clrGreen :i>6 && i<11 ? clrRed :clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+

Trong chức năng này, tất cả bắt nguồn từ việc tính toán tọa độ và màu sắc của một nút trong một vòng lặp theo số lượng thành viên liệt kê ENUM_BUTTONS. Các tọa độ và màu sắc được tính toán dựa trên chỉ số vòng lặp cho biết số lượng thành viên liệt kê ENUM_BUTTONS. Sau khi tính toán tọa độ x và y, hàm tạo nút với tọa độ và giá trị màu được tính trong vòng lặp được gọi.

EnumToButtText() function:

//+------------------------------------------------------------------+
//| Convert enumeration into the button text                         |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   return txt;
  }
//+------------------------------------------------------------------+

Tất cả đều đơn giản ở đây:hàm nhận thành viên liệt kê và chuyển đổi nó thành một chuỗi loại bỏ văn bản không cần thiết. Tiếp theo, tất cả các ký tự của chuỗi thu được được chuyển đổi thành chữ thường và tất cả các mục không phù hợp được tiếp tục thay thế bằng các ký tự cần thiết.
Chuỗi liệt kê đầu vào được chuyển đổi thành văn bản cuối cùng được trả về.

Các chức năng tạo nút, đặt và nhận trạng thái của nó:

//+------------------------------------------------------------------+
//| Create the button                                                |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Return the button status                                         |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Set the button status                                            |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+

Mọi thứ đều đơn giản và rõ ràng ở đây, vì vậy không cần giải thích.

Chức năng xử lý các nút bấm:

//+------------------------------------------------------------------+
//| Handling buttons pressing                                        |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Convert button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- If the button is pressed
   if(ButtonState(button_name))
     {
      //--- If the BUTT_BUY button is pressed:Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Get correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Open Buy position
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- If the BUTT_BUY_LIMIT button is pressed:Place BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Get correct order placement relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Set BuyLimit order
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_BUY_STOP button is pressed:Set BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Set BuyStop order
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_BUY_STOP_LIMIT button is pressed:Set BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Get the correct BuyStop order placement price relative to StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Calculate BuyLimit order price relative to BuyStop level considering StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Set BuyStopLimit order
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- If the BUTT_SELL button is pressed:Open Sell position
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Get correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Open Sell position
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- If the BUTT_SELL_LIMIT button is pressed:Set SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Get correct order price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Get correct StopLoss and TakeProfit prices relative to order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Set SellLimit order
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_SELL_STOP button is pressed:Set SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Get the correct price of placing an order relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Set SellStop order
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_SELL_STOP_LIMIT button is pressed:Set SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Get the correct SellStop order price relative to StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Calculate SellLimit order price relative to SellStop level considering StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Set the SellStopLimit order
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- If the BUTT_CLOSE_BUY button is pressed:Close Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the Buy position index with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Get the Buy position ticket and close the position by the ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_BUY2 button is closed:Close the half of the Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the Buy position index with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calculate the closed volume and close the half of the Buy position by the ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- If the BUTT_CLOSE_BUY_BY_SELL button is pressed:Close Buy with the maximum profit by the opposite Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Get the list of all open positions
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Get the list of all open positions
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Select Sell positions only from the list
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Select Buy position with the maximum profit
            COrder* position_buy=list_buy.At(index_buy);
            //--- Select Sell position with the maximum profit
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Close Buy position by the opposite Sell position
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL button is pressed:Close Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Get the list of all opened positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Get the Sell position ticket and close the position by the ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL2 button is pressed:Close the half of the Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calculate the closed volume and close the half of the Sell position by the ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed:Close Sell with the maximum profit by the opposite Buy with the maximum profit  
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Get the list of all open positions
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Get only Sell positions from the list
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Get the list of all open positions
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Select the Sell position with the maximum profit
            COrder* position_sell=list_sell.At(index_sell);
            //--- Select the Buy position with the maximum profit
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Close the Sell position by the opposite Buy position
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_ALL button is pressed:Close all positions starting with the one with the least profit
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Receive the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Sort the list by profit considering commission and swap
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- In the loop from the position with the least profit
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- close each position by its ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_PROFIT_WITHDRAWAL button is pressed:Withdraw the funds from the account
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- If the program is launched in the tester
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Emulate funds withdrawal
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Wait for 1/10 of a second
      Sleep(100);
      //--- "Unpress" the button and redraw the chart
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Hàm này khá lớn nhưng đơn giản:nó nhận được tên của đối tượng nút được chuyển đổi thành ID chuỗi. Trạng thái nút được kiểm tra tiếp theo và nếu được nhấn, ID chuỗi được chọn. Nhánh if-other thích hợp được thực thi tính toán tất cả các cấp và áp dụng một điều chỉnh cần thiết để không vi phạm giới hạn StopLevel. Phương thức tương ứng của lớp giao dịch được thực thi.

Tất cả các giải thích được viết trực tiếp trong các bình luận chuỗi của mã.

Đối với EA thử nghiệm, chúng ta đã thực hiện các kiểm tra cần thiết tối thiểu bỏ qua tất cả các kiểm tra khác quan trọng đối với tài khoản thực. Hiện tại, điều quan trọng nhất đối với chúng ta là kiểm tra hoạt động của thư viện, thay vì phát triển một EA để làm việc với nó trên các tài khoản thực.

Danh sách đầy đủ của EA thử nghiệm:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.mq5 |
//|                                   Copyright 2019, Forex365 Corp. |
//|                                              https://forex365.vn |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Forex365 Corp"
#property link      "https://forex365.vn"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input ulong    InpMagic       =  123;  // Magic number
input double   InpLots        =  0.1;  // Lots
input uint     InpStopLoss    =  50;   // StopLoss in points
input uint     InpTakeProfit  =  50;   // TakeProfit in points
input uint     InpDistance    =  50;   // Pending orders distance (points)
input uint     InpDistanceSL  =  50;   // StopLimit orders distance (points)
input uint     InpSlippage    =  0;    // Slippage in points
input double   InpWithdrawal  =  10;   // Withdrawal funds (in tester)
//--- global variables
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 :InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Check account type
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- set global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- create buttons
   if(!CreateButtons())
      return INIT_FAILED;
//--- set trading parameters
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete objects
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
   if(engine.TradeEventCode()!=TRADE_EVENT_FLAG_NO_EVENT)
     {
      
      Print(DFUN,EnumToString((ENUM_TRADE_EVENT_FLAGS)engine.TradeEventCode()));
     }
   engine.TradeEventCode();
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }  
  }
//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 :0);
      if(i==TOTAL_BUTT-2) x=cx;
      y=(cy-(i-(i>6 ? 7 :0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? w :w*2+2),h,butt_data[i].text,(i<4 ? clrGreen :i>6 && i<11 ? clrRed :clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the button                                                |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Return the button status                                         |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Set the button status                                            |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+
//| Transform enumeration into the button text                       |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   return txt;
  }
//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Convert the button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- If the button is pressed
   if(ButtonState(button_name))
     {
      //--- If the BUTT_BUY button is pressed:Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Open Buy position
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- If the BUTT_BUY_LIMIT button is pressed:Set BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Set BuyLimit order
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_BUY_STOP button is pressed:Set BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Set BuyStop order
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_BUY_STOP_LIMIT button is pressed:Set BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Get the correct BuyStop price relative to StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Calculate BuyLimit order price relative to BuyStop placement level considering StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Get correct StopLoss and TakeProfit prices relative to order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Set BuyStopLimit order
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- If the BUTT_SELL button is pressed:Open Sell position
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Open Sell position
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- If the BUTT_SELL_LIMIT button is pressed:Set SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Set SellLimit order
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_SELL_STOP button is pressed:Set SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Set SellStop order
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_SELL_STOP_LIMIT button is pressed:Set SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Get the correct SellStop order price relative to StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Calculate SellLimit order price relative to SellStop level considering StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Set SellStopLimit order
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- If the BUTT_CLOSE_BUY button is pressed:Close Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Get the Buy position ticket and close the position by the ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_BUY2 button is pressed:Close the half of the Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calculate the closed volume and close the half of the Buy position by the ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- If the BUTT_CLOSE_BUY_BY_SELL button is pressed:Close Buy with the maximum profit by the opposite Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Get the list of all open positions
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Get the list of all open positions
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Select the Buy position with the maximum profit
            COrder* position_buy=list_buy.At(index_buy);
            //--- Select the Sell position with the maximum profit
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Close the Buy position by the opposite Sell one
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL button is pressed:Close Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Get the Sell position ticket and close the position by the ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL2 button is pressed:Close the half of the Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calculate the closed volume and close the half of the Sell position by the ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed:Close Sell with the maximum profit by the opposite Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Get the list of all open positions
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Get the list of all open positions
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Select the Sell position with the maximum profit
            COrder* position_sell=list_sell.At(index_sell);
            //--- Select the Buy position with the maximum profit
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Close the Sell position by the opposite Buy one
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_ALL is pressed:Close all positions starting with the one with the least profit
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Sort the list by profit considering commission and swap
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- In the loop from the position with the least profit
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- close each position by its ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_PROFIT_WITHDRAWAL button is pressed:Withdraw funds from the account
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- If the program is launched in the tester
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Emulate funds withdrawal
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Wait for 1/10 of a second
      Sleep(100);
      //--- "Unpress" the button and redraw the chart
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Mã trông khá dài… Tuy nhiên, đây mới chỉ là khởi đầu vì mọi thứ sẽ được đơn giản hóa cho người dùng cuối. Hầu hết các hành động chúng ta thực hiện ở đây là được ẩn trong mã thư viện, trong khi cơ chế tương tác thân thiện hơn với người dùng sẽ được giới thiệu.

Hãy khởi chạy EA trong trình kiểm tra và thử các nút:

Tất cả được kích hoạt chính xác và tạp chí nhận được thông báo về các sự kiện xảy ra.

Hiện tại, sự kiện cuối cùng luôn luôn được cố định. Nói cách khác, nếu chúng ta đóng nhiều vị trí cùng một lúc, chỉ vị trí cuối cùng trong số nhiều vị trí đóng sẽ tự tìm thấy trong sự kiện. Đóng cửa hàng loạt có thể được theo dõi bởi số lượng giao dịch hoặc đơn đặt hàng mới trong lịch sử. Sau đó có thể lấy danh sách tất cả các vị trí mới đóng theo số của chúng và xác định toàn bộ tập hợp của chúng. Hãy phát triển một lớp bộ sưu tập sự kiện riêng biệt cho điều đó. Nó sẽ cho phép chúng ta có quyền truy cập liên tục vào tất cả các sự kiện xảy ra trong chương trình.

Hiện tại, tất cả các thông báo sự kiện trong tạp chí người kiểm tra được hiển thị trong phương thức CEngine ::WorkWithHedgeCollections() của đối tượng cơ sở thư viện và chúng ta cần chương trình tùy chỉnh để biết mã sự kiện để ‘hiểu’ những gì đã xảy ra trên tài khoản. Điều này sẽ cho phép chúng ta hình thành logic phản hồi của chương trình tùy thuộc vào một sự kiện. Để kiểm tra khả năng đạt được điều đó, chúng ta sẽ tạo hai phương thức trong đối tượng cơ sở của thư viện. Một phương pháp là lưu trữ mã của sự kiện cuối cùng, trong khi phương pháp khác là giải mã mã này bao gồm một bộ cờ sự kiện.
Trong bài viết tiếp theo, chúng ta sẽ tạo một lớp đầy đủ để làm việc với các sự kiện tài khoản.

Trong phần thân lớp CEngine, xác định phương thức giải mã mã sự kiện và đặt mã sự kiện giao dịch của tài khoản, phương thức kiểm tra sự hiện diện của cờ sự kiện trong mã sự kiện, phương thức nhận sự kiện giao dịch cuối cùng từ chương trình gọi, cũng như Như phương thức đặt lại giá trị của sự kiện giao dịch cuối cùng:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine :public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   int                  m_trade_event_code;              // Account trading event status code
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Account trading event
//--- Decode the event code and set the trading event on the account
   void                 SetTradeEvent(void);
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
//--- Return the (1) first launch flag, (2) presence of the flag in the trading event
   bool                 IsFirstStart(void);
   bool                 IsTradeEventFlag(const int event_code)    const { return (this.m_trade_event_code&event_code)==event_code;  }
//--- Working with (1) hedging, (2) netting collections
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(void);
//--- Return the last (1) market pending order, (2) market order, (3) last position, (4) position by ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Return the last (1) removed pending order, (2) historical market order, (3) historical market order by its ticket
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Return the (1) first and the (2) last historical market orders from the list of all position orders, (3) the last deal
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- Return the list of market (1) positions, (2) pending orders and (3) market orders
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListHistoryDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Reset the last trading event
   void                 ResetLastTradeEvent(void)                       { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;  }
//--- Return the (1) last trading event, (2) trading event code, (3) hedge account flag
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;                }
   int                  TradeEventCode(void)                      const { return this.m_trade_event_code;               }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;                       }
//--- Create the timer counter
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Ngoài phần thân lớp, hãy viết phương thức giải mã một sự kiện giao dịch (viết tất cả các nội dung làm rõ trực tiếp vào mã):

//+------------------------------------------------------------------+
//| Decode the event code and set a trading event                    |
//+------------------------------------------------------------------+
void CEngine::SetTradeEvent(void)
  {
//--- No trading event. Exit
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_NO_EVENT)
      return;
//--- Pending order is set (check if the event code is matched since there can be only one flag here)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- Pending order is removed (check if the event code is matched since there can be only one flag here)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- Position is opened (Check for multiple flags in the event code)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- If this pending order is activated by the price
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED :TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED :TRADE_EVENT_POSITION_OPENED_PARTIAL);
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- Position is closed (Check for multiple flags in the event code)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- if the position is closed by StopLoss
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- check the partial closing flag and set the "Position closed by StopLoss" or "Position closed by StopLoss partially" trading event
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL :TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- if the position is closed by TakeProfit
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- check the partial closing flag and set the "Position closed by TakeProfit" or "Position closed by TakeProfit partially" trading event
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP :TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- if the position is closed by an opposite one
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- check the partial closing flag and set the "Position closed by opposite one" or "Position closed by opposite one partially" event
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS :TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- If the position is closed
      else
        {
         //--- check the partial closing flag and set the "Position closed" or "Position closed partially" event
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED :TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
     }
//--- Balance operation on the account (clarify the event by the deal type)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Initialize the trading event
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Take the last deal
      COrder* deal=this.GetLastDeal();
      if(deal!=NULL)
        {
         ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE);
         //--- if the deal is balance operation
         if(deal_type==DEAL_TYPE_BALANCE)
           {
           //--- check the deal profit and set the event (funds deposit or withdrawal)
            this.m_acc_trade_event=(deal.Profit()>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL :TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
           }
         //--- Remaining balance operation types match the ENUM_DEAL_TYPE enumeration starting with DEAL_TYPE_CREDIT
         else if(deal_type>DEAL_TYPE_BALANCE)
           {
           //--- set the event
            this.m_acc_trade_event=(ENUM_TRADE_EVENT)deal_type;
           }
        }
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
  }
//+------------------------------------------------------------------+

Thêm cuộc gọi của phương thức mã hóa sự kiện giao dịch và xóa hiển thị các mô tả sự kiện trong tạp chí ở phần cuối của phương thức WorkWithHedgeCollections() kiểm tra và tạo mã sự kiện giao dịch:

//+------------------------------------------------------------------+
//| Check trading events (hedging)                                   |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- Initialize trading event code and flags
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Actions during the first lanch
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the market status and account history changes
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- If an event relates only to market orders and positions
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      //--- If the number of pending orders increased
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- Add the pending order placement flag
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
        }
      //--- If the number of market orders increased
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- do not add the event flag
         //--- ...
        }
     }
   
//--- If an event relates only to historical orders and deals
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      //--- If a new deal appears
      if(this.m_history.NewDeals()>0)
        {
         //--- Add the flag of an account balance event
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
        }
     }
   
//--- If events are related to market and historical orders and positions
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      //--- If the number of pending orders decreased and no new deals appeared
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- Add the flag of removing a pending order
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
        }
      
      //--- If there is a new deal and a new historical order
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- Take the last deal
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- In case of a market entry deal
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- Add the position opening flag
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               //--- If the number of pending orders decreased
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- Add the flag of a pending order activation
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                 }
               //--- Take the order's ticket
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- If the current order volume exceeds zero
                  if(order.VolumeCurrent()>0)
                    {
                     //--- Add the partial execution flag
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
            
            //--- In case of a market exit deal
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- Add the position closing flag
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Take the deal's order ticket
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- If the deal position is still present on the market
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Add the partial execution flag
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                  //--- Otherwise, if the position is closed in full
                  else
                    {
                     //--- If the order has the flag of closing by StopLoss
                     if(order.IsCloseByStopLoss())
                       {
                        //--- Add the flag of closing by StopLoss
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                       }
                     //--- If the order has the flag of closing by TakeProfit
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- Add the flag of closing by TakeProfit
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                       }
                    }
                 }
              }
            
            //--- If closed by the opposite one
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- Add the position closing flag
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Add the flag of closing by the opposite one
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- Take the deal order
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- If the order position is still present on the market
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Add the partial execution flag
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
           //--- end of the last deal handling block
           }
        }
     }
#endif 
   this.SetTradeEvent();
  }
//+------------------------------------------------------------------+

Do đó, sau khi tạo mã sự kiện trong phương thức WorkWithHedgeCollections(), chúng ta gọi phương thức giải mã sự kiện. Điều gì khiến chúng ta không giải mã được nó ngay lập tức? Vấn đề là phương pháp giải mã hiện tại của chúng ta là tạm thời. Nó là cần thiết để kiểm tra quá trình giải mã. Một lớp sự kiện giao dịch chính thức sẽ được tạo ra trong các bài viết sắp tới.

Phương thức SetTradeEvent() xác định sự kiện giao dịch, ghi giá trị của nó vào biến thành viên lớp m_acc_trade_event, trong khi phương thức LastTradeEvent() và ResetLastTradeEvent() cho phép đọc giá trị của biến là sự kiện giao dịch cuối cùng trên tài khoản và đặt lại nó (tương tự Đến GetLastError()) trong chương trình gọi.

Trong trình xử lý OnTick() của EA thử nghiệm TestDo EASPart04.mqh, thêm các chuỗi để đọc sự kiện giao dịch cuối cùng và hiển thị nó như trong nhận xét biểu đồ:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
   if(engine.LastTradeEvent()!=last_event)                                  
     {                                                                      
      Comment("\nLast trade event:",EnumToString(engine.LastTradeEvent()));
      last_event=engine.LastTradeEvent();                                   
     }                                                                      
  }
//+------------------------------------------------------------------+

Bây giờ, nếu bạn chạy EA này trong trình kiểm tra và nhấp vào nút, các sự kiện giao dịch đang diễn ra từ phương thức CEngine ::SetTradeEvent() được hiển thị trên tạp chí, trong khi nhận xét biểu đồ hiển thị mô tả về sự kiện cuối cùng xảy ra trên tài khoản nhận được Bởi EA từ thư viện bằng phương thức engine.LastTradeEvent():

What’s next?

Trong bài viết tiếp theo, chúng ta sẽ phát triển các lớp đối tượng sự kiện và tập hợp các đối tượng sự kiện tương tự như bộ sưu tập các thứ tự và vị trí và dạy đối tượng thư viện cơ bản gửi các sự kiện đến chương trình.

Tất cả các tệp của phiên bản hiện tại của thư viện được đính kèm bên dưới cùng với các tệp EA thử nghiệm để bạn kiểm tra và tải xuống.
Để lại câu hỏi, ý kiến ​​và đề xuất của bạn trong các bình luận ngay dưới đây.

HotForex tặng thưởng 100% và miễn phí nạp rút tiền

BÌNH LUẬN

Vui lòng nhập bình luận của bạn
Vui lòng nhập tên của bạn ở đây

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.