快捷手动交易工具箱:持仓和挂单操控·综合运用
🧰

快捷手动交易工具箱:持仓和挂单操控·综合运用

(3/3)·从仅能市价开单到持仓管理挂单修改,把手动交易的三个阶段一次性补齐

含代码示例 第 3/3 篇
很多手动交易者用工具箱只做到市价入场,持仓止损改不动、挂单单独删不了,决策效率卡在后两步。本篇把开仓之后的管理与修改界面一次补全,计算交给程序,你只管拍板。
本章目录
  1. 从持仓里抠出平仓触发条件
  2. 用总盈利阈值触发全平的逻辑落点
  3. 一键砍掉所有浮亏持仓
  4. 用持仓循环剥离掉期与浮亏点数
  5. 用浮亏点数和库存费触发平仓
  6. 平仓请求里的价格方向与重试逻辑
  7. 多单浮盈的两种累计口径
  8. 遍历持仓并按魔法码筛选平仓条件
  9. 盈利超限后的平仓请求构造
  10. 按键平掉多单的底层循环
  11. 遍历持仓时按魔术码与品种过滤
  12. 按浮亏阈值触发平仓的五种模式
  13. 平仓按钮背后的重试与盈利统计
  14. 遍历持仓时按魔术码与品种过滤空单
  15. 空单盈利平仓的五种触发逻辑
  16. 反手平空单的请求构造与重试
  17. 按键触发下的空单浮盈统计
  18. 遍历持仓并筛出空单的平仓触发逻辑
  19. 亏损阈值触发的平仓请求构造
  20. 订单回执判定与面板按钮排布
  21. 手搓 MT5 面板里的手数、止盈与止损输入框
  22. 手搓可改仓位的输入框与按钮
  23. 持仓面板的按钮与表格布局怎么落
  24. 持仓面板的表格与窗口联动
  25. 持仓面板只认自己开的单
  26. 把持仓属性抓出来填进表格
  27. EA 初始化与持仓表的挂接逻辑
  28. 把持仓盈亏和点击改单接进面板
  29. 用 TRADE_ACTION_SLTP 改挂止盈的实战片段
  30. 平仓请求的反向价格与重试逻辑
  31. 挂单表格的界面参数怎么定
  32. 挂单表格的初始化与数值填充
  33. 挂单属性抓取与面板回填
  34. 挂单面板的点击联动与改单入口
  35. 挂单改价与止损的移动实战
  36. 挂单删除的票据定位与重试逻辑
  37. 图表点击后把坐标转成价格填进输入框
  38. 把附件丢进终端根目录就完事
  39. 收束

「从持仓里抠出平仓触发条件」

想让 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,就会互相干扰。

MQL5 / C++
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 对照。

MQL5 / C++
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 就是单仓盈亏浮值,负值即浮亏,后面拿它累加做总砍仓判断。外汇和贵金属杠杆高,批量平仓可能瞬间放大滑点,实盘前务必在策略测试器用历史数据跑一遍。

MQL5 / C++
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 里看清真实暴露。

MQL5 / C++
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 策略测试器里改这个值跑一遍。

MQL5 / C++
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 构造流程。

MQL5 / C++
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) 看多单暴露。外汇与贵金属杠杆高,浮盈口径仅作持仓观察,不代表平仓收益。

MQL5 / C++
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 的差异。

MQL5 / C++
{
   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 策略测试器跑一遍验证。

MQL5 / C++
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 策略测试器用模拟品种验证按键响应与过滤条件。

MQL5 / C++
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 的拖累——贵金属隔夜息在高杠杆下可能明显吃掉短线利润。

MQL5 / C++
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 点,黄金这种跳空频繁的品种可能拒单,建议按品种点差实测改大。

MQL5 / C++
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 试试成交率变化,或者在策略测试器里观察重试打印的频率。

MQL5 / C++
            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 位小数品种上错位。

MQL5 / C++
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),把交易请求和回执结构体清空,避免上笔残留字段污染下个下单指令。

MQL5 / C++
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_DONEbreak,否则 PrintFormat 打错误码。这种重试能缓解瞬时流动性不足,但无法消除外汇高杠杆下的滑点风险,仓位重时仍可能以偏离预期的价格成交。 下面逐行拆这一段核心请求与发送逻辑,方便你直接抄进 EA 的平仓函数里改。

MQL5 / C++
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_numberSymbol()==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 账户验证最快。

MQL5 / C++
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 策略测试器用历史数据回验。

MQL5 / C++
      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 策略测试器验证。

MQL5 / C++
(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 个控件即可。

MQL5 / C++
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 微调,是最直接的验证方式。

MQL5 / C++
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 屏重排。

MQL5 / C++
  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 就能直观看到错位。

MQL5 / C++
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。外汇贵金属波动快,这类面板只做持仓速览,实际下单前仍需手动核对点差与滑点风险。 下面这段是面板初始化里表格与窗口联动的核心片段,逐行看就是上面说的落点。

MQL5 / C++
  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 的魔数,面板就只显示本策略单,不会误伤手动单。外汇贵金属波动剧烈,面板仅作辅助,下单前仍须人工核对。

MQL5 / C++
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 封装里,跑一晚就能看到颜色与数字是否对版。

MQL5 / C++
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 开的单。外汇与贵金属杠杆高,持仓盈亏随点值跳动剧烈,面板实时刷新有助于快速识别异动,但任何信号都不构成必赚保证,仅提高人工盯盘效率。

MQL5 / C++
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 前务必确认点位合理,滑点和点差可能让成交价偏离预期。

MQL5 / C++
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 若点差突扩可能瞬间滑点,建议在策略测试器用真实点差回放验证。

MQL5 / C++
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 日志核对是哪类拒绝。

MQL5 / C++
   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] 再放开,可能会让长符号名不再被挤掉——外汇与贵金属波动剧烈,这类界面细节直接影响撤单反应速度,属于高风险环境下的操作辅助。

MQL5 / C++
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。

MQL5 / C++
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 里跑这段时,若面板不刷新,先查滚动条指针是否为空——这是挂单面板常见隐形坑。外汇与贵金属挂单受滑点和流动性影响,面板数据仅作参考,实际成交价可能偏离显示价,属高风险操作。

MQL5 / C++
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 把这段塞进你的面板类,点两行不同订单看编辑框是否跟着跳,比读文档直观得多。

MQL5 / C++
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 不保证原价位触发,实盘前务必在策略测试器或模拟盘跑通这套请求结构。

MQL5 / C++
  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 对比成交回执延迟。

MQL5 / C++
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 里跑这段,你点图表任意位置,若此前点过某个价格框使其进入编辑,价格会立刻被鼠标对应纵坐标覆盖;外汇和贵金属点差跳动快,这种手动取价方式仅适合离线标注,实盘高风险。

MQL5 / C++
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 时间序列第四十五、四十六部分当作延伸阅读,顺着缓冲区对象类的迭代路径自己改一版,比反复等教程更有用。

把重复劳动交给小布
这些诊断与批量平仓条件的小布盯盘 AIGC 已内置,打开对应品种页即可看到持仓分布与风险敞口,你专注决策而非手工翻表。

常见问题

市价持仓与挂单的属性不同,分开列出能避免误操作和参数串填,修改止损止盈时只作用于在场持仓。
点击平仓前系统比对输入字段的成交量与持仓实际量,若不一致则以输入值为准并校验是否超过可用量,防止多平或少平。
不能互相调用,但小布的品种页可展示由工具箱创建的挂单与持仓,你对照着在工具箱里做单独修改更高效。
可按多空方向、盈利亏损状态、所属品种或成交量阈值组合筛选,选中子集后统一发送平仓指令。
原先的市价开单与批量删挂单入口都收进 Trading 选项卡,新增的持仓与挂单控制作为另外两个选项卡独立存在。