MQL5 クックブック:カスタムチャートイベント処理
はじめに
本稿は記事 MQL5 クックブック:典型的なチャートイベントの処理の論理的続編です。カスタムチャートイベントの処理方法を取り上げています。読者のみなさんはカスタムイベントの作成例と処理例を確認することができます。本稿で述べられる考えはすべてオブジェクト指向ツールによって実装されています。
カスタムイベントのテーマは幅広いため、プログラマーと開発者が各々の作業にクリエイティビティを取り入れることができます。
1. カスタムチャートイベント
このイベントがユーザーによって定義されるのは明らかです。正確に何をまた どのタスクまたはプログラムブロックがイベントの形式をとるのか決めるのはプログラマー次第です。MQL5 開発者は独自のイベントを作成します。それは複雑なアルゴリズムの実装に対して言語の能力を拡げるものです。
カスタムイベントはチャートイベントの2番目に可能なタイプです。最初のタイプは典型的なイベントです。ドキュメンテーションには『典型的チャートイベント』という言葉はありませんが、チャートイベントの最初の10タイプについてはその言葉を使うことを提案します。
開発者はチャートイベントすべてに対して1つの列挙しか提示しません。それはENUM_CHART_EVENTです。
ドキュメンテーションによれば、カスタムイベントの識別子は 65535 あります。カスタムイベントの最初と最後の識別子はの明示的な値CHARTEVENT_CUSTOM および CHARTEVENT_CUSTOM_LASTで設定されます。それはそれぞれ1000 と 66534 に等しくなっています(図1)。
図1 カスタムイベントの最初と最後の識別子
最初と最後の識別子を考察するシンプルな計算により次が得られます:66534-1000+1=65535。
カスタムイベント使用前にまず作成する必要があります。この意味では、開発者は将来のエキスパートのアルゴリズムとして実装されるイベントコンセプトの立案者であり筆者でもあります。カスタムイベントを分類すると役に立つでしょう。この認知方法ではあいまいさ排除ができませんが、確かにその度合いは低くなり、推論のラインを整理します。
ソースとしてのカスタムイベントのそういった基準について考えます。たとえば、開発者 sergeev は売買ロボットのプロトタイプの考えを提示しました。彼はすべてのイベントを3つのグループに分けます(図2)。
図2 カスタムイベントソースのグループ
それからこの主な考えによると、カスタムイベントはその所属グループを基に作成されるのです。
まずなにかシンプルなことから始めて見ます。最初に1番目のグループを採ります。それはインディケータイベントを含むものです。このグループに属するイベントは:インディケータの作成と削除、ポジションオープンとクローズのためのシグナル受信、です。2番目のグループには注文とポジションの状態変更のイベントが入ります。われわれの例ではポジションのオープンとクローズはこのグループになります。すべてたいへんシンプルです。そして最後に、公式化にとってもっとも複雑なグループは外部イベントのグループです。
2件のイベントを取り上げます。マニュアルトレードの有効化と無効化です。
図3 カスタムイベントのソース
初期パターンは演繹法(標準から特殊へ)で確率されます(図3)。これは対応するクラスのイベントタイプを作成するのにあとで使用しようと思っているまさにそのパターンです(表1)。
表1 カスタムイベント
この表はまだ『イベントコンセプト』とは呼べませんが、それがスタートです。そしてもうひとつ別のアプローチがあります。抽象的なトレーディングシステムのモデルが3つのサブシステム-基本モジュール、で構成されているのは周知のことです(図4)。
図4 抽象的なトレーディングシステムモデル
『ソース』規準を基にしたカスタムイベントは次で生成されるイベントとして分類されます。
- シグナルサブシステム
- トレーリングオープンポジションのサブシステム
- 資金管理のサブシステム
たとえば後者は許容できるドローダウンレベルに達するイベント、設定値によってトレードボリュームを増やすイベント、損失限界率を大きくするイベントなどといったものを含みます。
2. ChartEvent のハンドラとジェネレータ
以下の数行では特別にチャートイベントのハンドラとジェネレータについて説明します。カスタムチャートイベントのハンドラについては、その原則は一般的なチャートイベントのハンドラに類似しています。
ハンドラである OnChartEvent() 関数はパラメータとして定数を4つ取ります。イベント特定とそれについての追加情報の考えを実装するのに開発者は明らかにこのメカニズムを使用しました。私としては、それはひじょうに堅牢で便利なプログラムメカニズムだと思います。
関数EventChartCustom() はカスタムチャートイベントを生成します。すばらしいことに、カスタムチャートイベントは『自分自身の』チャートと『外部の』チャートに対して作成されることが可能です。私は自分のチャートと外部チャートの意味に関するもっともおもしろい記事は The Implementation of a Multi-currency Mode in MetaTrader 5 だと思います。
個人的には、イベント識別子がハンドラでは int タイプでありながら、ジェネレータでは ushort タイプであるということには調和がないと思います。ハンドラでも ushort データタイプを使用するのが合理的ではないのでしょうか。
3. カスタムイベントのクラス
前に申し上げた通り、イベントコンセプトはエキスパートの開発者次第です。ここで表 1 のイベントを処理していこうと思います。まずカスタムイベントCEventBase のクラスとその派生クラスをソートします(図5)。
図5 イベントクラスの階層
以下が基本クラスの記述です。
//+------------------------------------------------------------------+ //| Class CEventBase. | //| Purpose: base class for a custom event | //| Derives from class CObject. | //+------------------------------------------------------------------+ class CEventBase : public CObject { protected: ENUM_EVENT_TYPE m_type; ushort m_id; SEventData m_data; public: void CEventBase(void) { this.m_id=0; this.m_type=EVENT_TYPE_NULL; }; void ~CEventBase(void){}; //-- bool Generate(const ushort _event_id,const SEventData &_data, const bool _is_custom=true); ushort GetId(void) {return this.m_id;}; private: virtual bool Validate(void) {return true;}; };
イベントタイプは ENUM_EVENT_TYPE 列挙によって設定されます。
//+------------------------------------------------------------------+ //| A custom event type enumeration | //+------------------------------------------------------------------+ enum ENUM_EVENT_TYPE { EVENT_TYPE_NULL=0, // no event //--- EVENT_TYPE_INDICATOR=1, // indicator event EVENT_TYPE_ORDER=2, // order event EVENT_TYPE_EXTERNAL=3, // external event };
データメンバーはイベント識別子とデータストラクチャを持ちます。
基本クラス CEventBase の Generate() メソッドはイベント生成を処理します。GetId() メソッドはイベント ID を返し、仮想メソッド Validate() はイベント識別子の値をチェックします。まず私はイベント処理メソッドをそのクラスにインクルードしましたが、あとでイベントはそれぞれユニークで抽象メソッドはここでは十分ではないと気づきました。私はこのタスクをカスタムイベントを処理するCEventProcessor クラスに委託することにしました。
4. カスタムイベントハンドラクラス
CEventProcessor クラスは提供されるイベントを 8 件生成するものです。以下はこのクラスのデータメンバーです。
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+----------------------------Data members--------------------------+ protected: ulong m_magic; //--- flags bool m_is_init; bool m_is_trade; //--- CEventBase *m_ptr_event; //--- CTrade m_trade; //--- CiMA m_fast_ema; CiMA m_slow_ema; //--- CButton m_button; bool m_button_state; //+------------------------------------------------------------------+ };
属性のリストには初期化とトレードのフラグがあります。最初のものは、正常に起動しなければ EA がトレードすることを許可しません。2番目のものはトレードの許可をチェックします。
CEventBase タイプのオブジェクトに対するポインターもあり、それは多型により異なるタイプのイベントを処理します。CTrade クラスのインスタンスによりトレード処理にアクセスすることができます。
CiMA タイプのオブジェクトはインディケータから受け取られるデータの処理を会付けます。例をシンプルにするために、トレードシグナルを受け取る移動平均を 2つ 採りました。またマニュアルで EA を有効/無効にするための "CButton" クラスのインスタンスもあります。
このクラスのメソッドは『モジュール-プロシージャ-関数-マクロ』の原理で分けられました。
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+-------------------------------Methods----------------------------+ public: //--- constructor/destructor void CEventProcessor(const ulong _magic); void ~CEventProcessor(void); //--- Modules //--- event generating bool Start(void); void Finish(void); void Main(void); //--- event processing void ProcessEvent(const ushort _event_id,const SEventData &_data); private: //--- Procedures void Close(void); void Open(void); //--- Functions ENUM_ORDER_TYPE CheckCloseSignal(const ENUM_ORDER_TYPE _close_sig); ENUM_ORDER_TYPE CheckOpenSignal(const ENUM_ORDER_TYPE _open_sig); bool GetIndicatorData(double &_fast_vals[],double &_slow_vals[]); //--- Macros void ResetEvent(void); bool ButtonStop(void); bool ButtonResume(void); };
モジュールの中にはイベント生成だけを行うものが3つあります。開始をするもの-Start()、終了をするもの-Finish() 、メインのもの-Main() です。4番目のモジュール ProcessEvent() はイベントハンドラでありジェネレータでもあります。
4.1 開始モジュール
このモジュールは OnInit() ハンドラで呼ばれるようにできています。
//+------------------------------------------------------------------+ //| Start module | //+------------------------------------------------------------------+ bool CEventProcessor::Start(void) { //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+1 event if(this.m_ptr_event.Generate(1,data)) //--- create a button if(this.m_button.Create(0,"Start_stop_btn",0,25,25,150,50)) if(this.ButtonStop()) { this.m_button_state=false; return true; } } //--- return false; }
インディケータイベントオブジェクトに対するポインターはこのモジュール内で作成されます。それから『インディケータ作成』イベントが生成されます。ボタンが作成されるのは一番最後です。それは『停止』モードに切り替えられます。それは、ボタンが押されれば、エキスパートが操作を停止するということです。
SEventData ストラクチャもまたこのメソッド定義に入っています。それは単に、パラメータがカスタムイベントのジェネレータに渡されるためのコンテナです。ここではストラクチャの 1 フィールドだけが書き込まれます。それは long タイプのフィールドです。それは EA のマジックナンバーを持ちます。
4.2 終了モジュール
このモジュールは OnDeinit() ハンドラで呼ばれます。
//+------------------------------------------------------------------+ //| Finish module | //+------------------------------------------------------------------+ void CEventProcessor::Finish(void) { //--- reset the event object this.ResetEvent(); //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+2 event bool is_generated=this.m_ptr_event.Generate(2,data,false); //--- process CHARTEVENT_CUSTOM+2 event if(is_generated) this.ProcessEvent(CHARTEVENT_CUSTOM+2,data); } }
ここで前のイベントポインターは消去され、『インディケータ削除』イベントが生成されます。OnDeinit() ハンドラ内でカスタムイベントが生成されると、ランタイムエラー4001 (予想されない外部エラー)が発生することを注意申し上げます。そのため、イベント生成と処理は OnChartEvent() を呼び出さずにこのメソッド内で実行されます。
ふたたび EA のマジックナンバーはSEventData ストラクチャにより格納されます。
4.3 メインモジュール
このモジュールは OnTick() ハンドラで呼ばれます。
//+------------------------------------------------------------------+ //| Main module | //+------------------------------------------------------------------+ void CEventProcessor::Main(void) { //--- a new bar object static CisNewBar newBar; //--- if initialized if(this.m_is_init) //--- if not paused if(this.m_is_trade) //--- if a new bar if(newBar.isNewBar()) { //--- close module this.Close(); //--- open module this.Open(); } }
プロシージャOpen() および Close() はこのモジュール内で呼ばれます。第1のプロシージャは『開始シグナル受信』イベントを、第2のプロシージャは『終了シグナル受信』イベントを生成します。モジュールの現バージョンは新規バー登場時に完全に機能します。新規バーを検出するクラスは Konstantin Gruzdev 氏によって書かれました。
4.4 イベント処理モジュール
このモジュールは OnChartEvent() ハンドラで呼ばれます。サイズと機能性の面ではこのモジュールは最大です。
//+------------------------------------------------------------------+ //| Process event module | //+------------------------------------------------------------------+ void CEventProcessor::ProcessEvent(const ushort _event_id,const SEventData &_data) { //--- check event id if(_event_id==CHARTEVENT_OBJECT_CLICK) { //--- button click if(StringCompare(_data.sparam,this.m_button.Name())==0) { //--- button state bool button_curr_state=this.m_button.Pressed(); //--- to stop if(button_curr_state && !this.m_button_state) { if(this.ButtonResume()) { this.m_button_state=true; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+7 event ushort curr_id=7; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- to resume else if(!button_curr_state && this.m_button_state) { if(this.ButtonStop()) { this.m_button_state=false; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+8 event ushort curr_id=8; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } } } //--- user event else if(_event_id>CHARTEVENT_CUSTOM) { long magic=_data.lparam; ushort curr_event_id=this.m_ptr_event.GetId(); //--- check magic if(magic==this.m_magic) //--- check id if(curr_event_id==_event_id) { //--- process the definite user event switch(_event_id) { //--- 1) indicator creation case CHARTEVENT_CUSTOM+1: { //--- create a fast ema if(this.m_fast_ema.Create(_Symbol,_Period,21,0,MODE_EMA,PRICE_CLOSE)) if(this.m_slow_ema.Create(_Symbol,_Period,55,0,MODE_EMA,PRICE_CLOSE)) if(this.m_fast_ema.Handle()!=INVALID_HANDLE) if(this.m_slow_ema.Handle()!=INVALID_HANDLE) { this.m_trade.SetExpertMagicNumber(this.m_magic); this.m_trade.SetDeviationInPoints(InpSlippage); //--- this.m_is_init=true; } //--- break; } //--- 2) indicator deletion case CHARTEVENT_CUSTOM+2: { //---release indicators bool is_slow_released=IndicatorRelease(this.m_fast_ema.Handle()); bool is_fast_released=IndicatorRelease(this.m_slow_ema.Handle()); if(!(is_slow_released && is_fast_released)) { //--- to log? if(InpIsLogging) Print("Failed to release the indicators!"); } //--- reset the event object this.ResetEvent(); //--- break; } //--- 3) check open signal case CHARTEVENT_CUSTOM+3: { MqlTick last_tick; if(SymbolInfoTick(_Symbol,last_tick)) { //--- signal type ENUM_ORDER_TYPE open_ord_type=(ENUM_ORDER_TYPE)_data.dparam; //--- double open_pr,sl_pr,tp_pr,coeff; open_pr=sl_pr=tp_pr=coeff=0.; //--- if(open_ord_type==ORDER_TYPE_BUY) { open_pr=last_tick.ask; coeff=1.; } else if(open_ord_type==ORDER_TYPE_SELL) { open_pr=last_tick.bid; coeff=-1.; } sl_pr=open_pr-coeff*InpStopLoss*_Point; tp_pr=open_pr+coeff*InpStopLoss*_Point; //--- to normalize prices open_pr=NormalizeDouble(open_pr,_Digits); sl_pr=NormalizeDouble(sl_pr,_Digits); tp_pr=NormalizeDouble(tp_pr,_Digits); //--- open the position if(!this.m_trade.PositionOpen(_Symbol,open_ord_type,InpTradeLot,open_pr, sl_pr,tp_pr)) { //--- to log? if(InpIsLogging) Print("Failed to open the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+5 event ushort curr_id=5; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- break; } //--- 4) check close signal case CHARTEVENT_CUSTOM+4: { if(!this.m_trade.PositionClose(_Symbol)) { //--- to log? if(InpIsLogging) Print("Failed to close the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+6 event ushort curr_id=6; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } //--- break; } //--- 5) position opening case CHARTEVENT_CUSTOM+5: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_IN) { //--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 6) position closing case CHARTEVENT_CUSTOM+6: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_OUT) { //--- to log? if(InpIsLogging) { Print("\nClosed position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 7) stop trading case CHARTEVENT_CUSTOM+7: { datetime stop_time=(datetime)_data.dparam; //--- this.m_is_trade=false; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is stopped at: %s", TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } //--- 8) resume trading case CHARTEVENT_CUSTOM+8: { datetime resume_time=(datetime)_data.dparam; this.m_is_trade=true; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is resumed at: %s", TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } } } } }
それは二部構成になっています。最初の部分は『ボタン』オブジェクトをクリックすることに関連する処理イベントです。このクリックは外部カスタムイベントを生成します。それはハンドラによって後に処理されるものです。
次の部分は先生されたカスタムイベントを処理するために作成されています。それにはブロックが2つあり、そこで関連するイベントが処理された後、新しいイベントが生成されます。『開始シグナル受信』イベントは最初のブロックで処理されます。正常に処理されると新しい注文イベント『ポジションのオープン』が生成されます。『終了シグナル受信』イベントは2番目のブロックで処理されます。シグナルが処理されると、『ポジションのクローズ』イベントが発生します。
エキスパートCustomEventProcessor.mq5 はCEventProcessor クラス使用の好例です。EA はイベント作成とそれに対して適切に応答するために作成されています。OPP パラダイムを使用し、ソースコードを数行に最小化することができました。EA のソースコードは本稿に添付しております。
私の考えでは、毎回カスタムイベントのメカニズムを参照する必要はないと思っています。異なる形式を持ちうる戦略の面では、小規模な無意味でつまらないものが数多くあります。
おわりに
本稿では、MQL5 環境でカスタムイベントを処理する原理を説明しようとしました。本稿で取り上げられている考えが初心者だけでなくさまざまな経験を持つプログラマーの方にとっても興味を引くものとなることを願っております。
私は MQL5 言語が開発されてよかったと思っています。おそらく近い将来、クラスのテンプレートができ、関数に対するポインターもできることでしょう。そうなれば抽象的なオブジェクトのメソッドを指す本格的なデリゲートを書くこととなるのです。
アーカイブのソースファイルはプロジェクトフォルダに入れることが可能です。私の場合それはMQL5\Projects\ChartUserEventフォルダです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/1163
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索