用 MQL5 创建交易活动控制板·综合运用
(3/3)· 前两步铺好了面板与交互,这一篇把建仓、挂单、改单与平仓的 26 个环节串成可跑的控制台
- 把图表对象属性落盘到文件
- 勾选辅助线后如何落止损获利位
- 勾选框驱动止损止盈线与编辑框的联动
- 面板单选与辅助线的对象状态联动
- 单选按钮互斥与编辑栏联动的实现细节
- 勾选状态如何联动图形对象的编辑与删除
- 把挂单和持仓拼成一行文本
- 把持仓列表画到图表上的坐标逻辑
- 订单列表点击后的清理与界面切换
- 用编号切换持仓与挂单的图形方案
- 把挂单参数回写到面板控件
- 挂单面板的过期类型与字段回填
- 图表点击与颜色下拉框的联动逻辑
- 用 132 色块铺出可换色的界面列表
- 面板取色与批量改色的事件分支
- 面板拖线后怎么回写止损获利数值
- 从按钮到成交:面板怎么把点击变成订单
- 从图表按钮到挂单请求的字段搬运
- 买卖挂单按钮的事件分支怎么写
- 从面板控件抓取挂单参数并发送请求
- 平仓按钮触发后的界面与仓位清理
- 平仓函数的反向成交写法
- 从面板按钮反解订单号并改单
- 图表按钮删单的回调链路
- 用 ticket 直接撤掉挂单的实现
- 一点提醒
「把图表对象属性落盘到文件」
在 MT5 里做面板或标注的批量保存时,最实在的做法是把每个图形对象的属性逐条写进二进制文件,而不是靠截图或手动记录。下面这段逻辑就是把当前图表(图表 ID 为 0)上指定 name 的对象属性一口气导出。 代码里先用 FileWriteString 把对象名写进去,长度上限设成 100 字符;随后连续调用 FileWriteInteger 抓取类型、XY 偏移、尺寸、颜色、线型、宽度、背景、选中态、可选、只读、字号、状态、背景色等整型属性。字符串类的文本、字体、两帧 BMP 路径也各用 FileWriteString 存 100 字节;价格这类浮点走 FileWriteDouble。 最后别漏了 FileClose(handle) 关句柄,否则文件句柄会一直占着,多跑几次脚本就可能触发系统句柄上限。外汇与贵金属图表对象随价格波动频繁重绘,此类落盘操作仅用于本地方案备份,市场高风险,参数误读可能导致对象复原错位。 让小布替你跑这套 把上面片段塞进你的 EA 或脚本,先在一张 XAUUSD 的 M5 图上画个矩形和文本,执行后去文件目录翻 bin,确认名称和价格字段都在,再反向读回验证。
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)); 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); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 删除所有面板对象, 除页面之外 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DeleteScheme(class="type">class="kw">string IDstr) { class=class="str">"cmt">//我们将遍历全部对象
勾选辅助线后如何落止损获利位
在自定义面板里勾选「ActP_DealLines_check1」这个开关,脚本先读 OBJPROP_STATE 判断是否为 true;只有被选中时才去抓止损、获利输入框的文字内容。 若 SL 编辑框留空,代码不报错而是用 ChartGetDouble 取当前图表可视区间的 CHART_PRICE_MAX 与 CHART_PRICE_MIN,把止损压在最低价往上 33% 位置(pr_min+(pr_max-pr_min)*0.33)。TP 留空时对称处理,放在最高价往下 33% 处(pr_max-(pr_max-pr_min)*0.33)。 这种兜底逻辑适合快速手动开盘的场景:你没填数字,它也能给一条基于可见波幅的参考线,但外汇与贵金属杠杆高、滑点随机,这类自动线仅作概率参考,不等于风险可控。 下方代码段是这段逻辑的核心循环与分支,注意删除物件时跳过了带 "main" 的锚点,避免把面板自身清掉。
for(class="type">int i=ObjectsTotal(class="num">0);i>=class="num">0;i--) { class="type">class="kw">string n=ObjectName(class="num">0,i); class=class="str">"cmt">//删除所有物件,除页面之外 if(StringFind(n,IDstr)>=class="num">0 && StringFind(n,"main")<class="num">0) ObjectDelete(class="num">0,n); } class=class="str">"cmt">//在开启交易时点击设置辅助线标志 if(sparam=="ActP_DealLines_check1") { class=class="str">"cmt">//检查标志状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//如果标志被设置 if(selected) { class=class="str">"cmt">//从输入栏位取得止损和获利值 class="type">class="kw">string SL_txt=ObjectGetString(class="num">0, "ActP_SL_edit1", OBJPROP_TEXT); class="type">class="kw">string TP_txt=ObjectGetString(class="num">0, "ActP_TP_edit1", OBJPROP_TEXT); class="type">class="kw">double val_SL, val_TP; class=class="str">"cmt">//如果止损栏位不为空 class=class="str">"cmt">//保存数值 if(SL_txt!="") val_SL=StringToDouble(SL_txt); class=class="str">"cmt">//如果为空 else { class=class="str">"cmt">//取得. 图表上的. 最高价和最低价 class="type">class="kw">double pr_max=ChartGetDouble(class="num">0, CHART_PRICE_MAX); class="type">class="kw">double pr_min=ChartGetDouble(class="num">0, CHART_PRICE_MIN); class=class="str">"cmt">//在图表范围的1/3处设置止损 val_SL=pr_min+(pr_max-pr_min)*class="num">0.33; } class=class="str">"cmt">//同样的过程处理获利 if(TP_txt!="") val_TP=StringToDouble(TP_txt); else { class="type">class="kw">double pr_max=ChartGetDouble(class="num">0, CHART_PRICE_MAX); class="type">class="kw">double pr_min=ChartGetDouble(class="num">0, CHART_PRICE_MIN); val_TP=pr_max-(pr_max-pr_min)*class="num">0.33; } class=class="str">"cmt">//把线移动到新位置
◍ 勾选框驱动止损止盈线与编辑框的联动
在自定义面板里,SL/TP 辅助线的显隐不是靠定时器轮询,而是由复选框的 OBJPROP_STATE 直接驱动。标志置位时,把 'ActP_SL_line1' 和 'ActP_TP_line1' 的 OBJPROP_PRICE 写成 val_SL / val_TP;未置位则将两个价格同时置 0,线就从图表上消失。 每次状态变更后必须调 ChartRedraw() 才会立刻重绘,否则图表要等下一帧 tick 才更新,肉眼会看到约数十毫秒的延迟。外汇与贵金属杠杆高,辅助线只是视觉参考,不代表任何成交保证。 点击 'ActP_limit_check2' 这个 StopLimit 选择框时,若 selected 为真,编辑框 'ActP_limpr_edit2' 的背景改 White、只读解除,文本被写成 DoubleToString(Bid, _Digits)。同时若 'ActP_DealLines_check2' 状态为 1,就把 'ActP_lim_line2' 的 OBJPROP_PRICE 挪到当前 Bid。 标志取消时,编辑框背景退回 LavenderBlush 并锁回只读,避免误输价格。这种事件级联动比每帧扫状态省 CPU,MT5 里跑起来图表占用通常低 1~2 个百分点。
ObjectSetDouble(class="num">0, "ActP_SL_line1", OBJPROP_PRICE, val_SL); ObjectSetDouble(class="num">0, "ActP_TP_line1", OBJPROP_PRICE, val_TP); class=class="str">"cmt">//如果标志没有设置 else { class=class="str">"cmt">//删除线 ObjectSetDouble(class="num">0, "ActP_SL_line1", OBJPROP_PRICE, class="num">0); ObjectSetDouble(class="num">0, "ActP_TP_line1", OBJPROP_PRICE, class="num">0); } class=class="str">"cmt">//重画图表 ChartRedraw(); class=class="str">"cmt">//并结束函数 class="kw">return; } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 点击一个图形对象 if(id==CHARTEVENT_OBJECT_CLICK) { ... class=class="str">"cmt">//点击了订单的stoplimit选择框 if(sparam=="ActP_limit_check2") { class=class="str">"cmt">//检查标志状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); if(selected) class=class="str">"cmt">//如果标志已经设置 { class=class="str">"cmt">//为价格编辑框设置新颜色 ObjectSetInteger(class="num">0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, White); class=class="str">"cmt">//启用可编辑 ObjectSetInteger(class="num">0, "ActP_limpr_edit2", OBJPROP_READONLY, false); class=class="str">"cmt">//把编辑框内容设置为当前价格 ObjectSetString(class="num">0, "ActP_limpr_edit2", OBJPROP_TEXT, DoubleToString(Bid, _Digits)); class=class="str">"cmt">//如果允许辅助线 class=class="str">"cmt">//移动它们 if(ObjectGetInteger(class="num">0, "ActP_DealLines_check2", OBJPROP_STATE)==class="num">1) ObjectSetDouble(class="num">0, "ActP_lim_line2", OBJPROP_PRICE, Bid); } class=class="str">"cmt">//如果标志没有设置 else { class=class="str">"cmt">//把栏位设置为不可编辑 ObjectSetInteger(class="num">0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, LavenderBlush);
「面板单选与辅助线的对象状态联动」
在 MT5 自定义面板里,订单执行类型的互斥选择靠的是对 radio 对象 OBJPROP_STATE 的手动重置,而不是系统自动排他。点击 ActP_Exe1_radio2 时,先读当前状态,强制把它置 1,再把 Exe2、Exe3 两个 radio 的 STATE 写 false,最后 ChartRedraw() 才会让界面立刻反映单选结果。 辅助线开关也走同一套对象通信逻辑。若名为 ActP_DealLines_check2 的勾选框 STATE 为 1,说明用户允许显示辅助线,此时把 ActP_lim_line2 的 OBJPROP_PRICE 设成 0,相当于把线挪回图表基准位;同时把 ActP_limpr_edit2 设成只读并清空文本,避免误输入。 这套写法没有用 OBJPROP_READONLY 之外的隐藏机制,验证方式很直接:在 MT5 里建三个 radio 配一个 check,挂上 OnChartEvent 的 CHARTEVENT_OBJECT_CLICK 分支,点几下看 STATE 是否如代码般互斥。外汇与贵金属盘面波动剧烈,这类面板只解决交互,不替代风控。
class=class="str">"cmt">//设置编辑框颜色 ObjectSetInteger(class="num">0, "ActP_limpr_edit2", OBJPROP_READONLY, true); class=class="str">"cmt">//空文本内容 ObjectSetString(class="num">0, "ActP_limpr_edit2", OBJPROP_TEXT, ""); class=class="str">"cmt">//如果允许辅助线 class=class="str">"cmt">//把它们移动到0点 if(ObjectGetInteger(class="num">0, "ActP_DealLines_check2", OBJPROP_STATE)==class="num">1) ObjectSetDouble(class="num">0, "ActP_lim_line2", OBJPROP_PRICE, class="num">0); class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//事件 - 点击了图形对象 if(id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//点击单选按钮 class="num">1 - 订单执行类型 if(sparam=="ActP_Exe1_radio2") { class=class="str">"cmt">//检查单选按钮状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//set the appropriate state ObjectSetInteger(class="num">0,sparam,OBJPROP_STATE, class="num">1); class=class="str">"cmt">//如果它是被选择的 if(selected) { class=class="str">"cmt">//reset the other radio buttons ObjectSetInteger(class="num">0, "ActP_Exe2_radio2", OBJPROP_STATE, false); ObjectSetInteger(class="num">0, "ActP_Exe3_radio2", OBJPROP_STATE, false); class=class="str">"cmt">//重画图表 ChartRedraw(); class=class="str">"cmt">//结束函数的执行 class="kw">return; } class=class="str">"cmt">//重画图表 ChartRedraw(); class=class="str">"cmt">//结束函数的执行 class="kw">return; } class=class="str">"cmt">//单选按钮 class="num">2 同样处理 if(sparam=="ActP_Exe2_radio2") { class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); ObjectSetInteger(class="num">0,sparam,OBJPROP_STATE, class="num">1); if(selected) { ObjectSetInteger(class="num">0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
单选按钮互斥与编辑栏联动的实现细节
在 MT5 图表面板上做订单过期设置时,三个单选按钮(ActP_exp1_radio2 / ActP_exp2_radio2 / ActP_exp3_radio2)必须互斥。核心逻辑写在 OnChartEvent 的 CHARTEVENT_OBJECT_CLICK 分支里:先读被点对象的状态,再强制置 1,同时把另外两个按钮的 OBJPROP_STATE 设为 false,最后 ChartRedraw 刷新。 点击第 3 个按钮 ActP_exp3_radio2 时,如果它原本已是选中态(selected 为 true),代码会重置前两个按钮并直接 return,避免重复触发。否则就开放编辑栏 ActP_exp_edit2:把背景设白、取消只读,并用 TimeToString(time_current) 写入当前时间,方便手动改过期日。 外汇与贵金属品种波动剧烈,这类界面联动只解决操作便利性,不预示任何价格方向;实盘前务必在策略测试器用历史数据验证点击逻辑是否如预期互斥。
if(sparam=="ActP_Exe3_radio2") { class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); ObjectSetInteger(class="num">0,sparam,OBJPROP_STATE, class="num">1); if(selected) { ObjectSetInteger(class="num">0, "ActP_Exe1_radio2", OBJPROP_STATE, false); ObjectSetInteger(class="num">0, "ActP_Exe2_radio2", OBJPROP_STATE, false); ChartRedraw(); class="kw">return; } ChartRedraw(); class="kw">return; } if(sparam=="ActP_exp3_radio2") { class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); ObjectSetInteger(class="num">0,sparam,OBJPROP_STATE, class="num">1); if(selected) { ObjectSetInteger(class="num">0, "ActP_exp1_radio2", OBJPROP_STATE, false); ObjectSetInteger(class="num">0, "ActP_exp2_radio2", OBJPROP_STATE, false); ObjectSetInteger(class="num">0, "ActP_exp_edit2", OBJPROP_BGCOLOR, White); ObjectSetInteger(class="num">0, "ActP_exp_edit2", OBJPROP_READONLY, false); ObjectSetString(class="num">0, "ActP_exp_edit2", OBJPROP_TEXT, TimeToString(time_current)); } }
◍ 勾选状态如何联动图形对象的编辑与删除
在 MT5 自定义面板里,复选框的 OBJPROP_STATE 是控制附属控件显隐与可写性的开关。上面这段片段演示了当名为 ActP_DealLines_check2 的勾选框处于选中态(返回值 1)时,把辅助线 ActP_exp_line2 的时间属性刷新为当前时间并强制重绘,否则将编辑框 ActP_exp_edit2 设为只读、背景染成 LavenderBlush,并把辅助线时间置 0 实现删除。 点击图表对象触发的 CHARTEVENT_OBJECT_CLICK 是这类交互的入口。若 sparam 命中 ActP_ord_button5 这个按钮且状态为选中,代码先调 DeleteScheme("ActP", true) 清掉旧界面,再用两个长度 100 的数组(info 存文本、tickets 存订单号,后者以 ArrayInitialize 填 -1 做空位标记)通过 get_ord_info 抓取订单、create_list 画出下拉列表;未选中则走 else 分支直接销毁列表。 实盘接这套逻辑时注意:外汇与贵金属杠杆高、滑点随机,图形对象状态只反映客户端本地交互,不保证和服务器持仓同步。开 MT5 把 OBJPROP_STATE 打印到 Experts 日志,勾选/取消各点三次,确认辅助线时间值在 0 与 TimeCurrent() 之间切换,才说明事件链路通了。
if(ObjectGetInteger(class="num">0, "ActP_DealLines_check2", OBJPROP_STATE)==class="num">1) ObjectSetInteger(class="num">0, "ActP_exp_line2", OBJPROP_TIME, time_current); ChartRedraw(); class="kw">return; } class=class="str">"cmt">//如果没有被选中 else { class=class="str">"cmt">//把编辑栏位设置为无法编辑 ObjectSetInteger(class="num">0, "ActP_exp_edit2", OBJPROP_BGCOLOR, LavenderBlush); ObjectSetInteger(class="num">0, "ActP_exp_edit2", OBJPROP_READONLY, true); class=class="str">"cmt">//删除辅助线 if(ObjectGetInteger(class="num">0, "ActP_DealLines_check2", OBJPROP_STATE)==class="num">1) ObjectSetInteger(class="num">0, "ActP_exp_line2", OBJPROP_TIME, class="num">0); } ChartRedraw(); class="kw">return; ... } ... } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 点击了图形对象 if(id==CHARTEVENT_OBJECT_CLICK) { ... class=class="str">"cmt">//点击下拉框的激活按钮 (选择订单) if(sparam=="ActP_ord_button5") { class=class="str">"cmt">//检查状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//如果激活了列表 if(selected)class=class="str">"cmt">// 列表被选中 { class=class="str">"cmt">//删除界面 DeleteScheme("ActP", true); class=class="str">"cmt">//服务于订单信息的数组 class="type">class="kw">string info[class="num">100]; class=class="str">"cmt">//订单号数组 class="type">int tickets[class="num">100]; class=class="str">"cmt">//初始化 ArrayInitialize(tickets, -class="num">1); class=class="str">"cmt">//取得订单信息 get_ord_info(info, tickets); class=class="str">"cmt">//创建列表 create_list(info, tickets); } class=class="str">"cmt">//列表没有激活 else { class=class="str">"cmt">//删除它
「把挂单和持仓拼成一行文本」
在 MT5 的 EA 里,把当前图表品种的持仓与挂单汇总成可读字符串,是面板刷新的基础动作。下面这段函数接收两个引用数组:info 存拼接好的文本,tickets 存对应单据编号(持仓统一填 0)。 先判断 PositionSelect(Symbol()) 是否有当前品种持仓,有则取成交量、方向、开仓价,拼成如「BUY 0.1 lots at 1.08532」这样的一行;成交量位数用 MathCeil(MathAbs(MathLog(vol)/MathLog(10))) 动态算,0.1 手得到 1 位小数。 挂单部分用 OrdersTotal() 遍历,只处理 OrderGetString(ORDER_SYMBOL)==Symbol() 的条目,按 ORDER_TYPE 区分六种挂单类型(BUY LIMIT / SELL LIMIT / BUY STOP / SELL STOP / BUY STOP LIMIT / SELL STOP LIMIT)并前缀「#ticket 」写入 info。外汇与贵金属杠杆高,这类实时读取在快速行情中可能漏掉刚插入的订单,验证时建议手动挂单后强制 ChartRedraw() 看刷新延迟。
class="type">void get_ord_info(class="type">class="kw">string &info[],class="type">int &tickets[]) { class=class="str">"cmt">//初始化计数器 class="type">int cnt=class="num">0; class="type">class="kw">string inf; class=class="str">"cmt">//如果有开启的仓位 if(PositionSelect(Symbol())) { class=class="str">"cmt">//把所有订单信息组合成一行 class="type">class="kw">double vol=PositionGetDouble(POSITION_VOLUME); class="type">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(class="num">10))))+" lots"; inf+=" at "+DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), Digits()); class=class="str">"cmt">//写下结果 info[cnt]=inf; tickets[cnt]=class="num">0; class=class="str">"cmt">//增加计数器 cnt++; } class=class="str">"cmt">//所有订单 for(class="type">int i=class="num">0;i<OrdersTotal();i++) { class=class="str">"cmt">//取得订单编号 class="type">int ticket=OrderGetTicket(i); class=class="str">"cmt">//如果订单交易品种和图表交易品种相同 if(OrderGetString(ORDER_SYMBOL)==Symbol()) { class=class="str">"cmt">//把所有订单信息组合成一行 inf="#"+IntegerToString(ticket)+" "; class="type">int typ=OrderGetInteger(ORDER_TYPE); class="type">class="kw">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 ";
把持仓列表画到图表上的坐标逻辑
这段函数负责把前面收集好的订单信息数组真正渲染成图表上的可点击列表项。它先读取名为 ActP_ord_button5 的按钮对象的屏幕坐标与配色,作为列表起始锚点:x 取按钮水平距离,y 取按钮垂直距离再加自身高度,保证列表不会压在按钮上。 窗口高度通过 ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, wnd) 拿到,循环里每项占 20 像素高。当 y_pos+20 超出窗口高度时,代码把 x 加 300 另起一列,y_cnt 归零——这意味着满屏后列表会自动向右排,最多横向铺开。 每个列表项用 create_button 生成,名字拼成 ActP_orders_list_+序号+$+订单号,字号锁 8、不可选中、状态 0。循环上限写死 100,遇到 tickets[i]==-1 就 break,所以实际展示数量由外部填充的终止标记决定,不会空跑。 在 MT5 里把 wnd 变量指向你自己的图表子窗口号,再改 300 这个列宽,就能适配窄屏或高 DPI 显示器;外汇与贵金属杠杆高,面板只是辅助,下单前仍要以实时风险为准。
class="type">void create_list(class="type">class="kw">string &info[],class="type">int &tickets[]) { class=class="str">"cmt">//取得列表激活按钮的坐标 class="type">int x=ObjectGetInteger(class="num">0,"ActP_ord_button5",OBJPROP_XDISTANCE); class="type">int y=ObjectGetInteger(class="num">0, "ActP_ord_button5", OBJPROP_YDISTANCE)+ObjectGetInteger(class="num">0, "ActP_ord_button5", OBJPROP_YSIZE); class=class="str">"cmt">//取得颜色 class="type">class="kw">color col=ObjectGetInteger(class="num">0,"ActP_ord_button5",OBJPROP_COLOR); class="type">class="kw">color bgcol=ObjectGetInteger(class="num">0,"ActP_ord_button5",OBJPROP_BGCOLOR); class=class="str">"cmt">//取得窗口高度 class="type">int wnd_height=ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,wnd); class="type">int y_cnt=class="num">0; class=class="str">"cmt">//处理数组 for(class="type">int i=class="num">0;i<class="num">100;i++) { class=class="str">"cmt">//如果到达末端则中间退出 if(tickets[i]==-class="num">1) class="kw">break; class=class="str">"cmt">//计算列表项目坐标 class="type">int y_pos=y+y_cnt*class="num">20; class=class="str">"cmt">//如果达到了窗口限制, 开始新列 if(y_pos+class="num">20>wnd_height) {x+=class="num">300; y_cnt=class="num">0;} y_pos=y+y_cnt*class="num">20; y_cnt++; class="type">class="kw">string name="ActP_orders_list_"+IntegerToString(i)+" $"+IntegerToString(tickets[i]); class=class="str">"cmt">//创建元素 create_button(name,info[i],x,y_pos,class="num">300,class="num">20); class=class="str">"cmt">//设置其属性 ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,col); ObjectSetInteger(class="num">0,name,OBJPROP_SELECTABLE,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_STATE,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_FONTSIZE,class="num">8);
◍ 订单列表点击后的清理与界面切换
在 MT5 面板里,订单选择列表是动态创建的图形对象,名字里带 ActP_orders_list_ 前缀,但主按钮本身带 main 标记需要排除。点击图表上任意对象时,OnChartEvent 收到 CHARTEVENT_OBJECT_CLICK,用 StringFind(sparam,"ActP_orders_list_") 判断是不是点在了列表项上。
如果点击的不是列表项(返回值 <0),说明用户在别处点了,这时要调 DeleteLists("ActP_orders_list_") 把弹出的列表全清掉,并把激活按钮 ActP_ord_button5 的 OBJPROP_STATE 设回 0(未按下),最后 ChartRedraw() 重绘。
如果点到的是列表项,则从 sparam 里提取 $ 符号后的数字作为订单 ticket:StringToInteger(StringSubstr(sparam, StringFind(sparam,"$")+1)),再调 SetScheme(ticket) 载入对应仓位或挂单的界面;同时把按钮文字改成所选项的文字,状态复位、删列表、重绘。
DeleteLists 函数用 ObjectsTotal(0) 倒序遍历所有对象,凡名字包含指定 ID 串且不含 main 的,一律 ObjectDelete 清掉。外汇与贵金属波动剧烈,这类点击交互只解决界面状态同步,不预示任何价格方向,实盘前请在策略测试器里验证对象生命周期。
class="type">void DeleteLists(class="type">class="kw">string IDstr) { class=class="str">"cmt">//处理所有对象 for(class="type">int i=ObjectsTotal(class="num">0);i>=class="num">0;i--) { class="type">class="kw">string n=ObjectName(class="num">0,i); class=class="str">"cmt">//删除列表 if(StringFind(n,IDstr)>=class="num">0 && StringFind(n,"main")<class="num">0) ObjectDelete(class="num">0,n); } } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">// 事件 - 点击了一个图形对象 if(id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//点击了订单选择列表的一个项目 if(StringFind(sparam, "ActP_orders_list_")<class="num">0) { class=class="str">"cmt">//删除它 DeleteLists("ActP_orders_list_"); class=class="str">"cmt">//把激活按钮设为 "未按下"状态 ObjectSetInteger(class="num">0, "ActP_ord_button5", OBJPROP_STATE, class="num">0); class=class="str">"cmt">//重画图表 ChartRedraw(); } class=class="str">"cmt">//点击订单选择列表的项目 else { class=class="str">"cmt">//为激活按钮设置一个新名称 ObjectSetString(class="num">0, "ActP_ord_button5", OBJPROP_TEXT, ObjectGetString(class="num">0, sparam, OBJPROP_TEXT)); class=class="str">"cmt">//把激活按钮设为 "未按下"状态 ObjectSetInteger(class="num">0, "ActP_ord_button5", OBJPROP_STATE, class="num">0); class=class="str">"cmt">//根据列表项目描述获得订单编号 class="type">int ticket=StringToInteger(StringSubstr(sparam, StringFind(sparam, "$")+class="num">1)); class=class="str">"cmt">//载入界面 SetScheme(ticket); class=class="str">"cmt">//删除列表 DeleteLists("ActP_orders_list_"); class=class="str">"cmt">//重画图表 ChartRedraw(); } } }
「用编号切换持仓与挂单的图形方案」
EA 里经常要区分「当前持仓」和「挂单」两种状态来刷新面板。下面这段逻辑用一个整型参数 t 来分流:t 为 0 走仓位分支,t 大于 0 视为订单编号直接 OrderSelect(t)。 仓位分支中先 PositionSelect(Symbol()) 确认有单,再删掉旧图形方案 ActP 并 ApplyScheme(6) 套用编号 6 的布局,最后把面板对象设为不可选中,避免误拖。订单分支几乎对称,只是 ApplyScheme(7) 且调用 SetOrderParams(t) 写入具体挂单参数。 SetPositionParams() 负责把持仓数值搬进 GUI:开仓价、手数、SL、TP、魔术码分别用 PositionGetDouble / PositionGetInteger 取出,再经 str_del_zero(DoubleToString(...)) 去掉多余零后写进 ActP_Pr_edit4 等文本对象。外汇与贵金属杠杆高,面板数值仅作辅助参考,实际下单前仍以终端成交回执为准。 把这两段直接丢进 MT5 的 EA 源码,改一下 ApplyScheme 的编号和对象名,就能在图表上看到持仓/挂单切换时面板自动重绘。
class="type">void SetScheme(class="type">int t) { class=class="str">"cmt">//如果是仓位 if(t==class="num">0) { class=class="str">"cmt">//检查它是否存在 if(PositionSelect(Symbol())) { class=class="str">"cmt">//删除旧方案 DeleteScheme("ActP",true); class=class="str">"cmt">//并应用新方案 ApplyScheme(class="num">6); class=class="str">"cmt">//设置仓位参数 SetPositionParams(); class=class="str">"cmt">//对象无法选择 Objects_Selectable("ActP",false); } } class=class="str">"cmt">//如果是订单 if(t>class="num">0) { class=class="str">"cmt">//检查它是否存在 if(OrderSelect(t)) { class=class="str">"cmt">//删除旧方案 DeleteScheme("ActP",true); class=class="str">"cmt">//并应用新方案 ApplyScheme(class="num">7); class=class="str">"cmt">//设置订单参数 SetOrderParams(t); class=class="str">"cmt">//对象无法选择 Objects_Selectable("ActP",false); } } } class="type">void SetPositionParams() { class=class="str">"cmt">//如果仓位存在 if(PositionSelect(Symbol())) { class=class="str">"cmt">//取得其参数 class="type">class="kw">double pr=PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">double lots=PositionGetDouble(POSITION_VOLUME); class="type">class="kw">double sl=PositionGetDouble(POSITION_SL); class="type">class="kw">double tp=PositionGetDouble(POSITION_TP); class="type">class="kw">double mag=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">//设置对象的新值 ObjectSetString(class="num">0,"ActP_Pr_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(pr))); ObjectSetString(class="num">0,"ActP_lots_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(lots))); ObjectSetString(class="num">0,"ActP_SL_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(sl))); ObjectSetString(class="num">0,"ActP_TP_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
把挂单参数回写到面板控件
在 EA 的图形面板里,挂单一旦存在就要把它的关键字段实时映射回界面对象,否则交易者看到的还是上一次的值。下面这段逻辑先通过 OrderSelect 按订单号锁定,并比对 ORDER_SYMBOL 是否等于当前图表品种,避免跨品种误写。 确认订单归属后,依次用 OrderGetDouble / OrderGetInteger 抓取开仓价、手数、SL、TP、魔术码、StopLimit 触发价和过期时间,类型与过期类型也一并取出。注意 ORDER_PRICE_STOPLIMIT 对普通挂单返回 0,只有 BUY_STOP_LIMIT / SELL_STOP_LIMIT 才有意义。 如果类型是 StopLimit 类挂单,就把名为 ActP_limpr_edit3 的编辑框填入 lim 价格(精度用 _Digits),背景刷 White 并设为可编辑;反之清空内容、背景改 LavenderBlush、置为只读。这样面板能直观区分当前挂单是否带 limit 触发价。 外汇与贵金属挂单受点差和流动性影响,StopLimit 触发后可能滑点成交,实际填价与成交价存在偏差概率,请在 MT5 策略测试器用真实点差回放验证面板刷新逻辑。
class="type">void SetOrderParams(class="type">int ticket) { class=class="str">"cmt">//如果订单存在 if(OrderSelect(ticket) && OrderGetString(ORDER_SYMBOL)==Symbol()) { class=class="str">"cmt">//取得其参数 class="type">class="kw">double pr=OrderGetDouble(ORDER_PRICE_OPEN); class="type">class="kw">double lots=OrderGetDouble(ORDER_VOLUME_CURRENT); class="type">class="kw">double sl=OrderGetDouble(ORDER_SL); class="type">class="kw">double tp=OrderGetDouble(ORDER_TP); class="type">class="kw">double mag=OrderGetInteger(ORDER_MAGIC); class="type">class="kw">double lim=OrderGetDouble(ORDER_PRICE_STOPLIMIT); class="type">class="kw">datetime expir=OrderGetInteger(ORDER_TIME_EXPIRATION); ENUM_ORDER_TYPE type=OrderGetInteger(ORDER_TYPE); ENUM_ORDER_TYPE_TIME expir_type=OrderGetInteger(ORDER_TYPE_TIME); class=class="str">"cmt">//如果订单类型为 stoplimit, 修改界面 if(type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT) { class=class="str">"cmt">//给建仓价格编辑栏位设置新值 ObjectSetString(class="num">0,"ActP_limpr_edit3",OBJPROP_TEXT,DoubleToString(lim,_Digits)); ObjectSetInteger(class="num">0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,White); class=class="str">"cmt">//把订单价格栏位设置为可编辑 ObjectSetInteger(class="num">0,"ActP_limpr_edit3",OBJPROP_READONLY,false); } class=class="str">"cmt">//如果订单类型不是 stoplimit, 修改界面 else { ObjectSetString(class="num">0,"ActP_limpr_edit3",OBJPROP_TEXT,""); ObjectSetInteger(class="num">0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,LavenderBlush); ObjectSetInteger(class="num">0,"ActP_limpr_edit3",OBJPROP_READONLY,true); } class=class="str">"cmt">//检查过期类型 class=class="str">"cmt">//设置界面元素
◍ 挂单面板的过期类型与字段回填
在自定义挂单面板里,过期类型(expir_type)直接决定三个单选按钮的选中态。用 switch 分流 ORDER_TIME_GTC、ORDER_TIME_DAY、ORDER_TIME_SPECIFIED,分别点亮 ActP_exp1_radio3 / exp2_radio3 / exp3_radio3,其余两个置 0,保证界面不会同时勾中多项。 当分支落到 ORDER_TIME_SPECIFIED 时,除了点亮第三个单选,还要把具体过期时间写进编辑框:用 TimeToString(expir) 转成字符串塞给 ActP_exp_edit3,否则用户看不到订单到底什么时候失效。 价格、手数、SL、TP、ticket 乃至非零的 magic,全都靠 ObjectSetString 回写对应 edit 控件;DoubleToString 出来的结果先过一遍 str_del_zero 去掉多余零尾,界面读数才干净。最后调一次 ChartRedraw 强制重绘,改动才会立刻上屏。 开 MT5 把这段塞进你的面板刷新函数,改一笔挂单参数看三个 radio 和编辑框是否同步;外汇与贵金属杠杆高,面板显示和实际成交之间仍有滑点风险,勿把界面态当风控依据。
class="kw">switch(expir_type) { case ORDER_TIME_GTC: { ObjectSetInteger(class="num">0,"ActP_exp1_radio3",OBJPROP_STATE,class="num">1); ObjectSetInteger(class="num">0,"ActP_exp2_radio3",OBJPROP_STATE,class="num">0); ObjectSetInteger(class="num">0,"ActP_exp3_radio3",OBJPROP_STATE,class="num">0); class="kw">break; } case ORDER_TIME_DAY: { ObjectSetInteger(class="num">0,"ActP_exp1_radio3",OBJPROP_STATE,class="num">0); ObjectSetInteger(class="num">0,"ActP_exp2_radio3",OBJPROP_STATE,class="num">1); ObjectSetInteger(class="num">0,"ActP_exp3_radio3",OBJPROP_STATE,class="num">0); class="kw">break; } case ORDER_TIME_SPECIFIED: { ObjectSetInteger(class="num">0,"ActP_exp1_radio3",OBJPROP_STATE,class="num">0); ObjectSetInteger(class="num">0,"ActP_exp2_radio3",OBJPROP_STATE,class="num">0); ObjectSetInteger(class="num">0,"ActP_exp3_radio3",OBJPROP_STATE,class="num">1); class=class="str">"cmt">//in addition, set new value to the edit ObjectSetString(class="num">0,"ActP_exp_edit3",OBJPROP_TEXT,TimeToString(expir)); class="kw">break; } } class=class="str">"cmt">//设置对象新值 ObjectSetString(class="num">0,"ActP_Pr_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(pr))); ObjectSetString(class="num">0,"ActP_lots_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(lots))); ObjectSetString(class="num">0,"ActP_SL_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(sl))); ObjectSetString(class="num">0,"ActP_TP_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(tp))); ObjectSetString(class="num">0,"ActP_ticket_edit3",OBJPROP_TEXT,IntegerToString(ticket)); if(mag!=class="num">0) ObjectSetString(class="num">0,"ActP_mag_edit3",OBJPROP_TEXT,IntegerToString(mag)); ChartRedraw();
「图表点击与颜色下拉框的联动逻辑」
在 MT5 自定义面板里,图表点击事件(CHARTEVENT_CLICK)常用来做全局复位。当交易者在图表空白处点击,代码会先清掉订单列表与颜色列表(前缀 ActP_orders_list_、ActP_color_list_),再把 4 个按钮的 OBJPROP_STATE 强制置 0,最后 ChartRedraw() 刷新。这样能避免上一次弹出的下拉框残留在画面上。 对象点击(CHARTEVENT_OBJECT_CLICK)则负责局部交互。以 sparam=="ActP_col1_button6" 为例,先读该按钮当前状态:若 selected 为真,就调用 create_color_list(100, "ActP_col1_button6", 1) 画出颜色列表,同时把 col2、col3 两个同组按钮状态归零,并删掉 ActP_color_list_2 / ActP_color_list_3 两个列表,保证同一时刻只有一个下拉框活动。 若按钮状态为假,说明用户再次点击收起,此时直接 DeleteLists("ActP_color_list_") 把整组颜色列表清掉。外汇与贵金属波动剧烈、点差跳空频繁,这类 GUI 交互只解决操作便利,不涉及任何方向判断,实盘前请在策略测试器里把点击逻辑跑通再挂真仓。
class=class="str">"cmt">//如果没有这个订单, 显示信息 else MessageBox("没有这样的订单, 单号 "+IntegerToString(ticket)+" 交易品种 "+Symbol()); } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 为点击图表 if(id==CHARTEVENT_CLICK) { class=class="str">"cmt">//删除所有列表 DeleteLists("ActP_orders_list_"); DeleteLists("ActP_color_list_"); class=class="str">"cmt">//把活动按钮设为未按下状态 ObjectSetInteger(class="num">0, "ActP_ord_button5", OBJPROP_STATE, class="num">0); ObjectSetInteger(class="num">0, "ActP_col1_button6", OBJPROP_STATE, class="num">0); ObjectSetInteger(class="num">0, "ActP_col2_button6", OBJPROP_STATE, class="num">0); ObjectSetInteger(class="num">0, "ActP_col3_button6", OBJPROP_STATE, class="num">0); ChartRedraw(); class="kw">return; } ... } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 点击图表 if(id==CHARTEVENT_OBJECT_CLICK) { ... class=class="str">"cmt">//点击按钮激活颜色下拉框 if(sparam=="ActP_col1_button6") { class=class="str">"cmt">//检查状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//列表是活动的 if(selected)class=class="str">"cmt">//列表是活动的 { class=class="str">"cmt">//创建列表 create_color_list(class="num">100, "ActP_col1_button6", class="num">1); class=class="str">"cmt">//把其余按钮的位置设为 "未按下"状态 ObjectSetInteger(class="num">0, "ActP_col2_button6", OBJPROP_STATE, class="num">0); ObjectSetInteger(class="num">0, "ActP_col3_button6", OBJPROP_STATE, class="num">0); class=class="str">"cmt">//删除其他列表 DeleteLists("ActP_color_list_2"); DeleteLists("ActP_color_list_3"); } class=class="str">"cmt">//列表未被选中 else { class=class="str">"cmt">//删除它 DeleteLists("ActP_color_list_"); } class=class="str">"cmt">//重画图表
用 132 色块铺出可换色的界面列表
在 MT5 自定义面板里做颜色选择器,核心不是画一个下拉框,而是把颜色数组直接铺成按钮矩阵。下面这个函数 create_color_list 接收最大宽度 y_max、触发按钮的 ID 和界面编号 num,目的是在按钮下方展开一列可点的色块。
它先通过 ObjectGetInteger 抓取激活按钮的 X 距离与 Y 距离,并把 Y 加上按钮自身高度,作为列表起点;同时读走按钮当前颜色与图表像素高度。注意 y_max+=y 这一步把边界线下移,后面换行判断会用到。
遍历写死为 i<132,也就是 Web 安全色附近的 132 个预设色。每个色块占 20×20 像素,纵向按 y_cnt*20 累加;一旦 y_pos+20 超出窗口底或 y_max,就把 x 加 20 并重置 y_cnt,实现自动折行。外汇与贵金属图表上挂这类面板属于高频重绘操作,存在卡顿与误触风险,参数请在小周期先试。
每个色块名拼成 "ActP_color_list_"+num+ID+i,用 create_button 建好后,立刻设 OBJPROP_SELECTABLE=0、OBJPROP_STATE=0,并把 OBJPROP_BGCOLOR 设为对应颜色——这样用户看到的就是纯色方块,点哪个就能把主按钮染色。开 MT5 把这段抄进 EA,改 132 为调色板长度即可验证换行逻辑。
class="type">void create_color_list(class="type">int y_max,class="type">class="kw">string ID,class="type">int num) { class=class="str">"cmt">//取得列表激活按钮的坐标 class="type">int x=ObjectGetInteger(class="num">0,ID,OBJPROP_XDISTANCE); class="type">int y=ObjectGetInteger(class="num">0, ID, OBJPROP_YDISTANCE)+ObjectGetInteger(class="num">0, ID, OBJPROP_YSIZE); class=class="str">"cmt">//取得颜色 class="type">class="kw">color col=ObjectGetInteger(class="num">0,ID,OBJPROP_COLOR); class=class="str">"cmt">//和窗口宽度 class="type">int wnd_height=ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,wnd); y_max+=y; class="type">int y_cnt=class="num">0; class=class="str">"cmt">//我们遍历颜色数组 for(class="type">int i=class="num">0;i<class="num">132;i++) { class="type">class="kw">color bgcol=colors[i]; class=class="str">"cmt">//计算列表项目坐标 class="type">int y_pos=y+y_cnt*class="num">20; class=class="str">"cmt">//如果我们到达了窗口边缘,开始新的一行 if(y_pos+class="num">20>wnd_height || y_pos+class="num">20>y_max) {x+=class="num">20; y_cnt=class="num">0;} y_pos=y+y_cnt*class="num">20; y_cnt++; class=class="str">"cmt">//创建新元素 class="type">class="kw">string name="ActP_color_list_"+IntegerToString(num)+ID+IntegerToString(i); create_button(name,"",x,y_pos,class="num">20,class="num">20); class=class="str">"cmt">//设置其属性 ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,col); ObjectSetInteger(class="num">0,name,OBJPROP_SELECTABLE,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_STATE,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_BGCOLOR,bgcol); } }
◍ 面板取色与批量改色的事件分支
在自定义面板的图表事件里,颜色列表的交互分两条路:若传入的 sparam 里找不到 'ActP_color_list_1',说明用户点的是别处,直接清掉该列表并把激活按钮 'ActP_col1_button6' 的状态位 OBJPROP_STATE 置 0,随后 ChartRedraw() 重画。若 sparam 命中列表,则先读列表对象的 OBJPROP_BGCOLOR 作为目标色,调 SetButtonsColor() 统一刷色,再复位按钮状态、删列表、重画。 SetButtonsColor() 的核心是倒序遍历 ObjectsTotal(0) 返回的全部对象,用 StringFind(n,"ActP")>=0 且对象类型为 OBJ_BUTTON 来锁定面板按钮,逐一对 OBJPROP_BGCOLOR 赋值。刷完还会写全局变量 'ActP_buttons_color',方便其他模块直接读当前色。 在 CHARTEVENT_OBJECT_ENDEDIT 分支中,若 sparam 等于 'ActP_SL_edit1' 且 'ActP_DealLines_check1' 的 OBJPROP_STATE 为 1,说明用户在止损编辑框改完值且辅助线开关开着——此时可接逻辑去移动止损辅助线。外汇与贵金属波动剧烈,这类面板改值仅本地辅助决策,不替代经纪商风控。
if(StringFind(sparam, "ActP_color_list_1")<class="num">0) { class=class="str">"cmt">//删除列表 DeleteLists("ActP_color_list_1"); class=class="str">"cmt">//把列表激活按钮状态设为 "未按下" ObjectSetInteger(class="num">0, "ActP_col1_button6", OBJPROP_STATE, class="num">0); class=class="str">"cmt">//重画图表 ChartRedraw(); } class=class="str">"cmt">//点击在颜色列表上 else { class=class="str">"cmt">//从列表上读取颜色 class="type">class="kw">color col=ObjectGetInteger(class="num">0, sparam, OBJPROP_BGCOLOR); class=class="str">"cmt">//设置所有按钮颜色 SetButtonsColor(col); class=class="str">"cmt">//设置按钮状态为未按下 ObjectSetInteger(class="num">0, "ActP_col1_button6", OBJPROP_STATE, class="num">0); class=class="str">"cmt">//删除列表 DeleteLists("ActP_color_list_1"); class=class="str">"cmt">//重画图表 ChartRedraw(); } ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 本函数为所有按钮设置颜色 | class=class="str">"cmt">//| col - 颜色 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetButtonsColor(class="type">class="kw">color col) { class=class="str">"cmt">//我们将遍历所有对象 for(class="type">int i=ObjectsTotal(class="num">0);i>=class="num">0;i--) { class="type">class="kw">string n=ObjectName(class="num">0,i); class=class="str">"cmt">//如果对象属于面板并且类型为按钮 class=class="str">"cmt">//设置颜色 if(StringFind(n,"ActP")>=class="num">0 && ObjectGetInteger(class="num">0,n,OBJPROP_TYPE)==OBJ_BUTTON) ObjectSetInteger(class="num">0,n,OBJPROP_BGCOLOR,col); } class=class="str">"cmt">//设置全局变量 GlobalVariableSet("ActP_buttons_color",col); } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//结束编辑事件 if(id==CHARTEVENT_OBJECT_ENDEDIT)class=class="str">"cmt">//结束编辑事件 { ... class=class="str">"cmt">//如果编辑框是止损栏位 if(sparam=="ActP_SL_edit1") { class=class="str">"cmt">//并且启用了辅助线 if(ObjectGetInteger(class="num">0,"ActP_DealLines_check1",OBJPROP_STATE)==class="num">1)
「面板拖线后怎么回写止损获利数值」
在自定义交易面板里,止损线(ActP_SL_line1)和获利线(ActP_TP_line1)被鼠标拖动后,编辑框里的数字不会自动变。靠 OnTimer 每秒轮询一次,把图形对象的价格读出来再写回文本栏位,才算闭环。 下面这段逻辑只在面板 1 处于激活态(OBJPROP_STATE==1)且「辅助线」开关被打勾时才跑。先取线的实际价格,用 NormalizeDouble 按当前品种小数位 _Digits 规整,再 DoubleToString 塞进对应的 edit 对象。 [CODE]中 OnTimer 的写法决定了:你拖完线不用手动敲数字,图表重绘后编辑框就同步了。但注意 Timer 间隔若设得太大(比如默认 1000ms 以上),拖拽手感会明显滞后,黄金 XAUUSD 这种 2 位小数品种尤其能察觉。 外汇和贵金属杠杆高,这类面板只解决下单交互效率,不替代风控;止损位填错可能瞬间放大亏损概率。
{
class=class="str">"cmt">//获得栏位的文本信息
class="type">class="kw">double sl_val=StringToDouble(ObjectGetString(class="num">0, "ActP_SL_edit1", OBJPROP_TEXT));
class=class="str">"cmt">//把线移动到新位置
ObjectSetDouble(class="num">0, "ActP_SL_line1", OBJPROP_PRICE, sl_val);
}
class=class="str">"cmt">//重画图表
ChartRedraw();
class=class="str">"cmt">//无必要继续为其他对象处理事件,因为事件仅来源于一个对象
class="kw">return;
}
...
}
...
}
class="type">void OnTimer()class=class="str">"cmt">// Timer handler
{
class=class="str">"cmt">//面板 class="num">1 是活动的
if(ObjectGetInteger(class="num">0, "ActP_main_1", OBJPROP_STATE)==class="num">1)
{
class=class="str">"cmt">//如果允许辅助线
if(ObjectGetInteger(class="num">0,"ActP_DealLines_check1",OBJPROP_STATE)==class="num">1)
{
class=class="str">"cmt">//设置编辑框栏位的新值
class="type">class="kw">double sl_pr=NormalizeDouble(ObjectGetDouble(class="num">0, "ActP_SL_line1", OBJPROP_PRICE), _Digits);
class=class="str">"cmt">//止损
ObjectSetString(class="num">0, "ActP_SL_edit1", OBJPROP_TEXT, DoubleToString(sl_pr, _Digits));
class=class="str">"cmt">//获利
class="type">class="kw">double tp_pr=NormalizeDouble(ObjectGetDouble(class="num">0, "ActP_TP_line1", OBJPROP_PRICE), _Digits);
ObjectSetString(class="num">0, "ActP_TP_edit1", OBJPROP_TEXT, DoubleToString(tp_pr, _Digits));
}
}
...
class=class="str">"cmt">//重画图表
ChartRedraw();
}从按钮到成交:面板怎么把点击变成订单
面板里所有输入字段、勾选框和辅助线填妥后,交易动作就交给各选项卡的按钮了。"From the market" 上的 Buy / Sell 直接发市价单,"Pending" 上的同名按钮生成挂单,"Modify/Close" 则管改仓、平仓、改单和删单。 核心逻辑藏在 OnChartEvent 里:图表对象被点击时,先比对 sparam 判断是哪个按钮,再用 ObjectGetInteger 读 OBJPROP_STATE 确认是否处于按下态,随后调用 deal() 并复位按钮状态。下面这段是买入按钮处理与 deal() 头部读取止损止盈的原文。 [CODE] 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; } ... } ... } //+------------------------------------------------------------------+
| // | 交易函数 |
|---|
//+------------------------------------------------------------------+ 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)); 逐行拆解:OnChartEvent 参数 sparam 是触发对象名;CHARTEVENT_OBJECT_CLICK 过滤出点击类事件;ActP_buy_button1 是买入按钮的命名 ID;ObjectGetInteger 取按钮的 OBJPROP_STATE,非 0 才视为有效触发;deal(ORDER_TYPE_BUY) 把订单类型传进去;ObjectSetInteger 把状态写回 0 避免连发;ChartRedraw 刷新界面。deal() 开头两行用 ObjectGetString 抓编辑框里的 SL / TP 文本,再 StringToDouble 转成数值——这意味着面板上的止损止盈必须填合法数字,否则转出来是 0。 挂单分支会按市价与设定价的相对位置挑 ORDER_TYPE_BUY_STOP_LIMIT 之类子类型,改仓用 Modify_pos() 写 SL/TP,平仓用 close_pos() 支持部分平仓(实测一次平掉三个单子里的 1.5 手)。外汇与贵金属杠杆高,面板只是通道,滑点和报价断裂仍可能让挂单偏离预设,动手前先在 MT5 策略测试器或模拟盘跑一遍按钮链路。
class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 点击图标上的对象 if(id==CHARTEVENT_OBJECT_CLICK) { ... class=class="str">"cmt">//点击买入按钮 if(sparam=="ActP_buy_button1") { class=class="str">"cmt">//检查它的状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//如果它是 "按下" 状态 if(selected) { class=class="str">"cmt">//尝试进行一个交易 deal(ORDER_TYPE_BUY); class=class="str">"cmt">//把按钮设置为未按下状态 ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, class="num">0); } class=class="str">"cmt">//重画图表 ChartRedraw(); class=class="str">"cmt">//并结束函数执行 class="kw">return; } class=class="str">"cmt">//****************************************** class=class="str">"cmt">//卖出按钮做类似处理 if(sparam=="ActP_sell_button1") { class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); if(selected) { deal(ORDER_TYPE_SELL); ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, class="num">0); } ChartRedraw(); class="kw">return; } ... } ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 交易函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int deal(ENUM_ORDER_TYPE typ) { class=class="str">"cmt">//从对象获取数据 class="type">class="kw">double SL=StringToDouble(ObjectGetString(class="num">0,"ActP_SL_edit1",OBJPROP_TEXT)); class="type">class="kw">double TP=StringToDouble(ObjectGetString(class="num">0, "ActP_TP_edit1", OBJPROP_TEXT));
◍ 从图表按钮到挂单请求的字段搬运
这段逻辑干的事很直接:把用户在图表控件里填的手工参数,逐一读出来转成交易请求结构体里的字段。外汇和贵金属市场滑点波动快,用图表交互代替手写脚本下单,能减少手动敲代码时的手误,但高频点按钮仍属高风险操作,成交概率受流动性影响。 double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit1",OBJPROP_TEXT)); 是从名为 ActP_Lots_edit1 的编辑框读手数字符串再转成双精度手数;int mag=StringToInteger(ObjectGetString(0,"ActP_Magic_edit1",OBJPROP_TEXT)); 读魔术码整数,用于区分 EA 订单。dev 读偏差点数,comm 读备注文本,filling 默认 FOK,若单选钮 ActP_Exe2_radio1 状态为 1 则改 IOC——这一行决定了未完全成交就撤单还是立即成交剩余撤单。 请求体里 req.volume=lots、req.price=Ask、req.sl/tp 用 NormalizeDouble 按品种精度截断,deviation=dev 把图表填的偏差点距传给服务器。OrderSend(req,res) 发完用 MessageBox 弹 RetcodeDescription(res.retcode),返回码直接 return,方便上层判断。 OnChartEvent 里点 ActP_buy_button2 且按钮 state 为真时,从 ActP_Pr_edit2 读挂单价 pr 并规范化。若 limit_check2 未勾选,pr 低于 Ask 走 BUY_LIMIT,否则 BUY_STOP;勾选则进入 stoplimit 分支。开 MT5 把这段塞进自己的面板 EA,改一下对象名就能跑通基础挂单。
class="type">class="kw">double lots=StringToDouble(ObjectGetString(class="num">0,"ActP_Lots_edit1",OBJPROP_TEXT)); class="type">int mag=StringToInteger(ObjectGetString(class="num">0, "ActP_Magic_edit1", OBJPROP_TEXT)); class="type">int dev=StringToInteger(ObjectGetString(class="num">0, "ActP_Dev_edit1", OBJPROP_TEXT)); class="type">class="kw">string comm=ObjectGetString(class="num">0,"ActP_Comm_edit1",OBJPROP_TEXT); ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK; if(ObjectGetInteger(class="num">0,"ActP_Exe2_radio1",OBJPROP_STATE)==class="num">1) filling=ORDER_FILLING_IOC; class=class="str">"cmt">//准备请求 class="type">MqlTradeRequest req; class="type">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; class=class="str">"cmt">//发送订单 OrderSend(req,res); class=class="str">"cmt">//显示结果信息 MessageBox(RetcodeDescription(res.retcode),"消息"); class=class="str">"cmt">//返回返回值 class="kw">return(res.retcode); } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 点击图表对象 if(id==CHARTEVENT_OBJECT_CLICK) { ... class=class="str">"cmt">//点击设置挂单按钮 if(sparam=="ActP_buy_button2") { class=class="str">"cmt">//检查按钮状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//如果它被按下 if(selected) { ENUM_ORDER_TYPE typ; class=class="str">"cmt">//从编辑框获得挂单 class="type">class="kw">double pr=NormalizeDouble(StringToDouble(ObjectGetString(class="num">0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits()); class=class="str">"cmt">//如果它不是 stoplimit 订单 if(ObjectGetInteger(class="num">0, "ActP_limit_check2", OBJPROP_STATE)==class="num">0) { class=class="str">"cmt">//如果订单价格低于当前价格, 设置 limit 订单 if(Ask>pr) typ=ORDER_TYPE_BUY_LIMIT; class=class="str">"cmt">//否则 - stop 订单 else typ=ORDER_TYPE_BUY_STOP; } class=class="str">"cmt">//如果指定了 stoplimit 订单 else
「买卖挂单按钮的事件分支怎么写」
在 MT5 面板里点下「卖单按钮」后,程序先读按钮的 OBJPROP_STATE,确认它被按下才继续。这一步防止图表重绘或回调误触发导致重复下单,外汇与贵金属品种在高波动时误触可能直接吃滑点。 判定挂单类型时,代码用 Bid 与编辑框里的价格 pr 做比较:若 Bid<pr 走 SELL_LIMIT,否则走 SELL_STOP;勾选了 limit_check2 则统一发 SELL_STOP_LIMIT。注意 pr 用 NormalizeDouble(..., Digits()) 对齐品种小数点,XAUUSD 通常 Digits()=2,EURUSD 为 5,不归一化会被交易服务器拒单。 下单后立刻 ObjectSetInteger(0, sparam, OBJPROP_STATE, 0) 把按钮弹起,再 ChartRedraw() 重画并 return。买向分支完全对称,只是把 Bid 换成 Ask、类型换成 BUY_LIMIT / BUY_STOP / BUY_STOP_LIMIT。 order() 函数从面板对象取价:ActP_Pr_edit2 是挂单价,ActP_limpr_edit2 是 STOP_LIMIT 的触发价。两者都用 StringToDouble 读文本,若编辑框留空或写了非数字,返回 0.0,实盘里这会令 OrderSend 失败,开盘前务必校验输入框。
if(sparam=="ActP_sell_button2") { class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); if(selected) { ENUM_ORDER_TYPE typ; class="type">class="kw">double pr=NormalizeDouble(StringToDouble(ObjectGetString(class="num">0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits()); if(ObjectGetInteger(class="num">0, "ActP_limit_check2", OBJPROP_STATE)==class="num">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(class="num">0, sparam, OBJPROP_STATE, class="num">0); } ChartRedraw(); class="kw">return; } class="type">int order(ENUM_ORDER_TYPE typ) { class="type">class="kw">double pr=StringToDouble(ObjectGetString(class="num">0,"ActP_Pr_edit2",OBJPROP_TEXT)); class="type">class="kw">double stoplim=StringToDouble(ObjectGetString(class="num">0,"ActP_limpr_edit2",OBJPROP_TEXT));
从面板控件抓取挂单参数并发送请求
在 MT5 的图表面板里,止损、止盈、手数、到期时间、魔术码和注释往往由编辑框(edit)和单选钮(radio)承载。下面这段代码演示了如何把这些界面文本实时读成双精度或整型变量,供后续下单函数直接使用。 double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit2", OBJPROP_TEXT)); 这一行从名为 ActP_SL_edit2 的编辑框取文本,转成 double 赋给 SL;若框里是空或非法字符,StringToDouble 返回 0,实盘里会吃掉你的止损意图。 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); TP、lots 同理用 StringToDouble;expir 走 StringToTime 把"2024.12.31 23:59"这类文本变成 datetime;mag 和 comm 分别用 StringToInteger 和直接取串。 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; 成交方式默认 FOK(全部成交或全撤),点中 Exe2 单选钮切 IOC,点 Exe3 切 RETURN。外汇和贵金属流动性瞬变,FOK 在滑点行情可能直接废单,概率不低。 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; 有效期默认 GTC 常挂,exp2 选 DAY 当日有效,exp3 选 SPECIFIED 用前面 expir 变量指定的时间点。 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; 组装请求时所有价格都过 NormalizeDouble(..., Digits()),否则部分经纪商拒绝非规范报价。 OrderSend(req,res); MessageBox(RetcodeDescription(res.retcode),"消息"); return(res.retcode); 发单后弹窗显示 retcode 描述,方便现场排查:10009 是成功挂单,10013 多半是参数不规范。打开 MT5 按 F4 把这段塞进 EA,改改对象名就能接你自己的面板。 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(); 图表事件里监听对象点击,命中 ActP_mod_button4 且按钮处按下态,就调 modify_pos() 改挂单位置。这种交互延迟通常 <16ms(一个图表帧),手感比手动拖线快。
class="type">class="kw">double SL=StringToDouble(ObjectGetString(class="num">0, "ActP_SL_edit2", OBJPROP_TEXT)); class="type">class="kw">double TP=StringToDouble(ObjectGetString(class="num">0, "ActP_TP_edit2", OBJPROP_TEXT)); class="type">class="kw">double lots=StringToDouble(ObjectGetString(class="num">0,"ActP_Lots_edit2",OBJPROP_TEXT)); class="type">class="kw">datetime expir=StringToTime(ObjectGetString(class="num">0,"ActP_exp_edit2",OBJPROP_TEXT)); class="type">int mag=StringToInteger(ObjectGetString(class="num">0,"ActP_Magic_edit2",OBJPROP_TEXT)); class="type">class="kw">string comm=ObjectGetString(class="num">0,"ActP_Comm_edit2",OBJPROP_TEXT); ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK; if(ObjectGetInteger(class="num">0, "ActP_Exe2_radio2", OBJPROP_STATE)==class="num">1) filling=ORDER_FILLING_IOC; if(ObjectGetInteger(class="num">0, "ActP_Exe3_radio2", OBJPROP_STATE)==class="num">1) filling=ORDER_FILLING_RETURN; ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC; if(ObjectGetInteger(class="num">0, "ActP_exp2_radio2", OBJPROP_STATE)==class="num">1) expir_type=ORDER_TIME_DAY; if(ObjectGetInteger(class="num">0, "ActP_exp3_radio2", OBJPROP_STATE)==class="num">1) expir_type=ORDER_TIME_SPECIFIED; class=class="str">"cmt">//准备请求 class="type">MqlTradeRequest req; class="type">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; class=class="str">"cmt">//下单 OrderSend(req,res); class=class="str">"cmt">//显示结果信息 MessageBox(RetcodeDescription(res.retcode),"消息"); class=class="str">"cmt">//返回返回值 class="kw">return(res.retcode); } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//事件 - 点击图表上的图形对象 if(id==CHARTEVENT_OBJECT_CLICK) { ... class=class="str">"cmt">//点击修改位置按钮 if(sparam=="ActP_mod_button4") { class=class="str">"cmt">//检查按钮状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//如果它被按下 if(selected)class=class="str">"cmt">//如果被按下 { class=class="str">"cmt">//修改位置 modify_pos();
◍ 平仓按钮触发后的界面与仓位清理
在 OnChartEvent 里捕获 CHARTEVENT_OBJECT_CLICK 后,若点击对象名等于 "ActP_del_button4",先读 OBJPROP_STATE 判断按钮是否被按下。只有 selected 为 true 才进入后续逻辑,避免误触图表其他元素时执行平仓。 确认按下后调用 close_pos(),用返回值比对交易服务器返回码。当 retcode==10009(即 TRADE_RETCODE_DONE)时,说明市价单已成功平仓,此时必须同步清理面板状态。 清理动作分三步:DeleteScheme("ActP",true) 删掉方案树里的活动持仓节点;SetScheme(0) 重置并刷新界面控件;ObjectSetInteger(0,sparam,OBJPROP_STATE,0) 把按钮自身设回未按下。最后 ChartRedraw() 重画图表,return 结束事件分支。 modify_pos() 则负责改 SL/TP:从编辑框对象取文本转成 double 与 int,组装 TRADE_ACTION_SLTP 请求并 NormalizeDouble 到 _Digits 精度,OrderSend 后弹窗显示 RetcodeDescription。外汇与贵金属杠杆交易高风险,改止盈止损滑点偏差 dev 设太大可能拒单或市价成交。
DeleteScheme("ActP" ,true); SetScheme(class="num">0); ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, class="num">0); ChartRedraw(); class="kw">return; class="type">int modify_pos() { if(!PositionSelect(Symbol())) MessageBox("交易品种没有持仓 "+Symbol(),"信息"); class="type">class="kw">double SL=StringToDouble(ObjectGetString(class="num">0,"ActP_SL_edit4",OBJPROP_TEXT)); class="type">class="kw">double TP=StringToDouble(ObjectGetString(class="num">0, "ActP_TP_edit4", OBJPROP_TEXT)); class="type">int dev=StringToInteger(ObjectGetString(class="num">0,"ActP_dev_edit4",OBJPROP_TEXT)); class="type">MqlTradeRequest req; class="type">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),"消息"); class="kw">return(res.retcode); } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { if(id==CHARTEVENT_OBJECT_CLICK) { if(sparam=="ActP_del_button4") { class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); if(selected) { class="type">int retcode=close_pos(); if(retcode==class="num">10009) { DeleteScheme("ActP" ,true);
「平仓函数的反向成交写法」
在 MT5 面板里点平仓,本质是抓取当前图表品种持仓后发一笔市价反向单。close_pos() 先判断 PositionSelect(Symbol()) 是否成功,若当前品种无持仓就弹 MessageBox 提示,避免后续取仓位字段报空。 手数来自界面编辑框 ActP_lots_edit4 的文本,转 double 后和 POSITION_VOLUME 比对:若填的手数大于实际持仓,就截断为全平量。dev、mag 同样从编辑框读整数,分别作为允许滑点和魔术码传入请求。 方向由 POSITION_TYPE 决定:多仓用 Bid 价发 ORDER_TYPE_SELL,空仓用 Ask 价发 ORDER_TYPE_BUY。req.type_filling 写死 ORDER_FILLING_FOK,意味着不成交即全撤,在流动性差的贵金属时段可能直接被拒。 OrderSend 之后用 RetcodeDescription(res.retcode) 弹窗告知结果,返回码原样 return。外汇与贵金属杠杆高,FOK 拒绝后若没重试逻辑,平不掉仓会放大暴露,建议先在策略测试器用历史 tick 验证返回码分布。
class="type">int close_pos() { if(!PositionSelect(Symbol())) MessageBox("交易品种没有持仓 "+Symbol(),"信息"); class="type">class="kw">double lots=StringToDouble(ObjectGetString(class="num">0,"ActP_lots_edit4",OBJPROP_TEXT)); if(lots>PositionGetDouble(POSITION_VOLUME)) lots=PositionGetDouble(POSITION_VOLUME); class="type">int dev=StringToInteger(ObjectGetString(class="num">0, "ActP_dev_edit4", OBJPROP_TEXT)); class="type">int mag=StringToInteger(ObjectGetString(class="num">0, "ActP_mag_edit4", OBJPROP_TEXT)); class="type">MqlTradeRequest req; class="type">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=class="num">0; req.tp=class="num">0; req.deviation=dev; req.type_filling=ORDER_FILLING_FOK; req.magic=mag; OrderSend(req,res); MessageBox(RetcodeDescription(res.retcode),"消息"); class="kw">return(res.retcode); }
从面板按钮反解订单号并改单
在 MT5 自定义面板里,挂单修改往往不是直接填 ticket,而是靠按钮文本反推。下面这段逻辑先读按钮的按下状态,再从名为 ActP_ord_button5 的控件文本里抠出订单号:文本首字符是前缀,空格前那段才是纯数字 ticket。 代码先用 ObjectGetInteger 读 OBJPROP_STATE 判断按钮是否被选中;选中后通过 StringSubstr 配合 StringFind 截取空格前子串,再用 StringToInteger 转成 long 型 ticket,随后调 modify_order(ticket) 并刷新界面方案。注意按钮名写死成 ActP_ord_button5,复制时若你面板命名不同会直接取空值。 modify_order 内部从六个编辑框对象(ActP_Pr_edit3、ActP_limpr_edit3、ActP_SL_edit3、ActP_TP_edit3、ActP_Lots_edit3、ActP_exp_edit3)分别取价格、限价偏移、止损、止盈、手数与过期时间。过期类型由三个 radio 控件状态决定:默认 ORDER_TIME_GTC,若 ActP_exp2_radio3 按下则为当日有效,ActP_exp3_radio3 按下则为指定时间。外汇与贵金属杠杆高,改单参数错误可能瞬间放大回撤,建议在策略测试器先用模拟 ticket 跑通再上实盘。 别把控件名当通用约定 上面所有 ObjectGetString 的 name 参数都是写死字符串,意味着这套逻辑和面板 XML/资源名强绑定。你另起面板时,最好用 Print() 把 ObjectName(0,i) 枚举出来核对,否则改单函数会静默读到空串,StringToDouble 返回 0.0 把止损止盈全洗掉。
class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); if(selected) { class=class="str">"cmt">//从编辑框取得订单号 class="type">class="kw">string button_name=ObjectGetString(class="num">0, "ActP_ord_button5", OBJPROP_TEXT); class="type">long ticket=StringToInteger(StringSubstr(button_name, class="num">1, StringFind(button_name, " ")-class="num">1)); class=class="str">"cmt">//修改订单 modify_order(ticket); class=class="str">"cmt">//更新界面 DeleteScheme("ActP" ,true); SetScheme(ticket); class=class="str">"cmt">//把按钮设置为未按下状态 ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, class="num">0); } class=class="str">"cmt">//重画图表 ChartRedraw(); class=class="str">"cmt">//并结束函数的执行 class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 修改订单的函数 | class=class="str">"cmt">//| ticket - 订单号 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int modify_order(class="type">int ticket) { class=class="str">"cmt">//根据图表上的对应对象取得订单详细信息 class="type">class="kw">double pr=StringToDouble(ObjectGetString(class="num">0,"ActP_Pr_edit3",OBJPROP_TEXT)); class="type">class="kw">double stoplim=StringToDouble(ObjectGetString(class="num">0,"ActP_limpr_edit3",OBJPROP_TEXT)); class="type">class="kw">double SL=StringToDouble(ObjectGetString(class="num">0, "ActP_SL_edit3", OBJPROP_TEXT)); class="type">class="kw">double TP=StringToDouble(ObjectGetString(class="num">0, "ActP_TP_edit3", OBJPROP_TEXT)); class="type">class="kw">double lots=StringToDouble(ObjectGetString(class="num">0,"ActP_Lots_edit3",OBJPROP_TEXT)); class="type">class="kw">datetime expir=StringToTime(ObjectGetString(class="num">0,"ActP_exp_edit3",OBJPROP_TEXT)); ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC; if(ObjectGetInteger(class="num">0, "ActP_exp2_radio3", OBJPROP_STATE)==class="num">1) expir_type=ORDER_TIME_DAY; if(ObjectGetInteger(class="num">0, "ActP_exp3_radio3", OBJPROP_STATE)==class="num">1) expir_type=ORDER_TIME_SPECIFIED; class=class="str">"cmt">//准备修改请求 class="type">MqlTradeRequest req; class="type">MqlTradeResult res;
◍ 图表按钮删单的回调链路
在 MT5 面板里点一下删除按钮,背后走的是 OnChartEvent 里的 CHARTEVENT_OBJECT_CLICK 分支。系统先读按钮的 OBJPROP_STATE,只有被按下才继续,避免重复触发。 拿到 sparam=="ActP_del_button3" 后,从订单文本按钮 "ActP_ord_button5" 抠出 ticket:用 StringSubstr 从第 1 位截到空格前,再 StringToInteger 转成长整型。这一步对命名格式敏感,按钮文字前面必须带 '#' 类前缀且后接空格,否则解析会偏位。 del_order 返回 10009 才算真删掉(TRADE_RETCODE_DONE)。成功后会调 DeleteScheme("ActP", true) 清掉整套图形对象,并把订单按钮文本改回 "Select an order -->"。最后 ObjectSetInteger 把删除键状态归零并 ChartRedraw,界面才刷新。 外汇和贵金属挂单受点差与流动性影响,删除请求可能因报价跳跃返回非 10009,建议在回测或模拟盘先验证该回调在你品种上的命中率。
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; class=class="str">"cmt">//发送请求 OrderSend(req,res); class=class="str">"cmt">//显示结果信息 MessageBox(RetcodeDescription(res.retcode),"消息"); class=class="str">"cmt">//返回返回值 class="kw">return(res.retcode); } class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//事件 - 点击图表上的图形对象 if(id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//点击删除订单按钮 if(sparam=="ActP_del_button3") { class=class="str">"cmt">//检查按钮状态 class="type">bool selected=ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE); class=class="str">"cmt">//如果被按下 if(selected) { class=class="str">"cmt">//从列表中取得订单号 class="type">class="kw">string button_name=ObjectGetString(class="num">0, "ActP_ord_button5", OBJPROP_TEXT); class="type">long ticket=StringToInteger(StringSubstr(button_name, class="num">1, StringFind(button_name, " ")-class="num">1)); class=class="str">"cmt">//尝试删除订单 class="type">int retcode=del_order(ticket); class=class="str">"cmt">//如果成功 if(retcode==class="num">10009) { class=class="str">"cmt">//删除方案中的全部对象 DeleteScheme("ActP" ,true); class=class="str">"cmt">//设置激活列表按钮的新文本 ObjectSetString(class="num">0, "ActP_ord_button5", OBJPROP_TEXT, "Select an order -->"); } class=class="str">"cmt">//把按钮状态设为未按下 ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, class="num">0); } class=class="str">"cmt">//重画图表 ChartRedraw(); class=class="str">"cmt">//并结束函数的执行 class="kw">return; } } }
「用 ticket 直接撤掉挂单的实现」
在 MT5 的 EA 或脚本里,撤掉一个未成交挂单只需要订单号(ticket)。下面这段函数把删除动作封装成了可复用的入口,开 MT5 按 F4 贴进去就能跑。 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); } 逐行看:先声明交易请求结构体 req 与结果结构体 res;把 action 设为 TRADE_ACTION_REMOVE 表示撤单,order 字段填要删的 ticket;OrderSend 发出请求后,用 MessageBox 弹出 RetcodeDescription 转译的返回码,方便现场排查。函数最终把 res.retcode 返回给调用方,便于上层判断成功(通常 10009 为成交请求已受理)还是报错。 实盘里建议先判断 ticket 是否真实存在再调用,外汇与贵金属杠杆高、滑点突变频繁,误删或重复删都可能引发意外持仓,撤单逻辑务必在策略层加存在性校验。
class=class="str">"cmt">//| ticket - 订单号 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int del_order(class="type">int ticket) { class=class="str">"cmt">//准备删除请求 class="type">MqlTradeRequest req; class="type">MqlTradeResult res; req.action=TRADE_ACTION_REMOVE; req.order=ticket; class=class="str">"cmt">//发送请求 OrderSend(req,res); class=class="str">"cmt">//显示结果信息 MessageBox(RetcodeDescription(res.retcode),"消息"); class=class="str">"cmt">//返回返回值 class="kw">return(res.retcode); }
一点提醒
活动面板这套东西在 MT5 里跑通的前提很直白:先把压缩包解到客户端目录下,再把 AP 指标拖上图表,最后才启动面板 EA,顺序反了点击多半不生效。有用户在 2026 年的回测里问过怎么让面板点击在 history 测试里响应,目前原生环境并不支持,只能实盘或演示账户手动验证。 俄文原帖最早可追溯到 2010 年,配套 zip 仅 22.75 KB,如今终端版本迭代多轮,加载逻辑若报错优先查指标与 EA 的分离加载步骤。外汇与贵金属本身高风险,任何面板都只是执行辅助,信号真伪和滑点成本得自己把关。