介绍
本文从逻辑上继续文章MQL5 CookBook:处理典型的图表事件。它涵盖了自定义图表事件的工作方式。读者可以在这里找到定制的事件开发和处理例程。本文讨论的所有思想都是用面向对象的工具实现的。
自定义事件主题非常广泛,特别是当程序员和开发人员在他们的工作中引入想法时。
1。自定义图表事件
显然,这个事件是用户定义的。由程序员决定什么任务或模块可以作为事件的形式。MQL5开发人员可以创建自己的事件并扩展其语言功能以实现复杂的算法。
自定义事件是第二种可能的图表事件类型。第一类是典型事件。尽管文档中没有“典型图表事件”这样的术语,但我建议将其用于第一部分中的十种图表事件类型。
开发人员只建议枚举所有图表事件-枚举图表事件。
根据文档,自定义事件有65535个标识符。自定义事件的第一个和最后一个标识符由显式数字值chartevent_custom和chartevent_custom_last设置,数字对应于1000和66534(图例1)。
传说。1自定义事件的第一个和最后一个标识符
简单计算,考虑到将生成第一个和最后一个标识符:66534-1000+1=65535。
在使用自定义事件之前,必须首先设置它们。从这个意义上说,开发人员成为未来EA中实现事件概念算法的规划者和作者。对自定义事件进行分类非常有用。这种认知方法不能消除歧义,但一定会降低歧义的程度,安排推理的路径。
让我们把这样一个自定义事件标准作为源。例如,开发人员Sergeev提出了一个自动化交易原型的想法。他把所有事件分为三组(传奇。2)。
传说。2自定义事件源分组
然后,根据这个主要思想,可以根据自己的组成员身份开发定制事件。
让我们试着从一些简单的事情开始。首先,我们采用第一组,包括指标事件。可以属于该组的事件有:创建和删除一个指标,接收打开和关闭仓库的信号。第二组包括修改订单和职位状态的事件。在我们的例子中,开仓和平仓都在这个组中。这很简单。最后,最难规范化的组是外部事件组。
让我们使用两个事件:启用和禁用手动事务。
传说。3自定义事件源
主要例子可以通过扣除(从一般到特殊)(利润)来确定。3)。稍后将使用此示例在相应的类(表1)中创建事件类型。
表1定制事件
这个表还不能称为“事件概念”,但它是一个开始。这是另一种方式。众所周知,一个抽象的交易系统模型由三个子系统组成——基本模块(图例)。4)。
数字。4抽象系统模型
基于“源”的自定义事件可以按事件生成条件分类:
- 信号子系统;
- 仓库跟踪子系统;
- 资金管理子系统。
例如,后者可能包括达到撤销许可级别、通过设置值增加交易量、增加损失限额百分比等事件。
2。图表事件处理器和生成器
以下行将专用于图表事件处理程序和生成器。对于处理自定义图表事件,其原理类似于处理一个典型的图表事件。
在处理器中,onchartevent()函数需要四个常量作为参数。显然,开发人员使用这个机制来实现事件识别并获得更多关于它的信息。在我看来,这是一个非常复杂的程序机制。
函数eventChartCustom()生成自定义图形事件。值得注意的是,创建的自定义图表事件可以同时用于图表“本身”和“外部人员”。在我看来,关于自我图表和外部图表影响的最有趣的文章是在MetaTrader5中实现一个多货币模型。
在我看来,有一个不一致的事实,即事件标识符在生成器中是ushort,在处理器中是int。在处理器中使用ushort数据类型也是合乎逻辑的。
三。自定义事件类
正如我前面提到的,事件的概念取决于EA开发人员。现在,让我们从表1继续讨论事件。首先,我们对自定义事件类ceventbase及其派生类(legend)进行排序。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;}; };
事件类型由枚举事件类型枚举设置:
//+------------------------------------------------------------------+ //| 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类假定生成和处理八个给定的事件。类的数据成员如下:
//+------------------------------------------------------------------+ //| 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进行交易。第二个检查事务权限。
还有一个指向对象ceventbase类型的指针,用于多态性和不同类型的事件。ctrade类的实例可以访问事务操作。
CIMA类型对象易于处理从度量接收到的数据。为了简化程序,我使用了两个移动平均值作为要接收的事务信号。还有一个“cButton”类的实例,将用于手动启动/禁用EA。
分类依据“模块过程函数宏”的原理:
//+------------------------------------------------------------------+ //| 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); };
在模块中,只生成三个事件:开始是-开始(),结束是-完成(),主程序是-主程序()。第四个模块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; }
在此模块中创建指向事件对象的指针。稍后,将生成指针创建事件。最后创建一个按钮。它切换到停止模式。这意味着按下按钮后,EA将停止工作。
SeventData结构也参与了该方法的定义。这是一个将参数传递给自定义事件生成器的简单容器。这里只填写结构字段——它是一个长整型字段。它将保存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()。
第三,使用SeventData结构存储EA的幻数。
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()。第一个过程可以产生“接收打开位置信号”的事件和“接收关闭位置信号”的第二个事件。当新列出现时,模块的当前版本将完全正常工作。用于检测新列的类由KonstantinGruzdev描述。
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; } } } } }
它由两部分组成。第一部分处理与“button”对象单击相关的事件。此单击将生成一个外部自定义事件,该事件将由后续处理器处理。
第二部分设计用于处理生成的自定义事件。它由两个街区组成。处理相关事件后,将生成新事件。在第一个块中处理“接收打开的仓库信号”事件。如果处理成功,将生成新的订单事件“打开仓库”。“接收仓库信号”事件在第二个块中处理。如果信号已被处理,则发生“清算”事件。
此EA客户事件处理器。对于使用ceventprocessor类,MQ5是一个很好的例程。EA旨在创建事件并对其做出适当的响应。使用opp范式,我们可以最小化源代码的行数。EA的源代码可以在本文的附件中找到。
在我看来,没有必要每次都参考自定义事件机制。在策略中,有许多微小的、微不足道的和微不足道的东西可以采取不同的形式。
结论
在本文中,我试图描述MQL5环境中自定义事件的工作原理。我希望本文中的想法能够引起不同经验的程序员的兴趣,而不仅仅是新手。
我很高兴MQL5语言不断发展。在不久的将来,可能会有类模板和指向函数的指针。然后,我们可以写出一个指向任何对象的绝对委托。
存档中的源文件可以放在项目文件夹中。就我而言,它是mql5/projects/chartuserevent文件夹。
本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/1163。
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。