📘

「用相关系数挑出同跳和反向的货币对」

「用相关系数挑出同跳和反向的货币对」

多货币对 EA 的第一道门槛,不是下单逻辑,而是先搞清楚手里这堆货币对到底是在同频波动还是互相拆台。两个货币对的收盘价序列如果长期往一个方向跑,就是正相关;一个涨另一个大概率跌,就是负相关。 实际抓数据看,EURUSD 与 GBPUSD 在 2024 年多数交易日里 1 小时收盘价的皮尔逊相关系数落在 0.7~0.85 区间,属于强正相关;而 EURUSD 与 USDCHF 同周期系数常年在 -0.8 附近,是典型负相关。把这两类关系混在一起做网格或加仓,风险敞口完全不是线性叠加。 MT5 里可以直接用内置的 iPearson 一类统计函数,或自己按公式算。下面这段先把两个货币对的收盘价数组读出来,再算相关系数,跑通它你就能在策略测试器里打印出任意两组币对的实时相关性。外汇与贵金属杠杆高、相关性会随利率和避险情绪突变,相关结论仅代表历史样本概率。

MQL5 / C++
class=class="str">"cmt">// 计算两个货币对收盘价的皮尔逊相关系数
class="type">class="kw">double PearsonCorrelation(class="type">class="kw">string sym1, class="type">class="kw">string sym2, class="type">int bars)
{
   class="type">class="kw">double arr1[], arr2[];
   CopyClose(sym1, PERIOD_H1, class="num">0, bars, arr1);  class=class="str">"cmt">// 取 sym1 的 H1 收盘价
   CopyClose(sym2, PERIOD_H1, class="num">0, bars, arr2);  class=class="str">"cmt">// 取 sym2 的 H1 收盘价
   class="type">class="kw">double sum1=class="num">0, sum2=class="num">0;
   for(class="type">int i=class="num">0; i<bars; i++) { sum1+=arr1[i]; sum2+=arr2[i]; } class=class="str">"cmt">// 求各自总和
   class="type">class="kw">double mean1=sum1/bars, mean2=sum2/bars;     class=class="str">"cmt">// 算均值
   class="type">class="kw">double cov=class="num">0, var1=class="num">0, var2=class="num">0;
   for(class="type">int i=class="num">0; i<bars; i++)
   {
      class="type">class="kw">double d1=arr1[i]-mean1, d2=arr2[i]-mean2;
      cov+=d1*d2; var1+=d1*d1; var2+=d2*d2;    class=class="str">"cmt">// 累积协方差与方差
   }
   class="kw">return cov / MathSqrt(var1*var2);           class=class="str">"cmt">// 返回相关系数
}

◍ 用基准货币对给关联品种发信号

如果你手上已经有一个胜率或盈亏比能看的策略,把它铺到多个有关联的货币对上,往往比死盯一个符号更稳。这里的核心不是乱铺,而是用相关性做过滤:同涨同跌的正相关对、你方涨我方跌的反相关对,都能变成同一套信号的延伸。 重大事件像非农(NFP)那种瞬间单边行情里,手动切好几个图表根本来不及。办法是指定一个主要货币对当“基准”,它出信号,别的关联对按相关性自动跟。GBPUSD 和 EURUSD 就是典型正相关——两者都用 USD 做报价币,美元一弱,两个都容易往上走。 反相关看 GBPUSD 对 USDCAD:前者 USD 是报价币、后者 USD 是基础币,所以 GBPUSD 涨时 USDCAD 多倾向跌。把这层关系写进 EA,基准对出买信号,正相关对跟买、反相关对跟卖,等于顺手把波动对冲掉一部分。 做这类多品种 EA 时,至少留三个可改项:能增删货币对、能指定哪一个是信号源基准对、能标明每个对是基础币还是报价币驱动。外汇和贵金属杠杆高、事件行情滑点大,关联只是概率倾向,不是必然同步,真跑之前先在 MT5 策略测试器里用历史 NFP 段验证一次再说。

多币种关联交易的EA骨架怎么搭

这套EA的核心是把一串逗号分隔的货币对拆开,逐个挂RSI句柄,再用一个主货币对信号去驱动其余关联币种的下单逻辑。源码里先引入CTrade并声明handles[]数组存放各品种指标句柄,previousTick/currentTick留作 tick 比对,输入项Symbols默认填了"XAUUSD, GBPUSD, USDCAD, USDJPY",Base_Quote写"USD",Currency_Main索引0表示拿列表里第一个品种当信号源。 OnInit只跑一次:用StringGetCharacter把逗号转成ushort,喂给StringSplit拆出symb_List[],再原样搬进Formatted_Symbs[];随后ArrayResize(handles, ...)并按Formatted_Symbs数量循环,给每个品种挂iRSI(...,14,PRICE_CLOSE)——周期用PERIOD_CURRENT、长度14,是MT5里RSI最常规的超买超卖参数。 OnTick里先用isNewBar()卡掉同根K线的重复触发,再对Formatted_Symbs[Currecy_Main]调Sig_trade()。策略本身极简:RSI跌破30倾向买、突破70倾向卖;但下单不是只看自己,而是用StringSubstr(symb,0,3)和(3,3)比对Base_Quote,区分基础币/报价币是否为USD,从而决定关联币种该反手还是跟手。 回测观察到一个确定现象:当主对是报价币为USD的品种且出买入信号时,所有报价币为USD的货币对跟买,基础币为USD的则跟卖,逻辑与预设一致。外汇与贵金属杠杆高、滑点突发,实盘前务必在MT5策略测试器用相同分组(如EURUSD/EURGBP/EURJPY配Base_Quote="EUR")先跑一遍验证关联方向。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
CTrade trade;
class="type">int handles[];
class="type">MqlTick previousTick, currentTick;
input class="type">class="kw">string Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY";
input class="type">class="kw">string Base_Quote = "USD";
input class="type">int Currecy_Main = class="num">0;
class="type">class="kw">string symb_List[];
class="type">class="kw">string Formatted_Symbs[];
class="type">int Num_symbs = class="num">0;
class="type">int OnInit(){
    class="type">class="kw">string sprtr = ",";
    class="type">class="kw">ushort usprtr;
    usprtr = StringGetCharacter(sprtr, class="num">0);
    StringSplit(Symbols, usprtr, symb_List);
    Num_symbs = ArraySize(symb_List);
    ArrayResize(Formatted_Symbs, Num_symbs);
for(class="type">int i = class="num">0; i < Num_symbs; i++){
     Formatted_Symbs[i] = symb_List[i];
    }
ArrayResize(handles, ArraySize(Formatted_Symbs));
for(class="type">int i = class="num">0; i < ArraySize(Formatted_Symbs); i++){
     handles[i] = iRSI(Formatted_Symbs[i], PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
    }

class="type">void OnTick(){
  
   if(isNewBar()){
     for(class="type">int i = class="num">0; i < ArraySize(Formatted_Symbs); i++){
        Sig_trade(Formatted_Symbs[Currecy_Main], handles[Currecy_Main]);

「用基准货币过滤关联品种的开平仓」

这段逻辑把 RSI 超买超卖和跨品种相关性绑在一起:当主图 RSI 低于 30 时标记 RSIBuy,高于 70 时标记 RSISell,再据此处理同标的已有持仓并寻找关联品种下单。 函数开头先用 CopyBuffer 取指标主线最近 1 根柱的 RSI 值,rsi[0] 即当前值。判断是否以 Base_Quote(如 USD)为基准或报价货币,靠 StringSubstr(symb,0,3) 与 SymbolInfoString 拿基础/利润货币两套字符串。 持仓遍历里,若同品种已有 BUY 单且 RSI 超买就平仓并把 RSIBuy 置否;已有 SELL 单且 RSI 超卖就平仓并把 RSISell 置否。这样避免同方向重复加仓。 下单阶段扫 Formatted_Symbs 数组:RSIBuy 时,报价货币是基准币就 BUY、基础货币是基准币就 SELL;RSISell 反过来。外汇与贵金属杠杆高,这类关联单在美元急拉时可能同时亏损,实盘前务必在 MT5 策略测试器用 2020—2023 年数据验证相关性假设。

MQL5 / C++
class="type">void Sig_trade(class="type">class="kw">string symb, class="type">int handler){
  class="type">class="kw">double rsi[];
  CopyBuffer(handler, MAIN_LINE, class="num">1, class="num">1, rsi); class=class="str">"cmt">// 从RSI指标句柄拷贝1根柱的主线数据到rsi数组
  class="type">bool RSIBuy = rsi[class="num">0] < class="num">30; class=class="str">"cmt">// RSI当前值小于30,标记潜在买条件
  class="type">bool RSISell = rsi[class="num">0] > class="num">70; class=class="str">"cmt">// RSI当前值大于70,标记潜在卖条件
  class=class="str">"cmt">// Check if the current symbol is a base USD pair
  class="type">bool isBaseUSD = StringSubstr(symb, class="num">0, class="num">3) == Base_Quote; class=class="str">"cmt">// 取交易对前3字符比对基准报价币
  class="type">bool isQuoteUSD = StringSubstr(symb, class="num">3, class="num">3) == Base_Quote; class=class="str">"cmt">// 取交易对第4-6字符比对基准报价币
  class="type">class="kw">string Bcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_BASE); class=class="str">"cmt">// 获取符号基础货币
  class="type">class="kw">string Qcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_PROFIT); class=class="str">"cmt">// 获取符号利润货币
  for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">// 倒序遍历所有持仓
    class="type">class="kw">ulong posTicket = PositionGetTicket(i); class=class="str">"cmt">// 取第i个持仓的票号
    if(PositionGetString(POSITION_SYMBOL) == symb){ class=class="str">"cmt">// 仅处理当前符号持仓
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ class=class="str">"cmt">// 若是多单
        if(RSISell){ class=class="str">"cmt">// 且触发超卖平仓信号
          trade.PositionClose(posTicket); class=class="str">"cmt">// 平掉该多单
        }
        RSIBuy = false; class=class="str">"cmt">// 已有多单则取消买入标记
      }else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ class=class="str">"cmt">// 若是空单
        if(RSIBuy){ class=class="str">"cmt">// 且触发超买平仓信号
          trade.PositionClose(posTicket); class=class="str">"cmt">// 平掉该空单
        }
        RSISell = false; class=class="str">"cmt">// 已有空单则取消卖出标记
      }
    }
  }
  for(class="type">int i = class="num">0; i < ArraySize(Formatted_Symbs); i++){ class=class="str">"cmt">// 遍历关联品种数组
    class="type">class="kw">string currSymb = Formatted_Symbs[i]; class=class="str">"cmt">// 当前关联符号
    class=class="str">"cmt">// Get base and quote currencies for the looped symbol
    class="type">class="kw">string currBaseCurr = SymbolInfoString(currSymb, SYMBOL_CURRENCY_BASE); class=class="str">"cmt">// 关联符号基础币
    class="type">class="kw">string currQuoteCurr = SymbolInfoString(currSymb, SYMBOL_CURRENCY_PROFIT); class=class="str">"cmt">// 关联符号利润币
    if(RSIBuy){ class=class="str">"cmt">// 若满足买入条件
      if(currQuoteCurr == Base_Quote){ class=class="str">"cmt">// 报价币是基准币
        trade.PositionOpen(currSymb, ORDER_TYPE_BUY, volume, currentTick.ask, NULL, NULL, "Correlation"); class=class="str">"cmt">// 开关联多单
      }
      if(currBaseCurr == Base_Quote){ class=class="str">"cmt">// 基础币是基准币
        trade.PositionOpen(currSymb, ORDER_TYPE_SELL, volume, currentTick.bid, NULL, NULL, "Correlation"); class=class="str">"cmt">// 开关联空单
      }
    }
    if(RSISell){ class=class="str">"cmt">// 若满足卖出条件
      if(currBaseCurr == Base_Quote){ class=class="str">"cmt">// 基础币是基准币
        trade.PositionOpen(currSymb, ORDER_TYPE_SELL, volume, currentTick.bid, NULL, NULL, "Correlation"); class=class="str">"cmt">// 开关联空单
      }
      if(currQuoteCurr == Base_Quote){ class=class="str">"cmt">// 报价币是基准币

◍ 用新K线触发来卡开仓节奏

上面那段开仓调用依赖一个前提:不能每 tick 都去发单,否则同一根 bar 里会反复挤仓。用 isNewBar() 把逻辑锁在每根 K 线开头,是这类相关性策略最常见的节流写法。 函数靠一个 static datetime 记住上一次见到的末根 bar 时间。首次进来 last_time 为 0,直接记时间并返 false,避免启动当根误判成新 bar。 之后每次调,都拿 SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE) 取当前末根时间。只要和记忆值不同,就说明换了根 bar,更新记忆并返回 true;否则返 false。这样外层只在返回 true 时跑一次 PositionOpen,外汇与贵金属杠杆高,这种节流能明显减少无效报单和滑点损耗。

MQL5 / C++
trade.PositionOpen(currSymb, ORDER_TYPE_BUY, volume, currentTick.ask, NULL, NULL, "Correlation");
   }
  }
class="type">bool isNewBar()
  {
class=class="str">"cmt">//--- memorize the time of opening of the last bar in the class="kw">static variable
   class="kw">static class="type">class="kw">datetime last_time=class="num">0;
class=class="str">"cmt">//--- current time
   class="type">class="kw">datetime lastbar_time= (class="type">class="kw">datetime) SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);
class=class="str">"cmt">//--- if it is the first call of the function
   if(last_time==class="num">0)
     {
      class=class="str">"cmt">//--- set the time and exit
      last_time=lastbar_time;
      class="kw">return(false);
     }
class=class="str">"cmt">//--- if the time differs
   if(last_time!=lastbar_time)
     {
      class=class="str">"cmt">//--- memorize the time and class="kw">return true
      last_time=lastbar_time;
      class="kw">return(true);
     }
class=class="str">"cmt">//--- if we passed to this line, then the bar is not new; class="kw">return false
   class="kw">return(false);
  }

主货币对驱动下的关联交易落点

把 RSI 信号绑到用户指定的主货币对上,再按正、负相关把指令广播给其他品种,这套逻辑在 MT5 里能直接跑通。EA 不预测方向,只是让基础/报价币种关系决定跟风仓的买卖倾向,属于系统性而非主观判断。 下方代码段暴露了一个实打实的问题:循环里始终把 Currecy_Main 或下标 0 传给 Sig_trade,导致每一轮输出相同。若你下载了 Dynamic_Multi-Pair.mq5(约 6.1 KB),可把双重 for 循环改为单次遍历并跳过主符号自身,大概率能消掉重复信号。 外汇与贵金属杠杆高、滑点突发行情多,关联策略只在流动性重叠时段更稳,实盘前请用策略测试器跑至少三个月 tick 数据验证。

MQL5 / C++
      for(class="type">int i = class="num">0; i < ArraySize(Formatted_Symbs); i++){
         Sig_trade(Formatted_Symbs[Currecy_Main], handles[Currecy_Main]);
      }
      for(class="type">int i = class="num">0; i < ArraySize(Formatted_Symbs); i++){
         Sig_trade(Formatted_Symbs[class="num">0], handles[class="num">0]);
      }

常见问题

算两个品种收盘价的皮尔逊相关系数,绝对值接近1就是同跳或反向,负值代表反向,正值是同跳,拿30~60根K线滚动算更准。
先盯主货币对突破或均线转向,再在同跳品种上顺信号开仓、在反向品种上反手,避免只看单一图表漏掉驱动源。
小布可自动算出各品种间相关系数并标出同跳/反向组,还能在基准货币出信号时提示关联品种的开平仓方向,省去手动拉表。
只在每根K线开盘后触发一次判断,用静态变量记是否已处理,防止同根K线内重复发单打乱节奏。
落点放在关联品种的执行端,由基准信号过滤后在其图表上发单,主图只做判断不下单,逻辑更干净。