用 MQL5 创建交易活动控制板

Евгений | 9 十二月, 2013


简介

效率在一个工作环境中至关重要,尤其是在交易者的工作中,其中速度和准确性扮演着重要的角色。在准备工作客户端的同时,每个人都会让他的工作空间尽可能舒适,从而尽可能快地进行分析并进入市场。但是事实的真相是开发人员无法总是让每个人都高兴,并且也不可能与某人希望的某些功能完全合调。

例如,对于一名投机者,几分之一秒和每按一次“新建订单”键都非常重要,所有参数的后续设置在时间方面都可能非常关键。

那么,我们如何找到一个解决方案呢?解决方案贮藏在自定义控件中,因为 MetaTrader 5 提供如此精彩的组件,例如“按钮”、“编辑”和“标签”。让我们开始动手。


2. 控制板选项

首先,让我们决定哪些类型的功能对一个控制板至关重要。我们将重点放在交易、使用控制板上,因此包含以下功能:

同样,添加自定义颜色方案控制板、字号和保存设置的能力也不会造成危害。让我们更加详细地说明期货控制板上的所有控件。我们将为控制板的每个功能指定对象的名称、对象的类型以及其目的描述。每个对象的名称将以 "ActP" 开头 - 这将是一种区别,表示对象属于控制板。

2.1. 建仓

下面,我们将介绍建仓所需的所有必要参数,并且通过单击一个按钮来实施建仓。通过选中一个复选框来激活的辅助线,将帮助我们设置止损和获利水平。执行类型的选择通过单选按钮来进行。

名称
类型
说明
 ActP_buy_button1  按钮
 用于“买入”交易的按钮
 ActP_sell_button1
 按钮
 用于“卖出”交易的按钮
 ActP_DealLines_check1
 标记
 辅助线的设置/复位标记
 ActP_Exe_radio1
 单选按钮
 用于选择交易类型的一组单选按钮
 ActP_SL_edit1
 输入字段
 用于输入止损的字段
 ActP_TP_edit1
 输入字段
 用于输入获利的字段
 ActP_Lots_edit1
 输入字段
 用于输入数量的字段
 ActP_dev_edit1
 输入字段
 用于输入建仓期间允许偏差的字段
 ActP_mag_edit1
 输入字段
 用于输入数字的字段
 ActP_comm_edit1  输入字段  用于输入备注的字段

表 1 “建仓”控制板的控件列表

2.2 生成挂单

在下面,我们将介绍生成挂单所需的所有必要参数,并通过按一个键来生成挂单。通过选中一个标记来激活的辅助线将帮助设置止损、获利、止损限制水平和过期时间。执行类型和过期时间类型的选择将通过一组单选按钮的帮助来进行。

名称
类型
说明
 ActP_buy_button2  按钮
 用于设置“买入”订单的按钮
 ActP_sell_button2
 按钮
 用于设置交易订单的按钮
 ActP_DealLines_check2
 标记
 辅助线设置/复位标记
 ActP_lim_check2  标记  订单止损限制设置/复位标记 
 ActP_Exe_radio2
 单选按钮
 用于选择订单执行类型的一组单选按钮
 ActP_exp_radio2  单选按钮  用于选择订单过期类型的一组单选按钮
 ActP_SL_edit2
 输入字段
 用于输入止损的字段
 ActP_TP_edit2
 输入字段
 用于输入获利的字段
 ActP_Lots_edit2
 输入字段
 用于输入数量的字段
 ActP_limpr_edit2
 输入字段  用于输入止损限制订单价格的字段
 ActP_mag_edit2
 输入字段
 用于输入魔术数字的字段
 ActP_comm_edit2  输入字段  用于输入备注的字段
 ActP_exp_edit2  输入字段  用于输入过期时间的字段
 ActP_Pr_edit2  输入字段  用于输入订单执行价格的字段 

 表 2 “生成挂单”控制板的控件列表

2.3. 修改交易 / 平仓

下面,我们将介绍修改交易和平仓所需的所有必要参数。通过选中一个复选框来激活的辅助线,将帮助我们设置止损和获利水平。交易的选择将从下拉列表生成。

名称
类型
说明
 ActP_ord_button5   下拉列表   交易的选择列表
 ActP_mod_button4  按钮
 交易修改按钮 
 ActP_del_button4
 按钮
 平仓按钮 
 ActP_DealLines_check4
 标记
 辅助线设置/复位标记
 ActP_SL_edit4
 输入字段
 用于输入止损的字段
 ActP_TP_edit4
 输入字段
 用于输入获利的字段
 ActP_Lots_edit4
 输入字段
 用于输入数量的字段 
 ActP_dev_edit4
 输入字段
 用于允许偏差的字段 
 ActP_mag_edit4
 输入字段
 用于显示魔术数字的字段(只读)
 ActP_Pr_edit4  输入字段  用于显示建仓价的字段(只读)

表 3. “修改交易/平仓”控制板的控件列表

2.4. 修改/删除订单

下面,我们将介绍修改和删除挂单所需的所有必要参数。通过选中一个复选框来激活的辅助线,将帮助我们设置止损、获利、止损限制水平以及过期时间。到期时间类型的选择将在一组单选按钮的帮助下生成。订单的选择将从一个下拉列表生成。

名称
类型
说明
 ActP_ord_button5  下拉列表  订单选择列表
 ActP_mod_button3  按钮
 订单修改按钮 
 ActP_del_button3
 按钮
 订单删除按钮 
 ActP_DealLines_check3
 标记
 辅助线设置/复位标记
 ActP_exp_radio3  单选按钮  用于选择订单过期类型的一组单选按钮
 ActP_SL_edit3
 输入字段
 用于输入止损的字段
 ActP_TP_edit3
 输入字段
 用于输入获利的字段
 ActP_Lots_edit3
 输入字段
 用于显示数量的字段(只读)
 ActP_limpr_edit3
 输入字段  用于输入止损限制订单价格的字段 
 ActP_mag_edit3
 输入字段
 用于显示魔术数字的字段(只读)
 ActP_comm_edit3  输入字段  用于输入备注的字段
 ActP_exp_edit3  输入字段  用于输入过期时间的字段
 ActP_Pr_edit3  输入字段  用于输入订单执行价格的字段 
 ActP_ticket_edit3  输入字段  用于显示订单单证的字段(只读) 

表 4. “修改/删除订单”控制板的控件列表

2.5 设置

下面,我们将从下拉列表选择按钮、标签和文本的颜色,并设置各种字号。

名称
类型
说明
 ActP_col1_button6  下拉列表
 按钮的颜色选择列表
 ActP_col2_button6
 下拉列表
 标记的颜色选择列表
 ActP_col3_button6
 下拉列表
 文本的颜色选择列表
 ActP_font_edit6
 输入字段
 用于指定文本字号的字段

表 5. “设置”控制板的控件列表

还添加了一个按钮以便在不使用控制板时将其最小化。您可能已经注意到诸如“辅助线”等工具的存在。它们是什么?为什么我们需要它们?通过使用这些线条,我们能够设置止损、获利、触发挂单的价格、止损限制订单的价格(水平线)以及延期订单的过期时间(垂直线),只需要用鼠标将这些线条拖到需要的价格/时间即可。

毕竟,可视化设置比文本设置(在相应的字段中手动输入价格/时间)更加方便。这些线条还会向我们“突出显示”所选订单的参数。因为可能有很多订单,通常显示价格的标准客户端阴影线可能变得非常混淆。 


3. 创建界面的一般方法

目前,我们已经成功设置了我们的第四个目标 - 在交易内创建一个图形助手窗体。为此,我们需要最友好的用户界面。首先,必须清楚必须使用软件创建所有控件(将有很多控件),因此需要预先计算对象的位置和大小。

现在,想象一下我们经历了一段漫长、乏味和困难的时间,计算出对象的坐标,确保它们清晰可见,不会相互重叠;之后,需要添加一个新的对象,现在我们的整个方案需要重建!

那些熟悉快速应用程序开发环境(例如 Delphi、C + + Builder)的人知道如何能够快速创建最复杂的用户界面。

让我们尝试用 MQL5 实施。首先,使用鼠标,我们以最适当的方式定位控件,然后调整它们的大小。接着,我们编写一个简单的脚本,该脚本读取图表上所有对象的属性,然后将它们记录到一个文件,并且在需要时,我们将能够轻松地获取这些属性并在任何图表上完整重建对象。

脚本的代码看起来可能如下所示:

//+------------------------------------------------------------------+
//|                                  Component properties writer.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

input int interfaceID=1; //输入参数 - 保存界面的 ID
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //打开文件准备写入  
   int handle=FileOpen("Active_Panel_scheme_"+IntegerToString(interfaceID)+".bin", FILE_WRITE|FILE_BIN);
   if(handle!=INVALID_HANDLE)
     {
      //我们将遍历图表上的所有对象
      for(int i=0;i<ObjectsTotal(0);i++)
        {
         string name=ObjectName(0,i);
         //并且把它们的属性写入文件
         FileWriteString(handle,name,100);
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE));

         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR));

         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100);

         FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE));
        }
      //关闭文件
      FileClose(handle);
      Alert("Done!");
     }
  }
//+------------------------------------------------------------------+

如您所见,代码非常简单,它向一个二进制文件写入所有图表对象的某些属性。最重要的事情是在读取文件时不要忘记所记录属性的顺序。 

脚本已经准备就绪,因此让我们转到界面的创建。

我们要做的第一件事是按其选项卡类型组织主菜单。为什么需要选项卡?因为有很多对象,将它们全部显示在屏幕上会造成问题。并且因为对象是相应群组在一起的(见上表),将每个组放在单独的选项卡中更加容易。   

因此,使用客户端菜单 Insert(插入) -> Objects(对象) -> Button(按钮),我们将在图表的顶部创建作为主菜单的五个按钮。

图 1. 控制板的选项卡

1 控制板的选项卡

请记住,通过选择,然后在按住 "Ctrl" 键的同时拖动对象就可以轻松复制对象。通过这种方式,我们将创建对象的副本而不是移动原来的对象。

应特别注意对象的名称,不要忘记它们必须以 "ActP" 开头。此外,我在名称字符串中添加了 "main" 字样,表示对象属于主菜单栏。

图 2. 对象列表(控制板的选项卡)

2. 对象列表(控制板的选项卡)

类似地,让我们将选项卡内容应用到新的图表。每个选项卡的内容应放在单独的图表中!

选项卡 "Market"(市场):

图 3. 选项卡 "Market"(市场)的控件

图 3. 选项卡 "Market"(市场)的控件

选项卡 "Pending"(挂单):

图 4. 选项卡 "Pending"(挂单)的控件

图 4. 选项卡 "Pending"(挂单)的控件

选项卡 "Settings"(设置):

图 5. 选项卡 "Settings"(设置)的控件

5. 选项卡 "Settings"(设置)的控件

最后一个选项卡 "Modify / close"(修改/平仓)不同,它用于修改/删除挂单,以及修改交易和平仓。将交易处理和订单处理分成两个单独的子选项卡是合理的。首先,让我们创建一个激活下拉列表的按钮,我们从该列表选择要处理的订单或交易。

图 6. 选项卡 "Modify/Close"(修改/平仓)的控件

6. 选项卡 "Modify/Close" (修改/平仓)的控件

之后,我们创建子选项卡。处理交易:

图 7. 用于处理仓位的控件

7. 用于处理仓位的控件

处理订单:

图 8. 用于处理订单的子选项卡

8. 用于处理订单的子选项卡

就这样,界面创建完毕。

我们将脚本应用到每个图表,在单独的文件中保存每个选项卡。每个选项卡的输入参数 "interfaceID" 必须不同:

选项卡编号 5 对应于主菜单上的“最小化窗口”按钮,因此其上没有对象,我们可以跳过它。

经过所有这些处理之后,以下文件将出现在客户端 -> MQL5 -> 的目录文件夹中:

图 8. 控制板方案的文件列表

9. 控制板方案的文件列表


4. 下载界面控件

现在,接口控件已经存储在文件中,并准备好投入工作。要开始,让我们确定我们的控制板将处于的位置。如果我们将其直接放在主图表上,则它将挡住价格图,这样会非常不方便。因此,将控制板放在主图表的子窗口中是最为合理的。一个指标可创建此窗格。

让我们创建它:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window //把指标放置到独立窗口中

int OnInit()
  {
//--- 指标缓冲区映射
   //设置指标的短名称
   IndicatorSetString(INDICATOR_SHORTNAME, "AP");
//---
   return(0);
  }

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
//--- 返回 prev_calculated 值用于下次调用
   return(rates_total);
  }

代码非常简单,因为此指标的主要功能是创建子窗口,而不是进行各种计算。我们将要做的一件事情是设置指标的“短”名称,以便我们通过该名称找到其子窗口。我们将编译并应用一个图表到指标,并且一个窗口将出现。

现在,让我们聚焦于 EA 交易程序控制板。我们将创建一个新的 EA 交易程序。

OnInit () 函数将包含以下运算符:

double Bid,Ask;         //当前价格变量
datetime time_current;  //最后一个订单的时间
int wnd=-1;             //指标窗口的索引
bool last_loaded=false; //只是是否为第一次初始化的标志
//+------------------------------------------------------------------+
//| EA交易初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   //启动计时器, 时间间隔为 1 秒
   EventSetTimer(1); 
   //取得最新价格
   get_prices();
   //定义指标窗口
   wnd=ChartWindowFind(0,"AP");
   //如果是第一次初始化 - 创建界面
   if(!last_loaded) create_interface();
//---
   return(0);
  }

在这里,我们启动一个计时器(将在下文中解释为何这样做的原因),从市场获取最新的价格,使用 ChartWindowFind 找到指标窗口将其保存为一个变量。标记 last_loaded 指出 EA 交易程序是否为第一次初始化。为了避免在重新初始化期间重装加载界面,将需要此信息。 

函数 create_interface () 看起来如下所示:

//+------------------------------------------------------------------+
//| 界面创建函数                                                       |
//+------------------------------------------------------------------+
void create_interface()
  {
   //如果选择了重置设置
   if(Reset_Expert_Settings)
     {
     //重置
      GlobalVariableDel("ActP_buttons_color");
      GlobalVariableDel("ActP_label_color");
      GlobalVariableDel("ActP_text_color");
      GlobalVariableDel("ActP_font_size");
     }

   //创建主菜单界面
   ApplyScheme(0);
   //创建 "市场" 界面页面
   ApplyScheme(1);
   //把所有对象设为没有标记
   Objects_Selectable("ActP",false);
   //重画图表
   ChartRedraw();
  }

第一步是检查输入参数 "reset settings"(复位设置),并且如果设置了的话,删除负责设置的全局变量。以下说明此操作对控制板的影响。此后,函数 ApplyScheme () 将从文件创建一个界面。 

//+------------------------------------------------------------------+
//| 载入界面的函数                                                     |
//| ID - 保存界面的ID                                                 |
//+------------------------------------------------------------------+
bool ApplyScheme(int ID)
  {
   string fname="Active_Panel_scheme_custom_"+IntegerToString(ID)+".bin";
   //如果没有保存的方案,下载标准方案 
   if(!FileIsExist(fname)) fname="Active_Panel_scheme_"+IntegerToString(ID)+".bin";
   //打开文件准备读取
   int handle=FileOpen(fname,FILE_READ|FILE_BIN);
   //文件已打开
   if(handle!=INVALID_HANDLE)
     {
      //载入所有对象
      while(!FileIsEnding(handle))
        {
         string obj_name=FileReadString(handle,100);
         int _wnd=wnd;
         //辅助线在主窗口中
         if(StringFind(obj_name,"line")>=0) _wnd=0;
         ENUM_OBJECT obj_type=FileReadInteger(handle);
         //创建对象
         ObjectCreate(0, obj_name, obj_type, _wnd, 0, 0);   
         //并且应用属性 
         ObjectSetInteger(0,obj_name,OBJPROP_XDISTANCE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_YDISTANCE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_XSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_YSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_COLOR,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_STYLE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_WIDTH,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_BACK,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_SELECTED,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_SELECTABLE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_READONLY,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_STATE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,FileReadInteger(handle));

         ObjectSetString(0,obj_name,OBJPROP_TEXT,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_FONT,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_BMPFILE,0,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_BMPFILE,1,FileReadString(handle,100));

         ObjectSetDouble(0,obj_name,OBJPROP_PRICE,FileReadDouble(handle));

         //设置对象的颜色
         if(GlobalVariableCheck("ActP_buttons_color") && obj_type==OBJ_BUTTON)
            ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,GlobalVariableGet("ActP_buttons_color"));
         if(GlobalVariableCheck("ActP_label_color") && obj_type==OBJ_LABEL)
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_label_color"));
         if(GlobalVariableCheck("ActP_text_color") && (obj_type==OBJ_EDIT || obj_type==OBJ_BUTTON))
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_text_color"));
         if(GlobalVariableCheck("ActP_font_size") && (obj_type==OBJ_EDIT || obj_type==OBJ_LABEL))
            ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,GlobalVariableGet("ActP_font_size"));
         //设置字体大小全局变量
         if(obj_name=="ActP_font_edit6" && GlobalVariableCheck("ActP_font_size"))
            ObjectSetString(0,obj_name,OBJPROP_TEXT,IntegerToString(GlobalVariableGet("ActP_font_size")));
        }
      //关闭文件
      FileClose(handle);
      return(true);
     }
   return(false);
  }

再一次指出,关于这一点并没有复杂的东西。函数将打开需要的文件,以及一个预先保存的界面方案,并且在我们先前确定的窗口(指标窗口)中创建该界面。我们还从客户端的全局变量中选择对象的颜色和字号。 

函数 Objects_Selectable () 使除辅助线以外的所有对象都取消标记,以打开按钮的动画,避免意外删除必要的对象。

//+------------------------------------------------------------------+
//| 把对象设置为不能选择的函数                                           |
//+------------------------------------------------------------------+
void  Objects_Selectable(string IDstr,bool flag)
  {
   //检查所有对象
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //如果对象属于面板
      if(StringFind(n,IDstr)>=0)
        {
         //线保持在未接触状态
         if(!flag)
            if(StringFind(n,"line")>-1) continue; 
         //把每个对象设置为不能被选择, 除了线性
         ObjectSetInteger(0,n,OBJPROP_SELECTABLE,flag); 
        }
     }
  }

现在,让我们看一看函数 OnTick()。它用于获取市场中的最新价格。

//+------------------------------------------------------------------+
//| EA 交易订单函数                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   //取得最新价格
   get_prices();
  }

函数 get_prices() 具有以下形式:

//+------------------------------------------------------------------+
//| 获取订单信息的函数                                                  |
//+------------------------------------------------------------------+
void get_prices()
  {
   MqlTick tick;
   //如果获得订单
   if(SymbolInfoTick(Symbol(),tick))
     {
      //获得信息
      Bid=tick.bid;
      Ask=tick.ask;
      time_current=tick.time;
     }
  }

不要忘记 OnDeinit ():

//+------------------------------------------------------------------+
//| EA 交易去初始化函数                                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   //如果去初始化原因不是时段或交易品种的改变
   if(reason!=REASON_CHARTCHANGE)
     {
      //重置初始化标志
      last_loaded=false;  
      //删除所有面板对象
      ObjectsDeleteAll_my("ActP");
      //删除保存着页面状态的文件
      FileDelete("Active_Panel_scheme_custom_1.bin");
      FileDelete("Active_Panel_scheme_custom_2.bin");
      FileDelete("Active_Panel_scheme_custom_3.bin");
      FileDelete("Active_Panel_scheme_custom_4.bin");
      FileDelete("Active_Panel_scheme_custom_5.bin");
     }
   //否则设置一个标志
   else last_loaded=true;
   //停止计时器
   EventKillTimer();
  }

首先检查去初始化的原因:如果是因为时间范围和/或交易品种的改变,则我们将不删除控制板控件。在所有其他情况下,使用 ObjectsDeleteAll_my () 函数删除所有控件。

//+------------------------------------------------------------------+
//| 删除所有面板对象的函数                                              |
//| IDstr - 对象ID                                                   |
//+------------------------------------------------------------------+
void  ObjectsDeleteAll_my(string IDstr)
  {
   //检查所有对象
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //如果名称包含 ID - 删除对象
      if(StringFind(n,IDstr)>=0) ObjectDelete(0,n);
     }
  }

在编译和运行 EA 交易程序之后,我们得到以下结果:

图 10. EA 交易程序的工作示例

10. EA 交易程序的工作示例

但是,在我们能够使这些对象响应我们的操作之前,所有这些都没有用处。


5. 事件处理

界面已经创建好了,现在我们必须让它工作。我们对对象进行的所有操作都生成具体的事件。OnChartEvent 函数 OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) 是事件 ChartEvent 的处理机制。我们感兴趣的所有事件如下所述: 

在我们的例子中,id 函数的参数指定事件的 ID,sparam 指定生成此事件的对象的名称,并且我们对所有其他参数不感兴趣。

我们将探讨的第一件事是单击主菜单按钮。

5.1. 处理主菜单事件

请记住,主菜单由五个按钮构成。当单击其中一个按钮时,该按钮应进入被按下的模式,将我们引导到正确的界面并加载相应的选项卡。之后,所有其他菜单按钮应进入未被按下的模式。

//+------------------------------------------------------------------+
//| 事件处理函数                                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击一个图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击主菜带按钮
      if(sparam=="ActP_main_1") {Main_controls_click(1); ChartRedraw(); return;}
       //执行对应的操作函数
      if(sparam=="ActP_main_2") {Main_controls_click(2); ChartRedraw(); return;}
      if(sparam=="ActP_main_3") {Main_controls_click(3); ChartRedraw(); return;}
      if(sparam=="ActP_main_4") {Main_controls_click(4); ChartRedraw(); return;}
      if(sparam=="ActP_main_5") {Main_controls_click(5); ChartRedraw(); return;}
   ...   
   }
...
}

如果单击了菜单按钮,则我们执行 Main_controls_click() 函数。让我们使用 ChartRedraw() 重绘图表,并完成函数。我们应完成执行,因为一次只能单击一个对象,因此,所有进一步的实施都会导致 CPU 时间的浪费。

//+------------------------------------------------------------------+
//| 页面处理器                                                        |
//| ID - 点击的页面的索引                                              |
//+------------------------------------------------------------------+
void Main_controls_click(int ID)
  {
   int loaded=ID;
   //我们遍历所有页面
   for(int i=1;i<6;i++)
     {
      //对于所有页面, 除了不能活动的之外
      if(i!=ID) 
        {
         //记住上一个活动页面
         if(ObjectGetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE)==1) loaded=i;
         ObjectSetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE,0);
        }
     }
//if(loaded==ID) return;
   //把选中的页面设置为活动状态
   ObjectSetInteger(0,"ActP_main_"+IntegerToString(ID),OBJPROP_STATE,1);
   //删除下拉列表
   DeleteLists("ActP_orders_list_"); 
   DeleteLists("ActP_color_list_");
   //把列表按钮设为未按下状态
   ObjectSetInteger(0,"ActP_ord_button5",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col1_button6",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col2_button6",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col3_button6",OBJPROP_STATE,0);
   //设置上一个活动页面的状态
   SaveScheme(loaded);
   //删除旧页面
   DeleteScheme("ActP");
   //载入新页面
   ApplyScheme(ID);
   //把所有对象设为未选中
   Objects_Selectable("ActP",false);
  }

我们已经介绍了 Objects_Selectable() 和 ApplyScheme() 函数,并且以后我们将返回到 DeleteLists() 函数。

SaveScheme() 函数保存界面文件,因此对象在加载期间保留它们的所有属性:

//+------------------------------------------------------------------+
//| 保存界面的函数                                                     |
//+------------------------------------------------------------------+
void SaveScheme(int interfaceID)
  {
   //打开文件准备写入
   int handle=FileOpen("Active_Panel_scheme_custom_"+IntegerToString(interfaceID)+".bin",FILE_WRITE|FILE_BIN);
   //如果文件已打开
   if(handle!=INVALID_HANDLE)
     {
      //遍历所有图表对象
      for(int i=0;i<ObjectsTotal(0);i++)
        {
         string name=ObjectName(0,i);
         //如果对象属于面板
         if(StringFind(name,"ActP")<0) continue;
         //并且它不是页面
         if(StringFind(name,"main")>=0) continue; 
         //把对象属性写入文件
         FileWriteString(handle,name,100);
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE));

         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR));

         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100);

         FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE));
        }
      //关闭文件
      FileClose(handle);
     }
  }

DeleteScheme() 函数删除选项卡对象。

//+------------------------------------------------------------------+
//| 删除所有面板对象, 除页面之外                                         |
//+------------------------------------------------------------------+
void  DeleteScheme(string IDstr)
  {
   //我们将遍历全部对象
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //删除所有物件,除页面之外
      if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n);
     }
  }

因此,通过执行 Main_controls_click() 函数,我们将在保存之后删除旧的选项卡,然后加载新的选项卡。

编译 EA 交易程序之后,我们即可看到结果。

现在,我们将单击主菜单按钮,加载新的选项卡,使它们处于原来选项卡的状态。

图 11. 选项卡 "Pending"(挂单)的控件

11. 选项卡 "Pending"(挂单)的控件

图 12. 选项卡 "Modify/Close"(修改/平仓)的控件

12. 选项卡 "Modify/Close"(修改/平仓)的控件


图 12. 选项卡 "Settings"(设置)的控件

13. 选项卡 "Settings"(设置)的控件

通过这种方式,我们能够完成主菜单的处理,因为现在它完全实现了其功能。

5.2. 处理“标记”组件事件

辅助线和止损限制订单的设置通过使用“标记”组件来进行,但是该组件并不在 MT5 的图形对象列表中。因此让我们创建它。有一个“图形标签”,实际上是一个具有“开”状态和“关”状态的图像。单击对象可以改变状态。可以为每个状态设置一个单独的图像。为每个状态选择一个图像:

让我们在对象的属性中设置图片:

图 13. 设置“标记”控件的属性

图 13. 设置“标记”控件的属性

必须指出,要让图片出现在列表中,它们需要位于“客户端文件夹-> MQL5-> Images”中并且扩展名为 ".Bmp"。

让我们转到当您单击一个对象时发生的事件处理。我们将使用负责在交易建立时放置辅助线的标记作为例子。

      //在开启交易时点击设置辅助线标志
      if(sparam=="ActP_DealLines_check1")
      {
         //检查标志状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //如果标志被设置
         if(selected)
         {
            //从输入栏位取得止损和获利值
            string SL_txt=ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT);
            string TP_txt=ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT);
            double val_SL, val_TP;
            
            //如果止损栏位不为空
            //保存数值
            if(SL_txt!="")
               val_SL=StringToDouble(SL_txt);

            //如果为空
            else
            {
               //取得. 图表上的. 最高价和最低价
               double pr_max=ChartGetDouble(0, CHART_PRICE_MAX);
               double pr_min=ChartGetDouble(0, CHART_PRICE_MIN);
               //在图表范围的1/3处设置止损
               val_SL=pr_min+(pr_max-pr_min)*0.33;
            }   
            
            //同样的过程处理获利
            if(TP_txt!="")
               val_TP=StringToDouble(TP_txt);
            else
            {
               double pr_max=ChartGetDouble(0, CHART_PRICE_MAX);
               double pr_min=ChartGetDouble(0, CHART_PRICE_MIN);
               val_TP=pr_max-(pr_max-pr_min)*0.33;
            }      
            //把线移动到新位置
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, val_SL);  
            ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, val_TP);  
         }
          //如果标志没有设置
         else
         {
             //删除线
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, 0);
            ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, 0);
         }
          //重画图表
         ChartRedraw();
          //并结束函数 
         return;
      }

对于标记,使用负责在“修改挂单/平仓”选项卡上处理和放置辅助线的相同方法。因此,在本文中我们将不详细讨论它们。那些希望熟悉它们的人可以使用 EA 交易程序的代码。

在 "Pending"(挂单)选项卡上设置止损限制订单标记具有以下处理程序:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...

   //事件 - 点击一个图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //点击了订单的stoplimit选择框
      if(sparam=="ActP_limit_check2")
      {
         //检查标志状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected) //如果标志已经设置
         {
            //为价格编辑框设置新颜色
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, White);
            //启用可编辑
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, false);
            //把编辑框内容设置为当前价格
            //把编辑框内容设置为当前价格
            ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, DoubleToString(Bid, _Digits));
            //如果允许辅助线
            //移动它们
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, Bid);
         }  
          //如果标志没有设置
         else
         {
            //把栏位设置为不可编辑
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, LavenderBlush);
            //设置编辑框颜色
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, true);
            //空文本内容
            ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, "");
            //如果允许辅助线
            //把它们移动到0点
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, 0);
         }
      }       
   ...   
   }
...
}

现在,我们已经完成对标记的处理。让我们考虑以下我们自己创建的产品对象 - “单选按钮组”。

5.3. 处理“单选按钮组”组件事件

使用此组件,我们选择交易类型和订单到期类型。与处理标记的情形一样,我们将使用图形标记,但是这次使用新的图片。 

但是在这里,因为除了您单击的单选按钮以外,需要将所有单选按钮复位到不活动状态,问题更加复杂了。考虑订单执行类型单选按钮的例子:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击了图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //点击单选按钮 1 - 订单执行类型
      if(sparam=="ActP_Exe1_radio2")
      {
         //检查单选按钮状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //set the appropriate state
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         //如果它是被选择的
         if(selected)
         {
            //reset the other radio buttons
            ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false);
            //重画图表
            ChartRedraw();
            //结束函数的执行
            return;
         }
         //重画图表
         ChartRedraw();
         //结束函数的执行
         return;
      }
 
       //单选按钮 2 同样处理
      if(sparam=="ActP_Exe2_radio2") 
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         if(selected)
         {
            ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false);
            ChartRedraw();
            return;
         }
         ChartRedraw();
         return;
      }   

       //单选按钮 3 同样处理
      if(sparam=="ActP_Exe3_radio2") 
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         if(selected)
         {
            ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false);
            ChartRedraw();
            return;
         }
         ChartRedraw();
         return;
      }     
   ...   
   }
...
}

订单到期类型单选按钮仅有的不同之处在于,当您单击第三个单选按钮时,您必须执行一个附加步骤 - 您需要在订单的整个到期时间中设置一个新的日期:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击一个图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击了第 3 个单选按钮 - 订单过期日期
      if(sparam=="ActP_exp3_radio2")
      {
         //检查它的状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         //如果它是被选择的
         if(selected)
         {
            //重置其他剩下的单选按钮
            ObjectSetInteger(0, "ActP_exp1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE, false);
            //给日期编辑栏位设置新的日期
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, White);
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, false);
            ObjectSetString(0, "ActP_exp_edit2", OBJPROP_TEXT, TimeToString(time_current));
            //如果允许辅助线 
            //设置新的时间线
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, time_current);
            ChartRedraw();
            return;
         }

          //如果没有被选中
         else
         {
            //把编辑栏位设置为无法编辑
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, LavenderBlush);
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, true);
            //删除辅助线
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, 0);
         }      
         ChartRedraw();
         return;
   ...   
   }
...
}

现在,我们已经完成了对单选按钮的处理。

5.4. 创建和处理下拉列表事件

对于修改/平仓/删除和颜色选择控制板,我们将使用下拉列表来选择订单/交易。让我们从交易/订单列表开始。

出现在 "Modification / closure"(修改/平仓)选项卡上的第一个控件是一个标有 "Select an order -->"(选择订单 -->)字样的按钮,这是激活列表的按钮。当您单击该按钮时,下拉列表应打开,并且在我们做出选择之后,它应再次折叠。让我们看一看该按钮的 CHARTEVENT_OBJECT_CLICK 处理程序

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击了图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //点击下拉框的激活按钮 (选择订单)

      if(sparam=="ActP_ord_button5")
      {
         //检查状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //如果激活了列表
         if(selected)// 列表被选中
         {
            //删除界面
            DeleteScheme("ActP", true);
            //服务于订单信息的数组
            string info[100];
            //订单号数组
            int tickets[100];
            //初始化
            ArrayInitialize(tickets, -1);
            //取得订单信息
            get_ord_info(info, tickets);
            //创建列表
            create_list(info, tickets);
         }
          //列表没有激活
         else
         {
            //删除它
            DeleteLists("ActP_orders_list_");
         }
          //重画图表
         ChartRedraw();
          //函数结束
         return;
      }      
   ...   
   }
...
}

我们的主要目标是确定市场中是否有交易/订单,如果有,则提取它们的信息并在列表中显示。函数 get_ord_info() 执行此角色:

//+------------------------------------------------------------------+
//| 用于获取订单信息的函数                                              |
//+------------------------------------------------------------------+
void get_ord_info(string &info[],int &tickets[])
  {
   //初始化计数器
   int cnt=0;
   string inf;
   //如果有开启的仓位
   if(PositionSelect(Symbol()))
     {
     //把所有订单信息组合成一行
      double vol=PositionGetDouble(POSITION_VOLUME);
      int typ=PositionGetInteger(POSITION_TYPE);
      if(typ==POSITION_TYPE_BUY) inf+="BUY ";
      if(typ==POSITION_TYPE_SELL) inf+="SELL ";
      inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots";
      inf+=" at "+DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), Digits());
      //写下结果
      info[cnt]=inf;
      tickets[cnt]=0;
      //增加计数器
      cnt++;
     }

   //所有订单
   for(int i=0;i<OrdersTotal();i++)
     {
      //取得订单编号
      int ticket=OrderGetTicket(i);
      //如果订单交易品种和图表交易品种相同
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         //把所有订单信息组合成一行
         inf="#"+IntegerToString(ticket)+" ";
         int typ=OrderGetInteger(ORDER_TYPE);
         double vol=OrderGetDouble(ORDER_VOLUME_CURRENT);
         if(typ==ORDER_TYPE_BUY_LIMIT) inf+="BUY LIMIT ";
         if(typ==ORDER_TYPE_SELL_LIMIT) inf+="SELL LIMIT ";
         if(typ==ORDER_TYPE_BUY_STOP) inf+="BUY STOP ";
         if(typ==ORDER_TYPE_SELL_STOP) inf+="SELL STOP ";
         if(typ==ORDER_TYPE_BUY_STOP_LIMIT) inf+="BUY STOP LIMIT ";
         if(typ==ORDER_TYPE_SELL_STOP_LIMIT) inf+="SELL STOP LIMIT ";
         inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots";
         inf+=" at "+DoubleToString(OrderGetDouble(ORDER_PRICE_OPEN), Digits());
         //写下结果
         info[cnt]=inf;
         tickets[cnt]=ticket;
         //增加计数器
         cnt++;
        }
     }
  }

它将信息和订单单证及交易组合到一个块。

此外,函数 create_list() 将依据这些信息创建一个列表:

//+------------------------------------------------------------------+
//| 本函数创建仓位列表                                                 |
//| info - 仓位数组                                                   |
//| tickets - 订单号数组                                              |
//+------------------------------------------------------------------+
void create_list(string &info[],int &tickets[])
  {
   //取得列表激活按钮的坐标
   int x=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_XDISTANCE);
   int y=ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YDISTANCE)+ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YSIZE);
   //取得颜色
   color col=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_COLOR);
   color bgcol=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_BGCOLOR);
   //取得窗口高度
   int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd);
   int y_cnt=0;
   //处理数组
   for(int i=0;i<100;i++)
     {
      //如果到达末端则中间退出
      if(tickets[i]==-1) break;
      //计算列表项目坐标
      int y_pos=y+y_cnt*20;
      //如果达到了窗口限制, 开始新列
      if(y_pos+20>wnd_height) {x+=300; y_cnt=0;}
      y_pos=y+y_cnt*20;
      y_cnt++;
      string name="ActP_orders_list_"+IntegerToString(i)+" $"+IntegerToString(tickets[i]);
      //创建元素
      create_button(name,info[i],x,y_pos,300,20);
      //设置其属性
      ObjectSetInteger(0,name,OBJPROP_COLOR,col);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,name,OBJPROP_STATE,0);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8);
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol);
     }
  }

最后,函数 DeleteLists () 删除列表的元素:

//+------------------------------------------------------------------+
//| 删除列表的函数                                                     |
//+------------------------------------------------------------------+
void  DeleteLists(string IDstr)
  {
   //处理所有对象
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //删除列表
      if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n);
     }
  }

因此,现在当您单击激活按钮时,系统将创建一个列表。我们需要使其工作,因为每次单击列表的任何元素都必须采取某些具体的操作。具体而言:加载一个用于处理订单的界面,并且用订单/交易信息填写此界面。 

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   // 事件 - 点击了一个图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击了订单选择列表的一个项目
      if(StringFind(sparam, "ActP_orders_list_")<0)
      {
          //删除它
         DeleteLists("ActP_orders_list_");
          //把激活按钮设为 "未按下"状态
         ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
          //重画图表
         ChartRedraw();
      }     
       //点击订单选择列表的项目
      else
      {
          //为激活按钮设置一个新名称
         ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, ObjectGetString(0, sparam, OBJPROP_TEXT));
          //把激活按钮设为 "未按下"状态
         ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
          //根据列表项目描述获得订单编号
         int ticket=StringToInteger(StringSubstr(sparam, StringFind(sparam, "$")+1));
          //载入界面
         SetScheme(ticket);
          //删除列表
         DeleteLists("ActP_orders_list_");
          //重画图表
         ChartRedraw();
      }
   ...   
   }
...
}

这是它变得复杂的地方。因为事先我们不知道列表的大小及其对象的名称,我们必须通过获取列表元素的名称,从列表获取信息。函数 SetScheme() 将设置相应的界面 - 用于处理交易或挂单:

//+------------------------------------------------------------------+
//| 本函数根据类型设置界面:                                             |
//| 仓位或者挂单                                                      |
//| t - 订单编号                                                      |
//+------------------------------------------------------------------+
void SetScheme(int t)
  {
   //如果是仓位
   if(t==0)
     {
      //检查它是否存在
      if(PositionSelect(Symbol()))
        {
         //删除旧方案
         DeleteScheme("ActP",true);
         //并应用新方案
         ApplyScheme(6);
         //设置仓位参数
         SetPositionParams();
         //对象无法选择
         Objects_Selectable("ActP",false);
        }
     }
   //如果是订单
   if(t>0)
     {
      //检查它是否存在
      if(OrderSelect(t))
        {
         //删除旧方案
         DeleteScheme("ActP",true);
         //并应用新方案
         ApplyScheme(7);
         //设置订单参数
         SetOrderParams(t);
         //对象无法选择
         Objects_Selectable("ActP",false);
        }
     }
  }

函数 SetPositionParams() 和 SetOrderParams() 设置加载界面的所需属性:

//+------------------------------------------------------------------+
//| 设置对象的仓位数组                                                 |
//+------------------------------------------------------------------+
void SetPositionParams()
  {
   //如果仓位存在
   if(PositionSelect(Symbol()))
     {
      //取得其参数
      double pr=PositionGetDouble(POSITION_PRICE_OPEN);
      double lots=PositionGetDouble(POSITION_VOLUME);
      double sl=PositionGetDouble(POSITION_SL);
      double tp=PositionGetDouble(POSITION_TP);
      double mag=PositionGetInteger(POSITION_MAGIC);
      //设置对象的新值
      ObjectSetString(0,"ActP_Pr_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(pr)));
      ObjectSetString(0,"ActP_lots_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(lots)));
      ObjectSetString(0,"ActP_SL_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(sl)));
      ObjectSetString(0,"ActP_TP_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
      if(mag!=0) ObjectSetString(0,"ActP_mag_edit4",OBJPROP_TEXT,IntegerToString(mag));
      //重画图表
      ChartRedraw();
     }
   //如果没有仓位,显示信息 
   else MessageBox("没有开启的仓位, 交易品种 "+Symbol());
  }
//+------------------------------------------------------------------+
//| 为对象设置挂单参数                                                 |
//| ticket - 订单号                                                   |
//+------------------------------------------------------------------+
void SetOrderParams(int ticket)
  {
   //如果订单存在
   if(OrderSelect(ticket) && OrderGetString(ORDER_SYMBOL)==Symbol())
     {
      //取得其参数
      double pr=OrderGetDouble(ORDER_PRICE_OPEN);
      double lots=OrderGetDouble(ORDER_VOLUME_CURRENT);
      double sl=OrderGetDouble(ORDER_SL);
      double tp=OrderGetDouble(ORDER_TP);
      double mag=OrderGetInteger(ORDER_MAGIC);
      double lim=OrderGetDouble(ORDER_PRICE_STOPLIMIT);
      datetime expir=OrderGetInteger(ORDER_TIME_EXPIRATION);
      ENUM_ORDER_TYPE type=OrderGetInteger(ORDER_TYPE);
      ENUM_ORDER_TYPE_TIME expir_type=OrderGetInteger(ORDER_TYPE_TIME);
      
      //如果订单类型为 stoplimit, 修改界面
      if(type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT)
        {
         //给建仓价格编辑栏位设置新值
         ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,DoubleToString(lim,_Digits));
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,White);
         //把订单价格栏位设置为可编辑
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,false);
        }
      //如果订单类型不是 stoplimit, 修改界面
      else
        {
         ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,"");
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,LavenderBlush);
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,true);
        }

      //检查过期类型
      //设置界面元素
      switch(expir_type)
        {
         case ORDER_TIME_GTC:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,1);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0);
            break;
           }
         case ORDER_TIME_DAY:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,1);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0);
            break;
           }
         case ORDER_TIME_SPECIFIED:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,1);
            //in addition, set new value to the edit
            ObjectSetString(0,"ActP_exp_edit3",OBJPROP_TEXT,TimeToString(expir));
            break;
           }
        }
      //设置对象新值
      ObjectSetString(0,"ActP_Pr_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(pr)));
      ObjectSetString(0,"ActP_lots_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(lots)));
      ObjectSetString(0,"ActP_SL_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(sl)));
      ObjectSetString(0,"ActP_TP_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
      ObjectSetString(0,"ActP_ticket_edit3",OBJPROP_TEXT,IntegerToString(ticket));
      if(mag!=0) ObjectSetString(0,"ActP_mag_edit3",OBJPROP_TEXT,IntegerToString(mag));
      ChartRedraw();
     }
   //如果没有这个订单, 显示信息
   else MessageBox("没有这样的订单, 单号 "+IntegerToString(ticket)+" 交易品种 "+Symbol());
  }

作为最后的整理 - 当您单击图表时,应使用此事件的 CHARTEVENT_CLICK 删除列表:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 为点击图表
   if(id==CHARTEVENT_CLICK)
   {
       //删除所有列表
      DeleteLists("ActP_orders_list_");
      DeleteLists("ActP_color_list_"); 
       //把活动按钮设为未按下状态
      ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0);
      ChartRedraw(); 
      return;
   }
...
}

结果,我们得到一个非常不错的下拉列表:

图 14. "Modify/Close"(修改/平仓)控制板下拉列表的一个例子

14."Modify/Close"(修改/平仓)控制板下拉列表的一个例子

现在,我们需要在 Settings(设置)选项卡上创建一个颜色选择列表。 

考虑激活按钮的处理程序:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击按钮激活颜色下拉框
      if(sparam=="ActP_col1_button6")
      {
          //检查状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //列表是活动的
         if(selected)//列表是活动的
         {
             //创建列表
            create_color_list(100, "ActP_col1_button6", 1);
             //把其余按钮的位置设为 "未按下"状态
            ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0);
            ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0);
             //删除其他列表
            DeleteLists("ActP_color_list_2");
            DeleteLists("ActP_color_list_3");                 
         }
          //列表未被选中
         else
         {
             //删除它
            DeleteLists("ActP_color_list_");
         }
          //重画图表
         ChartRedraw();
          //结束函数的执行
         return;
      }
   ...   
   }
...
}

在这里,我们使用与订单选择列表相同的方法。

创建列表的函数有所不同:

//+------------------------------------------------------------------+
//| 创建颜色列表的函数                                                 |
//| y_max - 列表最大宽度                                              |
//| ID - 列表 ID                                                     |
//| num - 界面数量                                                    |
//+------------------------------------------------------------------+
void create_color_list(int y_max,string ID,int num)
  {
  //取得列表激活按钮的坐标
   int x=ObjectGetInteger(0,ID,OBJPROP_XDISTANCE);
   int y=ObjectGetInteger(0, ID, OBJPROP_YDISTANCE)+ObjectGetInteger(0, ID, OBJPROP_YSIZE);
   //取得颜色
   color col=ObjectGetInteger(0,ID,OBJPROP_COLOR);
   //和窗口宽度
   int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd);
   y_max+=y;
   int y_cnt=0;
   //我们遍历颜色数组
   for(int i=0;i<132;i++)
     {
      color bgcol=colors[i];
      //计算列表项目坐标
      int y_pos=y+y_cnt*20;
      //如果我们到达了窗口边缘,开始新的一行
      if(y_pos+20>wnd_height || y_pos+20>y_max) {x+=20; y_cnt=0;}
      y_pos=y+y_cnt*20;
      y_cnt++;
      //创建新元素
      string name="ActP_color_list_"+IntegerToString(num)+ID+IntegerToString(i);
      create_button(name,"",x,y_pos,20,20);
      //设置其属性
      ObjectSetInteger(0,name,OBJPROP_COLOR,col);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,name,OBJPROP_STATE,0);
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol);
     }
  }

此外,让我们完成列表元素的单击过程:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //没有点击在颜色列表按钮上
      if(StringFind(sparam, "ActP_color_list_1")<0)
      {
          //删除列表
         DeleteLists("ActP_color_list_1");
          //把列表激活按钮状态设为 "未按下"
         ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
          //重画图表
         ChartRedraw();
      }     
       //点击在颜色列表上
      else
      {
          //从列表上读取颜色
         color col=ObjectGetInteger(0, sparam, OBJPROP_BGCOLOR);
          //设置所有按钮颜色
         SetButtonsColor(col);
          //设置按钮状态为未按下
         ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
          //删除列表
         DeleteLists("ActP_color_list_1");
          //重画图表
         ChartRedraw();
      }
   ...   
   }
...
}

函数 SetButtonsColor() 设置按钮的颜色:

//+------------------------------------------------------------------+
//| 本函数为所有按钮设置颜色                                            |
//| col - 颜色                                                       |
//+------------------------------------------------------------------+
void SetButtonsColor(color col)
  {
   //我们将遍历所有对象
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //如果对象属于面板并且类型为按钮
      //设置颜色
      if(StringFind(n,"ActP")>=0 && ObjectGetInteger(0,n,OBJPROP_TYPE)==OBJ_BUTTON) 
         ObjectSetInteger(0,n,OBJPROP_BGCOLOR,col); 
     }
   //设置全局变量
   GlobalVariableSet("ActP_buttons_color",col);
  }

让我们查看下面的结果:

图 15. 设置按钮的颜色

15. 设置按钮的颜色

颜色选择列表和文本标签是类似的。因此,我们通过几次单击就能让控制板具有很好的配色:

图 16. 改变后的控制板、按钮和文本颜色

图 16. 改变后的控制板、按钮和文本颜色

现在,我们完成了对列表的处理。让我们移到输入字段。

5.5. 处理输入字段事件

输入字段将生成 CHARTEVENT_OBJECT_ENDEDIT 事件,该事件发生于在字段中完成文本编辑时。我们需要处理此事件的唯一原因是针对价格的,与输入字段中的价格有关的辅助线设置。

让我们考虑一个止损线的例子:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //结束编辑事件
   if(id==CHARTEVENT_OBJECT_ENDEDIT)//结束编辑事件
   {
   ...
      //如果编辑框是止损栏位
      if(sparam=="ActP_SL_edit1")
      {
        //并且启用了辅助线
         if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1)
         {
            //获得栏位的文本信息
            double sl_val=StringToDouble(ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT));
            //把线移动到新位置
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, sl_val);
         }
         //重画图表
         ChartRedraw();
         //无必要继续为其他对象处理事件,因为事件仅来源于一个对象
         return;
      }
   ...   
   }
...
}

其他输入字段的处理方式是类似的。

5.6 处理计时器事件

计时器用于监视辅助线。使用这种方式,当您移动辅助线时,与辅助线关联的价格将自动移入输入字段。在计时器的每一计时单位都会执行 OnTimer() 函数。

考虑放置止损线和获利线跟踪及活动 "Market"(市场)选项卡的例子:

void OnTimer()// Timer handler
{
   //面板 1 是活动的
   if(ObjectGetInteger(0, "ActP_main_1", OBJPROP_STATE)==1)
   {  
      //如果允许辅助线
      if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1)
      {
         //设置编辑框栏位的新值
         double sl_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_SL_line1", OBJPROP_PRICE), _Digits);
         //止损
         ObjectSetString(0, "ActP_SL_edit1", OBJPROP_TEXT, DoubleToString(sl_pr, _Digits));
         //获利
         double tp_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_TP_line1", OBJPROP_PRICE), _Digits);
         ObjectSetString(0, "ActP_TP_edit1", OBJPROP_TEXT, DoubleToString(tp_pr, _Digits));
      }   
   }
   ...
   //重画图表
   ChartRedraw();
}
//+------------------------------------------------------------------+

跟踪其他辅助线的实施方式是类似的。


6. 执行交易操作

此时,我们已经填写了所有必需的输入字段、复选框、辅助线和单选按钮。现在是时候依据我们拥有的所有数据尝试某些交易了。

6.1. 建立交易

选项卡 "From the market"(从市场)包含 "Buy"(买入)和 "Sell"(卖出)按钮。如果所有字段都填写正确,当我们单击其中任何一个按钮时应实施一个交易。

让我们看看这些按钮的处理程序:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图标上的对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击买入按钮
      if(sparam=="ActP_buy_button1")
      {
          //检查它的状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //如果它是 "按下" 状态
         if(selected)
         {
             //尝试进行一个交易
            deal(ORDER_TYPE_BUY);
             //把按钮设置为未按下状态
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }

          //重画图表
         ChartRedraw();
          //并结束函数执行
         return;
      }
      //******************************************
       //卖出按钮做类似处理
      if(sparam=="ActP_sell_button1")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {
            deal(ORDER_TYPE_SELL);
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
         ChartRedraw();
         return;
      }     
   ...   
   }
...
}

如您所见,deal() 函数正在工作。

//+------------------------------------------------------------------+
//| 交易函数                                                          |
//+------------------------------------------------------------------+
int deal(ENUM_ORDER_TYPE typ)
  {
   //从对象获取数据
   double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit1",OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit1",OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0, "ActP_Magic_edit1", OBJPROP_TEXT));
   int dev=StringToInteger(ObjectGetString(0, "ActP_Dev_edit1", OBJPROP_TEXT));
   string comm=ObjectGetString(0,"ActP_Comm_edit1",OBJPROP_TEXT);
   ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK;
   if(ObjectGetInteger(0,"ActP_Exe2_radio1",OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC;
   //准备请求
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_DEAL;
   req.symbol=Symbol();
   req.volume=lots;
   req.price=Ask;
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.deviation=dev;
   req.type=typ;
   req.type_filling=filling;
   req.magic=mag;
   req.comment=comm;
   //发送订单
   OrderSend(req,res);
   //显示结果信息
   MessageBox(RetcodeDescription(res.retcode),"消息");
   //返回返回值
   return(res.retcode);
  }

没有不可思议的事情。我们首先从对象读取必需的信息,然后依据这些信息创建一个交易请求。

让我们检查一下我们的工作:

图 17. 交易操作 - 执行买入交易的结果

图 17. 交易操作 - 执行买入交易的结果

如您所见,买入交易成功完成。

6.2. 生成挂单

选项卡 "Pending"(挂单)上的 "Buy"(买入)和 "Sell"(卖出)按钮负责生成挂单。

让我们考虑处理程序:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击设置挂单按钮
      if(sparam=="ActP_buy_button2")
      {
         //检查按钮状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //如果它被按下
         if(selected)
         {
            ENUM_ORDER_TYPE typ; 
            //从编辑框获得挂单
            double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits());
            //如果它不是 stoplimit 订单
            if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0)
            {
               //如果订单价格低于当前价格, 设置 limit 订单
               if(Ask>pr) typ=ORDER_TYPE_BUY_LIMIT; 
               //否则 - stop 订单
               else typ=ORDER_TYPE_BUY_STOP;
            }
              //如果指定了 stoplimit 订单
            else
            {
               //设置操作类型
               typ=ORDER_TYPE_BUY_STOP_LIMIT;
            }   
              //尝试下单
            order(typ);
              //把按钮设置为未按下状态
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //重画图表
         ChartRedraw();
          //并结束函数的执行
         return;
      }     
      //******************************************  

       //卖出挂单做类似处理
      if(sparam=="ActP_sell_button2")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {
            ENUM_ORDER_TYPE typ;
            double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits());
            if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0)
            {
               if(Bid<pr) typ=ORDER_TYPE_SELL_LIMIT;
               else typ=ORDER_TYPE_SELL_STOP;
            }
            else
            {
               typ=ORDER_TYPE_SELL_STOP_LIMIT;
            }   
            order(typ);
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
         ChartRedraw();
         return;
      }        
   ...   
   }
...
}

在这里,我们依据当前市场价与设定价格之间的关系确定期货订单的类型,之后用函数 order() 确定订单:

//+------------------------------------------------------------------+
//| 下单函数                                                          |
//+------------------------------------------------------------------+
int order(ENUM_ORDER_TYPE typ)
  {
   //从对象中取得订单详细信息
   double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit2",OBJPROP_TEXT));
   double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit2",OBJPROP_TEXT));
   double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit2", OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit2", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit2",OBJPROP_TEXT));
   datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit2",OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0,"ActP_Magic_edit2",OBJPROP_TEXT));
   string comm=ObjectGetString(0,"ActP_Comm_edit2",OBJPROP_TEXT);
   ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK;
   if(ObjectGetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC;
   if(ObjectGetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_RETURN;
   ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC;
   if(ObjectGetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY;
   if(ObjectGetInteger(0, "ActP_exp3_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED;

   //准备请求
   MqlTradeRequest req;
   MqlTradeResult res; 
   req.action=TRADE_ACTION_PENDING;
   req.symbol=Symbol();
   req.volume=lots;
   req.price=NormalizeDouble(pr,Digits());
   req.stoplimit=NormalizeDouble(stoplim,Digits());
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.type=typ;
   req.type_filling=filling;
   req.type_time=expir_type;
   req.expiration=expir;
   req.comment=comm;
   req.magic=mag;
   //下单
   OrderSend(req,res);
   //显示结果信息
   MessageBox(RetcodeDescription(res.retcode),"消息");
   //返回返回值
   return(res.retcode);
  }

让我们检查一下我们的工作:

图 18. 交易操作 - 生成挂单的结果

图 18. 交易操作 - 生成挂单的结果

买入止损限制设置成功。

6.3. 仓位修改

选项卡 "Modify/Close"(修改/平仓)上的 Edit(编辑)按钮负责修改选择的仓位:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表上的图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击修改位置按钮
      if(sparam=="ActP_mod_button4")
      {
          //检查按钮状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //如果它被按下
         if(selected)//如果被按下
         {
            //修改位置
            modify_pos();
            //删除方案中的元素
            DeleteScheme("ActP" ,true);
            //并且重置 (更新界面)
            SetScheme(0);
            //把按钮设置为未按下状态
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //重画图表
         ChartRedraw();
          //结束函数的执行
         return;
      }     
   ...   
   }
...
}

函数 Modify_pos() 直接负责修改:

//+------------------------------------------------------------------+
//| 修改仓位参数的函数                                                  |
//+------------------------------------------------------------------+
int modify_pos()
  {
   if(!PositionSelect(Symbol())) MessageBox("交易品种没有持仓 "+Symbol(),"信息");
   //从编辑框对象取得详细信息
   double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit4",OBJPROP_TEXT));  
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit4", OBJPROP_TEXT));
   int dev=StringToInteger(ObjectGetString(0,"ActP_dev_edit4",OBJPROP_TEXT));
   //准备请求
   MqlTradeRequest req;
   MqlTradeResult res;  
   req.action=TRADE_ACTION_SLTP;
   req.symbol=Symbol();
   req.sl=NormalizeDouble(SL, _Digits);
   req.tp=NormalizeDouble(TP, _Digits);
   req.deviation=dev;
   //发送请求
   OrderSend(req,res);
   //显示结果信息
   MessageBox(RetcodeDescription(res.retcode),"消息");
   //返回返回值
   return(res.retcode);
  }

结果:

图 19. 交易操作 - 修改交易属性的结果(设置获利和止损)

19. 交易操作 - 修改交易属性的结果(设置获利和止损)


止损和获利水平修改成功。

6.4. 平仓

选项卡 "Modify/Close"(修改/平仓)上的 Close(平仓)按钮负责平仓(可以部分平仓):

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //点击关闭按钮
      if(sparam=="ActP_del_button4")
      {
         //检查按钮状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //如果被按下
         if(selected)
         {
            //尝试平仓
            int retcode=close_pos();
            //如果成功
            if(retcode==10009)
            {
               //删除方案元素
               DeleteScheme("ActP" ,true);
               //为激活列表设置新文本
               ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Select order -->");
            }
            //把按钮状态设为未按下
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //重画图表
         ChartRedraw();
          //结束函数的执行
         return;
      }     
   ...   
   }
...
}

函数 close_pos() 负责平仓:

//+------------------------------------------------------------------+
//| 平仓                                                             |
//+------------------------------------------------------------------+
int close_pos()
  {
   if(!PositionSelect(Symbol())) MessageBox("交易品种没有持仓 "+Symbol(),"信息");
   //从对向中取得仓位详细信息
   double lots=StringToDouble(ObjectGetString(0,"ActP_lots_edit4",OBJPROP_TEXT));
   if(lots>PositionGetDouble(POSITION_VOLUME)) lots=PositionGetDouble(POSITION_VOLUME);
   int dev=StringToInteger(ObjectGetString(0, "ActP_dev_edit4", OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0, "ActP_mag_edit4", OBJPROP_TEXT));

   //准备请求
   MqlTradeRequest req;
   MqlTradeResult res;

   //根据仓位类型设置反向交易
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {
      req.price=Bid;
      req.type=ORDER_TYPE_SELL;
     }
   else
     {
      req.price=Ask;
      req.type=ORDER_TYPE_BUY;
     }

   req.action=TRADE_ACTION_DEAL;
   req.symbol=Symbol();
   req.volume=lots;
   req.sl=0;
   req.tp=0;
   req.deviation=dev;
   req.type_filling=ORDER_FILLING_FOK;
   req.magic=mag;
   //发送请求
   OrderSend(req,res);
   //显示结果信息
   MessageBox(RetcodeDescription(res.retcode),"消息");
   //返回返回值
   return(res.retcode);
  }

结果 - 三个选定交易平了 1.5 手:

图 20. 交易 - 部分平仓

20. 交易 - 部分平仓

6.5. 修改挂单

选项卡 "Modification/closure"(修改/平仓)上的 Edit(编辑)按钮负责修改选定订单:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表上的图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //点击修改订单按钮
      if(sparam=="ActP_mod_button3")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {     
            //从编辑框取得订单号
            string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT);
            long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1));
            //修改订单
            modify_order(ticket);
            //更新界面
            DeleteScheme("ActP" ,true);
            SetScheme(ticket);
            //把按钮设置为未按下状态
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //重画图表
         ChartRedraw();
          //并结束函数的执行
         return;
      }     
   ...   
   }
...
}

函数 Modify_order () 负责修改:

//+------------------------------------------------------------------+
//| 修改订单的函数                                                     |
//| ticket - 订单号                                                   |
//+------------------------------------------------------------------+
int modify_order(int ticket)
  {
   //根据图表上的对应对象取得订单详细信息
   double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit3",OBJPROP_TEXT)); 
   double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit3",OBJPROP_TEXT));
   double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit3", OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit3", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit3",OBJPROP_TEXT));
   datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit3",OBJPROP_TEXT));
   ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC;
   if(ObjectGetInteger(0, "ActP_exp2_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY;
   if(ObjectGetInteger(0, "ActP_exp3_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED;

   //准备修改请求
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_MODIFY;
   req.order=ticket;
   req.volume=lots;
   req.price=NormalizeDouble(pr,Digits());
   req.stoplimit=NormalizeDouble(stoplim,Digits());
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.type_time=expir_type;
   req.expiration=expir;
   //发送请求
   OrderSend(req,res);
   //显示结果信息
   MessageBox(RetcodeDescription(res.retcode),"消息");
   //返回返回值
   return(res.retcode);
  }

让我们查看结果 - 订单修改成功:

图 21. 修改挂单

21. 修改挂单

6.6. 删除挂单

选项卡 "Modification/closure"(修改/平仓)上的 Delete(编辑)按钮负责删除选定订单:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //事件 - 点击图表上的图形对象
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //点击删除订单按钮
      if(sparam=="ActP_del_button3")
      {
         //检查按钮状态
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //如果被按下
         if(selected)
         {
            //从列表中取得订单号
            string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT);
            long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1));
            //尝试删除订单
            int retcode=del_order(ticket);
            //如果成功
            if(retcode==10009)
            {
               //删除方案中的全部对象
               DeleteScheme("ActP" ,true);
               //设置激活列表按钮的新文本
               ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Select an order -->");
            }
             //把按钮状态设为未按下
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //重画图表
         ChartRedraw();
          //并结束函数的执行
         return;
      }     
   ...   
   }
...
}

函数 del_order() 负责删除订单:

//+------------------------------------------------------------------+
//| 删除挂单的函数                                                     |
//| ticket - 订单号                                                   |
//+------------------------------------------------------------------+
int del_order(int ticket)
  {
   //准备删除请求
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_REMOVE;
   req.order=ticket;
   //发送请求
   OrderSend(req,res);
   //显示结果信息
   MessageBox(RetcodeDescription(res.retcode),"消息");
   //返回返回值
   return(res.retcode);
  }

让我们查看结果 - 订单被删除。

图 22. 交易 - 删除挂单

22 交易 - 删除挂单


总结

最后,控制板的所有功能都经过测试并正常运行。

希望通过阅读本文所获得的知识对您开发活动控制板有所帮助,该控制板是您在市场中进行交易不可替代的助手。 

要使用控制板,您需要用客户端将压缩文件解压到一个夹,然后将 AP 指标应用到图表,然后才启动活动控制板 EA 交易程序。