使用Python和MQL5进行多品种分析(第三部分):三角汇率(基础篇)
📘

使用Python和MQL5进行多品种分析(第三部分):三角汇率(基础篇)

第 1/3 篇

「用三角汇率给白银报价去噪」

金融市场里假信号造成的无效回撤很常见,很多交易准则只说“等确认”却没说等到什么时候,结果往往错失已看准的入场位。与其每笔都潜在亏点,不如盯住跨市场里可复现的关联模式,把等待变成持续监控。 全球约 90% 大宗商品以美元计价,但白银这类贵金属常同时挂 XAGUSD 与 XAGEUR 两个报价。若你做 XAGUSD,又明知它与 XAGEUR 有底层绑定,就别干等确认,而该把 EURUSD 现价拉进来一起看。 由 XAGUSD、XAGEUR、EURUSD 三者构成的三角,理论上应满足 XAGUSD ≈ XAGEUR × EURUSD。实际报价持续偏离该等式时,往往暴露了普通行情窗口看不到的隐性情绪。接下来要做的,是用 MT5 实时报价验证这套三角的偏离规律,而不是凭感觉扛噪。

◍ 先认货币对,再谈银价镜像

做跨市场之前,先把货币对的结构拆清楚。以 EURUSD 为例,左边 EUR 是基础货币,右边 USD 是报价货币。图表汇率向上远离 0,说明基础货币(欧元)在升值——要换 1 欧元得付出更多美元;汇率越靠近 0,则是报价货币(美元)走强,换 1 欧元需要的美元变少。 这套策略只盯三个市场,但真正下单的只有一个:白银兑美元(XAGUSD)。思路起点是欧元兑美元。若 EURUSD 上行,欧元购买力增强、美元走弱,同一商品用欧元标价倾向更便宜,用美元标价倾向更贵。 把 EURUSD、欧元计价的银价、美元计价的银价放一起看,能搭出一个不依赖传统确认信号的框架。EURUSD 下跌时,预期欧标银更贵、美标银更便宜;若三市场出现这种特定镜像,就做空白银兑美元。反过来想做多,就要看到外汇走势与欧美两边银价变动同向支撑,否则没跨市场背书的单边很容易回吐。 实际推涨推跌白银的因素无数,这里只是用最直的关系去压缩复杂联动。开 MT5 调出 EURUSD 与 XAGUSD 的日线叠图,肉眼核对 2023 年欧美加息周期里的反向段,就能验证这套“镜像”假设成不成立。

这次回测切了哪两段数据

本次策略验证把时间轴劈成两截:训练窗口取 2022-11-01 至 2023-10-31 的整年残差数据,回测窗口压在 2023-11-01 到 2025-01-01,跨度 14 个月。这种切法让模型先在早一年市场里找相互关联的粗糙规律,再丢进没见过的最近段跑真实推演。 本篇仍用手写的简单关联模型,所以训练分区只作内部拟合用,不进本次讨论的正式评估。后续版本计划换成交由机器独立学习的模型,那时训练集才会真正参与闭环。 外汇与贵金属属高杠杆品种,这段回测结论仅说明样本内表现倾向,实盘仍可能因流动性断裂而偏离。开 MT5 把这两段日期钉进策略测试器的日期框,能直接复现图 5 所示的时间覆盖。

「三市场联动的EA骨架怎么搭」

做跨市场套利类策略,第一步是把要盯的标的固化成系统常量。原文用 XAGUSD、XAGEUR、EURUSD 三个符号,本质是在捕捉「白银兑美元」走势是否被「白银兑欧元」和「欧元兑美元」双向印证,这种三角关系比单品种裸看更抗假突破。 MQL5 的事件驱动很适合这块:新报价进来触发 OnTick(),但你不能每 tick 都算一遍,得先判断有没有收出完整 K 线。H1 框架下,从 2023-11-01 到 2025-01 的回测显示,完整 K 线切换时点才是更新变量、扫交易机会的正确入口,比 M1 少很多噪音,又比日线多不少信号。 增长衡量用现价除以过去值就够粗但有效:比值小于 1 说明该市场段内在缩水,大于 1 在扩张。三个市场同时算,就能拼出相对状态配置——这是开仓前的过滤器。 回测用真实 tick 加随机延迟来逼近实盘,资金曲线却呈现盈利爆发期和长期亏损期交替的振荡特征。当前版本能盈利但稳定性差,识别这种周期可能比硬改参数更管用。外汇贵金属自带高杠杆高风险,三角对冲也灭不掉爆仓可能。 下面这段常量定义是整套模型的地基,复制进 MT5 编译器就能跑通前置声明:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//|                 Baseline Model.mq5                               |
class=class="str">"cmt">//|                 Gamuchirai Ndawana                              |
class=class="str">"cmt">//|                 [MQL5官方文档]   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Ndawana"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| System Constants                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define SYMBOL_ONE   "XAGUSD"       class=class="str">"cmt">//--- Our primary  symbol, the price of Silver in USD
class="macro">#define SYMBOL_TWO   "XAGEUR"       class=class="str">"cmt">//--- Our secondary symbol, the price of Silver in EUR
class="macro">#define SYMBOL_THREE "EURUSD"       class=class="str">"cmt">//--- Our EURUSD exchange rate.
class="macro">#define FETCH        class="num">24             class=class="str">"cmt">//--- How many bars of data should we fetch?
class="macro">#define TF_1         PERIOD_H1      class=class="str">"cmt">//--- Our intended time frame

◍ EA 骨架与跨品种向量的初始化

这段 MQL5 片段搭起了一个跨品种套利的 EA 骨架:EURUSD、XAGUSD、XAGEUR 三个品种的价格序列用 vector 容器承接,并在全局直接声明,方便后续在 tick 里做增长差计算。 交易手数被写成宏 VOLUME,取当前品种最小成交量乘 10,例如迷你账户 SYMBOL_VOLUME_MIN 为 0.01 时,实际下单量就是 0.1 手;止损宽度 sl_width 固定为 300 点(3e2 * _Point),外汇与贵金属波动剧烈,这类固定点数止损在白银跳空时可能直接被打穿,实盘前务必在策略测试器里按品种重算。 OnInit 只做一件事:调用 setup() 完成指标与符号选择;OnTick 则把报价推给 new_quotes_received()。后者用静态变量 time_stamp 比对 iTime(SYMBOL_ONE,TF_1,0),只有 1 分钟 K 线换新才跑 update(),避免同根 K 线内重复计算拖慢 MT5。 下面这段代码就是上述骨架的原文,可直接复制到 MT5 的 EA 源文件里验证编译:

MQL5 / C++
class="macro">#define VOLUME         SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * class="num">10       class=class="str">"cmt">//--- Our trading volume
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector eurusd,xagusd,xageur;
class="type">class="kw">double eurusd_growth,xagusd_growth,xageur_growth,bid,ask;
class="type">class="kw">double sl_width = class="num">3e2 * _Point;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Libraries                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include  <Trade\Trade.mqh>
CTrade Trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Setup our technical indicators
   setup();
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- New prices have been quoted
   new_quotes_received();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom functions                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Updates system variables accordingly                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void new_quotes_received(class="type">void)
  {
   class="kw">static class="type">class="kw">datetime time_stamp;
   class="type">class="kw">datetime time = iTime(SYMBOL_ONE,TF_1,class="num">0);
   if(time_stamp != time)
     {
      time_stamp = time;
      update();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Setup our technical indicators and select the symbols we need     |
class=class="str">"cmt">//+------------------------------------------------------------------+

把三品种联动写进刷新循环

多品种套利思路落到 MT5 上,核心是把‘观察’和‘下单’拆成两个独立函数,由主刷新例程统一调度。上面这段把 XAGUSD、XAGEUR、EURUSD 的收盘价增长比算出来,再按组合条件触发单边入场,属于典型的跨品种相对强弱判定。 setup() 只在初始化时把三个交易品种加进市场观察窗,避免后续 CopyRates 取不到数据;真正干活的是 update(),它每次被定时器或 OnTick 调用时重新拉取 1 根 K 线的收盘价(FETCH 为数组长度),用最新值除以前一根算出 growth。 growth 小于 1 代表该品种在当前周期收跌、大于 1 代表收涨。当 EURUSD 与 XAGUSD 同向下跌、XAGEUR 却上涨时,脚本倾向卖出 SYMBOL_ONE;反过来三者反向排列则买入。外汇与贵金属杠杆高,这种联动失效时回撤可能很快,参数需自行回测。 find_setup() 里用 Trade.Sell / Trade.Buy 的止损写法值得注意:卖单 SL 放在 ask+sl_width、买单 SL 放在 bid-sl_width,是以挂单成交价为基准向外扩,不是简单对称。manage_setup() 开头用 PositionSelect 锁定 SYMBOL_ONE 持仓,后续才能读 current_sl 做移动止损,这部分逻辑在贴出的片段里只开了个头。

MQL5 / C++
class="type">void setup(class="type">void)
  {
class=class="str">"cmt">//--- Select the symbols we need
   SymbolSelect(SYMBOL_ONE,true);
   SymbolSelect(SYMBOL_TWO,true);
   SymbolSelect(SYMBOL_THREE,true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update our system setup                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void update(class="type">void)
  {
class=class="str">"cmt">//--- Fetch updated prices
   xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,class="num">1,FETCH);
   xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,class="num">1,FETCH);
   eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,class="num">1,FETCH);
class=class="str">"cmt">//--- Calculate the growth in market prices
   eurusd_growth = eurusd[class="num">0] / eurusd[FETCH - class="num">1];
   xageur_growth = xageur[class="num">0] / xageur[FETCH - class="num">1];
   xagusd_growth = xagusd[class="num">0] / xagusd[FETCH - class="num">1];
class=class="str">"cmt">//--- Update system variables
   SymbolSelect(SYMBOL_ONE,true);
   bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID);
   ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK);
class=class="str">"cmt">//--- Check if we need to setup a new position
   if(PositionsTotal() == class="num">0)
      find_setup();
class=class="str">"cmt">//--- Check if we need to manage our positions
   if(PositionsTotal() > class="num">0)
      manage_setup();
class=class="str">"cmt">//--- Give feedback on the market growth
   Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Find setup                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void find_setup(class="type">void)
  {
   class=class="str">"cmt">//--- Check if the current market setup matches our expectations for selling
   if((eurusd_growth < class="num">1) && (xageur_growth > class="num">1) && (xagusd_growth < class="num">1))
    {
      Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),"");
    }
   class=class="str">"cmt">//--- Check if the current market setup matches our expectations for buying
   if((eurusd_growth > class="num">1) && (xageur_growth < class="num">1) && (xagusd_growth > class="num">1))
    {
      Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),"");
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Manage setup                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void manage_setup(class="type">void)
  {
   class=class="str">"cmt">//--- Select our open position
   if(PositionSelect(SYMBOL_ONE))
    {
      class="type">class="kw">double current_sl = PositionGetDouble(POSITION_SL);

常见问题

先锁定与白银联动的货币三角组合,用其相对强弱做滤波权重,剔除脱离货币背景的银价毛刺,只保留跨市场共振段。
银价常是货币强弱的镜像,先确认主导货币对方向,才能判断银价异动是真实突破还是汇率噪声,避免逆势误判。
小布可自动拉取多品种联动数据、标记三角汇率异常时段,并把去噪后的银价镜像直接推到品种页,省去手动核对。
建议切一段美联储加息预期升温期、一段避险情绪陡增期,两段分别验证汇率传导与银价跟随的不同强度。
在初始化阶段建好跨品种向量,刷新循环只更新价差与三角权重,不重复创建对象,控制调用频率即可流畅跑。