如何利用 MQL5 创建简单的多币种智能交易系统(第 6 部分):两条 RSI 指标相互交叉(基础篇)
📘

如何利用 MQL5 创建简单的多币种智能交易系统(第 6 部分):两条 RSI 指标相互交叉(基础篇)

第 1/3 篇

◍ 用双 RSI 交叉搭多币种 EA 的骨架

在 MT5 里做多币种交易,不一定非得上复杂的神经网络,两条不同周期的 RSI 互相交叉就能当触发逻辑。思路是:快线 RSI 上穿慢线 RSI 视为动能转强,下穿则转弱,再按币种分别挂单。 原文示例里把这套逻辑跑在多个符号上,单篇阅读量 2041、收藏 11,说明社区对轻量多币种方案有真实需求。 落地时先在 EA 的 OnTick 里遍历 SymbolTotal 拿到的品种,逐个算 iRSI 句柄;句柄建一次就缓存,别每 tick 重算,否则 CPU 占用会肉眼可见地飙。外汇与贵金属杠杆高,多币种同开仓会放大回撤,参数没验过前先用策略测试器跑历史。

「一图多标的的 RSI 交叉机器人怎么跑」

多货币智能系统(EA)能从单一图表驱动多个品种对的自动开平与订单管理,包括尾随止损和尾随止盈。本文实例中它同时覆盖 30 个货币对,交易者只需挂在一个图表上即可。 核心信号用两条 RSI:快速 RSI 与慢速 RSI 发生交叉作为入场触发。MQL5 在交易终端和策略测试器里都原生支持多品种上下文,因此这类 EA 的回测与实盘路径是一致的。 对外汇与贵金属而言,多标的同向暴露会放大回撤,30 对同策略运行在极端行情下可能集中止损,部署前务必在 MT5 策略测试器用历史数据验证参数敏感性。

多币种 EA 怎么缩成单品种跑

这套多币种智能系统默认覆盖 28 个外汇对加 XAUUSD、XAGUSD 两个贵金属对,共 30 个品种;若经纪商品种名带前缀或后缀,系统内部用函数自动清洗,只认 MT5 里的外汇和贵金属标准名,指数和特殊品种不在范围内。 想把它当单货币 EA 用,有两条路。一是保持多对模式(MP),在“Trader Wishes Pairs”里只填一个名字,比如 XAUUSD,EA 挂在任何图上都只做 XAUUSD;二是把交易对模式切到“SP”,EA 只看自己所在图表那个对,挂 EURUSD 就只跑 EURUSD。 RSI 是 Wilder 在 1978 年提出的振荡器,经典参数是 14 周期、超买 70 超卖 30;本文信号不用裸 RSI 阈值,而是快 RSI 穿慢 RSI。有复盘指出 RSI 在日内外汇上并不实用,但穿越法仍值得在 MT5 里自测验证。 止损逻辑里有个细节:选“使用订单止损=否”时,EA 会盯信号疲软来保盈利。多头平仓条件是快 RSI 在慢 RSI 上方、且本根 K 快 RSI 值小于前一根;空头反之。该开关不激活就硬扛到信号逆转。 尾随用 VIDYA 而非抛物线 SAR,实测 VIDYA 比 SAR 贴价更紧,又比 AMA/DEMA 离价稍远,作为跟踪基准更顺。外汇和贵金属杠杆高,实盘前先用策略测试器跑历史数据。 下面这段是自动算止损价的成员函数,买单选 atprice-38*pip,卖单加 38*pip,关掉自动就取手工 SLval。

MQL5 / C++
class="type">class="kw">double MCEA::OrderSLSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice)
  {
class=class="str">"cmt">//---
    slv=class="num">0.0;
    class="type">int x=PairsIdxArray(xsymb);
    Pips(xsymb);
    RefreshTick(xsymb);
    class=class="str">"cmt">//--
    class="kw">switch(type)
      {
       case (ORDER_TYPE_BUY):
         {
           if(use_sl==Yes && autosl==Yes) slv=mc_symbol.NormalizePrice(atprice-class="num">38*pip);
           else
           if(use_sl==Yes && autosl==No) slv=mc_symbol.NormalizePrice(atprice-SLval*pip);
           else slv=class="num">0.0;
           class=class="str">"cmt">//--
           break;
         }
       case (ORDER_TYPE_SELL):
         {
           if(use_sl==Yes && autosl==Yes) slv=mc_symbol.NormalizePrice(atprice+class="num">38*pip);
           else

◍ 弱信号平仓与止盈价的代码落点

这段逻辑里,GetCloseInWeakSignal 用快慢 RSI 的差值判断持仓是否该在动能转弱时退出。它取最近 3 根 bar 的 RSIFast 与 RSISlow,当持空单且快线从低于慢线、上一根快线走低、当前快线反弹超过 rsidiff 时,返回 rise 信号,倾向平空;持多单则对称处理。 OrderTPSet 对 BUY 单在自动止盈开启时统一挂 atprice+50*pip,关闭自动则用外部 TPval*pip;SELL 单方向相反减点。注意 50 这个硬编码点数在黄金或交叉盘里可能偏窄,复制前先核对 pip 变量在你的品种上是否已是真实点值。 外汇与贵金属杠杆高,这类基于 RSI 背离的弱信号平仓只是概率优势,实盘前务必在 MT5 策略测试器用真实点差回测。

MQL5 / C++
if(use_sl==Yes && autosl==No)  slv=mc_symbol.NormalizePrice(atprice+SLval*pip);
   else slv=class="num">0.0;
   }
  }
class=class="str">"cmt">//---
class="kw">return(slv);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end OrderSLSet()
class=class="str">"cmt">//---------//
class="type">int MCEA::GetCloseInWeakSignal(const class="type">class="kw">string symbol,class="type">int exis) class=class="str">"cmt">// Signal Indicator Position Close in profit
  {
class=class="str">"cmt">//---
   class="type">int ret=class="num">0;
   class="type">int rise=class="num">1,
       down=-class="num">1;
   class="type">int bar=class="num">3;
   class=class="str">"cmt">//--
   class="type">class="kw">double rdif=rsidiff;
   class="type">class="kw">double RSIFast[],
          RSISlow[];
   class=class="str">"cmt">//--
   ArrayResize(RSIFast,bar,bar);
   ArrayResize(RSISlow,bar,bar);
   ArraySetAsSeries(RSIFast,true);
   ArraySetAsSeries(RSISlow,true);
   class=class="str">"cmt">//--
   class="type">int x=PairsIdxArray(symbol);
   UpdatePrice(symbol,TFt);
   class=class="str">"cmt">//--
   CopyBuffer(hRSIFast[x],class="num">0,class="num">0,bar,RSIFast);
   CopyBuffer(hRSISlow[x],class="num">0,class="num">0,bar,RSISlow);
   class=class="str">"cmt">//--
   if(exis==down && RSIFast[class="num">1]<=RSISlow[class="num">1] && RSIFast[class="num">1]<RSIFast[class="num">2] && RSIFast[class="num">0]>RSIFast[class="num">1]+rdif) ret=rise;
   if(exis==rise && RSIFast[class="num">1]>=RSISlow[class="num">1] && RSIFast[class="num">1]>RSIFast[class="num">2] && RSIFast[class="num">0]<RSIFast[class="num">1]-rdif) ret=down;
   class=class="str">"cmt">//--
   class="kw">return(ret);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end GetCloseInWeakSignal()
class=class="str">"cmt">//---------//
class="type">class="kw">double MCEA::OrderTPSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice)
  {
class=class="str">"cmt">//---
   tpv=class="num">0.0;
   class="type">int x=PairsIdxArray(xsymb);
   Pips(xsymb);
   RefreshTick(xsymb);
   class=class="str">"cmt">//--
   class="kw">switch(type)
     {
      case (ORDER_TYPE_BUY):
        {
         if(use_tp==Yes && autotp==Yes) tpv=mc_symbol.NormalizePrice(atprice+class="num">50*pip);
         else
         if(use_tp==Yes && autotp==No)  tpv=mc_symbol.NormalizePrice(atprice+TPval*pip);
         else tpv=class="num">0.0;
         class=class="str">"cmt">//--
         break;
        }
      case (ORDER_TYPE_SELL):
        {
         if(use_tp==Yes && autotp==Yes) tpv=mc_symbol.NormalizePrice(atprice-class="num">50*pip);
         else
         if(use_tp==Yes && autotp==No)  tpv=mc_symbol.NormalizePrice(atprice-TPval*pip);

「追踪止损价的两种算法落地」

MCEA 类的 TSPrice() 给了两套追踪止损价计算路径,由入参 TS_type 切换。case 0 是纯市价偏移:多单取 Bid 减去 TSval*pip 再 NormalizePrice,空单取 Ask 加 TSval*pip,逻辑直白,适合手数小、点值固定的品种。 case 1 则引入 VIDyAv 指标缓冲:先 ArrayResize 到 br=2 并设为时间序列,CopyBuffer 拉最近两根,再用指标值对比市价偏移阈值。多单仅在 VIDyAv[0] 低于 Bid-TSval*pip 时才把止损压到指标值,空单反之,相当于用波动带兜住回撤。 ModifySLTP() 里有一行容易看漏:TRSP=(Close_by_Opps==No && TS_type==1) ? 0 : TS_type。意思是当关闭反向平仓且走指标追踪时,实际传给下层的是 0,也就是退化成市价偏移模式,避免指标未就绪就改单。外汇与贵金属杠杆高,这类隐性分支可能导致止损比预期宽松,上 MT5 跑一遍 Print 出 TRSP 才能确认。

MQL5 / C++
else tpv=class="num">0.0;
   }
  }
  class=class="str">"cmt">//---
  class="kw">return(tpv);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end OrderTPSet()
class=class="str">"cmt">//---------//
class="type">class="kw">double MCEA::TSPrice(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE ptype,class="type">int TS_type)
  {
class=class="str">"cmt">//---
   class="type">int br=class="num">2;
   class="type">class="kw">double pval=class="num">0.0;
   class="type">int x=PairsIdxArray(xsymb);
   Pips(xsymb);
   class=class="str">"cmt">//--
   class="kw">switch(TS_type)
     {
      case class="num">0:
        {
         RefreshTick(xsymb);
         if(ptype==POSITION_TYPE_BUY)  pval=mc_symbol.NormalizePrice(mc_symbol.Bid()-TSval*pip);
         if(ptype==POSITION_TYPE_SELL) pval=mc_symbol.NormalizePrice(mc_symbol.Ask()+TSval*pip);
         break;
        }
      case class="num">1:
        {
         class="type">class="kw">double VIDyAv[];
         ArrayResize(VIDyAv,br,br);
         ArraySetAsSeries(VIDyAv,true);
         CopyBuffer(hVIDyAv[x],class="num">0,class="num">0,br,VIDyAv);
         RefreshPrice(xsymb,TFt,br);
         class=class="str">"cmt">//--
         if(ptype==POSITION_TYPE_BUY  && (VIDyAv[class="num">0]<mc_symbol.NormalizePrice(mc_symbol.Bid()-TSval*pip)))
           pval=VIDyAv[class="num">0];
         if(ptype==POSITION_TYPE_SELL && (VIDyAv[class="num">0]>mc_symbol.NormalizePrice(mc_symbol.Ask()+TSval*pip)))
           pval=VIDyAv[class="num">0];
         break;
        }
     }
   class=class="str">"cmt">//--
   class="kw">return(pval);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end TSPrice()
class=class="str">"cmt">//---------//
class="type">bool MCEA::ModifySLTP(const class="type">class="kw">string symbx,class="type">int TS_type)
  {
class=class="str">"cmt">//---
  ResetLastError();
  class="type">MqlTradeRequest req={};
  class="type">MqlTradeResult  res={};
  MqlTradeCheckResult check={};
  class=class="str">"cmt">//--
  class="type">int TRSP=(Close_by_Opps==No && TS_type==class="num">1) ? class="num">0 : TS_type;
  class="type">bool modist=false;
  class="type">int x=PairsIdxArray(symbx);
  Pips(symbx);
  class=class="str">"cmt">//--
  class="type">int total=PositionsTotal();
  class=class="str">"cmt">//--        
  for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
    {
     class="type">class="kw">string symbol=PositionGetSymbol(i);
     if(symbol==symbx && mc_position.Magic()==magicEA)
       {

多空持仓的追踪止损改写逻辑

上面这段是 EA 在逐笔持仓里做追踪止损改单的核心分支。先取持仓类型,如果是 BUY 就走多头分支,SELL 走空头分支,两个分支结构对称,只是价格运算方向相反。 以 BUY 为例:先 RefreshTick 拿最新报价,再从持仓对象里取出开仓价、止损、利润、库存费、佣金,合成 netp 净盈亏。modstart 以开仓价加 TSmin*pip 作为改单启动线,modminsl 以追踪线 vtrsb 再垫 TSmin-1 个 pip 作为最低允许止损。

改单触发条件写成 modbuy = (price>modminsl && modbuysl>modstart && (pos_stop==0.0modbuysl>pos_stop)),即现价过改单线、新止损优于启动线、且比原止损更优或原无止损。再叠加 netp>minprofit 才真正调用 PositionModify 把止损和止盈挂上去,外汇和贵金属杠杆高,滑点可能让改单失败,需在 MT5 里打印 modist 返回值排查。

SELL 分支里 modstart 变成开仓价减 TSmin*pip,modminsl 用 vtrss 减 (TSmin+1)*pip,modseltp 是现价减 TPval*pip,逻辑一致。把 TSmin 从 1.0 调到 2.0,你会看到 modstart 与 modminsl 间距拉大,改单更迟钝,适合高波动时段降低被扫频率。

MQL5 / C++
class="type">ENUM_POSITION_TYPE opstype = mc_position.PositionType();
if(opstype==POSITION_TYPE_BUY)
  {
     RefreshTick(symbol);
     class="type">class="kw">double price = mc_position.PriceCurrent();
     class="type">class="kw">double vtrsb = mc_symbol.NormalizePrice(TSPrice(symbx,opstype,TRSP));
     class="type">class="kw">double pos_open   = mc_position.PriceOpen();
     class="type">class="kw">double pos_stop   = mc_position.StopLoss();
     class="type">class="kw">double pos_profit = mc_position.Profit();
     class="type">class="kw">double pos_swap   = mc_position.Swap();
     class="type">class="kw">double pos_comm   = mc_position.Commission();
     class="type">class="kw">double netp=pos_profit+pos_swap+pos_comm;
     class="type">class="kw">double modstart=mc_symbol.NormalizePrice(pos_open+TSmin*pip);
     class="type">class="kw">double modminsl=mc_symbol.NormalizePrice(vtrsb+((TSmin-class="num">1.0)*pip));
     class="type">class="kw">double modbuysl=vtrsb;
     class="type">class="kw">double modbuytp=mc_symbol.NormalizePrice(price+TPval*pip);
     class="type">bool modbuy = (price>modminsl && modbuysl>modstart && (pos_stop==class="num">0.0||modbuysl>pos_stop));
     class=class="str">"cmt">//--
     if(modbuy && netp>minprofit)
       {
          modist=mc_trade.PositionModify(symbol,modbuysl,modbuytp);
       }  
     }
if(opstype==POSITION_TYPE_SELL)
  {
     RefreshTick(symbol);
     class="type">class="kw">double price = mc_position.PriceCurrent();
     class="type">class="kw">double vtrss = mc_symbol.NormalizePrice(TSPrice(symbx,opstype,TRSP));
     class="type">class="kw">double pos_open   = mc_position.PriceOpen();
     class="type">class="kw">double pos_stop   = mc_position.StopLoss();
     class="type">class="kw">double pos_profit = mc_position.Profit();
     class="type">class="kw">double pos_swap   = mc_position.Swap();
     class="type">class="kw">double pos_comm   = mc_position.Commission();
     class="type">class="kw">double netp=pos_profit+pos_swap+pos_comm;
     class="type">class="kw">double modstart=mc_symbol.NormalizePrice(pos_open-TSmin*pip);
     class="type">class="kw">double modminsl=mc_symbol.NormalizePrice(vtrss-((TSmin+class="num">1.0)*pip));
     class="type">class="kw">double modselsl=vtrss;
     class="type">class="kw">double modseltp=mc_symbol.NormalizePrice(price-TPval*pip);

常见问题

在 EA 初始化时对每个标的单独建 RSI 句柄并缓存,主循环里按符号遍历各自句柄取值,避免共用指标导致信号错位。
删掉符号遍历循环,把数组写死成当前图表品种,并保留 RSI 周期与交叉阈值参数即可直接降级运行。
小布可直接对指定品种跑回测摘要,列出交叉信号的出现次数与盈亏分布,省去你手写统计脚本。
通常写在交易决策函数末尾:先判短周期 RSI 回落破长周期则平多,再独立判断价格触达预设止盈位就市价离场。
按持仓方向分别记录开仓价与极值,多单只用最高价回撤算止损、空单只用最低价反弹算,各自独立改止损位。