![MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部): 取引イベント](https://c.mql5.com/i/articles/overlay.png)
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部): 取引イベント
内容
第1部では、MetaTrader 5とMetaTrader 4用のプログラムの開発を単純化するための大規模なクロスプラットフォームライブラリの作成を始めました。その後の記事では、ライブラリの開発を継続し、エンジンライブラリの基本オブジェクトと成行注文とポジションのコレクションを完成しました。本稿では、基本オブジェクトの開発を続け、口座での取引イベントが識別できるようにします。取引イベントをプログラムに送信する
3番目の記事の直近に作成されたテストEAを振り返ってみると、ライブラリは口座で発生した取引イベントを定義できることがわかります。ただし、操作にライブラリを使用しているプログラムにイベントを送信するには、発生するすべてのイベントをその種類で正確に分割する必要があります。
これには、イベントを定義するメソッドとイベントタイプを定義するメソッドを作成する必要があります。
どの取引イベントを特定する必要があるのか考えてみましょう。
- 未決注文を出す
- 未決注文を削除する
- 未決注文を発動してポジションを生成する
- 未決注文を部分的に発動してポジションを生成する
- ポジションを開く
- ポジションを閉じる
- ポジションを部分的に開く
- ポジションを部分的に閉じる
- 反対方向のポジションによってポジションを閉じる
- 反対方向のポジションによってポジションを部分的に閉じる
- 口座に入金する
- 口座から出金する
- 口座に残高操作が発生する
以下は現在追跡されていないイベントです。 - 未決注文が修正される(発動価格の変更、ストップロスとテイクプロフィットの追加/削除)
- ポジションが修正される(ストップロスとテイクプロフィットの追加/削除/変更)
上記に基づいて、イベントを明確に特定する方法を決定する必要があります。この時点で口座の種類に応じてソリューションを分割することをお勧めします。
ヘッジ口座
- 未決注文数が増加する: 未決注文の追加(市場環境でのイベント)
- 未決注文数が減少する
- ポジション数が増加する: 未決注文の発動(市場環境および履歴環境でのイベント)
- ポジション数が増加しない: 未決注文の削除(市場環境でのイベント)
- 未決注文数が減少しない
- ポジション数が増加する: 新規ポジションが開いた(市場環境および履歴環境でのイベント)
- ポジション数が減少する: ポジションが閉じた(市場環境および履歴環境でのイベント)
- ポジション数が変化しないがボリュームが減少する:
ポジションが部分的に閉じた(履歴環境でのイベント)
ネッティング口座
- 未決注文数が増加する: 未決注文の追加
- 未決注文数が減少する
- ポジション数が増加する: 未決注文の発動
- ポジション数が変化しないが、ポジション変更時間が変化し、ボリュームが変化しない: 未決注文の発動 とポジションボリュームの増加
- ポジション数が減少する: ポジションの決済
- 未決注文数が減少しない
- ポジション数が増加する: 新規ポジションを開く
- ポジション数が減少する: ポジションの決済
- ポジション数が変化しないが、ポジション変更時間が変化し、ボリュームが増加する: ポジションへのボリュームを追加
- ポジション数が変化しないが、ポジション変更時間が変化し、ボリュームが減少する: 部分的なポジション決済
取引イベントを識別するためには、プログラムが動作する口座を知る必要があります。ヘッジ口座タイプのフラグをCEngineクラスのprivateセクションに追加して、口座タイプをクラスコンストラクタで定義してその結果を個のフラグ変数に書き入れます。
//+------------------------------------------------------------------+ //| ライブラリ基本クラス | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // 過去の注文と取引のコレクション CMarketCollection m_market; // 注文と取引のコレクション CArrayObj m_list_counters; // タイマーカウンタのリスト bool m_first_start; // 初期実行フラグ bool m_is_hedge; // ヘッジ口座フラグ //--- IDによるカウンタインデックスを返す int CounterIndex(const int id) const; //--- 「初回実行」フラグを返す bool IsFirstStart(void); public: //--- タイマーカウンタを作成する void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- タイマー void OnTimer(void); CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+ //| CEngineコンストラクタ | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+
初期実行時には、クラスオブジェクトの構築中に、プログラムが起動されたときの口座タイプがそのコンストラクタで定義されます。したがって、取引イベントを定義するメソッドは直ちにヘッジまたはネッティングのどちらかの口座に起因するようになります。
着信取引イベントを定義したら、そのコードを保存する必要があります。コードは次のイベントまで変わらないため、プログラムは常に口座の直近のイベントを定義できます。イベントコードは一連のフラグで構成されます。各フラグは特定のイベントを説明します。たとえば、ポジション決済イベントは、それをより正確に特徴付ける特定のサブセットに分割することができます。
- 完全決済
- 部分決済
- 反対方向のポジションによる決済
- ストップロスによる決済
- テイクプロフィットによる決済
- など
これらすべての属性は、1つの「ポジション決済」イベントに固有のものです。つまり、イベントコードにばこれらすべてのデータが含まれている必要があります。フラグを使用してイベントを構築するために、ライブラリルートフォルダからDefines.mqhファイルに、追跡するアカウント2つの新しい列挙(取引イベントフラグと可能な取引イベント)を作成しましょう。
//+------------------------------------------------------------------+ //| 口座の取引イベントリスト | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT_FLAGS { TRADE_EVENT_FLAG_NO_EVENT = 0, // イベントなし TRADE_EVENT_FLAG_ORDER_PLASED = 1, // 未決注文が出された TRADE_EVENT_FLAG_ORDER_REMOVED = 2, // 未決注文の削除 TRADE_EVENT_FLAG_ORDER_ACTIVATED = 4, // 未決注文の発動 TRADE_EVENT_FLAG_POSITION_OPENED = 8, // ポジションが開いた TRADE_EVENT_FLAG_POSITION_CLOSED = 16, // ポジションが閉じた TRADE_EVENT_FLAG_ACCOUNT_BALANCE = 32, // 残高操作(取引タイプで分類) TRADE_EVENT_FLAG_PARTIAL = 64, // 部分実行 TRADE_EVENT_FLAG_BY_POS = 128, // 反対方向ポジションによる実行 TRADE_EVENT_FLAG_SL = 256, // ストップロスによる実行 TRADE_EVENT_FLAG_TP = 512 // テイクプロフィットによる実行 }; //+------------------------------------------------------------------+ //| 口座で可能な取引イベントのリスト | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT, // 取引イベントなし TRADE_EVENT_PENDING_ORDER_PLASED, // 未決注文が出された TRADE_EVENT_PENDING_ORDER_REMOVED, // 未決注文の削除 //--- ENUM_DEAL_TYPE列挙体メンバに一致する列挙体メンバ TRADE_EVENT_ACCOUNT_CREDIT, // クレジット課金 TRADE_EVENT_ACCOUNT_CHARGE, // 追加の課金 TRADE_EVENT_ACCOUNT_CORRECTION, // エントリの修正 TRADE_EVENT_ACCOUNT_BONUS, // ボーナスの課金 TRADE_EVENT_ACCOUNT_COMISSION, // 追加手数料 TRADE_EVENT_ACCOUNT_COMISSION_DAILY, // 一日の終わりに請求される手数料 TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY, // 月末に請求される手数料 TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY, // 取引日の終わりに請求されるエージェント手数料 TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY, // 月末に請求される代理人手数料 TRADE_EVENT_ACCOUNT_INTEREST, // フリーファンドへの利息の発生 TRADE_EVENT_BUY_CANCELLED, // キャンセルされた買い取引 TRADE_EVENT_SELL_CANCELLED, // キャンセルされた売り取引 TRADE_EVENT_DIVIDENT, // 配当金の発生 TRADE_EVENT_DIVIDENT_FRANKED, // 適格配当金の発生 TRADE_EVENT_TAX, // 税金の発生 //--- ENUM_DEAL_TYPE列挙からのDEAL_TYPE_BALANCE取引タイプに関連する列挙のメンバ TRADE_EVENT_ACCOUNT_BALANCE_REFILL, // 口座への入金 TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL, // 口座からの出金 //--- TRADE_EVENT_PENDING_ORDER_ACTIVATED, // 価格による未決注文の発動 TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL, // 格による未決注文の部分的な発動 TRADE_EVENT_POSITION_OPENED, // ポジションが開いた TRADE_EVENT_POSITION_OPENED_PARTIAL, // ポジションが部分的に開いた TRADE_EVENT_POSITION_CLOSED, // ポジションが閉じた TRADE_EVENT_POSITION_CLOSED_PARTIAL, // ポジションが部分的に閉じた TRADE_EVENT_POSITION_CLOSED_BY_POS, // 反対方向のポジションによってポジションが閉じた TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS, // 反対方向のポジションによってポジションが部分的に閉じた TRADE_EVENT_POSITION_CLOSED_BY_SL, // ストップロスによってポジションが閉じた TRADE_EVENT_POSITION_CLOSED_BY_TP, // テイクプロフィットによってポジションが閉じた TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL, // ストップロスによってポジションが部分的に閉じた TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP, // テイクプロフィットによってポジションが部分的に閉じた TRADE_EVENT_POSITION_REVERSED, // ポジション反転(ネッティング) TRADE_EVENT_POSITION_VOLUME_ADD // ボリュームをポジションに追加(ネッティング) }; //+------------------------------------------------------------------+
ここでは、ENUM_TRADE_EVENT列挙体についていくつか明確にしておく必要があります。
一部の取引イベントではプログラムや人的介入(課金手数料、手数料、ボーナスなど)が不要なため、MQL5の取引タイプ(ENUM_DEAL_TYPE列挙)からそのデータを取得します。後でイベントを追跡するのを簡単にするために、イベントがENUM_DEAL_TYPE列挙の値と一致するようにする必要があります。
口座残高の補充と資金の引き出しの2つのイベントに残高操作を分けます。DEAL_TYPE_CREDITから始まる取引タイプの列挙のその他のイベントは、残高の操作には関係ない売買(DEAL_TYPE_BUYおよびDEAL_TYPE_SELL)を除き、ENUM_DEAL_TYPEの列挙と同じ値になります。
成行注文とポジションコレクションのクラスを改善しましょう。
CMarketCollectionクラスのprivateセクションに標本注文を追加して、指定された注文プロパティで検索を実行します。一方、クラスのpublicセクションは注文とポジションの全リストを取得するためのメソッド、指定の時間範囲で選択された注文とポジションのリスト、注文またはポジションの整数、実数、文字列プロパティから返される注文とポジションのリスト
を受け取ります。これにより、コレクションから成行注文とポジションの必要なリストを受け取ることができます(ライブラリの説明の第2部と第3部で過去の注文と取引を収集したときと同じ)。
//+------------------------------------------------------------------+ //| 成行注文とポジションのコレクション | //+------------------------------------------------------------------+ class CMarketCollection { private: struct MqlDataCollection { long hash_sum_acc; // 口座のすべての注文とポジションのハッシュ合計 int total_pending; // 口座の未決注文数 int total_positions; // 口座のポジション数 double total_volumes; // 口座の注文とポジションの総量 }; MqlDataCollection m_struct_curr_market; // 口座の成行注文とポジションの現在のデータ MqlDataCollection m_struct_prev_market; // 口座の成行注文とポジションの以前のデータ CArrayObj m_list_all_orders; // 口座の未決注文とポジションのリスト COrder m_order_instance; // プロパティごとに検索する注文オブジェクト bool m_is_trade_event; // 取引イベントフラグ bool m_is_change_volume; // 総ボリューム変更フラグ double m_change_volume_value; // 総ボリューム変更値 int m_new_positions; // 新規ポジション数 int m_new_pendings; // 新規未決注文数 //--- 現在の口座データステータスを1つ前のものとして保存する void SavePrevValues(void) { this.m_struct_prev_market=this.m_struct_curr_market; } public: //--- 未決注文とポジションのリストを返す CArrayObj* GetList(void) { return &m_list_all_orders; } //--- 開いた時間がbegin_time~end_timeの注文とポジションのリストを返す CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- 比較された条件に適合する選択された(1)double、(2)integer、(3)文字列プロパティによって注文とポジションのリストを返す 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); } //--- (1)新規未決注文数、(2)新規ポジション数、(3)取引発生イベントフラグ数、(4)ボリューム変更数を返す 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; } //--- コンストラクタ CMarketCollection(void); //--- 未決注文とポジションのリストを更新する void Refresh(void); }; //+------------------------------------------------------------------+
クラス本体を超えた時間で注文とポジションを選択するためのメソッドを実装します。
//+------------------------------------------------------------------------+ //| begin_timeから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; } //+------------------------------------------------------------------+
このメソッドは、第3部で説明した過去の注文と取引を時間ごとに選択するメソッドとほぼ同じです。必要に応じて、3番目の記事の適切なセクションの説明をもう一度読んでください。このメソッドと過去のコレクションクラスのものとの違いは、時間順を選択することを選択していないことです。成行注文とポジションには開始時間しかありません。
また、過去の注文と取引のコレクションのクラスコンストラクタを変更します。MQL5注文システムには、決済時間の概念はありません。すべての注文と取引は、それらの配置時間(またはMQL4注文システムによると開始時間)に従ってリストに配置されます。これを行うには、CHistoryCollectionクラスのコンストラクタで、過去の注文と取引のコレクションリストで並べ替え方向を定義する文字列を変更します。
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ 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(); } //+------------------------------------------------------------------+
MQL5では過去の注文および取引コレクション内のすべての注文および取引は、デフォルトでそれらの配置時間によって並び替えられる一方、MQL4では、これらは注文プロパティで定義された決済時間で並び替えられます。
それでは、MQL5の注文システムの別の機能に移りましょう。反対のポジションでポジションを決済するときは、ORDER_TYPE_CLOSE_BYタイプの特別決済注文が出されますが、ストップ注文でポジションを決済するときは、代わりにポジションを決済する成行注文が出されます。
成行注文を考慮するには、基本注文の整数プロパティを受け取り、返すメソッドにさらに別のプロパティを追加する必要がありました(例として注文のマジックナンバーの受け取って返す)。
//+------------------------------------------------------------------+ //| マジックナンバーを返す | //+------------------------------------------------------------------+ 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 } //+------------------------------------------------------------------+
このステータスを実際に考慮する必要がある基本注文の整数プロパティを受信および返すすべてのメソッドで、このような変更(またはそのメソッドに論理的に対応するもの)を加えました。そのようなステータスが存在するため、そのような注文タイプを格納するために、ライブラリのObjectsフォルダに成行注文の新しいクラスであるCMarketOrderを作成します。このクラスは、以前に作成された残りの市場および過去の注文と取引のオブジェクトとまったく同じです。そのため、ここではリストのみを提供します。
//+------------------------------------------------------------------+ //| MarketOrder.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ja/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ja/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| ファイルをインクルードする | //+------------------------------------------------------------------+ #include "Order.mqh" //+------------------------------------------------------------------+ //| 成行注文 | //+------------------------------------------------------------------+ class CMarketOrder : public COrder { public: //--- コンストラクタ CMarketOrder(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_ORDER,ticket) {} //--- サポートされる注文プロパティ: (1)実数、(2)整数 virtual bool SupportProperty(ENUM_ORDER_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_ORDER_PROP_INTEGER property); }; //+------------------------------------------------------------------+ //| 注文が渡された整数プロパティをサポートする場合は「true」を返し | //| その他の場合は「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; } //+------------------------------------------------------------------+ //| 注文が渡された整数プロパティをサポートする場合は「true」を返し | //| その他の場合は「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; } //+------------------------------------------------------------------+
ライブラリのDefines.mqhファイルで、新しいステータス(成行注文)を書きます。
//+------------------------------------------------------------------+ //| 抽象注文型(ステータス) | //+------------------------------------------------------------------+ enum ENUM_ORDER_STATUS { ORDER_STATUS_MARKET_PENDING, // 市場の未決注文 ORDER_STATUS_MARKET_ORDER, // 成行注文 ORDER_STATUS_MARKET_POSITION, // 市場のポジション ORDER_STATUS_HISTORY_ORDER, // 過去の成行注文 ORDER_STATUS_HISTORY_PENDING, // 削除された未決注文 ORDER_STATUS_BALANCE, // 残高操作 ORDER_STATUS_CREDIT, // クレジット操作 ORDER_STATUS_DEAL, // 取引 ORDER_STATUS_UNKNOWN // 未知のステータス }; //+------------------------------------------------------------------+
ここで、リストに注文を追加するブロック
(成行注文とポジションのリストを更新するCMarketCollectionクラスのRefresh()メソッド)で、
注文タイプのチェックを実装します。タイプに応じて、成行注文オブジェクトまたは未決注文オブジェクトをコレクションリストに追加します。
//+------------------------------------------------------------------+ //| 注文リストを更新する | //+------------------------------------------------------------------+ 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 //--- ポジション 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; } } //--- 注文 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 //--- 初回実行 if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE) { this.SavePrevValues(); } //--- すべての注文とポジションのハッシュ合計が変更された場合 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(); } } //+------------------------------------------------------------------+
ORDER_TYPE_CLOSE_BY型の注文の決済を検討できるようにするには、CHistoryCollectionクラスの過去の注文と取引のリストを更新するRefresh()メソッドの注文型定義ブロックにこの注文型を追加して、そのような注文がコレクションに含まれるようにします。さもないと、CEngineライブラリのベースオブジェクトは、ポジションが反対のポジションによって決済されたことを定義することはできません。
//+------------------------------------------------------------------+ //| 注文とポジションのリストを更新する | //+------------------------------------------------------------------+ 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(); //--- 決済済みのポジいしょんおよび残高/クレジット操作 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 { //--- 削除された未決注文 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; //--- 注文 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; } } } //--- 最後に追加された注文のインデックスと前回のチェックと比較した差を保存する int delta_order=i-this.m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; //--- 取引 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); } //--- 直近に追加された取引のインデックスと前回のチェックと比較した差を保存する int delta_deal=j-this.m_index_deal; this.m_index_deal=j; this.m_delta_deal=delta_deal; //--- 履歴に新規イベントフラグを設定する this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal); #endif } //+------------------------------------------------------------------+
発生する口座イベントを定義するためにCEngineクラスをテストする際に、サービスメソッドのいくつかのマイナーな欠陥を検出して修正しました。パフォーマンスへの影響はないのでここで記述することには意味がありません。かえって重要なライブラリ機能の開発から注意をそらすことになります。すべての変更はすでにクラスのコードに加えられているので、以下に添付されているライブラリファイルでご覧になれます。
イベント定義に関する作業を続けましょう。
取引イベント定義をデバッグした後、発生したすべてのイベントは一連のフラグとして作成された単一のクラスメンバ変数にパックされます。特定のイベントを特徴付けるコンポーネントにその値を分解するために変数からデータを読み込むメソッドは後で作成されます。
取引イベントコードを保存するためのクラスメンバ変数、ヘッジおよびネッティング勘定の取引イベントを検証するメソッド、およびCEngineクラスのprivateセクションに必要な注文オブジェクトを返すメソッドを追加します。
publicセクションでは、市場ポジションと未決注文のリストを返すメソッド、過去の市場注文と取引、m_trade_event_code変数から取引イベントコードを返すメソッド、ヘッジ勘定フラグを返すメソッドを宣言します。
//+------------------------------------------------------------------+ //| ライブラリ基本クラス | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // 過去の注文と取引のコレクション CMarketCollection m_market; // 注文と取引のコレクション CArrayObj m_list_counters; // タイマーカウンタのリスト bool m_first_start; // 初期実行フラグ bool m_is_hedge; // ヘッジ口座フラグ bool m_is_market_trade_event; // 口座取引イベントフラグ bool m_is_history_trade_event; // 口座過去の取引イベントフラグ int m_trade_event_code; // 口座取引イベントステータスコード //--- IDによるカウンタインデックスを返す int CounterIndex(const int id) const; //--- 「初回実行」フラグを返す bool IsFirstStart(void); //--- (1)ヘッジ勘定(2)ネッティング勘定コレクションの使用 void WorkWithHedgeCollections(void); void WorkWithNettoCollections(void); //--- 直近の(1)市場未決注文、(2)成行注文、(3)直近のポジション、(4)チケット別ポジションを返す COrder* GetLastMarketPending(void); COrder* GetLastMarketOrder(void); COrder* GetLastPosition(void); COrder* GetPosition(const ulong ticket); //--- 直近の(1)削除済み未決注文、(2)過去の成行注文、(3)チケット別の過去の成行注文を返す COrder* GetLastHistoryPending(void); COrder* GetLastHistoryOrder(void); COrder* GetHistoryOrder(const ulong ticket); //--- (1)すべてのポジション注文のリストからの最初および(2)直近の成行注文、(3)直近の取引を返す COrder* GetFirstOrderPosition(const ulong position_id); COrder* GetLastOrderPosition(const ulong position_id); COrder* GetLastDeal(void); public: //--- 市場の(1)ポジション、(2)未決注文、(3)成行注文のリストを返す CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- 過去の(1)注文、(2)削除済み未決注文、(3)取引、(4)ID別の成行注文のポジションリストを返す CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListHistoryDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- (1)取引イベントコードと(2)ヘッジ勘定フラグを返す int TradeEventCode(void) const { return this.m_trade_event_code; } bool IsHedge(void) const { return this.m_is_hedge; } //--- タイマー口座を作成する void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- タイマー void OnTimer(void); //--- コンストラクタ/デストラクタ CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
コンストラクタの初期化リストで取引イベントコードを初期化します。
//+------------------------------------------------------------------+ //| CEngineコンストラクタ | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+
宣言されたメソッドをクラス本体の外側に実装します。
//+------------------------------------------------------------------+ //| 市場ポジションのリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketPosition(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL); return list; } //+------------------------------------------------------------------+ //| 市場未決注文のリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketPendings(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL); return list; } //+------------------------------------------------------------------+ //| 成行注文のリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketOrders(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL); return list; } //+------------------------------------------------------------------+ //| 過去の注文のリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListHistoryOrders(void) { CArrayObj* list=this.m_history.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL); return list; } //+------------------------------------------------------------------+ //| 削除された未決注文のリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListHistoryPendings(void) { CArrayObj* list=this.m_history.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL); return list; } //+------------------------------------------------------------------+ //| 取引のリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListHistoryDeals(void) { CArrayObj* list=this.m_history.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL); return list; } //+------------------------------------------------------------------+ //| ポジション注文のリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id) { CArrayObj* list=this.GetListHistoryOrders(); list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL); return list; } //+------------------------------------------------------------------+ //| 直近のポジションを返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| チケット別のポジションを返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 直近の取引を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 直近の市場未決注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 直近の過去の未決注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 直近の成行注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 直近の過去の成行注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| チケット別の過去の成行注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| すべてのポジション注文のリストの | //| 最初の過去の成行注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 直近の過去の成行注文を返す | //| 最初の過去の成行注文を返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+
次の例を使用してリストがどのように取得されるのかを見てみましょう。
//+------------------------------------------------------------------+ //| 市場ポジションのリストを返す | //+------------------------------------------------------------------+ CArrayObj* CEngine::GetListMarketPosition(void) { CArrayObj* list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL); return list; } //+------------------------------------------------------------------+
すべてが非常に単純で便利です。GetList() コレクションメソッドを使用して成行注文とポジションのコレクションからポジションの全リストを受け取ります。その後、CSelectクラスから指定されたプロパティで注文を選択するメソッドを使用して、「ポジション」ステータスを持つ注文を選択します。このメソッドはライブラリ説明の第3部の記事で説明されました。取得されたリストを返します。
リストが空(NULL)になっている可能性があるため、このメソッドによって返される結果を呼び出し側プログラムで確認する必要があります。
次の例を使って必要な注文を受け取るメソッドを見てみましょう。
//+------------------------------------------------------------------+ //| 直近のポジションを返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+
まず、上記のGetListMarketPosition()メソッドを使用して、ポジションの全リストを受け取ります。リストが空の場合は、NULLを返します。次に、リストをミリ秒単位の開始時間で並び替えて(直近のポジションを受け取るので、リストは時間順に並び替えられる必要があります)、直近の注文を選択します。その結果、リストから取得した注文を呼び出し側プログラムに返します。
メソッドによって返された注文を検索した結果は空になり(注文が見つからない)、NULLになる可能性があります。そのため、アクセスする前にNULLの取得結果を確認してください。
ご覧のとおり、構造は非常に迅速で簡単です。将来作成するものだけでなく、既存の任意のコレクションリストから任意のデータを受け取るための任意のメソッドを構築することができます。<これにより、このようなリストをより柔軟に使用できます。
リストを受け取るためのメソッドはpublicクラスセクションに配置されており、カスタムプログラムのリストから任意のデータを受け取ることができます。これは上記のメソッドで行われたのと同じです。
必要な注文を受け取るメソッドはprivateセクションに隠されています。これらは、内部的なニーズ、特に直近の注文、取引、およびポジションに関するデータを取得するために、CEngineクラスでのみ必要です。カスタムプログラムでは、指定されたプロパティで特定の注文を受け取るためのカスタム関数を作成できます(例は上に表示されています)。コレクションからデータを簡単に取得するために、最終的にエンドユーザー向けにさまざまな機能を作成します。
これは注文のコレクションの取り扱いに関する最終記事で行われます。
それでは、取引イベントを確認するためのメソッドを実装しましょう。
現時点では、これはMQL5のヘッジ勘定でのみ機能します。その後、MQL5ネッティング勘定とMQL4の取引イベントを確認するメソッドを開発します。メソッド操作をテストするために、確認とイベント結果の表示が特徴とされます。この機能は、口座の取引イベントを確認するメソッドをデバッグした後に作成される別のメソッドで実行されるため、後で削除される予定です。
//+------------------------------------------------------------------+ //| 取引イベントを確認する(ヘッジ) | //+------------------------------------------------------------------+ void CEngine::WorkWithHedgeCollections(void) { //--- 取引イベントコードとフラグを初期化する this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT; this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- リストを更新する this.m_market.Refresh(); this.m_history.Refresh(); //--- 初回実行時のアクション if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- 市場の状態のダイナミクスとアカウントの履歴を確認する 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(this.m_is_market_trade_event && !this.m_is_history_trade_event) { Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account")); //--- 未決注文数が増加する場合 if(this.m_market.NewPendingOrders()>0) { //--- 未決注文をインストールするためのフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED; string text=TextByLanguage("Установлен отложенный ордер: ","未決注文が出された: "); //--- 直近の成行注文を取得する COrder* order=this.GetLastMarketPending(); if(order!=NULL) { //--- メッセージにチケットを追加する text+=order.TypeDescription()+" #"+(string)order.Ticket(); } //--- 操作ログにメッセージを追加する Print(DFUN,text); } //--- 成行注文数が増加した場合 if(this.m_market.NewMarketOrders()>0) { //--- イベントフラグを追加しない //--- ... string text=TextByLanguage("Выставлен маркет-ордер: ","Market order placed: "); //--- 直近の成行注文を取得する COrder* order=this.GetLastMarketOrder(); if(order!=NULL) { //--- メッセージにチケットを追加する text+=order.TypeDescription()+" #"+(string)order.Ticket(); } //--- 操作ログにメッセージを追加する Print(DFUN,text); } } //--- イベントが過去の注文と取引のみの場合 else if(this.m_is_history_trade_event && !this.m_is_market_trade_event) { Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history")); //--- 新規取引がある場合 if(this.m_history.NewDeals()>0) { //--- 口座残高イベントフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE; string text=TextByLanguage("Новая сделка: ","New deal: "); //--- 直近の取引を取得する COrder* deal=this.GetLastDeal(); if(deal!=NULL) { //--- テキストに説明を追加する text+=deal.TypeDescription(); //--- 取引が残高操作の場合 if((ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE)==DEAL_TYPE_BALANCE) { //--- 取引利益を確認してメッセージにイベントを追加する(資金の入金または出金) text+=(deal.Profit()>0 ?TextByLanguage(": Пополнение счёта: ",": Account Recharge: ") : TextByLanguage(": Вывод средств: ",": Withdrawal: "))+::DoubleToString(deal.Profit(),(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS)); } } //--- 操作ログでメッセージを表示する Print(DFUN,text); } } //--- イベントが市場にあって過去の注文とポジションの場合 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(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0) { //--- 未決注文を削除するためのフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED; string text=TextByLanguage("Удалён отложенный ордер: ","Removed pending order: "); //--- 直近の過去の未決注文を取得する COrder* order=this.GetLastHistoryPending(); if(order!=NULL) { //--- チケットを適切なメッセージに追加する text+=order.TypeDescription()+" #"+(string)order.Ticket(); } //--- 操作ログでメッセージを表示する Print(DFUN,text); } //--- 新規取引と新規過去注文がある場合 if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0) { //--- 直近の取引を取得する COrder* deal=this.GetLastDeal(); if(deal!=NULL) { //--- 市場エントリ取引の場合 if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN) { //--- ポジションオープンフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED; string text=TextByLanguage("Открыта позиция: ","Position opened: "); //--- 未決注文数が減少した場合 if(this.m_market.NewPendingOrders()<0) { //--- 未決注文発動フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED; text=TextByLanguage("Сработал отложенный ордер: ","Pending order activated: "); } //--- 注文から注文のチケットを取得する ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- 現在の注文量が0を超える場合 if(order.VolumeCurrent()>0) { //--- 部分的実行フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; text=TextByLanguage("Частично открыта позиция: ","Position partially open: "); } //--- メッセージに注文の方向を追加する text+=order.DirectionDescription(); } //--- 取引からポジションチケットをメッセージに追加し、操作ログにメッセージを表示する text+=" #"+(string)deal.PositionID(); Print(DFUN,text); } //--- 市場エグジット取引の場合 if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT) { //--- ポジション決済フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; string text=TextByLanguage("Закрыта позиция: ","Position closed: "); //--- 取引から取引のチケットを取得する ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- 取引のポジションが市場にまだ存在する場合 COrder* pos=this.GetPosition(deal.PositionID()); if(pos!=NULL) { //--- 部分的実行フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; text=TextByLanguage("Частично закрыта позиция: ","Partially closed position: "); } //--- その他の場合に、ポジションが完全に決済されている場合 else { //--- 注文にストップロスによる決済のフラグがある場合 if(order.IsCloseByStopLoss()) { //--- ストップロスによる決済のフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_SL; text=TextByLanguage("Позиция закрыта по StopLoss: ","Position closed by StopLoss: "); } //--- 注文にテイクプロフィットによる決済のフラグがある場合 if(order.IsCloseByTakeProfit()) { //--- テイクプロフィットによる決済のフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_TP; text=TextByLanguage("Позиция закрыта по TakeProfit: ","Position closed by TakeProfit: "); } } //--- メッセージに注文の逆方向を追加する //--- 売りポジションの買い注文と買いポジションの売り注文を決済する、 //--- したがって、決済されたポジションを正しく記述するために注文の方向を逆にする text+=(order.DirectionDescription()=="Sell" ?"Buy " : "Sell "); } //--- 取引のポジションのチケットをメッセージに追加し、操作ログにメッセージを表示する text+="#"+(string)deal.PositionID(); Print(DFUN,text); } //--- 反対ポジションによる決済 if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY) { //--- ポジション決済フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; //--- 反対ポジションによる決済のフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS; string text=TextByLanguage("Позиция закрыта встречной: ","Position closed by opposite position: "); //--- 取引注文を取得する ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(ticket_from); if(order!=NULL) { //--- メッセージに注文の逆方向を追加する //--- 売りポジションの買い注文と買いポジションの売り注文を決済する、 //--- したがって、決済されたポジションを正しく記述するために注文の方向を逆にする text+=(order.DirectionDescription()=="Sell" ?"Buy" : "Sell"); text+=" #"+(string)order.PositionID()+TextByLanguage(" закрыта позицией #"," closed by position #")+(string)order.PositionByID(); //--- 注文のポジションが市場にまだ存在する場合 COrder* pos=this.GetPosition(order.PositionID()); if(pos!=NULL) { //--- 部分的実行フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; text+=TextByLanguage(" частично"," partially"); } } //--- 操作ログでメッセージを表示する Print(DFUN,text); } //--- 直近の取引を処理するブロックの終わり } } } #endif } //+------------------------------------------------------------------+
メソッドは単純ですが、かなり長いです。そのため、このコードにはすべてのチェックと対応するアクションが含まれており、メソッド内で何が行われているのかを簡単に確認できます。今のところ、このメソッドではMQL5ヘッジ勘定の確認が実装されています。実際には、すべては口座の履歴または市場で新しく出現した注文や取引の数の確認です。操作ログに表示する場合、データは直近のポジション、直近の取引、直近の注文、または直近の取引の順番から取得されます。これは、コードの実行を確認するために操作ログにデータを表示するために行われます。この機能は後でコードから削除され、ライブラリを扱うプログラムで使用される単一のメソッドに置き換えられます。
取引イベント処理のテスト
口座の取引イベントを定義するメソッドを確認するためのテスト用EAを作成しましょう。
新しいイベントを管理するためにボタンのセットを実装しましょう。
必要な操作と対応するボタンは次のとおりです。
- 買いポジションを開く
- BuyLimit未決注文を出す
- BuyStop未決注文を出す
- BuyStopLimit未決注文を出す<分
- 買いポジションを決済する
- 買いポジションの半分を決済する
- 買いポジションを反対の売りポジションによって決済する
- 売りポジションを開く
- SellLimit未決注文を出す
- SellStop未決注文を出す
- SellStopLimit未決注文を出す
- 売りポジションを決済する
- 売りポジションの半分を決済する
- 売りポジションを反対の買いポジションによって決済する
- すべてのポジションを決済する
- 口座から出金する
入力セットは次の通りです。
- Magic number - マジックナンバー
- Lots - ポジションボリューム
- ポイント単位のストップロス
- ポイント単位のテイクプロフィット
- 未決注文の距離(ポイント単位)
- StopLimit注文の距離(ポイント単位)
StopLimit注文は、未決注文距離の値で設定された価格からの距離で出されます。
価格が指定された注文に達して発動されるとすぐに、その価格レベルはStopLimit注文距離値によって設定された価格からの距離で指値注文を出すために使用されます。 - スリッページ(ポイント単位)
- 出勤(テスター) - テスターで口座から引き出された資金
StopLevel、逆指値注文、およびポジションボリュームに対する発注価格を定義するための正しい値を計算するための関数が必要になります。この段階で、取引クラスと銘柄クラスがまだない限り、DELib.mqhファイル内のサービス関数のライブラリに関数を追加しましょう。
//+------------------------------------------------------------------+ //| 銘柄の最小ロットを返す | //+------------------------------------------------------------------+ double MinimumLots(const string symbol_name) { return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN); } //+------------------------------------------------------------------+ //| 銘柄の最大ロットを返す | //+------------------------------------------------------------------+ double MaximumLots(const string symbol_name) { return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX); } //+------------------------------------------------------------------+ //| 銘柄のロット変更ステップを返す | //+------------------------------------------------------------------+ double StepLots(const string symbol_name) { return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP); } //+------------------------------------------------------------------+ //| 正規化されたロットを返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+ //| 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)); } //+------------------------------------------------------------------+ //| ポイント単位のStopLevelを返す | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+
これらの関数は単に、サーバに設定されている制限に違反しないように正しい値を計算します。銘柄とは別に、StopLevel計算関数はスプレッド乗数も受け取ります。これは、サーバ上のStopLevelがゼロに設定されている場合はレベルが浮動であり、StopLevelを計算するためには、特定の数値を掛けたスプレッド値を使用する必要があるためです。この乗数は関数に渡され、EAの設定でハードコードするか計算することができます。
時間を節約するために、カスタム取引関数は作成しません。代わりに、標準ライブラリの既製の取引クラス、つまり取引操作を実行するためのCTradeクラスを使用します。機能するチャートボタンを作成するために、特定のボタンが押されたことを確認するためのボタン名、ラベル、および値を設定するメンバを持つ列挙を実装します。
MQL5\Experts\TestDoEasy\Part04\で、新しいTestDoEasy04.mqh EAを作成します(EAを作成するときにMQLウィザードのOnTimerおよびOnChartEventイベントハンドラを確認してください。):
MQLウィザードでEAテンプレートを作成したら、カスタムライブラリと標準ライブラリの取引クラスをインクルードします。入力パラメータも追加します。
//+------------------------------------------------------------------+ //| TestDoEasyPart04.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ja/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ja/users/artmedia70" #property version "1.00" //--- include #include <DoEasy\Engine.mqh> #include <Trade\Trade.mqh> //--- 列挙体 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) //--- 構造体 struct SDataButt { string name; string text; }; //--- 入力変数 input ulong InpMagic = 123; // マジックナンバー input double InpLots = 0.1; // ロット input uint InpStopLoss = 50; // ストップロス(ポイント単位) input uint InpTakeProfit = 50; // テイクプロフィット(ポイント単位) input uint InpDistance = 50; // 未決注文の距離(ポイント単位) input uint InpDistanceSL = 50; // StopLimit注文の距離(ポイント単位) input uint InpSlippage = 0; // スリッページ(ポイント単位) input double InpWithdrawal = 10; // 出金(テスター) //--- グローバル変数 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; //+------------------------------------------------------------------+
CEngineライブラリのメインオブジェクトとCTrade取引クラスをインクルードします。
次に、必要なボタンを指定した列挙体を作成します。
列挙の順序は、ボタン作成の順序とチャート上のポジションを設定するので重要です。
その後、ボタンのグラフィックオブジェクトの名前とボタンに刻まれるテキストを格納するための構造体を宣言します。
入力ブロックで、上記のEAパラメータ変数をすべて設定します。EAのグローバル変数ブロックで、ライブラリオブジェクト、取引クラスオブジェクト、
ボタン構造体配列、 OnInit()ハンドラで入力変数が割り当てされる変数を宣言します。
//+------------------------------------------------------------------+ //| エキスパート初期化関数 | //+------------------------------------------------------------------+ int OnInit() { //--- 口座の種類を確認する if(!engine.IsHedge()) { Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge")); return INIT_FAILED; } //--- グローバル変数を設定する 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; //--- ボタンを作成する 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); } //+------------------------------------------------------------------+
OnInit()ハンドラで、口座の種類を確認して、ヘッジでなければ、そのことをエラーメッセージとして表示してプログラムを終了します。
次に、(EAがそのオブジェクトを認識できるように)オブジェクト名の接頭辞を設定し、ボタンの数だけ反復して、ループ内でボタンデータを構造体の配列に書き入れます。
ボタンオブジェクト名は、ループインデックスに対応するENUM_BUTTONS列挙体の接頭辞+文字列表現として設定される一方、ボタンテキストは、EnumToButtText()関数を使用して、ループインデックスに対応する列挙の文字列表現を変換することによってコンパイルされます。
その後、ポジションと注文のロットが計算されます。ポジションの半分が決済されるので、ポジションのロットは最低ロットの少なくとも2倍であるべきです。したがって、最大ロット数は次の2つから取られます。
1) 入力、2)
fmax(InpLots,MinimumLots(Symbol())*2.0)での最小ロットの2倍、取得されたロットは正規化されてlotグローバル変数に割り当てられます。その結果、入力でユーザが入力したロットが最小ロットの2倍より小さい場合、その2倍の最小ロットが使用されます。その他の場合は、ユーザによって入力されたロットが適用されます。
残りの入力は適切なグローバル変数に割り当てられ、前のステップですでにボタンデータが書き入れられた構造体配列からボタンを作成するためにCreateButtons()関数が呼び出されます。 ButtonCreate()関数を使用してボタンを作成出来なかった場合は、エラーメッセージが表示されてプログラムが初期化エラーによって終了します。
最後に、CTradeクラスが初期化されます。
//--- trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_ERRORS); //---
- スリッページ(ポイント単位)を設定します
- マジックナンバーを設定します
- 現在の銘柄の設定に従って、注文実行の種類を設定します
- 現在の口座設定に従って、証拠金計算モードを設定します
- 操作ログにエラーメッセージのみを表示するようにメッセージログレベルを設定します
(テスターではデフォルトですべてのログの表示がオンになっています)
OnDeinit()ハンドラで、オブジェクト名の接頭辞によるすべてのボタンの削除を実装します。
//+------------------------------------------------------------------+ //| エキスパート初期化解除関数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- オブジェクトを削除する ObjectsDeleteAll(0,prefix); } //+------------------------------------------------------------------+
それでは、ライブラリタイマーとEAイベントハンドラの起動順序を決定しましょう。
- EAがテスターで起動されていない場合は、ライブラリタイマーはEAタイマーから起動され、イベントハンドラは通常モードで動作します。
- EAがテスターで起動された場合、ライブラリタイマーはEAのOnTick()ハンドラから起動されます。ボタン押下イベントはOnTick()でも追跡されます。
以下はEAのOnTick()、OnTimer()、OnChartEvent()ハンドラです。
//+------------------------------------------------------------------+ //| エキスパートティック関数 | //+------------------------------------------------------------------+ 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); } } //+------------------------------------------------------------------+ //| タイマー関数 | //+------------------------------------------------------------------+ void OnTimer() { if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent関数 | //+------------------------------------------------------------------+ 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); } } //+------------------------------------------------------------------+
OnTick()で
- EAの起動場所を確認します。テスターで起動されている場合、 ライブラリのOnTimer()ハンドラを呼び出します。
- 次に、オブジェクト名をループで現在のすべてのチャートオブジェクトに対して確認します。ボタンに一致した場合、ボタン押下ハンドラを呼び出します。
OnTimer()で
- EAの起動場所を確認します。テスターで起動されていない場合、ライブラリのOnTimer()ハンドラを呼び出します。
OnChartEvent()で
- EAの起動場所を確認します。テスターで起動されている場合、ハンドラを終了します。
- 次に、イベントIDがチェックされます。これがグラフィカルオブジェクトをクリックするイベントであり、オブジェクト名にボタンに属するテキストが含まれている場合、適切なボタン押下ハンドラが呼び出されます。
CreateButtons()関数:
//+------------------------------------------------------------------+ //| ボタンパネルを作成する | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
この関数では、ENUM_BUTTONS列挙型メンバの数だけループ内でボタンの座標と色を計算することになります。ENUM_BUTTONS列挙型メンバの番号を示すループインデックスに基づいて、座標と色が計算されます。x座標とy座標を計算した後、ループ内で計算された座標と色の値を持つボタン作成関数が呼び出されます。
EnumToButtText()関数:
//+------------------------------------------------------------------+ //| 列挙をボタンテキストに変換する | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
ここではすべて簡単です。関数が列挙型メンバを受け取り、不要なテキストを削除した文字列に変換します。次に、取得した文字列のすべての文字を小文字に変換し、不適切なエントリをすべて必要なものに置き換えます。
テキストに変換された入力列挙文字列が最終的に返されます。
以下は、ボタンを作成して配置し、そのステータスを受け取るための関数です。
//+------------------------------------------------------------------+ //| ボタンを作成する | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| ボタンの状態を返す | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| ボタンの状態を設定する | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); } //+------------------------------------------------------------------+
すべてがシンプルで明確なので、説明は必要ありません。
以下は、ボタン押下を処理する関数です。
//+------------------------------------------------------------------+ //| ボタン押下の処理 | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- ボタン名を文字列IDに変換する string button=StringSubstr(button_name,StringLen(prefix)); //--- ボタンが押下された場合 if(ButtonState(button_name)) { //--- BUTT_BUYボタンが押下されたら、買いポジションを開く if(button==EnumToString(BUTT_BUY)) { //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- 買いポジションを開く trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp); } //--- BUTT_BUY_LIMITボタンが押下されたら、BuyLimit注文を出す else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- StopLevelに相対した正しい注文配置を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- BuyLimit注文を設定する trade.BuyLimit(lot,price_set,Symbol(),sl,tp); } //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する else if(button==EnumToString(BUTT_BUY_STOP)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- BuyStop注文を設定する trade.BuyStop(lot,price_set,Symbol(),sl,tp); } //--- BUTT_BUY_STOP_LIMITボタンが押下されたら、BuyStopLimitを設定する else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- StopLevelに相対した正しいBuyStop注文配置を取得する double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- StopLevelを考慮して、BuyStopレベルに相対したBuyLimit注文価格を計算する double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- 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); //--- BuyStopLimit注文を設定する trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- BUTT_SELLボタンが押下されたら、売りポジションを開く else if(button==EnumToString(BUTT_SELL)) { //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- 売りポジションを開く trade.Sell(lot,Symbol(),0,sl,tp); } //--- BUTT_SELL_LIMITボタンが押下されたら、SellLimitを設定する else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- SellLimit注文を設定する trade.SellLimit(lot,price_set,Symbol(),sl,tp); } //--- BUTT_SELL_STOPボタンが押下されたら、SellStopを設定する else if(button==EnumToString(BUTT_SELL_STOP)) { //--- StopLevelに相対した正しい注文配置価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- SellStop注文を設定する trade.SellStop(lot,price_set,Symbol(),sl,tp); } //--- BUTT_SELL_STOP_LIMITボタンが押下されたら、SellStopLimitを設定する else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- StopLevelに相対した正しいSellStop注文価格を取得する double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- StopLevelを考慮して、SellStopレベルに相対したSellLimit注文価格を計算する double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- 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); //--- SellStopLimit注文を設定する trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- BUTT_CLOSE_BUYボタンが押下されたら、最大利益を持つ買いポジションを決済する else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 買いポジションチケットを取得し、チケットでポジションを決済する trade.PositionClose(position.Ticket()); } } } //--- BUTT_CLOSE_BUY2ボタンが押下されたら、最大利益を持つ買いポジションを半分決済する else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 決済された量を計算し、チケットで買いポジションの半分を決済する trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- BUTT_CLOSE_BUY_BY_SELLボタンが押下されたら、最大利益を持つ買いポジションを反対方向の売りで決済する else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- すべてのポジションのリストを取得する CArrayObj* list_buy=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- すべてのポジションのリストを取得する CArrayObj* list_sell=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- 最大の利益を持つ買いポジションを選択する COrder* position_buy=list_buy.At(index_buy); //--- 最大の利益を持つ売りポジションを選択する COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- 買いポジションを反対の売りポジションによって決済する trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket()); } } } //--- BUTT_CLOSE_SELLボタンが押下されたら、最大利益を持つ売りポジションを決済する else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 売りポジションチケットを取得し、チケットでポジションを決済する trade.PositionClose(position.Ticket()); } } } //--- BUTT_CLOSE_SELL2ボタンが押下されたら、最大利益を持つ売りポジションを半分決済する else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 決済された量を計算し、チケットで売りポジションの半分を決済する trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- BUTT_CLOSE_SELL_BY_BUYボタンが押下されたら、最大利益を持つ売りポジションを反対方向の買いで決済する else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- すべてのポジションのリストを取得する CArrayObj* list_sell=engine.GetListMarketPosition(); //--- リストから売りポジションのみを取得する list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- すべてのポジションのリストを取得する CArrayObj* list_buy=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- 最大の利益を持つ売りポジションを選択する COrder* position_sell=list_sell.At(index_sell); //--- 最大の利益を持つ買いポジションを選択する COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- 売りポジションを反対の買いポジションによって決済する trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket()); } } } //--- BUTT_CLOSE_ALLボタンが押下されたら、最小利益を持つポジションから初めて、すべてのポジションを決済する else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); if(list!=NULL) { //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- 最小の利益を持つポジションからの反復処理 for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- 個々のポジションをチケットで決済する trade.PositionClose(position.Ticket()); } } } //--- BUTT_PROFIT_WITHDRAWALボタンが押下されたら、口座から出金する if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- プログラムがテスターで起動された場合 if(MQLInfoInteger(MQL_TESTER)) { //--- 資金出金のエミュレーション TesterWithdrawal(withdrawal); } } //--- 0.1秒待つ Sleep(100); //--- ボタンを放してチャートを再描画する ButtonState(button_name,false); ChartRedraw(); } } //+------------------------------------------------------------------+
この関数は非常に大きいですが単純です。文字列IDに変換されるボタンオブジェクトの名前を受け取ります。次にボタンのステータスが確認され、押された場合は文字列IDが確認されます。適切なif-else分岐が実行され、すべてのレベルが計算され、StopLevelの制限に違反しないように必要な調整が適用されます。取引クラスの対応するメソッドが実行されます。
すべての説明は、コードのコメント文字列に直接書かれています。
テストEAでは、実際の口座にとって重要な他のすべてのチェックをスキップして、必要最小限のチェックを行いました。現在最も重要なことは、EAを実際の口座で動作できるように開発するのではなく、ライブラリの動作を確認することです。
以下はテストEAのコード全部です。
//+------------------------------------------------------------------+ //| TestDoEasyPart04.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ja/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ja/users/artmedia70" #property version "1.00" //--- include #include <DoEasy\Engine.mqh> #include <Trade\Trade.mqh> //--- 列挙体 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) //--- 構造体 struct SDataButt { string name; string text; }; //--- 入力変数 input ulong InpMagic = 123; // マジックナンバー input double InpLots = 0.1; // ロット input uint InpStopLoss = 50; // ストップロス(ポイント単位) input uint InpTakeProfit = 50; // テイクプロフィット(ポイント単位) input uint InpDistance = 50; // 未決注文の距離(ポイント単位) input uint InpDistanceSL = 50; // StopLimit注文の距離(ポイント単位) input uint InpSlippage = 0; // スリッページ(ポイント単位) input double InpWithdrawal = 10; // 出金(テスター) //--- グローバル変数 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; //+------------------------------------------------------------------+ //| エキスパート初期化関数 | //+------------------------------------------------------------------+ int OnInit() { //--- 口座の種類を確認する if(!engine.IsHedge()) { Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge")); return INIT_FAILED; } //--- グローバル変数を設定する 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; //--- ボタンを作成する if(!CreateButtons()) return INIT_FAILED; //--- 取引パラメータを設定する trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| エキスパート初期化解除関数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- オブジェクトを削除する ObjectsDeleteAll(0,prefix); } //+------------------------------------------------------------------+ //| エキスパートティック関数 | //+------------------------------------------------------------------+ 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(); } //+------------------------------------------------------------------+ //| タイマー関数 | //+------------------------------------------------------------------+ void OnTimer() { if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent関数 | //+------------------------------------------------------------------+ 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); } } //+------------------------------------------------------------------+ //| ボタンパネルを作成する | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| ボタンを作成する | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| ボタンの状態を返す | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| ボタンの状態を設定する | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); } //+------------------------------------------------------------------+ //| 列挙をボタンテキストに変換する | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| ボタン押下を処理する | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- ボタン名を文字列IDに変換する string button=StringSubstr(button_name,StringLen(prefix)); //--- ボタンが押下された場合 if(ButtonState(button_name)) { //--- BUTT_BUYボタンが押下されたら、買いポジションを開く if(button==EnumToString(BUTT_BUY)) { //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- 買いポジションを開く trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp); } //--- BUTT_BUY_LIMITボタンが押下されたら、BuyLimitを設定する else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- BuyLimit注文を設定する trade.BuyLimit(lot,price_set,Symbol(),sl,tp); } //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する else if(button==EnumToString(BUTT_BUY_STOP)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- BuyStop注文を設定する trade.BuyStop(lot,price_set,Symbol(),sl,tp); } //--- BUTT_BUY_STOP_LIMITボタンが押下されたら、BuyStopLimitを設定する else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- StopLevelに相対した正しいBuyStop価格を取得する double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- StopLevelを考慮して、BuyStopレベルに相対したBuyLimit注文価格を計算する double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- 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); //--- BuyStopLimit注文を設定する trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- BUTT_SELLボタンが押下されたら、売りポジションを開く else if(button==EnumToString(BUTT_SELL)) { //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- 売りポジションを開く trade.Sell(lot,Symbol(),0,sl,tp); } //--- BUTT_SELL_LIMITボタンが押下されたら、SellLimitを設定する else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- SellLimit注文を設定する trade.SellLimit(lot,price_set,Symbol(),sl,tp); } //--- BUTT_SELL_STOPボタンが押下されたら、SellStopを設定する else if(button==EnumToString(BUTT_SELL_STOP)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- SellStop注文を設定する trade.SellStop(lot,price_set,Symbol(),sl,tp); } //--- BUTT_SELL_STOP_LIMITボタンが押下されたら、SellStopLimitを設定する else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- StopLevelに相対した正しいSellStop注文価格を取得する double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- StopLevelを考慮して、SellStopレベルに相対したSellLimit注文価格を計算する double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- 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); //--- SellStopLimit注文を設定する trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- BUTT_CLOSE_BUYボタンが押下されたら、最大利益を持つ買いポジションを決済する else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 買いポジションチケットを取得し、チケットでポジションを決済する trade.PositionClose(position.Ticket()); } } } //--- BUTT_CLOSE_BUY2ボタンが押下されたら、最大利益を持つ買いポジションを半分決済する else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 決済された量を計算し、チケットで買いポジションの半分を決済する trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- BUTT_CLOSE_BUY_BY_SELLボタンが押下されたら、最大利益を持つ買いポジションを反対方向の売りで決済する else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- すべてのポジションのリストを取得する CArrayObj* list_buy=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- すべてのポジションのリストを取得する CArrayObj* list_sell=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- 最大の利益を持つ買いポジションを選択する COrder* position_buy=list_buy.At(index_buy); //--- 最大の利益を持つ売りポジションを選択する COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- 買いポジションを反対の売りポジションによって決済する trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket()); } } } //--- BUTT_CLOSE_SELLボタンが押下されたら、最大利益を持つ売りポジションを決済する else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 売りポジションチケットを取得し、チケットでポジションを決済する trade.PositionClose(position.Ticket()); } } } //--- BUTT_CLOSE_SELL2ボタンが押下されたら、最大利益を持つ売りポジションを半分決済する else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- 決済された量を計算し、チケットで売りポジションの半分を決済する trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0)); } } } //--- BUTT_CLOSE_SELL_BY_BUYボタンが押下されたら、最大利益を持つ売りポジションを反対方向の買いで決済する else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- すべてのポジションのリストを取得する CArrayObj* list_sell=engine.GetListMarketPosition(); //--- リストから売りポジションのみを選択する list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ売りポジションのインデックスを取得する int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- すべてのポジションのリストを取得する CArrayObj* list_buy=engine.GetListMarketPosition(); //--- リストから買いポジションのみを選択する list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- 最大の利益を持つ買いポジションのインデックスを取得する int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- 最大の利益を持つ売りポジションを選択する COrder* position_sell=list_sell.At(index_sell); //--- 最大の利益を持つ買いポジションを選択する COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- 売りポジションを反対の買いポジションによって決済する trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket()); } } } //--- BUTT_CLOSE_ALLボタンが押下されたら、最小利益を持つポジションから初めて、すべてのポジションを決済する else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- すべてのポジションのリストを取得する CArrayObj* list=engine.GetListMarketPosition(); if(list!=NULL) { //--- 手数料とスワップを考慮して、リストを利益順に並べ替える list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- 最小の利益を持つポジションからの反復処理 for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- 個々のポジションをチケットで決済する trade.PositionClose(position.Ticket()); } } } //--- BUTT_PROFIT_WITHDRAWALボタンが押下されたら、口座から出金する if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- プログラムがテスターで起動された場合 if(MQLInfoInteger(MQL_TESTER)) { //--- 資金出金のエミュレーション TesterWithdrawal(withdrawal); } } //--- 0.1秒待つ Sleep(100); //--- ボタンを放してチャートを再描画する ButtonState(button_name,false); ChartRedraw(); } } //+------------------------------------------------------------------+
コードは非常に長いです...
ただし、これは始まりにすぎません。エンドユーザーのためにすべてが簡素化されるためです。ここで実行するほとんどのアクションはライブラリコードに隠されていますが、ライブラリと対話するためのよりユーザーフレンドリーなメカニズムが導入されています。
テスターでEAを起動してボタンを試してみましょう。
すべてが正しく活動化され、操作ログは発生したイベントに関するメッセージを受け取ります。
現在、直近のイベントは常に修正されています。言い換えれば、複数のポジションを同時に決済した場合、直近のポジションのみがイベント内に存在します。複数の決済は、履歴内の新規の取引または注文の数によって追跡できます。それから、それらの数によってすべての新しく決済されたポジションのリストを得て、セット全体を定義することは可能です。そのために別のイベントコレクションクラスを開発しましょう。プログラム内で発生したすべてのイベントに常にアクセスできるようになります。
現在、テスタージャーナル内のすべてのイベントメッセージは、ライブラリベースオブジェクトのCEngine::WorkWithHedgeCollections()メソッドに表示されます。口座で発生したことを「理解」するには、カスタムプログラムでイベントコードを知る必要があります。これにより、イベントに応じてプログラムの応答ロジックを作成することができます。それを実現する能力をテストするために、ライブラリのベースオブジェクトに2つのメソッドを作成します。1つは直近のイベントのコードを保存するものであり、もう1つは一連のイベントフラグからなるこのコードをデコードするものです。次回の記事では、口座イベントを処理するための本格的なクラスを作成します。
CEngineクラス本体で、イベントコードをデコードして口座の取引イベントコードを設定するメソッド、イベントコード内のイベントフラグの存在をチェックするメソッド、
呼び出し側プログラムから直近の取引イベントを受信するためのメソッド、直近の取引イベントの値をリセットするためのメソッドを定義します。
//+------------------------------------------------------------------+ //| ライブラリ基本クラス | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // 過去の注文と取引のコレクション CMarketCollection m_market; // 注文と取引のコレクション CArrayObj m_list_counters; // タイマーカウンタのリスト bool m_first_start; // 初期実行フラグ bool m_is_hedge; // ヘッジ口座フラグ bool m_is_market_trade_event; // 口座取引イベントフラグ bool m_is_history_trade_event; // 口座過去の取引イベントフラグ int m_trade_event_code; // 口座取引イベントステータスコード ENUM_TRADE_EVENT m_acc_trade_event; // 口座取引イベント //--- イベントコードをデコードし、アカウントに取引イベントを設定する void SetTradeEvent(void); //--- IDによるカウンタインデックスを返す int CounterIndex(const int id) const; //--- (1)初回実行フラグ、(2)取引イベントでのフラグの存在を返す bool IsFirstStart(void); bool IsTradeEventFlag(const int event_code) const { return (this.m_trade_event_code&event_code)==event_code; } //--- (1)ヘッジ勘定(2)ネッティング勘定コレクションの使用 void WorkWithHedgeCollections(void); void WorkWithNettoCollections(void); //--- 直近の(1)市場未決注文、(2)成行注文、(3)直近のポジション、(4)チケット別ポジションを返す COrder* GetLastMarketPending(void); COrder* GetLastMarketOrder(void); COrder* GetLastPosition(void); COrder* GetPosition(const ulong ticket); //--- 直近の(1)削除済み未決注文、(2)過去の成行注文、(3)チケット別の過去の成行注文を返す COrder* GetLastHistoryPending(void); COrder* GetLastHistoryOrder(void); COrder* GetHistoryOrder(const ulong ticket); //--- (1)すべてのポジション注文のリストからの最初および(2)直近の成行注文、(3)直近の取引を返す COrder* GetFirstOrderPosition(const ulong position_id); COrder* GetLastOrderPosition(const ulong position_id); COrder* GetLastDeal(void); public: //--- 市場の(1)ポジション、(2)未決注文、(3)成行注文のリストを返す CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- 過去の(1)注文、(2)削除済み未決注文、(3)取引、(4)ID別の成行注文のポジションリストを返す CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListHistoryDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- 直近の取引イベントをリセットする void ResetLastTradeEvent(void) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; } //--- (1)直近の取引イベント、(2)取引イベントコード、(3)ヘッジ勘定フラグを返す 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; } //--- タイマーカウンタを作成する void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- タイマー void OnTimer(void); //--- コンストラクタ/デストラクタ CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
クラス本体の外で、取引イベントをデコードするメソッドを書きます(すべての説明を直接コードに書きます):
//+------------------------------------------------------------------+ //| イベントコードを出コードして取引イベントを設定する | //+------------------------------------------------------------------+ void CEngine::SetTradeEvent(void) { //--- 取引イベントExit if(this.m_trade_event_code==TRADE_EVENT_FLAG_NO_EVENT) return; //--- 未決注文が設定される(ここではフラグが1つしかない場合があるため、イベントコードが一致するかどうかを確認する) 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; } //--- 未決注文が削除される(ここではフラグが1つしかない場合があるため、イベントコードが一致するかどうかを確認する) 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; } //--- ポジションが開かれる(イベントコードの複数のフラグを確認する) if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED)) { //--- 未決注文が価格によって発動した場合 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; } //--- ポジションが決済される(イベントコードの複数のフラグを確認する) if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED)) { //--- ポジションがストップロスによって決済された場合 if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_SL)) { //--- 部分決済フラグを確認し、「ストップロスによって決済されたポジション」または「ストップロスによって部分的に決済されたポジション」取引イベントを設定する 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; } //--- ポジションがテイクプロフィットによって決済された場合 else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_TP)) { //--- 部分決済フラグを確認し、「テイクプロフィットによって決済されたポジション」または「テイクプロフィットによって部分的に決済されたポジション」取引イベントを設定する 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; } //--- ポジションが反対方向のポジションによって決済された場合 else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_BY_POS)) { //--- 部分決済フラグを確認し、「反対方向のポジションによって決済されたポジション」または「反対方向のポジションによって部分的に決済されたポジション」取引イベントを設定する 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; } //--- ポジションが決済された場合 else { //--- 部分決済フラグを確認し、「ポジションが決済された」または「ポジションが部分的に決済された」取引イベントを設定する 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; } } //--- 口座の残高操作(取引タイプ別にイベントを明確にする) if(this.m_trade_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE) { //--- 取引イベントを初期化する this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; //--- 直近の取引を取得する COrder* deal=this.GetLastDeal(); if(deal!=NULL) { ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE); //--- 取引が残高操作の場合 if(deal_type==DEAL_TYPE_BALANCE) { //--- 取引利益を確認してイベントを設定する(資金の入金または出金) this.m_acc_trade_event=(deal.Profit()>0 ?TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL); } //--- 残りの口座操作タイプは、DEAL_TYPE_CREDITで始まるENUM_DEAL_TYPE列挙と一致する else if(deal_type>DEAL_TYPE_BALANCE) { //--- イベントを設定する 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; } } //+------------------------------------------------------------------+
取引イベント暗号化メソッドの呼び出しを追加し、WorkWithHedgeCollections()メソッドの直近のチェックで操作ログでの取引イベントの説明の表示を削除し、取引イベントコードを作成します。
//+------------------------------------------------------------------+ //| 取引イベントを確認する(ヘッジ) | //+------------------------------------------------------------------+ void CEngine::WorkWithHedgeCollections(void) { //--- 取引イベントコードとフラグを初期化する this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT; this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- リストを更新する this.m_market.Refresh(); this.m_history.Refresh(); //--- 初回実行時のアクション if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- 市場ステータスと口座履歴の変更を確認する 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(this.m_is_market_trade_event && !this.m_is_history_trade_event) { //--- 未決注文数が増加する場合 if(this.m_market.NewPendingOrders()>0) { //--- 未決注文発注フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED; } //--- 成行注文数が増加した場合 if(this.m_market.NewMarketOrders()>0) { //--- イベントフラグを追加しない //--- ... } } //--- イベントが過去の注文と取引のみに関連する場合 else if(this.m_is_history_trade_event && !this.m_is_market_trade_event) { //--- 新規取引がある場合 if(this.m_history.NewDeals()>0) { //--- 口座残高イベントフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE; } } //--- イベントが市場と過去の注文とポジションに関連する場合 else if(this.m_is_market_trade_event && this.m_is_history_trade_event) { //--- 未決注文数が減少して新規取引がない場合 if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0) { //--- 未決注文を削除するためのフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED; } //--- 新規取引と新規過去注文がある場合 if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0) { //--- 直近の取引を取得する COrder* deal=this.GetLastDeal(); if(deal!=NULL) { //--- 市場エントリ取引の場合 if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN) { //--- ポジションオープンフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED; //--- 未決注文数が減少した場合 if(this.m_market.NewPendingOrders()<0) { //--- 未決注文発動フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED; } //--- 注文のチケットを取得する ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- 現在の注文量が0を超える場合 if(order.VolumeCurrent()>0) { //--- 部分的実行フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; } } } //--- 市場エグジット取引の場合 if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT) { //--- ポジション決済フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; //--- 取引のチケットを取得する ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(order_ticket); if(order!=NULL) { //--- 取引のポジションが市場にまだ存在する場合 COrder* pos=this.GetPosition(deal.PositionID()); if(pos!=NULL) { //--- 部分的実行フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; } //--- その他の場合に、ポジションが完全に決済されている場合 else { //--- 注文にストップロスによる決済のフラグがある場合 if(order.IsCloseByStopLoss()) { //--- ストップロスによる決済のフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_SL; } //--- 注文にテイクプロフィットによる決済のフラグがある場合 if(order.IsCloseByTakeProfit()) { //--- テイクプロフィットによる決済のフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_TP; } } } } //--- 反対方向のポジションによる決済の場合 if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY) { //--- ポジション決済フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED; //--- 反対ポジションによる決済のフラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS; //--- 取引注文を取得する ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER); COrder* order=this.GetHistoryOrder(ticket_from); if(order!=NULL) { //--- 注文のポジションが市場にまだ存在する場合 COrder* pos=this.GetPosition(order.PositionID()); if(pos!=NULL) { //--- 部分的実行フラグを追加する this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL; } } } //--- 直近の取引を処理するブロックの終わり } } } #endif this.SetTradeEvent(); } //+------------------------------------------------------------------+
したがって、WorkWithHedgeCollections()
メソッドでイベントコードを作成した後で、イベントデコードメソッドを呼び出します。すぐにデコードできない理由は何でしょうか。要は、現在の復号化方法は一時的なものであるということです。デコード処理を確認する必要があります。本格的な取引イベントクラスは、今後の記事で作成される予定です。
SetTradeEvent()メソッドは取引イベントを定義し、その値をm_acc_trade_eventクラスメンバ変数に書き込みます。一方、LastTradeEvent()およびResetLastTradeEvent()メソッドを使用すると、口座の直近の取引イベントとして変数の値を読み取り、それを呼び出し側プログラムでリセットできます(GetLastError()と同様)。
TestDoEasyPart04.mqhテストEAのOnTick()ハンドラで、直近の取引イベントを読み取り、それをチャートのコメントのように表示するために文字列を追加します。
//+------------------------------------------------------------------+ //| エキスパートティック関数 | //+------------------------------------------------------------------+ 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(); } } //+------------------------------------------------------------------+
テスターでこのEAを実行してボタンをクリックすると、CEngine::SetTradeEvent()メソッドからの進行中の取引イベントが操作ログに表示されます。一方、チャートのコメントには、LastTradeEvent()メソッドを使用してEAがライブラリから受信した、口座で直近に発生したイベントの説明が表示されます。
次の段階
次の記事では、注文とポジションの集まりに似たイベントオブジェクトのクラスとイベントオブジェクトのコレクションを開発し、基本ライブラリオブジェクトがプログラムにイベントを送信できるようにします。
現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問、コメント、提案はコメント欄にお願いします。
シリーズのこれまでの記事:
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/5724
![ローソク足分析技術の研究(第3部): パターン操作のライブラリ](https://c.mql5.com/2/35/Pattern_I__4.png)
![フラクタル指数とハースト指数の財務時系列を予測する能力の評価](https://c.mql5.com/2/36/fraktal1.png)
![MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第5部): 取引イベントのクラスとコレクション、プログラムへのイベント送信](https://c.mql5.com/2/35/MQL5-avatar-doeasy__4.png)
![トレードにおけるOLAPの適用(パート2):インタラクティブな多次元データ分析結果の可視化](https://c.mql5.com/2/36/OLAP_02__1.png)
![MQL5 - MetaTrader 5クライアントターミナルにビルトインされたトレードストラテジーの言語](https://c.mql5.com/i/registerlandings/logo-2.png)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索