![用 MQL5 创建交易活动控制板](https://c.mql5.com/i/articles/overlay.png)
用 MQL5 创建交易活动控制板
简介
效率在一个工作环境中至关重要,尤其是在交易者的工作中,其中速度和准确性扮演着重要的角色。在准备工作客户端的同时,每个人都会让他的工作空间尽可能舒适,从而尽可能快地进行分析并进入市场。但是事实的真相是开发人员无法总是让每个人都高兴,并且也不可能与某人希望的某些功能完全合调。
例如,对于一名投机者,几分之一秒和每按一次“新建订单”键都非常重要,所有参数的后续设置在时间方面都可能非常关键。
那么,我们如何找到一个解决方案呢?解决方案贮藏在自定义控件中,因为 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 控制板的选项卡
请记住,通过选择,然后在按住 "Ctrl" 键的同时拖动对象就可以轻松复制对象。通过这种方式,我们将创建对象的副本而不是移动原来的对象。
应特别注意对象的名称,不要忘记它们必须以 "ActP" 开头。此外,我在名称字符串中添加了 "main" 字样,表示对象属于主菜单栏。
图 2. 对象列表(控制板的选项卡)
类似地,让我们将选项卡内容应用到新的图表。每个选项卡的内容应放在单独的图表中!
选项卡 "Market"(市场):
图 3. 选项卡 "Market"(市场)的控件
图 4. 选项卡 "Pending"(挂单)的控件
选项卡 "Settings"(设置):
图 5. 选项卡 "Settings"(设置)的控件
最后一个选项卡 "Modify / close"(修改/平仓)不同,它用于修改/删除挂单,以及修改交易和平仓。将交易处理和订单处理分成两个单独的子选项卡是合理的。首先,让我们创建一个激活下拉列表的按钮,我们从该列表选择要处理的订单或交易。
图6. 选项卡 "Modify/Close" (修改/平仓)的控件
之后,我们创建子选项卡。处理交易:
图 7. 用于处理仓位的控件
处理订单:
图 8. 用于处理订单的子选项卡
就这样,界面创建完毕。
我们将脚本应用到每个图表,在单独的文件中保存每个选项卡。每个选项卡的输入参数 "interfaceID" 必须不同:
- 0 - 首页
- 1 - 市场
- 2 - 挂单
- 3 - 用于激活交易/订单选择列表的按钮
- 4 - 设置
- 6 - 用于处理交易的子选项卡
- 7 - 用于处理订单的子选项卡
选项卡编号 5 对应于主菜单上的“最小化窗口”按钮,因此其上没有对象,我们可以跳过它。
经过所有这些处理之后,以下文件将出现在客户端 -> MQL5 -> 的目录文件夹中:
图 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 交易程序的工作示例
但是,在我们能够使这些对象响应我们的操作之前,所有这些都没有用处。
5. 事件处理
界面已经创建好了,现在我们必须让它工作。我们对对象进行的所有操作都生成具体的事件。OnChartEvent 函数 OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) 是事件 ChartEvent 的处理机制。我们感兴趣的所有事件如下所述:
- CHARTEVENT_CLICK - 单击图表
- CHARTEVENT_OBJECT_ENDEDIT - 完成输入字段的编辑
- CHARTEVENT_OBJECT_CLICK - 单击图形对象
在我们的例子中,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"(挂单)的控件
图 12. 选项卡 "Modify/Close"(修改/平仓)的控件
图 13. 选项卡 "Settings"(设置)的控件
通过这种方式,我们能够完成主菜单的处理,因为现在它完全实现了其功能。
5.2. 处理“标记”组件事件
辅助线和止损限制订单的设置通过使用“标记”组件来进行,但是该组件并不在 MT5 的图形对象列表中。因此让我们创建它。有一个“图形标签”,实际上是一个具有“开”状态和“关”状态的图像。单击对象可以改变状态。可以为每个状态设置一个单独的图像。为每个状态选择一个图像:
- 启用
- 禁用
让我们在对象的属性中设置图片:
图 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"(修改/平仓)控制板下拉列表的一个例子
现在,我们需要在 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. 设置按钮的颜色
颜色选择列表和文本标签是类似的。因此,我们通过几次单击就能让控制板具有很好的配色:
图 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. 交易操作 - 执行买入交易的结果
如您所见,买入交易成功完成。
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. 交易操作 - 生成挂单的结果
买入止损限制设置成功。
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. 交易操作 - 修改交易属性的结果(设置获利和止损)
止损和获利水平修改成功。
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. 交易 - 部分平仓
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. 修改挂单
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 交易 - 删除挂单
总结
最后,控制板的所有功能都经过测试并正常运行。
希望通过阅读本文所获得的知识对您开发活动控制板有所帮助,该控制板是您在市场中进行交易不可替代的助手。
要使用控制板,您需要用客户端将压缩文件解压到一个夹,然后将 AP 指标应用到图表,然后才启动活动控制板 EA 交易程序。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/62
![一个使用命名管道在 MetaTrader 5 客户端之间进行通信的无 DLL 解决方案](https://c.mql5.com/2/0/Named_Pipes_MetaTrader5.png)
![用 MQL5 创建“贪吃蛇”游戏](https://c.mql5.com/2/0/snake__2.png)
![通过指定的幻数计算总持仓量的最佳方法](https://c.mql5.com/2/0/calculate_volume_MQL5__1.png)
![在单一工具上使用不同的 EA 交易进行交易时 ORDER_MAGIC 的使用](https://c.mql5.com/2/0/order_magic_MQL5__1.png)