请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364704 新帖:25

从用户面板"动态"更改"EA 交易"参数

量化客发表于:4 月 17 日 16:58回复(1)

目录

简介
1. 焦点问题
2. “EA 交易”的结构
3. 与用户面板交互
总结

简介

当开发复杂“EA 交易”时,外部参数的数量可能极其庞大。而设置经常需要手动更改,考虑到庞大参数列表的情形,整个过程将极其耗时。当然,您也可以提前准备并保存设置,然而在某些情况下可能与要求并不完全相符。这是 MQL5 派上用场的地方——一如既往!

让我们尝试创建一个用户面板,以允许我们在交易时“动态”更改“EA 交易”的参数。这可能与手动交易或以半自动模式交易的交易人员息息相关。当做出任何更改时,参数将被写入接下来“EA 交易”将从中读取它们的文件中,并进一步显示在面板上。

1. 焦点问题

我们将创建一个简单 EA 用于说明,该 EA 以 JMA 指标的方向建立一个仓位。EA 将工作于当前交易品种和时间表上的完成柱上。外部参数将包括 Indicator Period(指标周期)、Stop Loss(止损)、Take Profit(获利)、Reverse(反向)和 Lot(手数)。在我们的示例中,这些选项就足够了。

让我们添加两个额外的参数,以便能够打开/关闭面板(开/关信息面板)和启用/禁用“EA 交易”参数设置模式("On The Fly"(动态)设置)。对于较大的参数数量,将额外选项置于列表的开头或结尾总是更加方便,以便能够简单和快速地访问。

图 1. 含“EA 交易”参数的信息面板

图 1. 含“EA 交易”参数的信息面板

默认情况下,"On The Fly"(动态)设置模式禁用。在您第一次启用该模式时,“EA 交易”创建一个文件以保存其当前具有的所有参数。如果文件被意外删除,“EA 交易”将进行同样的操作。“EA 交易”将检测到删除事件并重新创建文件。如果 "On The Fly"(动态)设置模式禁用,“EA 交易”将由外部参数引导。

如果该模式启用,“EA 交易”将从文件读取参数,并且只需通过点击信息面板上的任意参数,您就能够在弹出窗口中选择所需值或输入新值。每次在选择新值时,文件数据都将更新。

2. “EA 交易”的结构

尽管程序不大且所有函数可轻松置于一个文件中,在经过适当分类后,在所有项目信息中导航仍然要方便得多。因此,最好在一开始就将函数按类型分类并置于不同的文件中,稍后将它们置于主文件中。下图显示含 OnTheFly“EA 交易”和所有包含文件的共享项目文件。包含文件位于单独的文件夹 (Include) 中。

图 2. MetaEditor Navigator(导航)窗口中的项目文件

图 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
     }
  }

我还在主文件中包含了其他一些函数:

  • GetIndicatorsHandles – 获取指标句柄。
  • NewBar – 确定新柱事件。
  • SetParameters – 基于模式设置参数。
  • iZeroMemory – 使某些变量和数组归零。
这些函数源代码可在本文随附的文件中找到。在此,我们将仅仅讨论 SetParameters 函数(代码中包含说明性注释):
//+------------------------------------------------------------------+
//| 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 用于其他应用程序》中说明,因此在这里我们将不再赘述。交易函数十分明了且附有大量注释,您应该不会遇到任何困难。我们要关注的是本文的主题。

3. 与用户面板交互

!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';

接下来是处理事件的主函数。在我们的示例中,我们将需要处理两类事件:

  • CHARTEVENT_OBJECT_CLICK 事件 – 左键单击图形对象。
  • CHARTEVENT_OBJECT_EDIT 事件 – Edit 图形对象中的结束文本编辑。

您可以参阅《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. 用于修改所选参数的值的对话窗口

图 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 以期使用提供的简单示例获得许多问题的快速解答的读者提供帮助。在提供的代码片段中,我有意忽视了一些检查。

例如,当对话窗口打开,如果您更改图表高度/宽度,对话窗口将不会自动居中。如果您从列表选择其他选项将其填满,用于选择相关行的对象将显著改变。您可以将这些作为您的“家庭作业”。编程练习非常重要,练习越多越好。

祝您好运!

全部回复

0/140

量化课程

    移动端课程