用 MQL5 创建交易活动控制板(基础篇)
◍ 在MT5里搭一块交易活动控制板
很多老交易员习惯把账户状态、持仓分布、委托挂单集中盯在一个面板里,而不是在终端多个窗口间来回切。MQL5 的 EA 结构可以直接在图表上用图形对象画出这么一块控制板,刷新频率由 OnTimer 控制,比人工翻终端快得多。 这篇最早发布于 2013 年 12 月 9 日,至今在源站累计有 6467 次查看、13 条讨论,说明这类轻量面板需求一直没断过。外汇和贵金属杠杆高、跳空频繁,面板只做信息聚合,不构成任何方向建议。 实现上,核心是在 OnInit 里创建 Label/Edit 等 OBJ_LABEL 对象,在 OnTimer 里读取 AccountInfoDouble、PositionGetInteger 等接口回填文字。下面这段是面板初始化的骨架,注意对象名必须唯一,否则多图表的 EA 会互相覆盖。
class="type">int OnInit() { ObjectCreate(class="num">0,"pnl_acct",OBJ_LABEL,class="num">0,class="num">0,class="num">0); ObjectSetString(class="num">0,"pnl_acct",OBJPROP_TEXT,"Account: "); ObjectSetInteger(class="num">0,"pnl_acct",OBJPROP_XDISTANCE,class="num">10); ObjectSetInteger(class="num">0,"pnl_acct",OBJPROP_YDISTANCE,class="num">10); EventSetTimer(class="num">1); class="kw">return(INIT_SUCCEEDED); }
为什么原生面板总差一口气
交易者最贵的资源是反应时间。点开「新建订单」再逐项填手数、止损、魔术码,在剥头皮或数据行情里可能多耗掉几秒,而这几秒往往决定滑点大小。 MT5 自带的对话框为了照顾所有人,反而把高频操作者的路径拉长了。开发端不可能为每种手法预置专属界面,这是终端的固有妥协。 破法在自定义控件:MT5 暴露了按钮、编辑框、标签等基础组件,自己拼一个下单条,把常用参数固化成一键,速度才可能压到毫秒级。外汇与贵金属杠杆高,界面延迟会直接放大执行风险,值得动手改。
「交易控制板的控件规划」
做一套挂在图表上的交易控制板,先得把功能边界划清:建仓、挂单、改仓/平仓、删单,以及配色和字号这类界面设定。所有图形对象名称统一以 ActP 开头,相当于给控制板组件打上私有前缀,避免和图表上其它对象撞名。 建仓区放两个按钮 ActP_buy_button1 / ActP_sell_button1 触发买卖,ActP_DealLines_check1 是辅助线开关,勾上后用鼠标拖线设止损获利比手填更直观。ActP_SL_edit1、ActP_TP_edit1、ActP_Lots_edit1 分别收止损、获利、手数,ActP_dev_edit1 填建仓允许偏差,ActP_mag_edit1 和 ActP_comm_edit1 写魔术数字与备注。 挂单区比市价单多三样东西:ActP_lim_check2 开止损限价线,ActP_exp_radio2 选过期类型,ActP_Pr_edit2 填挂单触发价,ActP_exp_edit2 填过期时间。修改持仓用 ActP_ord_button5 下拉选单,ActP_mod_button4 改、ActP_del_button4 平;其中 ActP_mag_edit4 和 ActP_Pr_edit4 标了只读,只回显魔术数和开仓价。 删改挂单的控件里,ActP_ticket_edit3 只读显示单号,ActP_Lots_edit3 也只读——改挂单手数在 MT5 逻辑里本就不允许,控制板照实锁掉。设置页三个下拉 ActP_col1/2/3_button6 管按钮、标记、文本颜色,ActP_font_edit6 定字号,另配一个最小化按钮收起面板。 辅助线才是这套面板的高频实用件:水平线拖到价位即设 SL/TP/挂单价,垂直线拖到时间轴设过期。图表上十几张单子同时挂时,客户端自带阴影线容易看花眼,控制板用自己的线高亮选中单参数,视觉定位效率明显更高。外汇和贵金属杠杆高,这类一键下单组件只解决操作效率,不替代风控。
◍ 用脚本把控件布局固化成二进制方案
在 MT5 图表上手拖控件搭界面,最怕中途加一个新按钮就得重算全部坐标。解决思路是先用鼠标把按钮、下拉框摆到位,再跑一个脚本把每个对象的属性dump进文件,以后换图表直接重建,不用再手动对齐。 下面这段脚本遍历当前图表所有对象,把名称、类型、X/Y 距离和尺寸写进以 interfaceID 命名的 .bin 文件。读回时必须严格按写入顺序解析,否则布局会错位。 // 代码逐行拆解 // #property script_show_inputs 让脚本运行时弹出参数框 // input int interfaceID=1 决定存成哪个方案文件,例如 1 对应 Market 选项卡 // FileOpen 以 FILE_WRITE|FILE_BIN 打开 Active_Panel_scheme_1.bin // ObjectsTotal(0) 取主图表对象总数,for 循环逐个处理 // ObjectName 取对象名,FileWriteString 写名称(限100字节) // 随后六个 FileWriteInteger 依次写类型、XDISTANCE、YDISTANCE、XSIZE、YSIZE 实际落地时,每个选项卡放单独图表,脚本的 interfaceID 要不同:0首页、1市场、2挂单、3激活选择列表按钮、4设置、6处理交易子选项卡、7处理订单子选项卡;编号5是最小化按钮无对象可跳过。跑完会在 MQL5/Files 下生成对应 bin 文件。 外汇与贵金属杠杆高,界面方案仅解决布局效率,不涉及任何交易信号,实盘前请在模拟盘验证重建效果。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Component properties writer.mq5 | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="kw">input class="type">int interfaceID=class="num">1; class=class="str">"cmt">//输入参数 - 保存界面的 ID class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 脚本程序起始函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- class=class="str">"cmt">//打开文件准备写入 class="type">int handle=FileOpen("Active_Panel_scheme_"+IntegerToString(interfaceID)+".bin", FILE_WRITE|FILE_BIN); if(handle!=INVALID_HANDLE) { class=class="str">"cmt">//我们将遍历图表上的所有对象 for(class="type">int i=class="num">0;i<ObjectsTotal(class="num">0);i++) { class="type">class="kw">string name=ObjectName(class="num">0,i); class=class="str">"cmt">//并且把它们的属性写入文件 FileWriteString(handle,name,class="num">100); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_TYPE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_XDISTANCE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_YDISTANCE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_XSIZE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_YSIZE));
把图形对象属性落盘到文件
这段逻辑紧接前面打开二进制文件的动作,把 MT5 图表上指定对象的各类属性逐字段写进文件。整数类属性用 FileWriteInteger,字符串类用 FileWriteString 并限定 100 字节长度,双精度价格用 FileWriteDouble,覆盖颜色、线型、宽度、背景、可选中、只读、字体大小、状态、背景色,以及文本、字体名和两个 BMP 贴图路径。 写完后 FileClose 关闭句柄,Alert 弹出 "Done!" 提示。实战里若对象含中文文本,100 字节上限在 UTF-16 下仅够 50 字符,超长会被截断,开 MT5 跑一遍改大长度即可验证。 别把 Alert 当日志 图形对象多时弹窗会刷屏,建议换成 Print 或写独立日志文件,便于复盘外汇与贵金属图表的高风险标注。
FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_COLOR)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_STYLE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_WIDTH)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_BACK)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_SELECTED)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_SELECTABLE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_READONLY)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_FONTSIZE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_STATE)); FileWriteInteger(handle,ObjectGetInteger(class="num">0,name,OBJPROP_BGCOLOR)); FileWriteString(handle,ObjectGetString(class="num">0,name,OBJPROP_TEXT),class="num">100); FileWriteString(handle,ObjectGetString(class="num">0,name,OBJPROP_FONT),class="num">100); FileWriteString(handle,ObjectGetString(class="num">0,name,OBJPROP_BMPFILE,class="num">0),class="num">100); FileWriteString(handle,ObjectGetString(class="num">0,name,OBJPROP_BMPFILE,class="num">1),class="num">100); FileWriteDouble(handle,ObjectGetDouble(class="num">0,name,OBJPROP_PRICE)); } class=class="str">"cmt">//关闭文件 FileClose(handle); Alert("Done!"); }
「把控制板塞进独立子窗口」
界面控件已经落盘,下一步是决定它显示在图表的什么位置。直接堆在主图会挡掉价格走势,实战里基本没法用,所以更合理的做法是让指标开一个子窗口,把控制板挂进去。 一个极简指标就能完成这件事——它不做任何计算,只负责生成独立窗格并设置短名称,方便后面用名字反查窗口句柄。下面这段就是最小可用指标代码。 #property indicator_separate_window 把指标推到副图;OnInit 里只用 IndicatorSetString(INDICATOR_SHORTNAME,"AP") 把短名写成 AP;OnCalculate 直接 return(rates_total) 不做行情处理。编译挂上图表,副窗口就出来了。 EA 侧 OnInit 要做三件事:EventSetTimer(1) 起 1 秒定时器、get_prices() 抓最新 Bid/Ask、ChartWindowFind(0,"AP") 把副窗口索引存进 wnd。last_loaded 这个 bool 用来标记是否首次初始化,避免 EA 重初始化时重复加载界面。 create_interface() 先读输入参数 reset settings,若开启就清掉存设置的全局变量,再调 ApplyScheme() 从文件把界面画进前面定位的 wnd 窗口;Objects_Selectable() 则取消除辅助线外所有对象的可选标记,防止误删也避免动画干扰。OnTick 里只跑 get_prices() 更新报价。 OnDeinit 要先看去初始化原因:如果是切品种或周期就不删控件,其他情况用 ObjectsDeleteAll_my() 清掉全部。编译运行后副窗口会出现按钮和标签,但在接事件响应前它们只是摆设。
class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class=class="str">"cmt">//把指标放置到独立窗口中 class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓冲区映射 class=class="str">"cmt">//设置指标的短名称 IndicatorSetString(INDICATOR_SHORTNAME, "AP"); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime& time[], class="kw">const class="type">class="kw">double& open[], class="kw">const class="type">class="kw">double& high[], class="kw">const class="type">class="kw">double& low[], class="kw">const class="type">class="kw">double& close[], class="kw">const class="type">long& tick_volume[], class="kw">const class="type">long& volume[], class="kw">const class="type">int& spread[]) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- 返回 prev_calculated 值用于下次调用 class="kw">return(rates_total); } class="type">class="kw">double Bid,Ask; class=class="str">"cmt">//当前价格变量 class="type">class="kw">datetime time_current; class=class="str">"cmt">//最后一个订单的时间 class="type">int wnd=-class="num">1; class=class="str">"cmt">//指标窗口的索引 class="type">bool last_loaded=false; class=class="str">"cmt">//只是是否为第一次初始化的标志 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA交易初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- class=class="str">"cmt">//启动计时器, 时间间隔为 class="num">1 秒 EventSetTimer(class="num">1); class=class="str">"cmt">//取得最新价格 get_prices(); class=class="str">"cmt">//定义指标窗口 wnd=ChartWindowFind(class="num">0,"AP"); class=class="str">"cmt">//如果是第一次初始化 - 创建界面 if(!last_loaded) create_interface(); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 界面创建函数 |