简介
1. 焦点问题
2. “EA 交易”的结构
3. 与用户面板交互
总结
当开发复杂“EA 交易”时,外部参数的数量可能极其庞大。而设置经常需要手动更改,考虑到庞大参数列表的情形,整个过程将极其耗时。当然,您也可以提前准备并保存设置,然而在某些情况下可能与要求并不完全相符。这是 MQL5 派上用场的地方——一如既往!
让我们尝试创建一个用户面板,以允许我们在交易时“动态”更改“EA 交易”的参数。这可能与手动交易或以半自动模式交易的交易人员息息相关。当做出任何更改时,参数将被写入接下来“EA 交易”将从中读取它们的文件中,并进一步显示在面板上。
我们将创建一个简单 EA 用于说明,该 EA 以 JMA 指标的方向建立一个仓位。EA 将工作于当前交易品种和时间表上的完成柱上。外部参数将包括 Indicator Period(指标周期)、Stop Loss(止损)、Take Profit(获利)、Reverse(反向)和 Lot(手数)。在我们的示例中,这些选项就足够了。
让我们添加两个额外的参数,以便能够打开/关闭面板(开/关信息面板)和启用/禁用“EA 交易”参数设置模式("On The Fly"(动态)设置)。对于较大的参数数量,将额外选项置于列表的开头或结尾总是更加方便,以便能够简单和快速地访问。
图 1. 含“EA 交易”参数的信息面板
默认情况下,"On The Fly"(动态)设置模式禁用。在您第一次启用该模式时,“EA 交易”创建一个文件以保存其当前具有的所有参数。如果文件被意外删除,“EA 交易”将进行同样的操作。“EA 交易”将检测到删除事件并重新创建文件。如果 "On The Fly"(动态)设置模式禁用,“EA 交易”将由外部参数引导。
如果该模式启用,“EA 交易”将从文件读取参数,并且只需通过点击信息面板上的任意参数,您就能够在弹出窗口中选择所需值或输入新值。每次在选择新值时,文件数据都将更新。
尽管程序不大且所有函数可轻松置于一个文件中,在经过适当分类后,在所有项目信息中导航仍然要方便得多。因此,最好在一开始就将函数按类型分类并置于不同的文件中,稍后将它们置于主文件中。下图显示含 OnTheFly“EA 交易”和所有包含文件的共享项目文件。包含文件位于单独的文件夹 (Include) 中。
图 2. MetaEditor Navigator(导航)窗口中的项目文件
当包含文件与主文件位于同一文件夹时,代码如下所示:
//+------------------------------------------------------------------+ //| CUSTOM LIBRARIES | //+------------------------------------------------------------------+ #include "Include/!OnChartEvent.mqh" #include "Include/CREATE_PANEL.mqh" #include "Include/FILE_OPERATIONS.mqh" #include "Include/ERRORS.mqh" #include "Include/ARRAYS.mqh" #include "Include/TRADE_SIGNALS.mqh" #include "Include/TRADE_FUNCTIONS.mqh" #include "Include/GET_STRING.mqh" #include "Include/GET_COLOR.mqh" #include "Include/ADD_FUNCTIONS.mqh"
有关如何包含文件的更多信息,请参阅《MQL5 参考》。
我们将需要全局变量 - 外部参数的副本。取决于“EA 交易”的模式,它们的值将从外部参数或从文件分配。这些变量在整个程序代码中使用,例如,在信息面板上显示的值中、交易函数中等。
// COPY OF EXTERNAL PARAMETERS int gPeriod_Ind = 0; double gTakeProfit = 0.0; double gStopLoss = 0.0; bool gReverse = false; double gLot = 0.0;
与其他所有“EA 交易”一样,我们将具有主函数:OnInit、OnTick 和 OnDeinit。也会有 OnTimer 函数。每一秒它都会检查参数文件是否存在,并在意外删除时将其复原。由于我们需要与用户面板交互,还要使用 OnChartEvent 函数。这个函数连同其他相关函数放置在单独的文件中 (!OnChartEvent.mqh)。
主文件的核心代码如下所示:
#define szArrIP 5 // Size of the parameter array #define NAME_EXPERT MQL5InfoString(MQL5_PROGRAM_NAME) // Name of EA #define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) // Folder that contains the terminal data //+------------------------------------------------------------------+ //| STANDARD LIBRARIES | //+------------------------------------------------------------------+ #include <Trade/SymbolInfo.mqh> #include <Trade/Trade.mqh> //+------------------------------------------------------------------+ //| CUSTOM LIBRARIES | //+------------------------------------------------------------------+ #include "Include/!OnChartEvent.mqh" #include "Include/CREATE_PANEL.mqh" #include "Include/FILE_OPERATIONS.mqh" #include "Include/ERRORS.mqh" #include "Include/ARRAYS.mqh" #include "Include/TRADE_SIGNALS.mqh" #include "Include/TRADE_FUNCTIONS.mqh" #include "Include/GET_STRING.mqh" #include "Include/GET_COLOR.mqh" #include "Include/ADD_FUNCTIONS.mqh" //+------------------------------------------------------------------+ //| CREATING CLASS INSTANCES | //+------------------------------------------------------------------+ CSymbolInfo mysymbol; // CSymbolInfo class object CTrade mytrade; // CTrade class object //+------------------------------------------------------------------+ //| EXTERNAL PARAMETERS | //+------------------------------------------------------------------+ input int Period_Ind = 10; // Indicator Period input double TakeProfit = 100; // Take Profit (p) input double StopLoss = 30; // Stop Loss (p) input bool Reverse = false; // Reverse Position input double Lot = 0.1; // Lot //--- input string slash=""; // * * * * * * * * * * * * * * * * * * * sinput bool InfoPanel = true; // On/Off Info Panel sinput bool SettingOnTheFly = false; // "On The Fly" Setting //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ int hdlSI=INVALID_HANDLE; // Signal indicator handle double lcheck=0; // For the check of parameter values bool isPos=false; // Position availability //--- COPY OF EXTERNAL PARAMETERS int gPeriod_Ind = 0; double gTakeProfit = 0.0; double gStopLoss = 0.0; bool gReverse = false; double gLot = 0.0; //+------------------------------------------------------------------+ //| EXPERT ADVISOR INITIALIZATION | //+------------------------------------------------------------------+ void OnInit() { if(NotTest()) { EventSetTimer(1); } // If it's not the tester, set the timer //--- Init_arr_vparams(); // Initialization of the array of parameter values SetParameters(); // Set the parameters GetIndicatorsHandles(); // Get indicator handles NewBar(); // New bar initialization SetInfoPanel(); // Info panel } //+------------------------------------------------------------------+ //| CURRENT SYMBOL TICKS | //+------------------------------------------------------------------+ void OnTick() { // If the bar is not new, exit if(!NewBar()) { return; } else { TradingBlock(); } } //+------------------------------------------------------------------+ //| TIMER | //+------------------------------------------------------------------+ void OnTimer() { SetParameters(); SetInfoPanel(); } //+------------------------------------------------------------------+ //| EXPERT ADVISOR DEINITIALIZATION | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Get the deinitialization reason code if(NotTest()) { { Print(getUnitReasonText(reason)); } //--- // When deleting from the chart if(reason==REASON_REMOVE) { // Delete all objects created by the Expert Advisor DeleteAllExpertObjects(); //--- if(NotTest()) { EventKillTimer(); } // Stop the timer IndicatorRelease(hdlSI); // Delete the indicator handle } }
我还在主文件中包含了其他一些函数:
//+------------------------------------------------------------------+ //| SETTING PARAMETERS IN TWO MODES | //+------------------------------------------------------------------+ // If this variable is set to false, the parameters in the file are read from the array // where they are saved for quick access after they have been read for the first time. // The variable is zeroed out upon the change in the value on the panel. bool flgRead=false; double arrParamIP[]; // Array where the parameters from the file are saved //--- void SetParameters() { // If currently in the tester or // in real time but with the "On The Fly" Setting mode disabled if(!NotTest() || (NotTest() && !SettingOnTheFly)) { // Zero out the variable and parameter array flgRead=false; ArrayResize(arrParamIP,0); //--- // Check the Indicator Period for correctness if(Period_Ind<=0) { lcheck=10; } else { lcheck=Period_Ind; } gPeriod_Ind=(int)lcheck; //--- gStopLoss=StopLoss; gTakeProfit=TakeProfit; gReverse=Reverse; //--- // Check the Lot for correctness if(Lot<=0) { lcheck=0.1; } else { lcheck=Lot; } gLot=lcheck; } else // If "On The Fly" Setting mode is enabled { // Check whether there is a file to write/read parameters to/from the file string lpath=""; //--- // If the folder exists if((lpath=CheckCreateGetPath())!="") { // Write or read the file WriteReadParameters(lpath); } } }
SetParameters 函数的源代码简单明了。我们来仔细研究一下 WriteReadParameters 函数。这里一切都很简单。我们首先检查包含参数的文件是否存在。如果存在,我们使用 GetValuesParamsFromFile 函数读取文件并将参数值写入数组。如果文件不存在,将创建文件,并将当前外部参数写入文件。
下面是附上更详细注释的代码,用于实现上文中说明的操作:
//+------------------------------------------------------------------+ //| WRITE DATA TO FILE | //+------------------------------------------------------------------+ void WriteReadParameters(string pth) { string nm_fl=pth+"ParametersOnTheFly.ini"; // File name and path //--- // Get the file handle to read the file int hFl=FileOpen(nm_fl,FILE_READ|FILE_ANSI); //--- if(hFl!=INVALID_HANDLE) // If the handle has been obtained, the file exists { // Get parameters from the file if(!flgRead) { // Set the array size ArrayResize(arrParamIP,szArrIP); //--- // Fill the array with values from the file flgRead=GetValuesParamsFromFile(hFl,arrParamIP); } //--- // If the array size is correct,... if(ArraySize(arrParamIP)==szArrIP) { // ...set the parameters to the variables //--- // Check the Indicator Period for correctness if((int)arrParamIP[0]<=0) { lcheck=10; } else { lcheck=(int)arrParamIP[0]; } gPeriod_Ind=(int)lcheck; //--- gTakeProfit=arrParamIP[1]; gStopLoss=arrParamIP[2]; gReverse=arrParamIP[3]; //--- // Check the Lot for correctness if(arrParamIP[4]<=0) { lcheck=0.1; } else { lcheck=arrParamIP[4]; } gLot=lcheck; } } else // If the file does not exist { iZeroMemory(); // Zero out variables //--- // When creating the file, write current parameters of the Expert Advisor //--- // Get the file handle to write to the file int hFl2=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,""); //--- if(hFl2!=INVALID_HANDLE) // If the handle has been obtained { string sep="="; //--- // Parameter names and values are obtained from arrays in the ARRAYS.mqh file for(int i=0; i<szArrIP; i++) { FileWrite(hFl2,arr_nmparams[i],sep,arr_vparams[i]); } //--- FileClose(hFl2); // Close the file //--- Print("File with parameters of the "+NAME_EXPERT+" Expert Advisor created successfully."); } } //--- FileClose(hFl); // Close the file }
WriteReadParameters 和 GetValuesParamsFromFile 函数可在 FILE_OPERATIONS.mqh 文件中找到。
部分函数已在我上一篇文章《如何准备 MetaTrader 5 Quotes 用于其他应用程序》中说明,因此在这里我们将不再赘述。交易函数十分明了且附有大量注释,您应该不会遇到任何困难。我们要关注的是本文的主题。
!OnChartEvent.mqh 文件包含用于和用户面板交互的函数。在许多函数中使用的变量和数组在最开始的全局范围内声明:
// Current value on the panel or // entered in the input box string currVal=""; bool flgDialogWin=false; // Flag for panel existence int szArrList=0,// Size of the option list array number=-1; // Parameter number in the panel list string nmMsgBx="", // Name of the dialog window nmValObj=""; // Name of the selected object //--- // Option list arrays in the dialog window string lenum[],lenmObj[]; //--- // colors of the dialog window elements color clrBrdBtn=clrWhite, clrBrdFonMsg=clrDimGray,clrFonMsg=C'15,15,15', clrChoice=clrWhiteSmoke,clrHdrBtn=clrBlack, clrFonHdrBtn=clrGainsboro,clrFonStr=C'22,39,38';
接下来是处理事件的主函数。在我们的示例中,我们将需要处理两类事件:
您可以参阅《MQL5 参考》了解其他 MQL5 事件。
让我们首先仅为实时处理事件设置一个检查,始终假设 "On The Fly"(动态)设置模式启用 (SettingOnTheFly)。事件的处理由单独的函数执行:ChartEvent_ObjectClick 和 ChartEvent_ObjectEndEdit。
//+------------------------------------------------------------------+ //| USER EVENTS | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // If the event is real time and the "On The Fly" setting mode is enabled if(NotTest() && SettingOnTheFly) { //+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_CLICK EVENT | //+------------------------------------------------------------------+ if(ChartEvent_ObjectClick(id,lparam,dparam,sparam)) { return; } //--- //+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_ENDEDIT EVENT | //+------------------------------------------------------------------+ if(ChartEvent_ObjectEndEdit(id,lparam,dparam,sparam)) { return; } } //--- return; }
当您单击属于列表的对象时,信息面板上将出现一个对话窗口,允许您选择其他值或在输入框中输入新值。
图 3. 用于修改所选参数的值的对话窗口
我们来仔细研究一下它是如何工作的。点击图形对象后,程序首先使用 ChartEvent_ObjectClick 函数通过事件标识符检查是否真的点击了图形对象。
如果希望对话窗口在图表中间打开,您需要知道图表大小。这可以通过在 ChartGetInteger 函数中指明 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS 属性获得。然后程序切换至 DialogWindowInfoPanel。您可以在《MQL5 参考》中熟悉所有图表属性。
下面是实现上述操作的代码:
//+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_CLICK EVENT | //+------------------------------------------------------------------+ bool ChartEvent_ObjectClick(int id,long lparam,double dparam,string sparam) { // If there was an event of clicking on a graphical object if(id==CHARTEVENT_OBJECT_CLICK) // id==1 { Get_STV(); // Get all data on the symbol //--- string clickedChartObject=sparam; // Name of the clicked object //--- // Get the chart size width_chart=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,0); height_chart=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0); //--- DialogWindowInfoPanel(clickedChartObject); } //--- return(false); }
我们首先使用 DialogWindowInfoPanel 函数检查对话窗口目前是否打开。如果未找到窗口,GetNumberClickedObjIP 函数检查点击是否与来自信息面板上的列表的对象相关。如果点击对象是来自列表的对象,函数将返回来自对象数组的相关元素编号。使用该编号,InitArraysAndDefault 函数接下来确定对话窗口中的列表数组大小和默认值。如果所有操作成功,对话窗口将出现。
如果 DialogWindowInfoPanel 函数确定该对话窗口已经打开,程序将检查是否点击了对话窗口中的对象。例如,打开对话窗口后,其值当前在面板上显示的行将显示为选定。如果您单击列表中的其他选项,程序将使用选择点击的对话窗口列表选项的 SelectionOptionInDialogWindow 函数。
如果您点击当前选定的列表选项,此对象将被识别为待编辑对象且一个输入框将出现,这样当您点击输入框时可以输入新值。SetEditObjInDialogWindow 函数用于设置输入框。
最后,如果单击 Apply(应用)按钮,程序将检查是否已修改值。如果已修改,新值将出现在面板上并将写入文件。
对话窗口的主函数的代码提供如下:
//+------------------------------------------------------------------+ //| DIALOG WINDOW OF THE INFO PANEL | //+------------------------------------------------------------------+ void DialogWindowInfoPanel(string clickObj) { // If there is currently no dialog window if(!flgDialogWin) { // Get the object number in the array // Exit if none of the parameters displayed on the panel has been clicked if((number=GetNumberClickedObjIP(clickObj))==-1) { return; } //--- // Initialization of default values //and determination of the list array size if(!InitArraysAndDefault()) { return; } //--- // Set the dialog window SetDialogWindow(); //--- flgDialogWin=true; // Mark the dialog window as open ChartRedraw(); } else // If the dialog window is open { // Set the input box for the modification of the value SetEditObjInDialogWindow(clickObj); //--- // If one of the buttons in the dialog box is clicked if(clickObj=="btnApply" || clickObj=="btnCancel") { // If the Apply button is clicked if(clickObj=="btnApply") { // Compare values on the panel with the ones on the list // If the value on the list is different from the one that is currently displayed on the panel // (which means it is different from the one in the file), // ...change the value on the panel and update the file if(currVal!=ObjectGetString(0,nmValObj,OBJPROP_TEXT)) { // Update the value on the panel ObjectSetString(0,nmValObj,OBJPROP_TEXT,currVal); ChartRedraw(); //--- // Read all data on the panel and write it to the file WriteNewData(); } } //--- DelDialogWindow(lenmObj); // Delete the dialog window iZeroMemory(); // Zero out the variables //--- // Update the data SetParameters(); GetHandlesIndicators(); SetInfoPanel(); //--- ChartRedraw(); } else // If neither Apply nor Cancel has been clicked { // Selection of the dialog window list option SelectionOptionInDialogWindow(clickObj); //--- ChartRedraw(); } } }
每次在输入框中输入新值,CHARTEVENT_OBJECT_EDIT 事件生成,且程序切换至 ChartEvent_ObjectEndEdit 函数。如果对话窗口中的值已经修改,输入值将被保存,以检查正确性和分配至列表中的对象。您可以在下述代码中查看它的更多细节:
//+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_ENDEDIT EVENT | //+------------------------------------------------------------------+ bool ChartEvent_ObjectEndEdit(int id,long lparam,double dparam,string sparam) { if(id==CHARTEVENT_OBJECT_ENDEDIT) // id==3 { string editObject=sparam; // Name of the edited object //--- // If the value has been entered in the input box in the dialog window if(editObject=="editValIP") { // Get the entered value currVal=ObjectGetString(0,"editValIP",OBJPROP_TEXT); //--- // (0) Period Indicator if(number==0) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal="1"; } //--- // Set the entered value ObjectSetString(0,"enumMB0",OBJPROP_TEXT,currVal); } //--- // (4) Lot if(number==4) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal=DS(SS.vol_min,2); } //--- // Set the entered value ObjectSetString(0,"enumMB0",OBJPROP_TEXT,DS2(SD(currVal))); } //--- // (1) Take Profit (p) // (2) Stop Loss (p) if(number==1 || number==2) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal="1"; } //--- // Set the entered value ObjectSetString(0,"enumMB1",OBJPROP_TEXT,currVal); } //--- DelObjbyName("editValIP"); ChartRedraw(); } } //--- return(false); }
运行中的“EA 交易”请参见下面的视频:
可以下载随附于文末的压缩文件用于进一步研究。
我希望本文将为那些开始学习 MQL5 以期使用提供的简单示例获得许多问题的快速解答的读者提供帮助。在提供的代码片段中,我有意忽视了一些检查。
例如,当对话窗口打开,如果您更改图表高度/宽度,对话窗口将不会自动居中。如果您从列表选择其他选项将其填满,用于选择相关行的对象将显著改变。您可以将这些作为您的“家庭作业”。编程练习非常重要,练习越多越好。
祝您好运!
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程