快捷手动交易工具箱:持仓和挂单操控·综合运用
(3/3)·从仅能市价开单到持仓管理挂单修改,把手动交易的三个阶段一次性补齐
- 从持仓里抠出平仓触发条件
- 用总盈利阈值触发全平的逻辑落点
- 一键砍掉所有浮亏持仓
- 用持仓循环剥离掉期与浮亏点数
- 用浮亏点数和库存费触发平仓
- 平仓请求里的价格方向与重试逻辑
- 多单浮盈的两种累计口径
- 遍历持仓并按魔法码筛选平仓条件
- 盈利超限后的平仓请求构造
- 按键平掉多单的底层循环
- 遍历持仓时按魔术码与品种过滤
- 按浮亏阈值触发平仓的五种模式
- 平仓按钮背后的重试与盈利统计
- 遍历持仓时按魔术码与品种过滤空单
- 空单盈利平仓的五种触发逻辑
- 反手平空单的请求构造与重试
- 按键触发下的空单浮盈统计
- 遍历持仓并筛出空单的平仓触发逻辑
- 亏损阈值触发的平仓请求构造
- 订单回执判定与面板按钮排布
- 手搓 MT5 面板里的手数、止盈与止损输入框
- 手搓可改仓位的输入框与按钮
- 持仓面板的按钮与表格布局怎么落
- 持仓面板的表格与窗口联动
- 持仓面板只认自己开的单
- 把持仓属性抓出来填进表格
- EA 初始化与持仓表的挂接逻辑
- 把持仓盈亏和点击改单接进面板
- 用 TRADE_ACTION_SLTP 改挂止盈的实战片段
- 平仓请求的反向价格与重试逻辑
- 挂单表格的界面参数怎么定
- 挂单表格的初始化与数值填充
- 挂单属性抓取与面板回填
- 挂单面板的点击联动与改单入口
- 挂单改价与止损的移动实战
- 挂单删除的票据定位与重试逻辑
- 图表点击后把坐标转成价格填进输入框
- 把附件丢进终端根目录就完事
- 收束
「从持仓里抠出平仓触发条件」
想让 EA 按规则自动平掉当前图表品种的持仓,第一步是把每张持仓的关键字段读出来。下面这段 MT5 代码演示了如何取出品种名、小数位、魔术码、成交量、持仓方向与浮动盈亏,并算出以点为单位的浮盈点数 profit_pp。 对于 BUY 单,profit_pp = (当前 BID - 开仓价) / _Point;对于 SELL 单则是 (开仓价 - 当前 ASK) / _Point。注意 _Point 是品种最小变动价,XAUUSD 通常 _Point=0.01,而 EURUSD 多数经纪商 _Point=0.00001,算点数时别漏了小数位 digits。 真正决定是否平仓,靠 m_current_mode[0] 这个模式开关:0 为平掉所有净盈利单(profit_cur+swap>0);1 为点数超过 N 点;2 为金额超过 N 账户币种单位;3 为总盈利点数超阈值。模式 1 下若 m_mode_edit[0].GetValue() 设成 50,就是浮盈超 50 点才平,外汇与贵金属波动大,这类阈值需结合品种 ATR 调,杠杆交易高风险。 别把魔术码过滤当摆设 代码里先判 magic==m_magic_number 再判 position_symbol==Symbol(),只处理自己 EA 开的、且和当前图表同品种的单。若你手动开仓混在里面,不会被这套逻辑误平,但回测时若多个 EA 共用魔术码 0,就会互相干扰。
class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class="type">int profit_pp; class=class="str">"cmt">//--- if(type==POSITION_TYPE_BUY) profit_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else profit_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); class=class="str">"cmt">//--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { if( (m_current_mode[class="num">0]==class="num">0 && profit_cur+swap>class="num">0) || class=class="str">"cmt">// Close all profitable positions (m_current_mode[class="num">0]==class="num">1 && profit_pp>class="type">int(m_mode_edit[class="num">0].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N points (m_current_mode[class="num">0]==class="num">2 && profit_cur+swap>class="type">class="kw">double(m_mode_edit[class="num">0].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N deposit currency units (m_current_mode[class="num">0]==class="num">3 && sum_pp>class="type">int(m_mode_edit[class="num">0].GetValue())) || class=class="str">"cmt">// Close all positions with the total profit more than N points
用总盈利阈值触发全平的逻辑落点
当面板模式位 m_current_mode[0] 等于 4,且累计浮盈 sum_cur 超过 m_mode_edit[0].GetValue() 设定的存款货币单位数时,EA 会进入平仓分支。这个条件相当于把“总盈利达到 N 个账户货币单位”作为硬触发线,适合用来锁住一段趋势行情里的复合仓位收益。 进入分支后先 ZeroMemory 清掉 request 与 result,避免上一次交易残留字段污染本次发单。随后填 TRADE_ACTION_DEAL 表明是即时成交,position 填原仓 ticket,symbol 与 volume 沿用持仓变量,deviation 写死为 5 点容差,magic 对齐 EA 自身魔数。 买卖方向按原仓类型反转:BUY 仓用 SYMBOL_BID 发 ORDER_TYPE_SELL,SELL 仓用 SYMBOL_ASK 发 ORDER_TYPE_BUY。发单不是一次侥幸,而是 for 循环跑 5 次 OrderSend,任一次返回 true 即视为送出,这种重试结构在滑点较大的黄金时段可能提高成交概率。 外汇与贵金属杠杆高,这类“盈利达标全平”规则若阈值设得太小,在震荡市会被频繁触发,实际回测中 N 取 50 与取 200 点差对平仓次数影响显著,建议直接开 MT5 用策略测试器跑不同 GetValue 对照。
if(m_current_mode[class="num">0]==class="num">4 && sum_cur>class="type">class="kw">double(m_mode_edit[class="num">0].GetValue())) { ZeroMemory(request); ZeroMemory(result); request.action =TRADE_ACTION_DEAL; request.position =position_ticket; request.symbol =position_symbol; request.volume =volume; request.deviation=class="num">5; request.magic =m_magic_number; if(type==POSITION_TYPE_BUY) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; } else { request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; } class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result);
◍ 一键砍掉所有浮亏持仓
在面板里点第 6 个按钮,或在图表上按 D 键,会触发 CloseAllMarketLoss 把当前账户里所有浮亏仓位平掉。触发条件写死在事件判断里:自定义按钮点击或键盘 KEY_D 按下才进逻辑,避免误触。 函数先清两个结构体再取 PositionsTotal 得到未平仓位总数。重点在 m_current_mode[3]>2 这个开关——只有面板模式参数大于 2 才允许跑亏损扫描,否则整段跳过,相当于给你留了手动熔断位。 循环从 total-1 倒序取到 0,依次抓 ticket、symbol、magic、type 和 POSITION_PROFIT。loss_cur 就是单仓盈亏浮值,负值即浮亏,后面拿它累加做总砍仓判断。外汇和贵金属杠杆高,批量平仓可能瞬间放大滑点,实盘前务必在策略测试器用历史数据跑一遍。
class="type">void CFastTrading::CloseAllMarketLoss(class="type">int id,class="type">long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">6].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_D)) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int sum_pp=class="num">0; class="type">class="kw">double sum_cur=class="num">0.0; class=class="str">"cmt">//--- Calculate losses if(m_current_mode[class="num">3]>class="num">2) { for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double loss_cur=PositionGetDouble(POSITION_PROFIT);
「用持仓循环剥离掉期与浮亏点数」
这段逻辑先抓出当前持仓的掉期值,再按多空方向把开仓价与实时买卖价之差折算成点数。买仓用 BID 减开仓价,卖仓用开仓价减 ASK,除以 _Point 后强转 int,得到 loss_pp,这一步直接决定了后续统计的粒度是「点」还是「钱」。 当 magic 与图表品种都匹配时,代码进入 m_current_mode[3] 的分支:模式 3 把 loss_pp 累加进 sum_pp,模式 4 把 loss_cur 加 swap 累加进 sum_cur。也就是说,同一段遍历既能输出总浮亏点数,也能输出含库存费的金额,切换只靠一个数组下标。 下面的 for 循环从 total-1 倒序扫所有持仓,依次取 ticket、symbol、digits、magic、volume。digits 用 SymbolInfoInteger 拿 SYMBOL_DIGITS,避免硬编码小数位;magic 用 POSITION_MAGIC 过滤,保证只统计本 EA 开的仓。外汇与贵金属杠杆高,swap 在周三三倍仓时可能吃掉数点利润,倒序遍历配合实时刷新才能在 MT5 里看清真实暴露。
class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class="type">int loss_pp; class=class="str">"cmt">//--- if(type==POSITION_TYPE_BUY) loss_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else loss_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) { class="kw">switch(m_current_mode[class="num">3]) { case class="num">3: sum_pp+=loss_pp; class="kw">break; case class="num">4: sum_cur+=loss_cur+swap; class="kw">break; } } class=class="str">"cmt">//--- iterate over open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume
用浮亏点数和库存费触发平仓
这段逻辑跑在 EA 遍历持仓的循环里,先抓出持仓类型、当前盈亏和 swap,再按多空分别算浮亏点数(loss_pp)。买仓用 BID 减开仓价,卖仓用开仓价减 ASK,都除以 _Point 转成整数点,方便和阈值比。 平仓触发由 m_current_mode[3] 这个模式开关控制,共 5 种:0 是只要浮亏加 swap 为负就平;1 是浮亏点数超过输入框值;2 是金额亏损超阈值;3 和 4 则看累计点数或累计金额(sum_pp / sum_cur)。注意模式 1 的阈值用 int 强转,你填 50 就是亏 50 点才动。 命中条件后先 ZeroMemory 清掉 request 和 result,再填 TRADE_ACTION_DEAL 做市价平仓。deviation 写死 5 点,在外汇和贵金属这种高波动品种上,滑点可能吃掉预期,实盘前建议在 MT5 策略测试器里改这个值跑一遍。
class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double loss_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class="type">int loss_pp; if(type==POSITION_TYPE_BUY) loss_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else loss_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); class=class="str">"cmt">//--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { if( (m_current_mode[class="num">3]==class="num">0 && loss_cur+swap<class="num">0) || (m_current_mode[class="num">3]==class="num">1 && loss_pp<-class="type">int(m_mode_edit[class="num">3].GetValue())) || (m_current_mode[class="num">3]==class="num">2 && loss_cur+swap<-class="type">class="kw">double(m_mode_edit[class="num">3].GetValue())) || (m_current_mode[class="num">3]==class="num">3 && sum_pp<-class="type">int(m_mode_edit[class="num">3].GetValue())) || (m_current_mode[class="num">3]==class="num">4 && sum_cur<-class="type">class="kw">double(m_mode_edit[class="num">3].GetValue())) ) { class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- set the operation parameters request.action=TRADE_ACTION_DEAL; class=class="str">"cmt">// trading operation type request.position=position_ticket; class=class="str">"cmt">// position ticket request.symbol=position_symbol; class=class="str">"cmt">// symbol request.volume=volume; class=class="str">"cmt">// position volume request.deviation=class="num">5; class=class="str">"cmt">// allowable price deviation
◍ 平仓请求里的价格方向与重试逻辑
在 MT5 的 EA 平仓函数里,MagicNumber 先写进 request.magic,用来把这笔平仓指令和原开仓单绑定,避免误平别的策略仓位。 根据持仓类型反手取价:如果是 POSITION_TYPE_BUY 多单,平仓要用 SYMBOL_BID 买价发 ORDER_TYPE_SELL 空单;反之空单用 SYMBOL_ASK 卖价发 ORDER_TYPE_BUY。这是市价平仓的基本反方向规则。 发单不是一次就完。代码里用 for 循环最多跑 5 次,每次调 OrderSend,若返回 res 为真且 result.retcode 等于 TRADE_RETCODE_DONE 就 break 退出;否则 PrintFormat 打出 GetLastError 的错误码。这种 5 次重试能扛掉部分瞬时报价拒绝,但外汇和贵金属点差跳变时仍可能连续失败,属于高风险操作。 CloseBuyMarketProfit 这种封装函数,靠图表按钮自定义事件 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 或快捷键 KEY_U 触发,进入后先取 PositionsTotal 和 sum_pp 做持仓统计,再走上面的 request 构造流程。
request.magic=m_magic_number; class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- Set order price and type depending on the position type if(type==POSITION_TYPE_BUY) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; } else { request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; } class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">break; else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } class="type">void CFastTrading::CloseBuyMarketProfit(class="type">int id,class="type">long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">4].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_U)) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int sum_pp=class="num">0;
「多单浮盈的两种累计口径」
当面板模式 m_current_mode[1] 大于 2 时,脚本会专门统计当前图表品种、且 Magic 号匹配的多头持仓。模式 3 走点数累加,模式 4 走金额累加(含 swap),两种口径在面板里对应不同的展示逻辑。 下面这段是核心遍历逻辑:先按持仓索引倒序取 ticket,再过滤 symbol 与 magic,只认 POSITION_TYPE_BUY。利润点数用 (BID - 开仓价)/_Point 取整,金额则是 POSITION_PROFIT 加 POSITION_SWAP。 double sum_cur=0.0; //--- Calculate profit for modes 4 and 5 if(m_current_mode[1]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { int profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); switch(m_current_mode[1]) { case 3: sum_pp+=profit_pp; break; case 4: sum_cur+=profit_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) 逐行拆解:sum_cur 初值 0 承接金额累计;m_current_mode[1]>2 才进统计分支。循环从 total-1 到 0 倒取持仓,PositionGetTicket 拿 ticket,PositionGetString 取品种,PositionGetInteger 取 magic 与类型。profit_cur 和 swap 分别取浮盈与库存费。三层 if 卡死 magic、symbol、买单类型。profit_pp 把当前 BID 与开仓价差转为点数;switch 里模式 3 累加点数、模式 4 累加金额加 swap。 开 MT5 把这段塞进 EA 的 OnTick 前面,把 m_magic_number 设成自己实盘用的幻数,切到模式 3/4 就能在日志里 Print(sum_pp) 或 Print(sum_cur) 看多单暴露。外汇与贵金属杠杆高,浮盈口径仅作持仓观察,不代表平仓收益。
class="type">class="kw">double sum_cur=class="num">0.0; class=class="str">"cmt">//--- Calculate profit for modes class="num">4 and class="num">5 if(m_current_mode[class="num">1]>class="num">2) { for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { class="type">int profit_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); class="kw">switch(m_current_mode[class="num">1]) { case class="num">3: sum_pp+=profit_pp; class="kw">break; case class="num">4: sum_cur+=profit_cur+swap; class="kw">break; } } } } class=class="str">"cmt">//--- iterate over open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
遍历持仓并按魔法码筛选平仓条件
这段逻辑跑在持仓遍历循环里,先通过 PositionGetTicket(i) 拿到第 i 个持仓的票据号,再用 PositionGetString(POSITION_SYMBOL) 取品种、SymbolInfoInteger 取小数位,后面 magic 和 volume 等字段一并读出,构成下单参数快照。 过滤只处理本 EA 自己的单:magic==m_magic_number 且 position_symbol==Symbol() 且为 POSITION_TYPE_BUY 时,才进入平仓判断。profit_pp 用 (SYMBOL_BID - POSITION_PRICE_OPEN)/_Point 折算成点数,注意这里用的是实时卖价对比多单开仓价。 平仓触发有四种模式由 m_current_mode[1] 切换:0 为浮盈加库存费大于 0 就平;1 为点数盈利超 m_mode_edit[1] 设定值;2 为金额盈利超设定存款币种单位;3 为累计点数超阈值。外汇与贵金属杠杆高,库存费可能吞噬薄利,实盘前建议在 MT5 策略测试器用 2023 年 XAUUSD 数据验证 mode 1 与 mode 2 的差异。
{
class=class="str">"cmt">//--- order parameters
class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket
class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol
class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places
class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber
class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume
class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type
class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT);
class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP);
class=class="str">"cmt">//---
if(magic==m_magic_number)
if(position_symbol==Symbol())
if(type==POSITION_TYPE_BUY)
{
class="type">int profit_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point);
if( (m_current_mode[class="num">1]==class="num">0 && profit_cur+swap>class="num">0) || class=class="str">"cmt">// Close all profitable positions
(m_current_mode[class="num">1]==class="num">1 && profit_pp>class="type">int(m_mode_edit[class="num">1].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N points
(m_current_mode[class="num">1]==class="num">2 && profit_cur+swap>class="type">class="kw">double(m_mode_edit[class="num">1].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N deposit currency units
(m_current_mode[class="num">1]==class="num">3 && sum_pp>class="type">int(m_mode_edit[class="num">1].GetValue())) || class=class="str">"cmt">// Close all positions with the total profit more than N points◍ 盈利超限后的平仓请求构造
当模式数组第二项等于 4,且当前累计利润超过界面设定的 N 个存款货币单位时,程序进入全平逻辑。此时不再判断方向,只按持仓 ticket 逐个回收仓位。 先调用 ZeroMemory 清空 request 与 result 结构体,避免上一笔交易的残留字段污染新请求。随后填充 action 为 TRADE_ACTION_DEAL、position 写回原 ticket、symbol 与 volume 取自持仓变量,deviation 固定为 5 点容许偏差,magic 对齐策略编号。 价格取 SYMBOL_BID 并挂 ORDER_TYPE_SELL,意味着这段代码片段默认处理多单平仓;空单需对称改为 ASK 与 BUY。发送环节用 for 循环重试最多 5 次,只有 retcode 等于 TRADE_RETCODE_DONE 才 break,否则打印 GetLastError 方便排查。外汇与贵金属杠杆高,这类市价全平在滑点行情中可能少赚或多亏,建议先在 MT5 策略测试器跑一遍验证。
if(m_current_mode[class="num">1]==class="num">4 && sum_cur>class="type">class="kw">double(m_mode_edit[class="num">1].GetValue())) { ZeroMemory(request); ZeroMemory(result); request.action =TRADE_ACTION_DEAL; request.position =position_ticket; request.symbol =position_symbol; request.volume =volume; request.deviation=class="num">5; request.magic =m_magic_number; request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">break; else PrintFormat("OrderSend error %d",GetLastError()); } }
「按键平掉多单的底层循环」
这段代码是 CFastTrading 类里 CloseBuyMarketLoss 方法的后半段,负责在图表按钮点击或按下 H 键时,遍历当前账户下属于本 EA 魔法码、且品种等于当前图表的全部多单。触发条件写得很死:id 必须是 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 且 lparam 等于第 7 号按钮 Id,或者 id 为 CHARTEVENT_KEYDOWN 且 lparam 等于 KEY_H。 进入函数后先取 PositionsTotal 得到持仓总数,再用一个从 total-1 到 0 的倒序 for 循环逐个检查持仓。循环里通过 PositionGetTicket、PositionGetString、PositionGetInteger 分别拿到 ticket、品种、魔法码和持仓类型,并过滤 magic==m_magic_number、symbol==Symbol()、type==POSITION_TYPE_BUY 三项才继续。 符合条件时,用 (SymbolInfoDouble(品种,SYMBOL_BID) - PositionGetDouble(POSITION_PRICE_OPEN)) / _Point 算出以点为单位的浮动盈利 profit_pp,这是后续按点盈利批量平仓逻辑的输入。外汇与贵金属杠杆高,这类一键平多脚本若误触 H 键可能在不利价差下立刻成交,实盘前应在 MT5 策略测试器用模拟品种验证按键响应与过滤条件。
class="type">void CFastTrading::CloseBuyMarketLoss(class="type">int id,class="type">long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">7].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_H)) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int sum_pp=class="num">0; class="type">class="kw">double sum_cur=class="num">0.0; class=class="str">"cmt">//--- Calculate profit for modes class="num">4 and class="num">5 if(m_current_mode[class="num">4]>class="num">2) { for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { class="type">int profit_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point));
遍历持仓时按魔术码与品种过滤
EA 统计浮盈不能一把抓全部账户持仓,必须先按自身魔术码和当前图表品种做双重过滤,否则会把手动单或其他策略的仓位算进自己的风控里。 下面这段从已开仓位倒序遍历,total 是持仓总数,i 从 total-1 跑到 0,保证不漏掉任何一张单。 先用 PositionGetTicket(i) 取 ticket,再用 PositionGetString(POSITION_SYMBOL) 拿品种名,digits 通过 SymbolInfoInteger 查 SYMBOL_DIGITS 得到报价小数位,后续算点数才不会因为黄金 3 位、外汇 5 位而错位。 magic 取自 POSITION_MAGIC,volume 取自 POSITION_VOLUME,type 强转为 ENUM_POSITION_TYPE;profit_cur 和 swap 分别取 POSITION_PROFIT 与 POSITION_SWAP,这两项是算净浮盈的硬数据。 过滤条件连写三层:magic==m_magic_number 且 symbol==Symbol() 且 type==POSITION_TYPE_BUY,才进入买仓点数换算;profit_pp 用 (SYMBOL_BID - POSITION_PRICE_OPEN)/_Point 转成整数点,外汇黄金点值差异大,直接用 _Point 除最稳。 开 MT5 把这段塞进你自己的面板 EA,把 m_magic_number 改成测试值,切到 XAUUSD 图表能直观看到 swap 对 sum_cur 的拖累——贵金属隔夜息在高杠杆下可能明显吃掉短线利润。
class="kw">switch(m_current_mode[class="num">1]) { case class="num">3: sum_pp+=profit_pp; class="kw">break; case class="num">4: sum_cur+=profit_cur+swap; class="kw">break; } } } } class=class="str">"cmt">//--- iterate over open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { class="type">int profit_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point);
◍ 按浮亏阈值触发平仓的五种模式
这段逻辑控制的是第 5 种平仓模式(数组下标 4),由 m_current_mode[4] 决定用哪种阈值判断。外汇与贵金属杠杆高,浮亏阈值设错可能在几根 K 线内放大账户回撤,参数须结合品种波动验证。 模式 0:当前持仓浮亏加掉期费用小于 0 就平掉所有盈利单;模式 1:单笔浮亏点数低于 -m_mode_edit[4] 设定值才触发;模式 2 改用保证金货币单位计量,阈值取自 double 型编辑框。 模式 3 看总浮亏点数、模式 4 看总浮亏金额,均对比 -GetValue() 后决定是否全平。下面这段是条件判断与平仓请求赋值的原始片段,可在 MT5 里直接对照调参。 代码里 deviation 写死为 5 点,黄金这种跳空频繁的品种可能拒单,建议按品种点差实测改大。
if( (m_current_mode[class="num">4]==class="num">0 && profit_cur+swap<class="num">0) || class=class="str">"cmt">// Close all profitable positions (m_current_mode[class="num">4]==class="num">1 && profit_pp<-class="type">int(m_mode_edit[class="num">4].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N points (m_current_mode[class="num">4]==class="num">2 && profit_cur+swap<-class="type">class="kw">double(m_mode_edit[class="num">4].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N deposit currency units (m_current_mode[class="num">4]==class="num">3 && sum_pp<-class="type">int(m_mode_edit[class="num">4].GetValue())) || class=class="str">"cmt">// Close all positions with the total profit more than N points (m_current_mode[class="num">4]==class="num">4 && sum_cur<-class="type">class="kw">double(m_mode_edit[class="num">4].GetValue())) class=class="str">"cmt">// Close all positions with the total profit more than N deposit currency units ) { class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- set the operation parameters request.action =TRADE_ACTION_DEAL; class=class="str">"cmt">// trading operation type request.position =position_ticket; class=class="str">"cmt">// position ticket request.symbol =position_symbol; class=class="str">"cmt">// symbol request.volume =volume; class=class="str">"cmt">// position volume request.deviation=class="num">5; class=class="str">"cmt">// allowable price deviation request.magic =m_magic_number; class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL;
「平仓按钮背后的重试与盈利统计」
在 MT5 的面板交易类里,发单不是一次就完事。上面这段把 OrderSend 包在 5 次循环里,只要返回码不是 TRADE_RETCODE_DONE 就打印 GetLastError 并继续重试,最多跑 5 遍才放弃。 这种重试逻辑在实盘里能扛掉部分瞬时报价拒绝,但也可能在流动性极差时连发 5 单都失败,外汇和贵金属品种在高波动时段滑点放大,属于典型高风险场景。 CloseSellMarketProfit 函数则绑定了图表按钮点击和键盘 J 键,触发后先取 PositionsTotal 得到当前持仓数,再倒序遍历持仓。当 m_current_mode[2] 大于 2 时,才进入盈利统计分支,用 sum_pp 和 sum_cur 累计持仓数与金额,为后续平仓决策提供数据。 你可以直接把这段抄进自己的 EA 面板类,把循环次数从 5 改成 3 试试成交率变化,或者在策略测试器里观察重试打印的频率。
class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">break; else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::CloseSellMarketProfit(class="type">int id,class="type">long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">5].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_J)) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int sum_pp=class="num">0; class="type">class="kw">double sum_cur=class="num">0.0; class=class="str">"cmt">//--- Calculate profit for modes class="num">4 and class="num">5 if(m_current_mode[class="num">2]>class="num">2) { for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol
遍历持仓时按魔术码与品种过滤空单
这段逻辑出现在 EA 遍历终端持仓的循环体内,目的是只统计由本策略开出的、且当前图表品种一致的卖单。外汇与贵金属杠杆高,持仓过滤写错会误算浮盈或重复平仓,实盘前务必在策略测试器里跑一遍。 先通过 PositionGetInteger 取持仓的魔术码与类型,再用 PositionGetDouble 拿浮盈和库存费。注意 profit_pp 用开仓价减当前 ASK 再除 _Point,得到的是卖单相对现价的点数差,不是账户货币金额。 m_current_mode[2] 决定累加方式:等于 3 时只把点数差 sum_pp 累加;等于 4 时把浮盈加 swap 的账户货币值塞进 sum_cur。两种模式互斥,改这个下标的值就能切换统计口径。 循环从 total-1 倒数到 0,每次用 PositionGetTicket(i) 定位持仓,再取 symbol 和 digits。digits 来自 SYMBOL_DIGITS,后面算价格精度或显示用得着,漏了会在黄金这类 3 位小数品种上错位。
class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { class="type">int profit_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); class="kw">switch(m_current_mode[class="num">2]) { case class="num">3: sum_pp+=profit_pp; class="kw">break; case class="num">4: sum_cur+=profit_cur+swap; class="kw">break; } } } class=class="str">"cmt">//--- iterate over open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places
◍ 空单盈利平仓的五种触发逻辑
这段 MT5 代码只处理已匹配 magic 号、且符号为当前图表品种的空头持仓,把平仓判断收敛在 m_current_mode[2] 这一个开关上。 先抓取持仓核心字段:magic 用 PositionGetInteger(POSITION_MAGIC) 取标识,volume 取 POSITION_VOLUME,type 转型为 ENUM_POSITION_TYPE,profit_cur 与 swap 分别取浮点盈亏和库存费。 profit_pp 的计算把开仓价与实时 SYMBOL_ASK 的差除以 _Point,得到以点数计的浮盈点数;这是模式 1 判断的依据。 模式 0 是只要 profit_cur+swap>0 就平掉所有盈利空单;模式 1 要求点数盈利超过 m_mode_edit[2] 的设定值;模式 2 看绝对金额超过 N 存款币种单位;模式 3 和 4 则分别基于 sum_pp、sum_cur 的总盈利阈值,适合多单累加后统一收割。外汇与贵金属杠杆高,库存费可能让微利变负,实盘前应在策略测试器用历史数据验证各模式触发频率。 条件命中后执行 ZeroMemory(request) 与 ZeroMemory(result),把交易请求和回执结构体清空,避免上笔残留字段污染下个下单指令。
class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { class="type">int profit_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); if( (m_current_mode[class="num">2]==class="num">0 && profit_cur+swap>class="num">0) || class=class="str">"cmt">// Close all profitable positions (m_current_mode[class="num">2]==class="num">1 && profit_pp>class="type">int(m_mode_edit[class="num">2].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N points (m_current_mode[class="num">2]==class="num">2 && profit_cur+swap>class="type">class="kw">double(m_mode_edit[class="num">2].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N deposit currency units (m_current_mode[class="num">2]==class="num">3 && sum_pp>class="type">int(m_mode_edit[class="num">2].GetValue())) || class=class="str">"cmt">// Close all positions with the total profit more than N points (m_current_mode[class="num">2]==class="num">4 && sum_cur>class="type">class="kw">double(m_mode_edit[class="num">2].GetValue())) class=class="str">"cmt">// Close all positions with the total profit more than N deposit currency units ) { class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result);
「反手平空单的请求构造与重试」
在面板按钮触发平空仓逻辑后,代码先给 MQL5 的 request 结构体填操作参数,再按品种实时卖价发市价买单平仓。注意 request.deviation=5 代表允许 5 点滑点,外汇与贵金属点差跳动的行情里这个数值偏小,实际可能频繁因超限报错。
循环里用 for(int j=0;j<5;j++) 最多重试 5 次 OrderSend,只要返回码是 TRADE_RETCODE_DONE 就 break,否则 PrintFormat 打错误码。这种重试能缓解瞬时流动性不足,但无法消除外汇高杠杆下的滑点风险,仓位重时仍可能以偏离预期的价格成交。
下面逐行拆这一段核心请求与发送逻辑,方便你直接抄进 EA 的平仓函数里改。
class=class="str">"cmt">//--- set the operation parameters request.action =TRADE_ACTION_DEAL; class=class="str">"cmt">// trading operation type request.position =position_ticket; class=class="str">"cmt">// position ticket request.symbol =position_symbol; class=class="str">"cmt">// symbol request.volume =volume; class=class="str">"cmt">// position volume request.deviation=class="num">5; class=class="str">"cmt">// allowable price deviation request.magic =m_magic_number; class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0;j<class="num">5;j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">break; else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code }
按键触发下的空单浮盈统计
在图表脚本里监听键盘事件时,可以用 CHARTEVENT_KEYDOWN 配合 lparam==KEY_L 捕获按下 L 键的动作,借此手动触发一次持仓扫描。比起定时器轮询,这种交互式触发更省资源,适合临场核对而非高频刷新。
当模式参数 m_current_mode[5] 大于 2 时,代码会倒序遍历 PositionsTotal() 返回的当前持仓数,逐个取出 ticket、品种、魔术码与持仓类型。这里只筛选 magic==m_magic_number 且 Symbol()==position_symbol 的空单(POSITION_TYPE_SELL),把它们的浮盈点数或金额累加进 sum_pp / sum_cur。
具体浮盈点数用开仓价与实时卖价之差除以 _Point 取整:(POSITION_PRICE_OPEN - SYMBOL_ASK)/_Point。若 m_current_mode[1]==3 则累加点数,若为 4 则累加 profit_cur+swap 的现金值。外汇与贵金属杠杆高,swap 可能吃掉可观利润,统计时别漏掉。
把这段逻辑直接塞进你的 EA 的 OnChartEvent 里,按 L 键就能在日志打出当前空单合计浮盈,开 MT5 挂个 XAUUSD demo 账户验证最快。
if(id==CHARTEVENT_KEYDOWN && lparam==KEY_L) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int sum_pp=class="num">0; class="type">class="kw">double sum_cur=class="num">0.0; class=class="str">"cmt">//--- Calculate profit for modes class="num">4 and class="num">5 if(m_current_mode[class="num">5]>class="num">2) { for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { class="type">int profit_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); class="kw">switch(m_current_mode[class="num">1]) { case class="num">3: sum_pp+=profit_pp; class="kw">break; case class="num">4: sum_cur+=profit_cur+swap; } } } } }
◍ 遍历持仓并筛出空单的平仓触发逻辑
这段代码片段承接前文的订单遍历,转向对当前账户已开仓位的扫描。循环从 total-1 倒序到 0,用 PositionGetTicket 按索引取持仓单号,再批量读取 symbol、小数位、magic、成交量、持仓类型与浮动盈亏、库存费。 注意这里用 position_symbol==Symbol() 做了品种过滤,且 magic==m_magic_number 限定只处理本 EA 自己开的单;类型判断锁在 POSITION_TYPE_SELL,也就是只盯空单。 profit_pp 把「开仓价 - 当前 ASK」换算成点数差,用来衡量空单浮盈点数。平仓条件分两路:m_current_mode[5]==0 时,只要 profit_cur+swap<0(含库存费算下来净亏)就可能触发;m_current_mode[5]==1 时,则看 profit_pp 是否小于负的 m_mode_edit[5].GetValue(),即空单浮亏超过设定点数才倾向处理。外汇与贵金属杠杆高,这类逻辑实盘前务必在 MT5 策略测试器用历史数据回验。
class="kw">break; } } } class=class="str">"cmt">//--- iterate over open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { class="type">int profit_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); if( (m_current_mode[class="num">5]==class="num">0 && profit_cur+swap<class="num">0) || class=class="str">"cmt">// Close all profitable positions (m_current_mode[class="num">5]==class="num">1 && profit_pp<-class="type">int(m_mode_edit[class="num">5].GetValue())) || class=class="str">"cmt">// Close all positions having profit more than N points
「亏损阈值触发的平仓请求构造」
这段逻辑嵌在 EA 的持仓巡检循环里,当第 5 号管理模式被激活且浮亏越过设定边界,就会进入平仓分支。模式 2 看单仓「利润+库存费」低于某存款币种数值;模式 3 看总点数亏损低于整型阈值;模式 4 看总存款币种亏损低于双精度阈值,三者用或运算合并判断。 进入分支后先 ZeroMemory 清空 request 与 result,再逐项填交易请求。action 写 TRADE_ACTION_DEAL 表示即时成交,position 绑原仓 ticket,symbol 取原仓品种,volume 沿用原仓手数,deviation 写死 5 点容许偏差,magic 回写 EA 识别码。 价格与方向在这里被硬性设为 ASK 价与 BUY 类型,说明代码片段处于「平空仓」语境。随后用 for 循环连发 5 次 OrderSend,任何一次返回 false 都会令 res 置否——这种重试至多 5 回的机制,在外汇与贵金属高波动时段可能提高成交概率,但滑点风险依旧偏高,需自行在 MT5 策略测试器验证。
(m_current_mode[class="num">5]==class="num">2 && profit_cur+swap<-class="type">class="kw">double(m_mode_edit[class="num">5].GetValue())) || (m_current_mode[class="num">5]==class="num">3 && sum_pp<-class="type">int(m_mode_edit[class="num">5].GetValue())) || (m_current_mode[class="num">5]==class="num">4 && sum_cur<-class="type">class="kw">double(m_mode_edit[class="num">5].GetValue())) ) { class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- set the operation parameters request.action =TRADE_ACTION_DEAL; class=class="str">"cmt">// trading operation type request.position =position_ticket; class=class="str">"cmt">// position ticket request.symbol =position_symbol; class=class="str">"cmt">// symbol request.volume =volume; class=class="str">"cmt">// position volume request.deviation=class="num">5; class=class="str">"cmt">// allowable price deviation request.magic =m_magic_number; class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result);
订单回执判定与面板按钮排布
发单逻辑收口处先卡交易回执:只有 res 为真且 result.retcode 等于 TRADE_RETCODE_DONE 才 break 跳出重试,否则用 PrintFormat 把 GetLastError() 的错误码打进日志,方便在 MT5 专家标签页里定位发单失败原因。 回执处理完后,立刻调用四个平仓函数扫一遍持仓:CloseBuyMarketProfit、CloseSellMarketProfit、CloseBuyMarketLoss、CloseSellMarketLoss,按盈利与亏损、多空方向分批处理,外汇与贵金属品种在此类批量平仓下滑点可能放大,属于高风险操作。 面板按钮用 CreateButton 逐个生成,坐标按 45 像素纵向步进:第 0 个在 (10,10),第 1 个在 (300,10),第 2 个起 y 值变为 10+45*n。按钮颜色用 C'87,128,255'(蓝)与 C'31,209,111'(绿)区分功能组,任一创建失败直接 return(false) 终止初始化。 想验证这套排布,把上面按钮坐标段抄进 EA 的 OnInit,编译后看子窗口是否严格按 45 像素间隔落下 9 个控件即可。
if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">break; else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } } } } } class=class="str">"cmt">//--- CloseBuyMarketProfit(id,lparam); CloseSellMarketProfit(id,lparam); CloseBuyMarketLoss(id,lparam); CloseSellMarketLoss(id,lparam); class=class="str">"cmt">//--- if(!CreateButton(m_order_button[class="num">0],MARKET_ORDER_NAME+"(M)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">1],PENDING_ORDER_NAME+"(P)",C&class="macro">#x27;class="num">31,class="num">209,class="num">111&class="macro">#x27;,class="num">300,class="num">10)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">2],CLOSE_ALL_PROFIT+"(C)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">1)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">3],PEND_ORDERS_ALL_CLOSE+"(R)",C&class="macro">#x27;class="num">31,class="num">209,class="num">111&class="macro">#x27;,class="num">300,class="num">10+class="num">45*class="num">1)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">4],CLOSE_BUY_PROFIT+"(U)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">2)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">5],CLOSE_SELL_PROFIT+"(J)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">3)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">6],CLOSE_ALL_LOSS+"(D)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">4)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">7],CLOSE_BUY_LOSS+"(H)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">5)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">8],CLOSE_SELL_LOSS+"(L)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">6)) class="kw">return(class="kw">false);
◍ 手搓 MT5 面板里的手数、止盈与止损输入框
在 MT5 自定义交易面板里,手数、止盈(TP)、止损(SL)的输入框往往共用同一套 CTextEdit 封装逻辑。下面三段函数分别创建这三种控件,结构高度一致,区别仅在于调用时的语义标签不同。 以 CreateLotControl 为例:先通过 MainPointer 把标签页指针挂给控件,再用 AddToElementsArray 把它绑到指定 tab 页。控件本体宽 80 像素、高 24 像素,字体走基类 m_base_font,最大可输 9999,步长取 _Point,小数位跟随品种 _Digits,并开启 SpinEditMode 让交易者能用上下箭头微调。 CreateTakeProfitControl 与 CreateStopLossControl 的属性设置块和手数控件逐行相同——XSize(80)、YSize(24)、MaxValue(9999)、StepValue(_Point) 等一个不差。这意味着如果你要改面板里三类数值框的默认宽度,得在三个函数里同步改,否则会出现对齐错位。 外汇与贵金属杠杆高、点值波动大,这类输入框若没限制 MaxValue,手动填错手数可能瞬间放大风险敞口。开 MT5 把这段代码塞进你的 EA 面板类里,跑起来看三个框是否都能正常 Spin 微调,是最直接的验证方式。
class="type">bool CFastTrading::CreateLotControl(CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int tab) { class=class="str">"cmt">//--- Store the pointer to the main control text_edit.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(tab,text_edit); class=class="str">"cmt">//--- Properties text_edit.XSize(class="num">80); text_edit.YSize(class="num">24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(class="num">9999); text_edit.StepValue(_Point); text_edit.SetDigits(_Digits); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(class="num">1); text_edit.GetTextBoxPointer().XSize(class="num">80); class=class="str">"cmt">//--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) class="kw">return(class="kw">false); text_edit.SetValue(class="type">class="kw">string(class="num">0)); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,text_edit); class="kw">return(true); } class="type">bool CFastTrading::CreateTakeProfitControl(CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int tab) { class=class="str">"cmt">//--- Store the pointer to the main control text_edit.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(tab,text_edit); class=class="str">"cmt">//--- Properties text_edit.XSize(class="num">80); text_edit.YSize(class="num">24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(class="num">9999); text_edit.StepValue(_Point); text_edit.SetDigits(_Digits); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(class="num">1); text_edit.GetTextBoxPointer().XSize(class="num">80); class=class="str">"cmt">//--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) class="kw">return(class="kw">false); text_edit.SetValue(class="type">class="kw">string(class="num">0)); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,text_edit); class="kw">return(true); } class="type">bool CFastTrading::CreateStopLossControl(CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int tab) { class=class="str">"cmt">//--- Store the pointer to the main control text_edit.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(tab,text_edit); class=class="str">"cmt">//--- Properties
「手搓可改仓位的输入框与按钮」
在 MT5 自建交易面板时,手动画输入框比拖标准控件更可控。下面这段把文本框宽高锁在 80×24 像素,字体走基类 m_base_font,最大可填 9999、最小 0,步长直接绑 _Point,小数位跟 _Digits 走,并开启 SpinEditMode 让滚轮能微调——外汇与贵金属点差跳动快,这种框在手动改仓时会比默认编辑框顺手,但高杠杆下误触大步长可能瞬间放大风险。 text_edit.XSize(80); // 文本框宽 80 像素 text_edit.YSize(24); // 高 24 像素 text_edit.Font(m_base_font); // 用面板基础字体 text_edit.FontSize(m_base_font_size); // 基础字号 text_edit.MaxValue(9999); // 上限 9999 text_edit.StepValue(_Point); // 滚轮步长=1 点 text_edit.SetDigits(_Digits); // 小数位跟随品种 text_edit.MinValue(0); // 下限 0 text_edit.SpinEditMode(true); // 开微调模式 text_edit.GetTextBoxPointer().XGap(1); // 内边距 1 text_edit.GetTextBoxPointer().XSize(80); // 内部宽 80 按钮部分另写两个工厂函数分管“修改”和“平仓”。CreateModifyButton 把背景设 clrDarkOrange、按下变 clrOrange,文字恒白且居中;CreateCloseButton 结构同型,只差颜色与挂载逻辑。两者都先 MainPointer(m_tab) 绑父窗,再 AddToElementsArray 挂到对应标签页,最后 CreateButton 生成——你拷进 EA 的 CWndContainer 派生类,改 clrDarkOrange 就能换肤。 实际排布上,第 6 组仓位控件用 375 为基准 X:Lot 框在 375,SL 框在 455(375+80),TP 框在 541(375+83*2),Y 统一 3。这种硬编码间距在 125% 缩放屏可能错位,建议把 80/83 提为宏,方便 4K 屏重排。
text_edit.XSize(class="num">80); text_edit.YSize(class="num">24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(class="num">9999); text_edit.StepValue(_Point); text_edit.SetDigits(_Digits); text_edit.MinValue(class="num">0); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(class="num">1); text_edit.GetTextBoxPointer().XSize(class="num">80); class=class="str">"cmt">//--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) class="kw">return(class="kw">false); text_edit.SetValue(class="type">class="kw">string(class="num">0)); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,text_edit); class="kw">return(true); } class=class="str">"cmt">//--- Input field for editing open positions if(!CreateLotControl(m_lot_edit[class="num">6],class="num">375,class="num">3,class="num">1)) class="kw">return(class="kw">false); if(!CreateStopLossControl(m_sl_edit[class="num">6],class="num">375+class="num">80,class="num">3,class="num">1)) class="kw">return(class="kw">false); if(!CreateTakeProfitControl(m_tp_edit[class="num">6],class="num">375+class="num">83*class="num">2,class="num">3,class="num">1)) class="kw">return(class="kw">false); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::CreateModifyButton(CButton &button,class="type">class="kw">string text,class="type">int x_gap,class="type">int y_gap,class="type">int tab) { class=class="str">"cmt">//--- Store the window pointer button.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(tab,button); class="type">class="kw">color baseclr=clrDarkOrange; class="type">class="kw">color pressclr=clrOrange; class=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">80); button.YSize(class="num">24); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); class=class="str">"cmt">//--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(class="num">0,button); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::CreateCloseButton(CButton &button,class="type">class="kw">string text,class="type">int x_gap,class="type">int y_gap,class="type">int tab) { class=class="str">"cmt">//--- Store the window pointer button.MainPointer(m_tab);
持仓面板的按钮与表格布局怎么落
给交易面板做修改、平仓两个小按钮时,先通过 CreateModifyButton 在 x=622、y=3 处生成宽 80 的「Modify」键,再用 CreateCloseButton 在 x=702、y=3 处生成同尺寸「Close」键;两者都挂到 m_small_button 数组下标 0 和 1,俄语环境下宏会把标签自动换成「Изменить / Закрыть」。 按钮底色用 clrCrimson、按下态用 clrFireBrick,文字统一 clrWhite 且居中,XSize(80) 与 YSize(24) 是面板里小控件的标准占位,改这两个值会直接挤动后面表格的起始 x 坐标。 持仓表格 CreatePositionsTable 固定 9 列(COLUMNS2_TOTAL=9),总宽 782、单元格高 24;列宽数组初始化为 80,但第 0/1/2/3/6 列分别覆盖成 100、110、100、60、90,其余列维持 80,因此实际总宽 = 100+110+100+60+80*4+90 = 782,和 XSize 对得上。 文本对齐全列 ALIGN_CENTER,文字 X 偏移 7、图片 X 偏移 3、图片 Y 偏移 2,这些偏移量若调大,单元格里的图标和标签就可能重叠,开 MT5 把 image_x_offset 改成 10 就能直观看到错位。
class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(tab,button); class="type">class="kw">color baseclr=clrCrimson; class="type">class="kw">color pressclr=clrFireBrick; class=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">80); button.YSize(class="num">24); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); class=class="str">"cmt">//--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(class="num">0,button); class="kw">return(true); } class=class="str">"cmt">//--- Position editing/closing buttons if(!CreateModifyButton(m_small_button[class="num">0],MODIFY,class="num">622,class="num">3,class="num">1)) class="kw">return(class="kw">false); if(!CreateCloseButton(m_small_button[class="num">1],CLOSE,class="num">622+class="num">80,class="num">3,class="num">1)) class="kw">return(class="kw">false); class="macro">#define MODIFY(m_language==RUSSIAN ? "Изменить" : "Modify") class="macro">#define CLOSE(m_language==RUSSIAN ? "Закрыть" : "Close") class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a table of positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::CreatePositionsTable(CTable &table,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { class="macro">#define COLUMNS2_TOTAL class="num">9 class=class="str">"cmt">//--- Store the pointer to the main control table.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(class="num">1,table); class=class="str">"cmt">//--- Array of column widths class="type">int width[COLUMNS2_TOTAL]; ::ArrayInitialize(width,class="num">80); width[class="num">0]=class="num">100; width[class="num">1]=class="num">110; width[class="num">2]=class="num">100; width[class="num">3]=class="num">60; width[class="num">6]=class="num">90; class=class="str">"cmt">//--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS2_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); class=class="str">"cmt">//--- Array of text offset along the X axis in the columns class="type">int text_x_offset[COLUMNS2_TOTAL]; ::ArrayInitialize(text_x_offset,class="num">7); class=class="str">"cmt">//--- Array of column image offsets along the X axis class="type">int image_x_offset[COLUMNS2_TOTAL]; ::ArrayInitialize(image_x_offset,class="num">3); class=class="str">"cmt">//--- Array of column image offsets along the Y axis class="type">int image_y_offset[COLUMNS2_TOTAL]; ::ArrayInitialize(image_y_offset,class="num">2); class=class="str">"cmt">//--- Properties table.Font(m_base_font); table.FontSize(m_base_font_size); table.XSize(class="num">782); table.CellYSize(class="num">24); table.TableSize(COLUMNS2_TOTAL,class="num">1);
◍ 持仓面板的表格与窗口联动
在 MT5 自定义面板里画持仓表,先通过 table 对象把对齐、列宽、悬停高亮、斑马纹都设好:表头底色用 C'87,128,255',悬停态切到 clrCornflowerBlue,文字保持 clrWhite,行可选、斑马纹用 clrWhiteSmoke,这些视觉参数直接决定盘口可读性。 建表后调 SetHeaderText 按索引填 9 列:0=TICKET、1=SYMBOL、2=TYPE_POS、3=PRICE、4=VOLUME、5=SL、6=TP、7=SWAP、8=PROFIT;宏里 SYMBOL / VOLUME 等都做了俄英双语分支,跟着 m_language 走。 切 Tab 时窗口宽度要跟着变:选第 0 页给 600 宽,第 1 页 782+10,第 2 页 682+10,高度沿用当前 YSize。外汇贵金属波动快,这类面板只做持仓速览,实际下单前仍需手动核对点差与滑点风险。 下面这段是面板初始化里表格与窗口联动的核心片段,逐行看就是上面说的落点。
table.TextAlign(align); table.ColumnsWidth(width); table.TextXOffset(text_x_offset); table.ImageXOffset(image_x_offset); table.ImageYOffset(image_y_offset); table.ShowHeaders(true); table.HeadersColor(C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;); table.HeadersColorHover(clrCornflowerBlue); table.HeadersTextColor(clrWhite); table.IsSortMode(class="kw">false); table.LightsHover(true); table.SelectableRow(true); table.IsZebraFormatRows(clrWhiteSmoke); table.DataType(class="num">0,TYPE_LONG); table.AutoYResizeMode(true); table.AutoYResizeBottomOffset(class="num">5); class=class="str">"cmt">//--- Create a control element if(!table.CreateTable(x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Set the header titles table.SetHeaderText(class="num">0,TICKET); table.SetHeaderText(class="num">1,SYMBOL); table.SetHeaderText(class="num">2,TYPE_POS); table.SetHeaderText(class="num">3,PRICE); table.SetHeaderText(class="num">4,VOLUME); table.SetHeaderText(class="num">5,SL); table.SetHeaderText(class="num">6,TP); table.SetHeaderText(class="num">7,SWAP); table.SetHeaderText(class="num">8,PROFIT); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,table); class="kw">return(true); } class="macro">#define SYMBOL(m_language==RUSSIAN ? "Символ" : "Symbol") class="macro">#define VOLUME(m_language==RUSSIAN ? "Объем" : "Volume") class="macro">#define TYPE_POS(m_language==RUSSIAN ? "Тип позиции" : "Position Type") class="macro">#define SWAP(m_language==RUSSIAN ? "Своп" : "Swap") class="macro">#define PROFIT(m_language==RUSSIAN ? "Прибыль" : "Profit") class="type">void CFastTrading::WindowResize(class="type">int x_size,class="type">int y_size) { m_main_window.GetCloseButtonPointer().Hide(); m_main_window.ChangeWindowWidth(x_size); m_main_window.ChangeWindowHeight(y_size); m_main_window.GetCloseButtonPointer().Show(); } class=class="str">"cmt">//--- Tab switching event if(id==CHARTEVENT_CUSTOM+ON_CLICK_TAB) { if(m_tab.SelectedTab()==class="num">0) WindowResize(class="num">600,m_main_window.YSize()); if(m_tab.SelectedTab()==class="num">1) WindowResize(class="num">782+class="num">10,m_main_window.YSize()); if(m_tab.SelectedTab()==class="num">2) WindowResize(class="num">682+class="num">10,m_main_window.YSize()); }
「持仓面板只认自己开的单」
做一键平仓面板时,最容易踩的坑是把别人 EA 开的单也拽进表格里。下面这段初始化函数用 MagicNumber 和当前图表品种双重过滤,只把「自己负责」的持仓塞进 m_table_positions。 循环里先 PositionsTotal() 拿到总持仓数,再逐个用 PositionGetTicket(i) 取ticket、PositionGetString(POSITION_SYMBOL) 取品种、PositionGetInteger(POSITION_MAGIC) 取魔数。只有 magic==m_magic_number 且 position_symbol==Symbol() 同时满足,才 AddRow(cnt) 并让 cnt 自增。 过滤完若 cnt>0,才调用 SetValuesToPositionsTable() 填具体字段、UpdatePositionsTable() 刷新界面。反向遍历版本(i 从 PositionsTotal()-1 到 0)在 SetValuesToPositionsTable 里出现,顺手用 SymbolInfoInteger(position_symbol,SYMBOL_DIGITS) 抓小数位,避免黄金报价格式错位。 开 MT5 把这段贴进 CFastTrading 类,改 m_magic_number 为你 EA 的魔数,面板就只显示本策略单,不会误伤手动单。外汇贵金属波动剧烈,面板仅作辅助,下单前仍须人工核对。
class="type">void CFastTrading::InitializePositionsTable(class="type">void) { class=class="str">"cmt">//--- Get symbols of open positions class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int cnt=class="num">0; class=class="str">"cmt">//--- Delete all rows m_table_positions.DeleteAllRows(); class=class="str">"cmt">//--- Set the number of rows equal to the number of positions for(class="type">int i=class="num">0; i<total; i++) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_positions.AddRow(cnt); cnt++; } } class=class="str">"cmt">//--- If there are positions if(cnt>class="num">0) { class=class="str">"cmt">//--- Set the values in the table SetValuesToPositionsTable(); class=class="str">"cmt">//--- Update the table UpdatePositionsTable(); } } class="type">void CFastTrading::SetValuesToPositionsTable(class="type">void) { class=class="str">"cmt">//--- class="type">int cnt=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">int digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); class=class="str">"cmt">// the number of decimal places } }
把持仓属性抓出来填进表格
在 EA 面板里实时盯持仓,第一步是用 PositionGetInteger / PositionGetDouble 把当前仓位的字段逐个读出来。下面这段代码从魔数、成交量、多空类型,到止损、止盈、库存费、浮动盈亏和开仓价,一口气抓了 9 个关键属性。 注意 profit+=swap 这一行:MT5 的 POSITION_PROFIT 默认不含库存费,必须把 POSITION_SWAP 显式加上,面板里的“净利润”才不会和账户历史对不上。外汇与贵金属持仓隔夜会产生 swap,属正常成本,但方向反了可能连续吃负,属高风险点。 过滤逻辑只看 magic==m_magic_number 且 symbol 匹配当前图表品种,避免把手动单或其他 EA 的仓混进表。填表时第 2 列(多空)和第 8 列(盈亏)按正绿负红上色,profit==0 时用银色,视觉上一眼能分出死水和活水。 最后 UpdatePositionsTable() 里调了两次 Update(true),一次刷主表、一次刷纵向滚动条指针;漏掉第二个,持仓多了以后滚动条不会跟着重绘。开 MT5 把这段塞进你自己的 CTable 封装里,跑一晚就能看到颜色与数字是否对版。
class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">// position volume class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double stoploss=PositionGetDouble(POSITION_SL); class="type">class="kw">double takeprofit=PositionGetDouble(POSITION_TP); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class="type">class="kw">double profit=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double openprice=PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">string pos=(type==POSITION_TYPE_BUY)?"BUY":"SELL"; profit+=swap; class=class="str">"cmt">//--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_positions.SetValue(class="num">0,i,class="type">class="kw">string(position_ticket)); m_table_positions.SetValue(class="num">1,i,class="type">class="kw">string(position_symbol)); m_table_positions.SetValue(class="num">2,i,pos); m_table_positions.SetValue(class="num">3,i,class="type">class="kw">string(openprice)); m_table_positions.SetValue(class="num">4,i,class="type">class="kw">string(volume)); m_table_positions.SetValue(class="num">5,i,class="type">class="kw">string(stoploss)); m_table_positions.SetValue(class="num">6,i,class="type">class="kw">string(takeprofit)); m_table_positions.SetValue(class="num">7,i,class="type">class="kw">string(swap)); m_table_positions.SetValue(class="num">8,i,DoubleToString(profit,class="num">2)); class=class="str">"cmt">//--- m_table_positions.TextColor(class="num">2,i,(pos=="BUY")? clrForestGreen : clrCrimson); m_table_positions.TextColor(class="num">8,i,(profit!=class="num">0)? (profit>class="num">0)? clrForestGreen : clrCrimson : clrSilver); cnt++; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update the table of positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::UpdatePositionsTable(class="type">void) { class=class="str">"cmt">//--- Update the table m_table_positions.Update(true); m_table_positions.GetScrollVPointer().Update(true); }
◍ EA 初始化与持仓表的挂接逻辑
EA 在 OnInit 里先记下 tick_counter=GetTickCount(),作为启动耗时与后续时序判断的基准点;随后对 program 类做字体、背景色、语言、MagicNumber 等变量初始化,再调用 CreateGUI() 搭建交易面板。若界面创建失败直接返回 INIT_FAILED,MT5 终端日志会打印 'Failed to create graphical interface!',EA 不会加载。 program.OnInitEvent() 内部只做一件事:InitializePositionsTable(),把当前账户持仓快照填进面板表格。注意 OnTrade 事件也挂接到 program.OnTradeEvent(),而该函数体同样调用 InitializePositionsTable()——也就是说,只要有新成交或持仓变动,面板持仓表就会被重算刷新,不用手动轮询。 UpdatePositionProfit() 用 for(int i=PositionsTotal()-1; i>=0; i--) 倒序遍历持仓,避免平仓导致索引错位。循环里依次取 position_ticket、position_symbol、magic,其中 magic 用来和 EA 自己的 MagicNumber 比对,只处理本 EA 开的单。外汇与贵金属杠杆高,持仓盈亏随点值跳动剧烈,面板实时刷新有助于快速识别异动,但任何信号都不构成必赚保证,仅提高人工盯盘效率。
class=class="str">"cmt">//| Expert initialization function |//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- tick_counter=GetTickCount(); class=class="str">"cmt">//--- Initialize class variables program.FontName("Trebuchet MS"); program.FontSize(Inp_BaseFont); program.BackgroundColor(Background); program.CaptionColor(Caption); program.SetLanguage(Language); program.SetMagicNumber(MagicNumber); class=class="str">"cmt">//--- Set up the trading panel if(!program.CreateGUI()) { Print(__FUNCTION__," > Failed to create graphical interface!"); class="kw">return(INIT_FAILED); } program.OnInitEvent(); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialization | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::OnInitEvent(class="type">void) { InitializePositionsTable(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Trade event | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::OnTradeEvent(class="type">void) { class=class="str">"cmt">//--- If a new trade InitializePositionsTable(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Trade function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTrade(class="type">void) { program.OnTradeEvent(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::UpdatePositionProfit(class="type">void) { class=class="str">"cmt">//--- class="type">int cnt=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber }
「把持仓盈亏和点击改单接进面板」
这段逻辑把隔夜利息并进浮动盈亏,再按魔法码和品种过滤后写进第 8 列,并用颜色区分盈亏状态:盈利显森林绿、亏损显深红、持平显银灰。注意 profit 先加 swap 再判断,所以表格里的数字已经是含库存费的真实浮盈。 点击表格某一行时,程序把该行的第 4、5、6 列(手数、SL、TP)抓出来塞进对应的编辑框,并强制刷新文本框。这样交易者在面板上点一下持仓,就能直接看到并修改它的参数,不必去 MT5 原生持仓列表里翻。 改单函数 ModifyPosition 先用 WRONG_VALUE 拦掉未选中的情况,再用 PositionSelectByTicket 按 ticket 定位持仓,最后把编辑框里的 SL 用 NormalizeDouble 按当前品种小数位四舍五入。外汇与贵金属杠杆高,修改 SL/TP 前务必确认点位合理,滑点和点差可能让成交价偏离预期。
class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class="type">class="kw">double profit=PositionGetDouble(POSITION_PROFIT); profit+=swap; class=class="str">"cmt">//--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_positions.SetValue(class="num">8,i,DoubleToString(profit,class="num">2)); class=class="str">"cmt">//--- m_table_positions.TextColor(class="num">8,i,(profit!=class="num">0)? (profit>class="num">0)? clrForestGreen : clrCrimson : clrSilver); cnt++; } class=class="str">"cmt">//--- if(cnt>class="num">0) UpdatePositionsTable(); } class="type">void OnTick() { program.UpdatePositionProfit(); } class=class="str">"cmt">//--- Event of clicking on a table row if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { class=class="str">"cmt">//--- Check the element ID if(lparam==m_table_positions.Id()) { class=class="str">"cmt">//--- class="type">int row=m_table_positions.SelectedItem(); m_lot_edit[class="num">6].SetValue(class="type">class="kw">string(m_table_positions.GetValue(class="num">4,row))); m_sl_edit[class="num">6].SetValue(class="type">class="kw">string(m_table_positions.GetValue(class="num">5,row))); m_tp_edit[class="num">6].SetValue(class="type">class="kw">string(m_table_positions.GetValue(class="num">6,row))); m_lot_edit[class="num">6].GetTextBoxPointer().Update(true); m_sl_edit[class="num">6].GetTextBoxPointer().Update(true); m_tp_edit[class="num">6].GetTextBoxPointer().Update(true); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Modifying a selected open position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::ModifyPosition(class="type">long lparam) { class=class="str">"cmt">//--- Check the element ID if(lparam==m_small_button[class="num">0].Id()) { class=class="str">"cmt">//--- Get index and symbol if(m_table_positions.SelectedItem()==WRONG_VALUE) class="kw">return(class="kw">false); class="type">int row=m_table_positions.SelectedItem(); class="type">class="kw">ulong ticket=(class="type">class="kw">ulong)m_table_positions.GetValue(class="num">0,row); class=class="str">"cmt">//--- if(PositionSelectByTicket(ticket)) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class=class="str">"cmt">//--- calculation and rounding of the Stop Loss and Take Profit values class="type">class="kw">double sl=NormalizeDouble((class="type">class="kw">double)m_sl_edit[class="num">6].GetValue(),_Digits);
用 TRADE_ACTION_SLTP 改挂止盈的实战片段
在 MT5 面板交易代码里,修改持仓止损止盈走的是 TRADE_ACTION_SLTP,不是下新单。先把界面里第 6 个编辑框的止盈值用 NormalizeDouble 按 _Digits 精度规整,避免跨品种小数位错配导致报错。 请求结构体每次发送前必须用 ZeroMemory 清空 request 和 result,否则残留字段可能让服务器拒绝。下面这段把 ticket、Symbol()、sl、tp、magic 一次性填好,循环最多发 5 次,命中 TRADE_RETCODE_DONE 就退出,否则打印 GetLastError 方便排查。 平仓侧则先校验按钮 ID 与表格选中行,用 PositionSelectByTicket 按 ticket 锁定持仓,再读 POSITION_TYPE 和 POSITION_SYMBOL。外汇与贵金属杠杆高,改挂 SL/TP 若点差突扩可能瞬间滑点,建议在策略测试器用真实点差回放验证。
class="type">class="kw">double tp=NormalizeDouble((class="type">class="kw">double)m_tp_edit[class="num">6].GetValue(),_Digits); class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- set the operation parameters request.action =TRADE_ACTION_SLTP; class=class="str">"cmt">// trading operation type request.position=ticket; class=class="str">"cmt">// position ticket request.symbol=Symbol(); class=class="str">"cmt">// symbol request.sl =sl; class=class="str">"cmt">// position Stop Loss request.tp =tp; class=class="str">"cmt">// position Take Profit request.magic=m_magic_number; class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0;j<class="num">5;j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">return(true); else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } } } class=class="str">"cmt">//--- class="kw">return(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::ClosePosition(class="type">long lparam) { class=class="str">"cmt">//--- Check the element ID if(lparam==m_small_button[class="num">1].Id()) { class=class="str">"cmt">//--- Get index and symbol if(m_table_positions.SelectedItem()==WRONG_VALUE) class="kw">return(class="kw">false); class="type">int row=m_table_positions.SelectedItem(); class="type">class="kw">ulong ticket=(class="type">class="kw">ulong)m_table_positions.GetValue(class="num">0,row); class=class="str">"cmt">//--- if(PositionSelectByTicket(ticket)) { class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol
◍ 平仓请求的反向价格与重试逻辑
在 MT5 里用市价单平掉已有持仓,核心是先判断持仓方向再反向取价。多头仓用 SYMBOL_BID 发 SELL,空头仓用 SYMBOL_ASK 发 BUY,价格来源必须和持仓方向相反,否则会吃到不必要的滑点。 下面这段把请求结构体清零后按类型赋值,并做了一次成交量保护:如果界面填的平仓量大于持仓量,直接截断为持仓量,避免报手数非法。 request.deviation 设为 5 点,代表允许的最大偏差;发单不是一次就放弃,而是用 for 循环最多重试 5 次,只有 retcode 等于 TRADE_RETCODE_DONE 才返回 true。外汇与贵金属杠杆高,实盘里这种重试能扛掉瞬时报价拒绝,但也可能在剧烈行情下连发 5 次都失败,概率上需接受部分平仓未成交的风险。 别把重试当万能 循环里只 PrintFormat 了 GetLastError,没做退单或对冲补偿。真要在小布盯盘里跑,建议把每次 result.retcode 也打出来,方便你回 MT5 日志核对是哪类拒绝。
class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- Set order price and type depending on the position type if(type==POSITION_TYPE_BUY) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; } else if(type==POSITION_TYPE_SELL) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; } class=class="str">"cmt">//--- check volume class="type">class="kw">double position_volume=PositionGetDouble(POSITION_VOLUME); class="type">class="kw">double closing_volume=(class="type">class="kw">double)m_lot_edit[class="num">6].GetValue(); if(closing_volume>position_volume) closing_volume=position_volume; class=class="str">"cmt">//--- setting request request.action =TRADE_ACTION_DEAL; request.position =ticket; request.symbol =Symbol(); request.volume =NormalizeLot(Symbol(),closing_volume); request.magic =m_magic_number; request.deviation=class="num">5; class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">return(true); else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } }
「挂单表格的界面参数怎么定」
这段逻辑属于快速交易面板里挂单列(pending orders)的构建函数,核心是把一个 7 列、单行起底的表格对象按坐标和样式初始化好,再交还给调用层。 从代码看,表格总宽被写死为 602 像素,单元格行高 24,列宽数组默认全 80,但第 0 列与第 2 列被单独拉到 100,意味着这两列(通常是订单类型与时间)需要更宽的空间显示完整文本。 表头配色用了 C'87,128,255' 作底色、悬停转 clrCornflowerBlue、文字白色,且关闭了排序模式(IsSortMode=false),说明这表定位是实时刷新而非人工翻查。 数据类型映射也值得记:0 列 LONG、1/2 列 STRING、3/4/5 列 DOUBLE,对应价量类字段直接走双精度,避免面板显示时出现截断。 如果你在 MT5 里改这个面板,把 table.XSize(602) 调大或把 width[0] 再放开,可能会让长符号名不再被挤掉——外汇与贵金属波动剧烈,这类界面细节直接影响撤单反应速度,属于高风险环境下的操作辅助。
class="type">bool CFastTrading::CreateOrdersTable(CTable &table,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { class="macro">#define COLUMNS1_TOTAL class="num">7 class=class="str">"cmt">//--- Store the pointer to the main control table.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(class="num">2,table); class=class="str">"cmt">//--- Array of column widths class="type">int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,class="num">80); width[class="num">0]=class="num">100; width[class="num">2]=class="num">100; class=class="str">"cmt">//--- Array of text offset along the X axis in the columns class="type">int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,class="num">7); class=class="str">"cmt">//--- Array of column image offsets along the X axis class="type">int image_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_x_offset,class="num">3); class=class="str">"cmt">//--- Array of column image offsets along the Y axis class="type">int image_y_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_y_offset,class="num">2); class=class="str">"cmt">//--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[class="num">6]=ALIGN_LEFT; class=class="str">"cmt">//--- Properties table.Font(m_base_font); table.FontSize(m_base_font_size); table.XSize(class="num">602); table.CellYSize(class="num">24); table.TableSize(COLUMNS1_TOTAL,class="num">1); table.TextAlign(align); table.ColumnsWidth(width); table.TextXOffset(text_x_offset); table.ImageXOffset(image_x_offset); table.ImageYOffset(image_x_offset); table.ShowHeaders(true); table.HeadersColor(C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;); table.HeadersColorHover(clrCornflowerBlue); table.HeadersTextColor(clrWhite); table.IsSortMode(class="kw">false); table.LightsHover(true); table.SelectableRow(true); table.IsZebraFormatRows(clrWhiteSmoke); table.AutoYResizeMode(true); table.AutoYResizeBottomOffset(class="num">5); table.DataType(class="num">0,TYPE_LONG); table.DataType(class="num">1,TYPE_STRING); table.DataType(class="num">2,TYPE_STRING); table.DataType(class="num">3,TYPE_DOUBLE); table.DataType(class="num">4,TYPE_DOUBLE); table.DataType(class="num">5,TYPE_DOUBLE); class=class="str">"cmt">//--- Create a control element if(!table.CreateTable(x_gap,y_gap))
挂单表格的初始化与数值填充
在 MT5 自定义面板里做一張挂单总览表,核心不在画界面,而在数据同步逻辑。下面这段从建表头到按 MagicNumber 过滤写入,是可直接抄进 EA 的骨架。 表头先写死 7 列:Ticket、Symbol、类型、手数、开价、SL、TP。用 SetHeaderText 按索引 0~6 赋值,列顺序一旦定下,后面 AddRow 填单元格就必须对齐,否则会出现 SL 填到价格列的低级错。 InitializeOrdersTable 里先用 OrdersTotal() 拿挂单总数,再 DeleteAllRows 清表,避免上一次刷新残留行。循环里只保留 magic==m_magic_number 且 symbol==Symbol() 的挂单——这意味着同品种多 EA 混跑时,本表只认你这只 EA 的单,不会误显别人开的单。 过滤通过的单用 m_table_orders.AddRow(cnt) 逐行追加,cnt 自增计数。若 cnt>0 才调用 SetValuesToOrderTable 和 UpdateOrdersTable,空表就不必刷新,省一次界面重绘。外汇与贵金属挂单受点差滑点影响,实际成交可能偏离表格显示价,属高风险操作,验证时建议开模拟账户跑。 让小布替你跑这套 把 m_magic_number 改成你在用的幻数,编译后切到对应品种图表,看表格是否只显示本 EA 挂单;若全空,先查 OrdersTotal 在非交易时段是否返回 0。
class="kw">return(class="kw">false); class=class="str">"cmt">//--- Set the header titles table.SetHeaderText(class="num">0,TICKET); table.SetHeaderText(class="num">1,SYMBOL); table.SetHeaderText(class="num">2,TYPE_POS); table.SetHeaderText(class="num">3,VOLUME); table.SetHeaderText(class="num">4,PRICE); table.SetHeaderText(class="num">5,SL); table.SetHeaderText(class="num">6,TP); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,table); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initializing the table of positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::InitializeOrdersTable(class="type">void) { class=class="str">"cmt">//--- class="type">int total=OrdersTotal(); class="type">int cnt=class="num">0; class=class="str">"cmt">//--- Delete all rows m_table_orders.DeleteAllRows(); class=class="str">"cmt">//--- Set the number of rows equal to the number of positions for(class="type">int i=class="num">0; i<total; i++) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong order_ticket=OrderGetTicket(i); class=class="str">"cmt">// order ticket class="type">class="kw">string order_symbol=OrderGetString(ORDER_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=OrderGetInteger(ORDER_MAGIC); class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- if MagicNumber matches if(magic==m_magic_number) if(order_symbol==Symbol()) { m_table_orders.AddRow(cnt); cnt++; } } class=class="str">"cmt">//--- If there are positions if(cnt>class="num">0) { class=class="str">"cmt">//--- Set the values in the table SetValuesToOrderTable(); class=class="str">"cmt">//--- Update the table UpdateOrdersTable(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::SetValuesToOrderTable(class="type">void) { class=class="str">"cmt">//--- class="type">int cnt=class="num">0; class="type">class="kw">ulong ticket; for(class="type">int i=class="num">0; i<OrdersTotal(); i++) { class=class="str">"cmt">//--- order parameters if((ticket=OrderGetTicket(i))>class="num">0) { class="type">class="kw">string position_symbol=OrderGetString(ORDER_SYMBOL); class=class="str">"cmt">// symbol
◍ 挂单属性抓取与面板回填
在挂单遍历循环里,先用 OrderGetInteger 和 OrderGetDouble 把每笔挂单的核心字段拉出来:魔术码、初始手数、订单类型、开仓价、止损与止盈。其中 ORDER_TYPE 返回的是枚举整型,必须强转成 ENUM_ORDER_TYPE 才能和限价、止损类常量比对。 类型判定用一串 if-else 把四种挂单映射成可读字符串:Buy Limit、Sell Limit、Buy Stop、Sell Stop。这一步只是给表格显示用,不影响订单本身逻辑。 真正写表前有两道过滤:magic==m_magic_number 且 Symbol() 等于当前图表品种,才把 ticket、品种、类型、手数、价格、SL、TP 依次塞进 m_table_orders 的对应列,计数器 cnt 自增。这样面板里只留自己 EA 下的当前品种挂单,不会和手动单或其它魔术码 EA 混淆。 UpdateOrdersTable 只做两件事:m_table_orders.Update(true) 重绘表格,再对纵向滚动条指针 Update(true)。在 MT5 里跑这段时,若面板不刷新,先查滚动条指针是否为空——这是挂单面板常见隐形坑。外汇与贵金属挂单受滑点和流动性影响,面板数据仅作参考,实际成交价可能偏离显示价,属高风险操作。
class="type">class="kw">ulong magic=OrderGetInteger(ORDER_MAGIC); class=class="str">"cmt">// 读取挂单的魔术码 class="type">class="kw">double volume=OrderGetDouble(ORDER_VOLUME_INITIAL); class=class="str">"cmt">// 读取挂单初始手数 ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); class=class="str">"cmt">// 读取并强转订单类型 class="type">class="kw">double price=OrderGetDouble(ORDER_PRICE_OPEN); class="type">class="kw">double stoploss=OrderGetDouble(ORDER_SL); class="type">class="kw">double takeprofit=OrderGetDouble(ORDER_TP); class="type">class="kw">string pos=""; if(type==ORDER_TYPE_BUY_LIMIT) pos="Buy Limit"; else if(type==ORDER_TYPE_SELL_LIMIT) pos="Sell Limit"; else if(type==ORDER_TYPE_BUY_STOP) pos="Buy Stop"; else if(type==ORDER_TYPE_SELL_STOP) pos="Sell Stop"; class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_orders.SetValue(class="num">0,i,class="type">class="kw">string(ticket)); m_table_orders.SetValue(class="num">1,i,class="type">class="kw">string(position_symbol)); m_table_orders.SetValue(class="num">2,i,pos); m_table_orders.SetValue(class="num">3,i,class="type">class="kw">string(volume)); m_table_orders.SetValue(class="num">4,i,class="type">class="kw">string(price)); m_table_orders.SetValue(class="num">5,i,class="type">class="kw">string(stoploss)); m_table_orders.SetValue(class="num">6,i,class="type">class="kw">string(takeprofit)); cnt++; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::UpdateOrdersTable(class="type">void) { class=class="str">"cmt">//--- Update the table m_table_orders.Update(true); m_table_orders.GetScrollVPointer().Update(true); }
「挂单面板的点击联动与改单入口」
在快速交易面板里,挂单表格和持仓表格都不是死数据。图表自定义事件 CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM 触发后,程序先比对 lparam 与表格对象 Id(),命中后才去读选中行。 点中持仓行时,代码取 SelectedItem() 行号,再把第 4、5、6 列(手数、SL、TP)分别写进 m_lot_edit[6]、m_sl_edit[6]、m_tp_edit[6],并逐个 Update(true) 刷新文本框。订单表同理,但落到 m_pr_edit[4]、m_sl_edit[7]、m_tp_edit[7],列索引同样是 4/5/6。 改单函数 ModifyOrder(long lparam) 以 m_small_button[2] 的 Id() 为入口。若 m_table_orders.SelectedItem() 返回 WRONG_VALUE 直接 false 退出;否则取行号,把第 0 列强制转 ulong 作为 ticket。外汇与贵金属杠杆高,这类一键填参只降低操作延迟,不消除滑点与拒单风险,参数填错可能瞬间反向建仓。 开 MT5 把这段塞进你的面板类,点两行不同订单看编辑框是否跟着跳,比读文档直观得多。
class="type">void CFastTrading::OnInitEvent(class="type">void) { InitializeOrdersTable(); InitializePositionsTable(); } class="type">void CFastTrading::OnTradeEvent(class="type">void) { InitializePositionsTable(); InitializeOrdersTable(); } if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { if(lparam==m_table_positions.Id()) { class="type">int row=m_table_positions.SelectedItem(); m_lot_edit[class="num">6].SetValue(class="type">class="kw">string(m_table_positions.GetValue(class="num">4,row))); m_sl_edit[class="num">6].SetValue(class="type">class="kw">string(m_table_positions.GetValue(class="num">5,row))); m_tp_edit[class="num">6].SetValue(class="type">class="kw">string(m_table_positions.GetValue(class="num">6,row))); m_lot_edit[class="num">6].GetTextBoxPointer().Update(true); m_sl_edit[class="num">6].GetTextBoxPointer().Update(true); m_tp_edit[class="num">6].GetTextBoxPointer().Update(true); } if(lparam==m_table_orders.Id()) { class="type">int row=m_table_orders.SelectedItem(); m_pr_edit[class="num">4].SetValue(class="type">class="kw">string(m_table_orders.GetValue(class="num">4,row))); m_sl_edit[class="num">7].SetValue(class="type">class="kw">string(m_table_orders.GetValue(class="num">5,row))); m_tp_edit[class="num">7].SetValue(class="type">class="kw">string(m_table_orders.GetValue(class="num">6,row))); m_pr_edit[class="num">4].GetTextBoxPointer().Update(true); m_sl_edit[class="num">7].GetTextBoxPointer().Update(true); m_tp_edit[class="num">7].GetTextBoxPointer().Update(true); } } class="type">bool CFastTrading::ModifyOrder(class="type">long lparam) { if(lparam==m_small_button[class="num">2].Id()) { if(m_table_orders.SelectedItem()==WRONG_VALUE) class="kw">return(class="kw">false); class="type">int row=m_table_orders.SelectedItem(); class="type">class="kw">ulong ticket=(class="type">class="kw">ulong)m_table_orders.GetValue(class="num">0,row);
挂单改价与止损的移动实战
在 MT5 里修改一笔挂单的价位、止损和止盈,核心是先 OrderSelect 拿到 ticket 对应的订单上下文,再组装 MqlTradeRequest 发 TRADE_ACTION_MODIFY。下面这段逻辑把 SL/TP/price 全用 NormalizeDouble 按 _Digits 精度收敛,避免跨品种小数位不一致导致服务器拒单。 注意 request.magic 要回填订单自身的 MagicNumber,而不是 EA 默认值;若填错,部分券商会返回参数错误而非修改成功。代码里对 OrderSend 做了最多 5 次重试,每次成功且 retcode==TRADE_RETCODE_DONE 就直接返回 true,否则打印 GetLastError 方便排查。 外汇与贵金属杠杆高,挂单改价可能在流动性稀薄时滑点成交,修改 SL/TP 不保证原价位触发,实盘前务必在策略测试器或模拟盘跑通这套请求结构。
class=class="str">"cmt">//--- if(OrderSelect(ticket)) { class="type">class="kw">string position_symbol=OrderGetString(ORDER_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=OrderGetInteger(ORDER_MAGIC); class=class="str">"cmt">// order MagicNumber ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); class=class="str">"cmt">// order type class=class="str">"cmt">//--- calculation and rounding of the Stop Loss and Take Profit values class="type">class="kw">double sl=NormalizeDouble((class="type">class="kw">double)m_sl_edit[class="num">7].GetValue(),_Digits); class="type">class="kw">double tp=NormalizeDouble((class="type">class="kw">double)m_tp_edit[class="num">7].GetValue(),_Digits); class="type">class="kw">double price=NormalizeDouble((class="type">class="kw">double)m_pr_edit[class="num">4].GetValue(),_Digits); class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- set the operation parameters request.action=TRADE_ACTION_MODIFY; class=class="str">"cmt">// trading operation type request.order = ticket; class=class="str">"cmt">// order ticket request.symbol=Symbol(); class=class="str">"cmt">// symbol request.sl =sl; class=class="str">"cmt">// position Stop Loss request.tp =tp; class=class="str">"cmt">// position Take Profit request.price=price; class=class="str">"cmt">// new price request.magic=m_magic_number; class=class="str">"cmt">// position MagicNumber class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">return(true); else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } }
◍ 挂单删除的票据定位与重试逻辑
在自定义面板里点删除按钮时,先靠 m_small_button[3].Id() 判定是不是删单事件,再读表格选中行。若 m_table_orders.SelectedItem() 返回 WRONG_VALUE(即没选行),直接 return false,避免误操作。 选中行后取第 0 列数值强转 ulong 得到 ticket,用 OrderSelect(ticket) 调出挂单的 symbol、magic、type。这里 ORDER_MAGIC 和 ORDER_TYPE 都是整数型,类型转换要显式写 (ENUM_ORDER_TYPE)。 撤单请求只需填 action=TRADE_ACTION_REMOVE 和 order=ticket 两个字段,其余用 ZeroMemory 清掉。发送放在 for 循环里跑 5 次,命中 TRADE_RETCODE_DONE 就退出,否则 PrintFormat 打 GetLastError()。外汇与贵金属杠杆高,撤单失败可能让挂单继续暴露在区间外,建议自己在 MT5 里把循环次数改成 3 或 10 对比成交回执延迟。
class="type">bool CFastTrading::RemoveOrder(class="type">long lparam) { class=class="str">"cmt">//--- Check the element ID if(lparam==m_small_button[class="num">3].Id()) { class=class="str">"cmt">//--- Get index and symbol if(m_table_orders.SelectedItem()==WRONG_VALUE) class="kw">return(class="kw">false); class="type">int row=m_table_orders.SelectedItem(); class="type">class="kw">ulong ticket=(class="type">class="kw">ulong)m_table_orders.GetValue(class="num">0,row); class=class="str">"cmt">//--- if(OrderSelect(ticket)) { class="type">class="kw">string position_symbol=OrderGetString(ORDER_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=OrderGetInteger(ORDER_MAGIC); class=class="str">"cmt">// order MagicNumber ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); class=class="str">"cmt">// order type class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class=class="str">"cmt">//--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); class=class="str">"cmt">//--- set the operation parameters request.action=TRADE_ACTION_REMOVE; class=class="str">"cmt">// trading operation type request.order = ticket; class=class="str">"cmt">// order ticket class=class="str">"cmt">//--- sending a request class="type">bool res=true; for(class="type">int j=class="num">0; j<class="num">5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) class="kw">return(true); else PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// if unable to send the request, output the error code } } } class=class="str">"cmt">//--- class="kw">return(class="kw">false); }
「图表点击后把坐标转成价格填进输入框」
这段逻辑挂在 EA 的 OnChartEvent 里,先尝试改单和删单,若命中就直接 return,不再处理点击事件。 真正有意思的是 CHARTEVENT_CLICK 分支:它用 for 循环扫 0~3 共 4 个价格编辑框(m_pr_edit 数组),看哪个正处于文本编辑态,把下标记到 m_last_index 并 break。 如果点击发生时没有框在编辑,但之前记录过 m_last_index>=0,就调用 ChartXYToTimePrice(0, lparam, dparam, ...) 把鼠标 X/Y 转成时间 dt 和价格 m_xy_price。转换成功就把价格写回那个编辑框并刷新,随后把 m_last_index 置 -1。 在 MT5 里跑这段,你点图表任意位置,若此前点过某个价格框使其进入编辑,价格会立刻被鼠标对应纵坐标覆盖;外汇和贵金属点差跳动快,这种手动取价方式仅适合离线标注,实盘高风险。
if(ModifyOrder(lparam)) class="kw">return; if(RemoveOrder(lparam)) class="kw">return; class=class="str">"cmt">//--- The event of clicking on the chart if(id==CHARTEVENT_CLICK) { for(class="type">int i=class="num">0; i<class="num">4; i++) { if(m_pr_edit[i].GetTextBoxPointer().TextEditState()) { m_last_index=i; class="kw">break; } else { if(m_last_index>=class="num">0) { class=class="str">"cmt">//--- class="type">class="kw">datetime dt =class="num">0; class="type">int window=class="num">0; class=class="str">"cmt">//--- convert X and Y coordinates to date/time if(ChartXYToTimePrice(class="num">0,(class="type">int)lparam,(class="type">int)dparam,window,dt,m_xy_price)) { m_pr_edit[m_last_index].SetValue(DoubleToString(m_xy_price)); m_pr_edit[m_last_index].GetTextBoxPointer().Update(true); m_last_index=-class="num">1; } } } } }
把附件丢进终端根目录就完事
这套指标与脚本的附件已打包齐全,按原目录结构放好即可,不需要手动去挑文件。真正要做的只有一件事:把压缩包里的 MQL5 文件夹整体丢进终端根目录,覆盖同名路径,MT5 重启后就能在导航器里看到。 想快速定位终端根目录,在 MetaTrader 5 里按 Ctrl+Shift+D 会直接弹出文件管理器窗口,比从设置里翻数据文件夹省事得多。右键报价栏或导航器的「打开数据文件夹」也能到同一位置。 外汇与贵金属品种波动受杠杆和消息面影响大,加载第三方脚本前先在模拟账户跑一遍,确认没乱发订单再上真仓。
◍ 收束
这套多周期多品种指标缓冲区的改造思路,落地时最实际的入口就是去 MT5 终端根目录打开 MQL5 文件夹,把官方示例的 ListView.mqh 和指标类源码拖进本地编辑器对照编译。有读者在 2020 年 12 月的回帖里提到,新编译器会抛“已过时的行为,隐藏方法调用将在未来的 MQL 编译器版本中禁用 ListView.mqh”,这说明老写法迟早要改,现在验证一遍能少踩坑。 外汇和贵金属行情受杠杆与跳空影响,这类自定义指标只解决“看”的问题,信号出现后是否下单仍属高概率事件而非确定性。把文末列出的 DoEasy 时间序列第四十五、四十六部分当作延伸阅读,顺着缓冲区对象类的迭代路径自己改一版,比反复等教程更有用。