创建一个在若干工具上交易的 EA 交易程序·进阶篇
📘

创建一个在若干工具上交易的 EA 交易程序·进阶篇

第 2/2 篇

「TEMA 斜率反转如何变成进出场开关」

这段逻辑把三重指数均线(TEMA)的逐根斜率变化,直接映射成多空触发与平仓信号,核心在比较最近三根 TEMA 的差值。 先把 TEMA 数组设为时间序列(最新值在下标 0),再用 iTEMA 拿句柄;若当前 Bars 少于 MinBars[Number] 直接 return(true) 跳过,避免样本不足导致误判。 只在 IsNewBar 或 Recount 为真时重算:将缓冲区 0 从偏移 0 复制 4 根到 TEMA 数组,复制失败置 Recount=true 并返回 false。复制成功后取 Digits_ = 报价小数位+4,把 TEMA[2]-TEMA[3] 记为 dtema2、TEMA[1]-TEMA[2] 记为 dtema1,均做 NormalizeDouble 精度对齐。 信号判定很直白:dtema2>0 且 dtema1<0 置 DnSignal(上涨斜率转跌,倾向开空);dtema2<0 且 dtema1>0 置 UpSignal(下跌斜率转涨,倾向开多)。平仓侧则是 dtema1>0 置 DnStop、dtema1<0 置 UpStop——即斜率方向延续就平掉反向仓。 外汇与贵金属杠杆高,斜率反转只是概率信号,实盘前务必在 MT5 用 EURUSD 15M 跑一遍看信号频率。

MQL5 / C++
   ArraySetAsSeries(TEMA,true);
   class=class="str">"cmt">//--- 取得指标句柄
   Handle[Number]=iTEMA(Symbol_,class="num">0,period,class="num">0,ApPrice);
 }
class=class="str">"cmt">//--- 检查柱数是否够用于计算 
   if(Bars(Symbol_,class="num">0)<MinBars[Number])class="kw">return(true);
class=class="str">"cmt">//--- 取得交易信号 
   if(IsNewBar(Number,Symbol_,class="num">0) || Recount[Number]) class=class="str">"cmt">// 在柱有改变或者复制数据失败时进入此区块
   {
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;
      class=class="str">"cmt">//--- 使用指标句柄把指标
      class=class="str">"cmt">//--- 缓冲区数值复制到特别准备的静态数组中
      if(CopyBuffer(Handle[Number],class="num">0,class="num">0,class="num">4,TEMA)<class="num">0)
        {
         Recount[Number]=true; class=class="str">"cmt">//由于没有收到数据,我们应该返回 
                            class=class="str">"cmt">// 进入此区块(当收到交易信号时)在下一个订单号!
         class="kw">return(false);       class=class="str">"cmt">// 没有收到交易信号则退出TradeSignalCounter() 函数
        }
      class=class="str">"cmt">//--- 所有从指标缓冲区复制操作成功完成
      Recount[Number]=false; class=class="str">"cmt">// 我们可以直到下一个柱的改变时再回到此区块
      class="type">int Digits_ = class="type">int(SymbolInfoInteger(Symbol_,SYMBOL_DIGITS)+class="num">4);
      dtema2 = NormalizeDouble(TEMA[class="num">2] - TEMA[class="num">3], Digits_);
      dtema1 = NormalizeDouble(TEMA[class="num">1] - TEMA[class="num">2], Digits_);
      class=class="str">"cmt">//---- 决定输入信号
      if(dtema2 > class="num">0 && dtema1 < class="num">0) DnSignal[Number] = true;
      if(dtema2 < class="num">0 && dtema1 > class="num">0) UpSignal[Number] = true;
      class=class="str">"cmt">//---- 决定输出信号
      if(dtema1 > class="num">0) DnStop[Number] = true;
      if(dtema1 < class="num">0) UpStop[Number] = true;
   }
class=class="str">"cmt">//----+
   class="kw">return(true);
   }
class="type">bool TradePerformer(class="type">int       Number,
                    class="type">class="kw">string Symbol_,
                    class="type">bool   Trade,
                    class="type">int    StLoss,
                    class="type">int    TkProfit,
                    class="type">class="kw">double Lots,
                    class="type">int    Slippage,
                    class="type">bool  &UpSignal[],
                    class="type">bool  &DnSignal[],

◍ 信号触发后的持仓翻转逻辑

这段函数负责在每根 K 线信号判定后执行实际动作:先检查全局 Trade 开关,若禁止交易直接返回 true 不做任何操作。接着根据 UpStop / DnStop 数组状态平掉对应方向持仓,再判断 UpSignal / DnSignal 是否触发新单。 新单成交后立刻把对应信号位复位成 false,保证同一根柱上不会重复开仓——这是避免 MT5 回测里一根 bar 反复刷单的关键细节。外汇与贵金属杠杆高,这类重复触发会让滑点损耗成倍放大。 下方 BuyPositionClose 展示了平仓请求的组装方式:用 PositionSelect 确认品种有买单,否则返回 false;request.type 设为 ORDER_TYPE_SELL 以市价反手,type_filling 用 ORDER_FILLING_FOK 要求全部成交或全不成交。deviation 直接透传,实盘里建议按点差波动设 10~30 点而非 ULONG_MAX。 代码里用 Print 把平仓动作打到日志,方便你开 MT5 后对照「专家」标签页看每笔平单是否按预期触发。复制下面这段到 EA 里,把 Symbol_ 换成自己盯的 XAUUSD 或 EURUSD 就能跑。

MQL5 / C++
class="type">bool  &UpStop[],
                class="type">bool  &DnStop[])
  {
class=class="str">"cmt">//--- 检查交易是否被禁止
   if(!Trade)class="kw">return(true);
class=class="str">"cmt">//--- 平掉持仓 
   if(UpStop[Number])BuyPositionClose(Symbol_,Slippage);
   if(DnStop[Number])SellPositionClose(Symbol_,Slippage);
class=class="str">"cmt">//--- 建新仓位
   if(UpSignal[Number])
      if(BuyPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit))
         UpSignal[Number]=false; class=class="str">"cmt">//这个柱上不会有更多此交易信号!
class=class="str">"cmt">//---
   if(DnSignal[Number])
      if(SellPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit))
         DnSignal[Number]=false; class=class="str">"cmt">//这个柱上不会有更多此交易信号!
class=class="str">"cmt">//---
   class="kw">return(true);
  }
BuyPositionClose();
SellPositionClose();
BuyPositionOpen();
SellPositionOpen();
class="type">bool BuyPositionClose(const class="type">class="kw">string symbol,class="type">class="kw">ulong deviation)
  {
class=class="str">"cmt">//--- 声明交易请求结构和交易结果
   class="type">MqlTradeRequest request;
   class="type">MqlTradeResult result;
   ZeroMemory(request);
   ZeroMemory(result);
class=class="str">"cmt">//--- 检查是否有买入持仓
   if(PositionSelect(symbol))
     {
      if(PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_BUY) class="kw">return(false);
     }
   else  class="kw">return(false);
class=class="str">"cmt">//--- 初始化 class="type">MqlTradeRequest 结构用于平掉买入持仓
   request.type   = ORDER_TYPE_SELL;
   request.price  = SymbolInfoDouble(symbol, SYMBOL_BID);
   request.action = TRADE_ACTION_DEAL;
   request.symbol = symbol;
   request.volume = PositionGetDouble(POSITION_VOLUME);
   request.sl = class="num">0.0;
   request.tp = class="num">0.0;
   request.deviation=(deviation==ULONG_MAX) ? deviation : deviation;
   request.type_filling=ORDER_FILLING_FOK;
class=class="str">"cmt">//---
   class="type">class="kw">string word="";
   StringConcatenate(word,
                     "<<< ============ BuyPositionClose():  平买入持仓,交易品种 ",
                     symbol," ============ >>>");
   Print(word);
class=class="str">"cmt">//--- 发送订单平仓交易服务器
   if(!OrderSend(request,result))
     {
      Print(ResultRetcodeDescription(result.retcode));
      class="kw">return(false);
     }
class=class="str">"cmt">//----+
   class="kw">return(true);
  }
class="type">bool IsNewBar(class="type">int Number, class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframe);
class="type">class="kw">string ResultRetcodeDescription(class="type">int retcode);

跨品种共振怎么喂给EA

把不同金融资产的关联搬进交易系统,核心不是新理论,而是把趋势分析从单标的扩到一组关联标的。以 EUR/USD 为主资产时,同时取 EUR/JPY 与 USD/JPY 上同一指标的读数,用相同变化区间做规范化,才方便横向加减和比较。 随机动量指标(Stoh)和它的信号线 Sign 的差值 dStoh 被用来判方向。若 EUR/JPY 与 USD/JPY 的 dStoh 异号,就求和:和大于 0 视为正向共振,否则负向。主资产无信号、其余两标的 dStoh 合计小于 0 时,平掉已有多仓——这是退出逻辑里最容易被漏掉的一条。 工程上更干净的做法是把分析全塞进多货币指标,EA 只从指标缓存拿现成信号。MultiStochastic.mq5 用绿柱表示做多、红柱做空,图顶的粉点/浅绿点分别是多空退出提示;嫌画图缓存拖慢,就砍到 MultiStochastic_Exp.mq5 只留信号部分。 下面这段 OnTick 只跑三个品种,结构极简:三次 TradeSignalCounter 填信号数组,再三次 TradePerformer 下单。注意 TradeSignalCounter 里对三个时间序列最少柱数做了校验,并用 SynchroCheck 保证各序列柱同步切换——外汇与贵金属跨品种共振受点差和跳空影响,实盘前务必在 MT5 策略测试器用 2001 年后的历史数据核对同步逻辑。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 声明交易信号的变量数组
  class="kw">static class="type">bool UpSignal[], DnSignal[], UpStop[], DnStop[];

class=class="str">"cmt">//--- 取得交易信号
  TradeSignalCounter(class="num">0, Trade0, Kperiod0, Dperiod0, slowing0, ma_method0, price_0, SymbolA0, SymbolB0, SymbolC0, UpSignal, DnSignal, UpStop, DnStop);
  TradeSignalCounter(class="num">1, Trade1, Kperiod1, Dperiod1, slowing1, ma_method1, price_1, SymbolA1, SymbolB1, SymbolC1, UpSignal, DnSignal, UpStop, DnStop);
  TradeSignalCounter(class="num">2, Trade2, Kperiod2, Dperiod2, slowing2, ma_method2, price_2, SymbolA2, SymbolB2, SymbolC2, UpSignal, DnSignal, UpStop, DnStop);
                  
class=class="str">"cmt">//--- 进行交易操作
  TradePerformer( class="num">0, SymbolA0,  Trade0,  StopLoss0,  class="num">0,  Lots0,  Slippage0,  UpSignal, DnSignal, UpStop, DnStop);
  TradePerformer( class="num">1, SymbolA1,  Trade1,  StopLoss1,  class="num">0,  Lots1,  Slippage1,  UpSignal, DnSignal, UpStop, DnStop);
  TradePerformer( class="num">2, SymbolA2,  Trade2,  StopLoss2,  class="num">0,  Lots2,  Slippage2,  UpSignal, DnSignal, UpStop, DnStop);
class=class="str">"cmt">//---
  }
class="type">bool TradeSignalCounter(class="type">int Number,
                      class="type">bool Trade,
                      class="type">int Kperiod,
                      class="type">int Dperiod,
                      class="type">int slowing,
                      ENUM_MA_METHOD ma_method,
                      ENUM_STO_PRICE price_,
                      class="type">class="kw">string SymbolA,
                      class="type">class="kw">string SymbolB,
                      class="type">class="kw">string SymbolC,
                      class="type">bool &UpSignal[],
                      class="type">bool &DnSignal[],
                      class="type">bool &UpStop[],
                      class="type">bool &DnStop[])
  {
class=class="str">"cmt">//--- 检查交易是否被禁止
   if(!Trade)class="kw">return(true);

「多周期随机指标的句柄与数组预扩容」

在 MT5 里用 iCustom 挂载自定义多货币随机指标(如 MultiStochastic_Exp)时,句柄和配套数组必须用 static 存住,否则每次调用函数都会重复申请句柄,造成资源泄漏与计算错位。下面这段逻辑先用 Size_ 记录已分配长度,只有 Number+1 超过当前尺寸才做 ArrayResize,把 Handle、Recount、MinBars 等七个数组一次性扩到目标大小。 扩容失败的分支很关键:只要任意一个 ArrayResize 返回 -1,就拼出错描述并取 GetLastError(),若错误码大于 4000 才 Print 出来,同时把 Size_ 置为 -2 并返回 false,避免后续空句柄跑信号。成功时则把 MinBars[Number] 设为 Kperiod + Dperiod + slowing,这是该随机指标最少需要的柱数。 句柄申请那行把三个货币对 SymbolA/B/C 和均线参数一并传进去,说明它是跨品种共振版随机指标。之后有两道闸门:Rates_Total 小于 MinBars[Number] 或 SynchroCheck 不通过都直接 return(true),即暂不计算,等数据齐了再说。外汇与贵金属跨品种策略对时间同步极敏感,不同服务器点差与停盘会让柱不对齐,这类检查不可省,否则信号可能失真。 真正取信号前还有一道 IsNewBar 或 Recount 判定:不是新柱且不强制重算就跳过,并把四类信号数组(Dn/Up Signal、Dn/Up Stop)先清零,防止上一根柱的状态污染当前判断。

MQL5 / C++
class=class="str">"cmt">//--- 声明变量用于保存变量数组的大小
  class="kw">static class="type">int Size_=class="num">0;
class=class="str">"cmt">//--- 声明数组用于以静态变量形式保存指标句柄
  class="kw">static class="type">int Handle[];
  class="kw">static class="type">int Recount[],MinBars[];
class=class="str">"cmt">//---
  class="type">class="kw">double dUpSignal_[class="num">1],dDnSignal_[class="num">1],dUpStop_[class="num">1],dDnStop_[class="num">1];
class=class="str">"cmt">//--- 改变变量数组大小
  if(Number+class="num">1>Size_)
    {
      class="type">uint size=Number+class="num">1;
      class=class="str">"cmt">//----
      if(ArrayResize(Handle,size)==-class="num">1
        || ArrayResize(Recount,size)==-class="num">1
        || ArrayResize(UpSignal, size) == -class="num">1
        || ArrayResize(DnSignal, size) == -class="num">1
        || ArrayResize(UpStop, size) == -class="num">1
        || ArrayResize(DnStop, size) == -class="num">1
        || ArrayResize(MinBars,size) == -class="num">1)
        {
         class="type">class="kw">string word="";
         StringConcatenate(word,"TradeSignalCounter( ",Number,
                           " ): 错误!!!无法改变变量数组大小!!!");
         class="type">int error=GetLastError();
         ResetLastError();
         class=class="str">"cmt">//---
         if(error>class="num">4000)
           {
            StringConcatenate(word,"TradeSignalCounter( ",Number," ): Error code ",error);
            Print(word);
           }
         Size_=-class="num">2;
         class="kw">return(false);
        }
      Size_=class="type">int(size);
      Recount[Number] = false;
      MinBars[Number] = Kperiod + Dperiod + slowing;
      class=class="str">"cmt">//--- 取得指标句柄
      Handle[Number]=iCustom(SymbolA,class="num">0,"MultiStochastic_Exp",
                             Kperiod,Dperiod,slowing,ma_method,price_,
                             SymbolA,SymbolB,SymbolC);
     }
class=class="str">"cmt">//--- 检查柱数是否够用于计算 
   if(Rates_Total(SymbolA,SymbolB,SymbolC)<MinBars[Number])class="kw">return(true);
class=class="str">"cmt">//--- 检查时间序列同步
   if(!SynchroCheck(SymbolA,SymbolB,SymbolC))class="kw">return(true);
class=class="str">"cmt">//--- 取得交易信号 
   if(IsNewBar(Number,SymbolA,class="num">0) || Recount[Number])
     {
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;
      class=class="str">"cmt">//--- 使用指标句柄,把指标 
      class=class="str">"cmt">//--- 缓冲区值复制到特别准备的静态数组

◍ 缓冲区取数失败就重算这一手

这段逻辑干的事很直接:从指标句柄里把 4 个缓冲区各取 1 根bar的数据,分别是下行信号、上行信号、下行止损、上行止损,索引 1 到 4 对应这四个通道。 只要任意一个 CopyBuffer 返回小于 0,就说明这次拷贝没成功,立刻把 Recount[Number] 置 true 并 return(false),放弃本次计算,等下一轮再试。外汇和贵金属行情跳空时这种失败概率会抬升,属于高风险环境下的正常容错。 拷贝成功后,代码把数值等于 300 的缓冲值映射成对应的布尔交易命令:dDnSignal_[0]==300 触发 DnSignal,上行和止损逻辑同理。300 是这个指标自定义的信号魔法数,开 MT5 把指标挂上就能在数据窗口里核对。 最后把 Recount[Number] 清为 false,意味着在当前 bar 走完前不必再进这块重算,省 CPU 也避免重复发指令。

MQL5 / C++
if(CopyBuffer(Handle[Number], class="num">1, class="num">1, class="num">1, dDnSignal_) < class="num">0){Recount[Number] = true; class="kw">return(false);}
if(CopyBuffer(Handle[Number], class="num">2, class="num">1, class="num">1, dUpSignal_) < class="num">0){Recount[Number] = true; class="kw">return(false);}
if(CopyBuffer(Handle[Number], class="num">3, class="num">1, class="num">1, dDnStop_  ) < class="num">0){Recount[Number] = true; class="kw">return(false);}
if(CopyBuffer(Handle[Number], class="num">4, class="num">1, class="num">1, dUpStop_  ) < class="num">0){Recount[Number] = true; class="kw">return(false);}
class=class="str">"cmt">//--- 把获得的数值转换为交易命令的逻辑变量值
if(dDnSignal_[class="num">0] == class="num">300)DnSignal[Number] = true;
if(dUpSignal_[class="num">0] == class="num">300)UpSignal[Number] = true;
if(dDnStop_  [class="num">0] == class="num">300)DnStop  [Number] = true;
if(dUpStop_  [class="num">0] == class="num">300)UpStop  [Number] = true;
class=class="str">"cmt">//--- 所有从指标缓冲区复制的操作都已成功完成
class=class="str">"cmt">//--- 直到下次柱改变之前都没有必要回到此区块了
Recount[Number]=false;
   }
class=class="str">"cmt">//----+
   class="kw">return(true);
   }

多货币EA的代码本质与一处类型瑕疵

多货币 EA 在 MQL5 里的代码骨架和单品种 EA 高度相似,差异主要在跨品种循环与全局数组的组织方式,而非语言机制本身。 附件里 exp_tema.mq5 体积 45.47 KB、multistochastic.mq5 仅 29.54 KB,说明多货币逻辑不一定更臃肿,关键看指标调用是否被压缩。 有读者在 EURUSD M1 加载 exp_tema 时报“无法加载三重指数移动平均线指标 [4302]”,这类报错多源于自定义指标未先编译进终端,而非 EA 算法问题。 作者本人确认 exp_tema 里 Recount[] 声明为 int 却存 bool 值,不会影响结果,但本应是逻辑静态变量——这种写法在复制代码时容易误导。 下方片段展示了信号触发与 Recount 标记的写法,注意 volume 写死 0.1 仅作示例,实盘需按仓位规则替换。 外汇与贵金属杠杆高,跨品种同时持仓会放大回撤,任何多货币策略都先在 MT5 策略测试器跑历史再上模拟。

MQL5 / C++
    if (dtema2 > class="num">0 && dtema1 < class="num">0) 
    {
    DnSignal[Number] = true;
    volume = class="num">0.1;
    } class=class="str">"cmt">// 如果 if 函数为真,买入和卖出的交易量将为 class="num">0.1
 class="kw">static class="type">int Recount[], MinBars[];
Recount[Number] = true;
...
Recount[Number] = false;

常见问题

用 iMA 取 TEMA 句柄后比较前一根与当前斜率符号,符号由正转负平多开空、由负转正平空开多;斜率阈值可设最小角度过滤杂讯。
在 OnTradeTransaction 里判持仓品种与信号方向,若反向持仓存在则用反向订单覆盖;注意用 PositionSelect 校验真实持仓再发指令。
小布可把多个关联品种的同周期形态与指标状态并列展示,标出共振强度与背离点,你只需看一眼汇总页就能过滤掉弱共振。
句柄在 OnInit 建一次复用即可;数组用 ArrayResize 按最大柱数预开,避免每帧 CopyBuffer 动态扩容造成卡顿。
取数返回空时标记该品种待重算,下一帧重试而非跳过;类型瑕疵多在枚举与字符串映射处,统一用强类型结构体可减少误判。