用于交易事件和信号的语音通知系统·综合运用
🔊

用于交易事件和信号的语音通知系统·综合运用

(3/3)·从指标到EA再到快捷工具,用一套CSoundsLib类打通11类场景的语音播报

进阶 第 3/3 篇

很多交易者盯着图表等信号,却漏掉了持仓被平或挂单触发的瞬间。MetaTrader 5自带的声音警报只覆盖11个固定事件,远不够用。把信号变成语音,耳朵比眼睛更不容易错过关键动作。

◍ 持仓筛选与指标缓冲的取数逻辑

这段代码片段把 Tirone 通道下轨与标准差的实时取值、以及当前品种持仓统计封装成了独立函数,方便主逻辑里直接复用。 GetIndValue() 用 CopyBuffer 把两个指标的缓冲拉进数组:Handle1 取标准差 stdev 的 0~1 号元素,Handle2 取 Tirone 下轨 tirone_b 与上轨 tirone_s 各 2 个元素。只要任意一次 CopyBuffer 返回值 ≤0,函数就返回 false,说明指标句柄失效或历史数据不足,主流程应当跳过这一根 K 线。 开仓条件那一行写得很直白:tirone_b[1] < iClose(Symbol(),PERIOD_CURRENT,1) 且 stdev[0] > FlatLevel 时才返回 true,也就是前一根 Tirone 下轨被收盘价跌破、同时标准差突破平稳阈值,倾向视为波动启动信号;外汇与贵金属杠杆品种下这种突破假信号率不低,需结合周期验证。 ExistPositions() 遍历 PositionsTotal() 给出的全部持仓,用 SelectByIndex 按索引选仓,再比对品种名、订单类型 op 与魔术码 mn,三个过滤条件任一设 -1 即代表不限定。返回的是匹配持仓数,用它判断同品种是否已有敞口比直接数总仓更精准。 SelectByIndex() 内部先读 ACCOUNT_MARGIN_MODE,遇到 RETAIL_HEDGING 账户才用 PositionGetTicket(index) 按索引取ticket——这意味着对冲账户里持仓序号和 ticket 不是一一映射,硬套顺序选仓会漏单。

MQL5 / C++
  class="kw">return(tirone_b[class="num">1]<iClose(Symbol(),PERIOD_CURRENT,class="num">1) && stdev[class="num">0]>FlatLevel)?true:class="kw">false;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Getting the current values of indicators                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
{
   class="kw">return(CopyBuffer(Handle1,class="num">0,class="num">0,class="num">2,stdev)<=class="num">0     ||
          CopyBuffer(Handle2,class="num">0,class="num">0,class="num">2,tirone_b)<=class="num">0 ||
          CopyBuffer(Handle2,class="num">2,class="num">0,class="num">2,tirone_s)<=class="num">0
         )?class="kw">false:true;
}
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//|  Returns the number of open orders                                         |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//|  Parameters:                                                               |
class=class="str">"cmt">//|     op - operation(-class="num">1   - any position)                     |
class=class="str">"cmt">//|     mn - MagicNumber(-class="num">1   - any magic number)                 |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">int ExistPositions(class="type">class="kw">string sy,class="type">int op=-class="num">1,class="type">int mn=-class="num">1)
{
   class="type">int pos=class="num">0;
   class="type">uint total=PositionsTotal();
class=class="str">"cmt">//---
   for(class="type">uint i=class="num">0; i<total; i++)
   {
      if(SelectByIndex(i))
         if(PositionGetString(POSITION_SYMBOL)==sy)
            if(op<class="num">0 || PositionGetInteger(POSITION_TYPE)==op)
               if(mn<class="num">0 || PositionGetInteger(POSITION_MAGIC)==mn)
                  pos++;
   }
   class="kw">return(pos);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Select a position on the index                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SelectByIndex(const class="type">int index)
{
   ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
class=class="str">"cmt">//---
   if(margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      class="type">ulong ticket=PositionGetTicket(index);

持仓校验与交易环境初始化的落地写法

这段逻辑先判断订单号或持仓索引是否可取,取不到直接返回 false,避免后续对空对象操作导致 EA 崩溃。ticket==0 或 PositionGetSymbol 返回空串都意味着上下文无效,早退是最省事的保护。 OnInitTrading 里先把当前图表品种塞进 array_used_symbols,再交给 trade 对象统一管理。CreateUsedSymbolsArray 用 SYMBOLS_MODE_CURRENT 和空过滤串,等于只认当前品种,多币种扩展时改这里就行。 trade.TradingSetAsyncMode(false) 把下单改成同步模式,回测和实盘里订单确认更稳,但高频场景会拖慢主循环。TradingSetCorrectTypeExpiration 与 TradingSetCorrectTypeFilling 按经纪商规则纠正过期与填充类型,外汇和贵金属杠杆高、滑点突发行情多,类型不对订单会被拒。 开 MT5 把这段贴进 EA 的初始化函数,把 InpMagic 改成你的魔数,能立刻验证同步下单是否报 4756 类填充错误。

MQL5 / C++
  if(ticket==class="num">0)
     class="kw">return(class="kw">false);
  }
  else
  {
    class="type">class="kw">string name=PositionGetSymbol(index);
    if(name=="")
       class="kw">return(class="kw">false);
  }
class=class="str">"cmt">//---
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Trading Environment Initialization                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInitTrading()
{
  class="type">class="kw">string array_used_symbols[];
class=class="str">"cmt">//--- Fill in the array of used symbols
  CreateUsedSymbolsArray(SYMBOLS_MODE_CURRENT,"",array_used_symbols);
class=class="str">"cmt">//--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries
  trade.SetUsedSymbols(array_used_symbols);
class=class="str">"cmt">//--- Pass all existing collections to the trading class
  trade.TradingOnInit();
  trade.TradingSetMagic(InpMagic);
  trade.TradingSetLogLevel(LOG_LEVEL_ERROR_MSG);
class=class="str">"cmt">//--- Set synchronous passing of orders for all used symbols
  trade.TradingSetAsyncMode(class="kw">false);
class=class="str">"cmt">//--- Set correct order expiration and filling types to all trading objects
  trade.TradingSetCorrectTypeExpiration();
  trade.TradingSetCorrectTypeFilling();
}

「给手动交易工具箱接上语音警报」

手动交易工具箱原本只管搜信号、下单、管仓、平仓,这次往里塞一套语音通知,证明任何 EA 都能低成本挂上声音警报。基础直接借一个现成的声音函数库,不必从零写音频。 先动 Program.mqh:在文件头把 SoundsLib 包含进来,并在 CFastTrading 类的私有段声明一个 CSoundsLib 实例 m_notify。这一步只是把库挂到项目上,编译若不报找不到头文件,说明路径没问题。 EA 输入参数要暴露两个开关:UseVoiceNotify 控制总闸,Language 选播报语种。在 SimpleTrading.mq5 的 input 区加好后,还得在基类公开段写两个 setter 把值灌给 m_notify,并在 OnInit() 里调用,否则运行时语音实例拿不到配置。 开仓反馈点落在 SetBuyOrder() / SetSellOrder():下单请求返回成功的位置插入 BUY_ORDER_SET / SELL_ORDER_SET 语音。挂单同理,BuyStop、SellStop 等四类各写一个提示,删除挂单则在 RemoveOrder() 里按 type 分支调 Message() 播报。 一个容易漏的点:工具箱本质是被终端认作自动交易的 EA,若 MT5 里禁了自动交易,手动单也发不出去。在基类 OnEvent() 的 ON_END_CREATE_GUI 分支加检测并语音提醒,能省掉对着灰掉的下单键发呆的时间。外汇与贵金属杠杆高,语音响不代表单一定成交,终端状态要先确认。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Program.mqh |
class=class="str">"cmt">//|                                                                 Alex2356 |
class=class="str">"cmt">//|                                         [MQL5官方文档]       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <EasyAndFastGUI\WndEvents.mqh>
class="macro">#include <DoEasy25\Engine.mqh>
class="macro">#include "Defines.mqh"
class="macro">#include <SoundsLib/SoundsLib.mqh>
  class=class="str">"cmt">//---
  CSoundsLib          m_notify;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Advisor input parameters                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">int               Inp_BaseFont       =  class="num">10;            class=class="str">"cmt">// Base FontSize
input class="type">color             Caption            =  C&class="macro">#x27;class="num">0,class="num">130,class="num">225&class="macro">#x27;;  class=class="str">"cmt">// Caption Color
input class="type">color             Background         =  clrWhite;      class=class="str">"cmt">// Back class="type">color
input LANG              Language           =  ENGLISH;       class=class="str">"cmt">// Interface language
input class="type">ulong             MagicNumber        =  class="num">1111;          class=class="str">"cmt">// Magic Number
class=class="str">"cmt">//---
input class="type">bool              UseVoiceNotify     =  true;          class=class="str">"cmt">// Use Voice Notify

◍ 面板里的语音与语种开关怎么接

快速交易面板把通知能力收口在两个成员函数上:SetNotifyLanguage 负责把语言枚举写进 m_notify,UseVoiceNotify 则直接切换 m_notify 的激活态。这两个调用都发生在 OnInit 里,紧跟在 program.SetMagicNumber 之后,意味着 EA 启动阶段就把语音播报和英文通知固化下来。 看 OnInit 的实际顺序:program.UseVoiceNotify(UseVoiceNotify) 先执行,program.SetNotifyLanguage(NotifyLanguage) 紧随。输入参数 NotifyLanguage 默认是 ENGLISH,所以不改动的话,MT5 弹出的成交提示和语音都是英文语境,做非英文盘口的交易者容易漏听关键报单。 SetBuyOrder 里能看到触发买单的两条路径:图表自定义事件 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 且 lparam 等于买钮 ID,或键盘事件 CHARTEVENT_KEYDOWN 且 lparam 等于 KEY_B。手点按钮和按 B 键等价,但键盘路径不依赖 GUI 渲染,掉帧时反而更稳。

MQL5 / C++
input LANGUAGE              NotifyLanguage     =  ENGLISH;                 class=class="str">"cmt">// Notification Language
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::SetNotifyLanguage(LANGUAGE lang)
{
   m_notify.Language(lang);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::UseVoiceNotify(class="type">bool state)
{
   m_notify.IsActive(state);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
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);
   program.UseVoiceNotify(UseVoiceNotify);
   program.SetNotifyLanguage(NotifyLanguage);
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">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::SetBuyOrder(class="type">int id,class="type">long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) ||
       (id==CHARTEVENT_KEYDOWN && lparam==KEY_B))
   {
      class=class="str">"cmt">//---
      class="type">class="kw">double lot;
      if(m_switch_button[class="num">0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[class="num">0].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">0].GetValue()));
      if(m_switch_button[class="num">1].IsPressed() && m_switch_button[class="num">2].IsPressed())
      {
逐行拆一下关键段: input LANGUAGE NotifyLanguage = ENGLISH; —— 暴露一个输入项,决定通知语种,默认英文。 void CFastTrading::SetNotifyLanguage(LANGUAGE lang){ m_notify.Language(lang); } —— 把传入枚举转交给通知对象,无返回值。 void CFastTrading::UseVoiceNotify(bool state){ m_notify.IsActive(state); } —— 用布尔态开闭语音,true 才播报。 program.UseVoiceNotify(UseVoiceNotify);program.SetNotifyLanguage(NotifyLanguage); —— OnInit 中顺序调用,EA 加载即定调。 if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_B)) —— 买单双触发:点按钮或按 B 键均可,键盘事件不卡界面。 外汇与贵金属杠杆高,语音漏听或语种错位都可能让你错过平仓时点,上 MT5 把 NotifyLanguage 改成对应语种、用 B 键试一单更踏实。

MQL5 / C++
input LANGUAGE              NotifyLanguage     =  ENGLISH;                 class=class="str">"cmt">// Notification Language
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::SetNotifyLanguage(LANGUAGE lang)
{
   m_notify.Language(lang);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::UseVoiceNotify(class="type">bool state)
{
   m_notify.IsActive(state);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
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);
   program.UseVoiceNotify(UseVoiceNotify);
   program.SetNotifyLanguage(NotifyLanguage);
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">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::SetBuyOrder(class="type">int id,class="type">long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) ||
       (id==CHARTEVENT_KEYDOWN && lparam==KEY_B))
   {
      class=class="str">"cmt">//---
      class="type">class="kw">double lot;
      if(m_switch_button[class="num">0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[class="num">0].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">0].GetValue()));
      if(m_switch_button[class="num">1].IsPressed() && m_switch_button[class="num">2].IsPressed())
      {

买卖按钮背后的开仓分支逻辑

这段面板交易代码的核心,是根据两个开关按钮(索引1、2)的按下状态,决定止损止盈用 double 还是 int 类型传参给 OpenBuy。外汇与贵金属杠杆高,参数类型混用若没对齐后台设置,可能在极端点差下把整数点距误当成价格绝对值。 当两开关都未按,tp/sl 走 int 分支,意味着面板里填的是点数;当仅开关1按下,sl 为 int、tp 为 double,适合止损用点、止盈用价的混合场景。下面保留原文四个分支的 Buy 调用骨架供对照。 Sell 一侧由图表自定义事件或快捷键 S 触发,lot 计算先看开关3是否按下:按下则按账户净值百分比动态算手数,否则走固定手数分支。这类逻辑在 MT5 里直接挂 EA 面板就能验证,改一下 m_switch_button 状态看成交单的 SL/TP 字段变化最直观。

MQL5 / C++
class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">0].GetValue());
class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">0].GetValue());
if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
{
m_notify.Message(BUY_ORDER_SET);
class="kw">return(true);
}
else if(!m_switch_button[class="num">1].IsPressed() && !m_switch_button[class="num">2].IsPressed())
{
class="type">int tp=class="type">int(m_tp_edit[class="num">0].GetValue());
class="type">int sl=class="type">int(m_sl_edit[class="num">0].GetValue());
if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
{
m_notify.Message(BUY_ORDER_SET);
class="kw">return(true);
}
}
else if(m_switch_button[class="num">1].IsPressed() && !m_switch_button[class="num">2].IsPressed())
{
class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">0].GetValue());
class="type">int sl=class="type">int(m_sl_edit[class="num">0].GetValue());
if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
{
m_notify.Message(BUY_ORDER_SET);
class="kw">return(true);
}
}
else if(!m_switch_button[class="num">1].IsPressed() && m_switch_button[class="num">2].IsPressed())
{
class="type">int tp=class="type">int(m_tp_edit[class="num">0].GetValue());
class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">0].GetValue());
if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
{
m_notify.Message(BUY_ORDER_SET);
class="kw">return(true);
}
}
class="kw">return(class="kw">false);
}
class="type">bool CFastTrading::SetSellOrder(class="type">int id,class="type">long lparam)
{
if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sell_execute.Id()) ||
   (id==CHARTEVENT_KEYDOWN && lparam==KEY_S))
{
class="type">class="kw">double lot;
if(m_switch_button[class="num">3].IsPressed())
   lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[class="num">1].GetValue()));
else

「卖单开仓里的点数与价格双模式」

这段逻辑处理的是面板触发卖单时的参数解析:先通过 NormalizeLot 把手数字符串转成合规交易量,再依据两个开关按钮(索引4、5)的状态,决定 TP、SL 是按价格(double)还是按点数(int)传入 OpenSell。 四个分支覆盖了 4/5 按钮的全组合:都按下去 → 双价格;都不按 → 双点数;仅5按 → 点数TP+价格SL;仅4按 → 价格TP+点数SL。每种情况都调 m_trade.OpenSell(lot, Symbol(), m_magic_number, sl, tp),成功就发 SELL_ORDER_SET 通知并 return true。 这种写法在 MT5 面板 EA 里很常见,但容易踩坑:如果经纪商对止损止盈只接受绝对价格,传 int 点数会被当成 0 点距处理,订单可能不带防护直接裸奔。外汇与贵金属杠杆高,这类静默失效会让回撤概率显著上升,建议开 MT5 用策略测试器跑一遍不同开关组合验证。 下面把核心分支原样贴出,方便你直接对照改自己的面板代码。

MQL5 / C++
lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">1].GetValue()));
class=class="str">"cmt">//---
if(m_switch_button[class="num">4].IsPressed() && m_switch_button[class="num">5].IsPressed())
{
   class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">1].GetValue());
   class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">1].GetValue());
   if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
   {
      m_notify.Message(SELL_ORDER_SET);
      class="kw">return(true);
   }
}
else if(!m_switch_button[class="num">4].IsPressed() && !m_switch_button[class="num">5].IsPressed())
{
   class="type">int tp=class="type">int(m_tp_edit[class="num">1].GetValue());
   class="type">int sl=class="type">int(m_sl_edit[class="num">1].GetValue());
   if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
   {
      m_notify.Message(SELL_ORDER_SET);
      class="kw">return(true);
   }
}
else if(!m_switch_button[class="num">4].IsPressed() && m_switch_button[class="num">5].IsPressed())
{
   class="type">int tp=class="type">int(m_tp_edit[class="num">1].GetValue());
   class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">1].GetValue());
   if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
   {
      m_notify.Message(SELL_ORDER_SET);
      class="kw">return(true);
   }
}
else if(m_switch_button[class="num">4].IsPressed() && !m_switch_button[class="num">5].IsPressed())
{
   class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">1].GetValue());
   class="type">int sl=class="type">int(m_sl_edit[class="num">1].GetValue());
   if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
   {
      m_notify.Message(SELL_ORDER_SET);
      class="kw">return(true);
   }
}

◍ BuyStop 挂单的两种参数分支

在自定义交易面板里,BuyStop 挂单的触发不只靠鼠标点击,也绑定了键盘 KEY_1。代码先判断订单窗口 m_orders_windows[1] 是否可见,不可见直接返回 false,避免盲挂。 手数计算分两路:当 m_p_switch_button[0] 被按下,走 LotPercent 按净值百分比换算;否则用 NormalizeLot 对编辑框 m_lot_edit[2] 里的裸数字做规范化。价格取自 m_pr_edit[0],这是挂单价而非市价。 TP/SL 的传入类型由另外两个开关决定。若 m_p_switch_button[1] 和 [2] 同时按下,tp、sl 都按 double 绝对值传给 PlaceBuyStop;若两者都未按,则按 int 点数处理。混合状态下([1] 按、[2] 不按)tp 取 double、sl 取 int,这种不一致在回测中容易引发滑点偏差,建议实盘前在 MT5 策略测试器用 EURUSD 15 分钟数据跑一遍确认。 外汇与贵金属杠杆高,挂单未成交也可能因跳空失效,任何挂单逻辑都只是概率优势而非确定性执行。

MQL5 / C++
class="type">bool SetSellLimitOrder(class="type">int id,class="type">long lparam);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::SetBuyStopOrder(class="type">int id,class="type">long lparam)
{
   if(!m_orders_windows[class="num">1].IsVisible())
      class="kw">return(class="kw">false);
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buystop_execute.Id()) ||
       (id==CHARTEVENT_KEYDOWN && lparam==KEY_1))
   {
      class=class="str">"cmt">//---
      class="type">class="kw">double lot;
      if(m_p_switch_button[class="num">0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[class="num">2].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">2].GetValue()));
      class=class="str">"cmt">//---
      class="type">class="kw">double pr=class="type">class="kw">double(m_pr_edit[class="num">0].GetValue());
      class=class="str">"cmt">//---
      if(m_p_switch_button[class="num">1].IsPressed() && m_p_switch_button[class="num">2].IsPressed())
      {
         class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">2].GetValue());
         class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            class="kw">return(true);
         }
      }
      else if(!m_p_switch_button[class="num">1].IsPressed() && !m_p_switch_button[class="num">2].IsPressed())
      {
         class="type">int tp=class="type">int(m_tp_edit[class="num">2].GetValue());
         class="type">int sl=class="type">int(m_sl_edit[class="num">2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            class="kw">return(true);
         }
      }
      else if(m_p_switch_button[class="num">1].IsPressed() && !m_p_switch_button[class="num">2].IsPressed())
      {
         class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">2].GetValue());
         class="type">int sl=class="type">int(m_sl_edit[class="num">2].GetValue());

挂单触发与撤单的类型分支

这段逻辑里,BuyStop 挂单的布置被两个开关状态组合控制:当第二个按钮未按、第三个按钮按下时,会从对应编辑框取 tp、sl 数值,再调用 PlaceBuyStop 下突破买入挂单;若成交则返回 true 并推送 BUYSTOP_ORDER_SET 通知。 撤单函数 RemoveOrder 先校验被点击的是否为 small_button[3],再读取表格选中行,用 OrderSelect 按 ticket 拉取订单。这里把 ORDER_TYPE 强转为枚举后,按 BuyStop / SellStop / BuyLimit / SellLimit 四种类型分别发不同删除提示,说明面板对挂单类别做了显式区分。 在 MT5 里跑这套,重点看 m_sl_edit[2]、m_tp_edit[2] 的取值是否被转成了正确的 double / int,以及 magic 号是否和你 EA 的过滤条件一致,否则可能下出不被管理的单。外汇与贵金属杠杆高,挂单未成交前也占保证金,价格跳空可能导致滑点成交,属正常市场风险。

MQL5 / C++
if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
      {
         m_notify.Message(BUYSTOP_ORDER_SET);
         class="kw">return(true);
      }

if(type==ORDER_TYPE_BUY_STOP)
         m_notify.Message(BUYSTOP_ORDER_DELETE);
      else if(type==ORDER_TYPE_SELL_STOP)
         m_notify.Message(SELLSTOP_ORDER_DELETE);
      else if(type==ORDER_TYPE_BUY_LIMIT)
         m_notify.Message(BUYLIMIT_ORDER_DELETE);
      else if(type==ORDER_TYPE_SELL_LIMIT)

「撤单请求的重试与界面收尾」

删除挂单不能只发一次请求就完事。下面这段逻辑在 TRADE_ACTION_REMOVE 之外套了 5 次循环,用 OrderSend 反复尝试,直到 retcode 等于 TRADE_RETCODE_DONE 才返回 true,否则打印 GetLastError 的错误码。外汇与贵金属品种在实时行情跳变时,撤单可能因报价过期被拒,重试机制能降低这类假失败的概率。 GUI 创建完成的回调里,代码把 6 个主开关按钮和 12 个面板按钮分别用 SetButtonParam 绑定了 LOT 或 POINTS 单位,顺序固定。若你改了按钮数组长度,这里必须同步补绑定,否则新按钮会沿用默认参数。 最后一段灰色标记的逻辑检查 TERMINAL_TRADE_ALLOWED,若终端禁止自动交易就推一条 AUTO_TRADING_OFF 通知。实盘前开 MT5 把「允许算法交易」勾上,否则面板建完也不会真撤单。

MQL5 / C++
m_notify.Message(SELLLIMIT_ORDER_DELETE);
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);
}
class=class="str">"cmt">// --- GUI creation completion
 if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
  {
   class=class="str">"cmt">//---
   SetButtonParam(m_switch_button[class="num">0],LOT);
   SetButtonParam(m_switch_button[class="num">1],POINTS);
   SetButtonParam(m_switch_button[class="num">2],POINTS);
   SetButtonParam(m_switch_button[class="num">3],LOT);
   SetButtonParam(m_switch_button[class="num">4],POINTS);
   SetButtonParam(m_switch_button[class="num">5],POINTS);
   class=class="str">"cmt">//---
   SetButtonParam(m_p_switch_button[class="num">0],LOT);
   SetButtonParam(m_p_switch_button[class="num">1],POINTS);
   SetButtonParam(m_p_switch_button[class="num">2],POINTS);
   SetButtonParam(m_p_switch_button[class="num">3],LOT);
   SetButtonParam(m_p_switch_button[class="num">4],POINTS);
   SetButtonParam(m_p_switch_button[class="num">5],POINTS);
   SetButtonParam(m_p_switch_button[class="num">6],LOT);
   SetButtonParam(m_p_switch_button[class="num">7],POINTS);
   SetButtonParam(m_p_switch_button[class="num">8],POINTS);
   SetButtonParam(m_p_switch_button[class="num">9],LOT);
   SetButtonParam(m_p_switch_button[class="num">10],POINTS);
   SetButtonParam(m_p_switch_button[class="num">11],POINTS);
   class=class="str">"cmt">//---
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      m_notify.Message(AUTO_TRADING_OFF);
  }

◍ 把文件丢进正确的终端目录

这一节只讲一件最容易被忽略的事:代码下完了,放错地方就白搭。附件里列的全部文件都要归位,但让程序真正跑起来,核心是把 MQL5 文件夹整体丢进终端根目录下的对应路径,少一层目录 EA 就加载不出。 在 MT5 里定位终端根目录最快的方式是按 Ctrl+Shift+D,直接弹出数据文件夹;也可以右键托盘图标走「打开数据文件夹」的关联菜单,和图例 7 的入口一致。 外汇与贵金属品种波动剧烈、杠杆风险高,部署完先开模拟账户跑一遍,确认日志无报错再上真仓。

把工具请下神坛

打开 MT5 终端根目录里的 MQL5 文件夹,是很多手动交易者第一次接触 EA 源码和脚本的入口。下载的 MQL5.zip 约 8868.77 KB,解压后能看到作者 Alexander Fedosov 那套语音通知与快捷交易工具的工程文件,但需注意这些材料版权归 MetaQuotes 所有,部分复制或转载被明确禁止。 从评论区能看到真实落地情况:有用户在 2020 年 11 月反馈编译无错但日志报错,作者仅回应这是 MT5 专版、无 MT4 支持;到 2024 年 1 月仍有人无法复现视频里的语音播报。这说明工具链依赖本地环境配置,照搬可能踩坑。 外汇与贵金属交易本身高风险,这类第三方工具只该当辅助,不该当成自动盈利的圣杯。打开文件夹、读一遍代码、在自己模拟盘跑通,比反复追问作者更有用。

让小布替你听盘口异动
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到事件语音化的逻辑雏形,把重复劳动交给小布,你专注决策。

常见问题

演示实现中通过LANGUAGE枚举内置了英语和俄语两种,扩展时可在枚举里追加其它语种对应的音频调用。
在指标代码里包含SoundsLib.mqh,实例化CSoundsLib后调用Message()并传入对应MESSAGE枚举值即可,不需重写播放逻辑。
默认应置于终端根目录的MQL5\Files或对应Sounds路径下,具体以类内播放函数拼接的相对位置为准,放错目录会静默失效。
可以,小布的品种页已把部分事件转为可读提醒,逻辑同源;若你想自建EA语音层,本篇的类结构是最轻量的起点。
播放调用是异步文件读取,正常音频时长下对下单延迟影响极小,但建议在非高频按钮里挂通知,避免极端行情卡顿。