重思经典策略(第八部分):基于美元兑加元(USDCAD)探讨外汇市场与贵金属·综合运用
🪙

重思经典策略(第八部分):基于美元兑加元(USDCAD)探讨外汇市场与贵金属·综合运用

(3/3)·从相关系数到MQL5落地,拆解美元兑加元怎么吃贵金属的跨市场信息

含代码示例偏理论 第 3/3 篇

不少老手仍把黄金与美元的反向关系当铁律,却在量化宽松年代被反复打脸。用直觉押注跨市场传导,往往忽略钯金这类工业金属的真实权重。本篇把前三篇的模型训练收口到可运行的交易终端里。

把回归模型落进ONNX的实操链路

ONNX 是一套与语言无关的模型交换格式,核心思路是把机器学习模型拆成节点与图结构,再用支持该 API 的环境重新拼回原模型。对做跨平台推理的交易者来说,这意味着在 Python 里训好的东西可以脱离 sklearn 直接进其他运行时。 这一步先确认工具链版本:onnx 1.15.0、netron 7.8.0、skl2onnx 1.16.0。版本对齐能避开不少转换期的隐性报错,尤其 skl2onnx 对小版本差异挺敏感。 模型输入被显式定义为 1×12 的浮点张量,对应 12 个特征列。用全量数据拟合了 LinearSVR(tol=1e-05,loss='squared_epsilon_insensitive',epsilon=0,C=10000),随后转 ONNX 并存为 "USDCAD XAUUSD XPDUSD M1 Float.onnx"。外汇与贵金属 M1 周期信号迁移到 ONNX 后,部署风险仍高,实盘前务必在 MT5 做样本外核对。 用 netron 起本地视图能看到节点图和输入输出签名,确认参数形态与预期一致再往下走,不然后面推理端会卡在 dtype 不匹配上。

MQL5 / C++
class="macro">#Let&class="macro">#x27;s prepare to class="kw">export our model to ONNX format
class="kw">import onnx
class="kw">import netron
class="kw">import skl2onnx
from skl2onnx class="kw">import convert_sklearn
from skl2onnx.common.data_types class="kw">import FloatTensorType
class="macro">#Display library versions
print(f"Onnx version: {onnx.__version__}")
print(f"Netron version: {netron.__version__}")
print(f"Skl2onnx version: {skl2onnx.__version__}")
class="macro">#Define the class="kw">input type
initial_types = [(&class="macro">#x27;float_input&class="macro">#x27;,FloatTensorType([class="num">1,class="num">12]))]
class="macro">#Train the model on all the data we have
customized_lsvr = LinearSVR(tol=class="num">1e-05,loss=&class="macro">#x27;squared_epsilon_insensitive&class="macro">#x27;,epsilon=class="num">0,C=class="num">10000)
customized_lsvr.fit(X,y)
class="macro">#Covert the sklearn model
onnx_model = convert_sklearn(customized_lsvr,initial_types=initial_types)
class="macro">#Save the onnx model
onnx_name = "USDCAD XAUUSD XPDUSD M1 Float.onnx"
onnx.save(onnx_model,onnx_name)
class="macro">#View the onnx model
netron.start(onnx_name)

「把ONNX模型塞进EA的运行骨架」

在MT5里跑AI信号,第一步是把训练好的ONNX模型编译进EA。用 #resource.onnx 文件以字节数组形式嵌进程序,再用 OnnxCreateFromBuffer 从内存缓冲区建模型,避免运行时去磁盘找文件。 加载交易库用 <Trade/Trade.mqh> 里的 CTrade,开平仓都靠它。全局变量里要预留12维的均值与标准差数组 mean_values[12]std_values[12],这是做特征缩放用的,少一维模型输入形状就对不上。 load_onnx 这个函数只做两件事:从buffer建模型,然后设输入输出形状。输入定为 {1,12}、输出 {1,1},即一次喂12个特征、吐1个预测值。若 OnnxSetInputShape 返回false,直接在图表上 Comment 报错并终止,不然后面全错。 初始化时先调 load_onnx 再读CSV里的缩放值,任一步失败就 return INIT_FAILED 结束EA;OnDeinit 里释放模型资源。每次 OnTick 更新 ask/bid 后重算预测,无仓按模型方向开,有仓则检查预测是否反向,反向就倾向平仓或反向对冲。外汇与贵金属杠杆高,模型信号错一次可能吞掉周盈利,实盘前务必用策略测试器跑USDCAD M1回测。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 USDCAD AI.mq5 |
class=class="str">"cmt">//|                                                     Gamuchirai Zororo Ndawana |
class=class="str">"cmt">//|                                        [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Zororo 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">//| Resources                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#resource  "\\Files\\USDCAD XAUUSD XPDUSD M1 Float.onnx" as class="kw">const class="type">uchar onnx_buffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Libraries we need                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade/Trade.mqh>
CTrade Trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long onnx_model;
class="type">class="kw">double mean_values[class="num">12],std_values[class="num">12];
vectorf model_output = vectorf::Zeros(class="num">1);
class="type">int model_forecast,state;
class="type">class="kw">double ask,bid;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| This function is responsible for loading our ONNX model               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool load_onnx(class="type">void)
  {
class=class="str">"cmt">//--- First we must create the model from the buffer
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
class=class="str">"cmt">//--- Now we shall define our I/O shape
   class="type">class="kw">ulong input_shape [] = {class="num">1,class="num">12};
   class="type">class="kw">ulong output_shape []  = {class="num">1,class="num">1};
   if(!OnnxSetInputShape(onnx_model,class="num">0,input_shape))
     {
      Comment("Failed to set ONNX class="kw">input shape!");
      class="kw">return(class="kw">false);
     }

◍ 从 CSV 把标准化参数喂进数组

把 ONNX 推理用的均值和标准差从外部 CSV 读进来,是模型在 MT5 里跑起来的前提。下面这段 load_scaling_factors 函数专门干这事:打开 usd_cad_xau_xpd_scaling_factors.csv,按行扫,把第 14~25 行写进 mean_values,第 27~38 行写进 std_values。

文件用 FILE_READFILE_CSVFILE_ANSI 打开,分隔符是逗号。循环里有个 counter > 100 的硬熔断,防止 csv 异常时死读;真正落库只看 counter 在 [14,26) 和 [27,39) 两个区间,说明这份因子表前 13 行可能是表头或无关元信息。

外汇与贵金属行情的高波动会让标准化参数失效偏快,建议每次换品种或重训模型后重导该 csv。开 MT5 把文件丢进 MQL5/Files,跑一遍看 ArrayPrint 打出的 mean_values 是不是 12 个浮点,就能验证读取偏移对不对。

MQL5 / C++
if(!OnnxSetOutputShape(onnx_model,class="num">0,output_shape))
      {
      Comment("Failed to set ONNX output shape!");
      class="kw">return(class="kw">false);
      }
class=class="str">"cmt">//--- Everything went fine
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| This function will read our scaling factors and store them         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool load_scaling_factors(class="type">void)
  {
class=class="str">"cmt">//--- Read in the file
   class="type">class="kw">string file_name = "usd_cad_xau_xpd_scaling_factors.csv";
class=class="str">"cmt">//--- Try open the file
   class="type">int result = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,","); class=class="str">"cmt">//Strings of ANSI type(one byte symbols).
class=class="str">"cmt">//--- Check the result
   if(result != INVALID_HANDLE)
      {
      Print("Opened the file");
      class=class="str">"cmt">//--- Store the values of the file
      class="type">int counter = class="num">0;
      class="type">class="kw">string value = "";
      class="kw">while(!FileIsEnding(result) && !IsStopped()) class=class="str">"cmt">//read the entire csv file to the end
      {
      if(counter > class="num">100)  class=class="str">"cmt">//if you aim to read class="num">10 values set a break point after class="num">10 elements have been read
            break; class=class="str">"cmt">//stop the reading progress
      value = FileReadString(result);
      Print("Trying to read class="type">class="kw">string: ",value," count value: ",counter);
      class=class="str">"cmt">//--- Check where we are
      if((counter >= class="num">14) && (counter < class="num">26))
      {
            mean_values[counter - class="num">14] = (class="type">class="kw">float) value;
      }
      class=class="str">"cmt">//--- Check where we are
      if((counter >= class="num">27) && (counter < class="num">39))
      {
            std_values[counter - class="num">27] = (class="type">class="kw">float) value;
      }
      class=class="str">"cmt">//--- Reading a new row
      if(FileIsLineEnding(result))
      {
            Print("row++");
      }
      counter++;
      }
      class=class="str">"cmt">//---Close the file
      ArrayPrint(mean_values);
      ArrayPrint(std_values);
      FileClose(result);
      class="kw">return(true);
      }
class=class="str">"cmt">//--- We failed to find the file
   else
      {

把多品种报价塞进ONNX模型前先标准化

这段逻辑干的事很直接:先抓 USDCAD、XAUUSD、XPDUSD 三个品种当前 K 线的开高低收,拼成长度 12 的向量,再丢给训练好的 ONNX 模型跑推理。外汇与贵金属跨品种联合输入时,量纲差异极大——金价 1900 上下,美加汇率 1.3 上下,不标准化模型输出会失真,这是实盘前必须验证的点。 标准化循环写死了 i<12,对应前面三个品种各 4 个价格字段。每一维都用各自预存的均值 mean_values[i] 和标准差 std_values[i] 做 z-score,若你换品种或加字段,数组长度和归一化参数必须同步改,否则 OnnxRun 可能直接报错或给出无意义预测。 模型输出 model_output[0] 只跟 USDCAD 当前收盘比大小:高于收盘则 model_forecast 置 1(倾向上涨),低于则置 -1(倾向下跌)。这种二值化很粗糙,真实外汇高风险环境下建议把阈值做成可调参数,而非硬比现价。

MQL5 / C++
class="type">void update_market_prices(class="type">void)
  {
  ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
  bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
  }
class="type">void model_predict(class="type">void)
  {
class=class="str">"cmt">//--- First fetch the market data
  vectorf model_input =
    {
    iOpen("USDCAD",PERIOD_CURRENT,class="num">0),iHigh("USDCAD",PERIOD_CURRENT,class="num">0),iLow("USDCAD",PERIOD_CURRENT,class="num">0),iClose("USDCAD",PERIOD_CURRENT,class="num">0),
    iOpen("XAUUSD",PERIOD_CURRENT,class="num">0),iHigh("XAUUSD",PERIOD_CURRENT,class="num">0),iLow("XAUUSD",PERIOD_CURRENT,class="num">0),iClose("XAUUSD",PERIOD_CURRENT,class="num">0),
    iOpen("XPDUSD",PERIOD_CURRENT,class="num">0),iHigh("XPDUSD",PERIOD_CURRENT,class="num">0),iLow("XPDUSD",PERIOD_CURRENT,class="num">0),iClose("XPDUSD",PERIOD_CURRENT,class="num">0)
    };
class=class="str">"cmt">//--- Now standardize and scale the data
  for(class="type">int i =class="num">0; i < class="num">12; i++)
    {
    model_input[i] = ((model_input[i] - mean_values[i]) / std_values[i]);
    }
class=class="str">"cmt">//--- Now fetch a prediction from our model
  OnnxRun(onnx_model,ONNX_DEFAULT,model_input,model_output);
class=class="str">"cmt">//--- Store our model&class="macro">#x27;s forecat
  if(model_output[class="num">0] > iClose("USDCAD",PERIOD_CURRENT,class="num">0))
    {
    model_forecast = class="num">1;
    }
  else
    if(model_output[class="num">0] < iClose("USDCAD",PERIOD_CURRENT,class="num">0))
    {
    model_forecast = -class="num">1;
    }
  }

「EA 生命周期与 ONNX 推理主循环」

把训练好的 ONNX 模型塞进 MT5,关键在 OnInit 里把模型和归一化参数一次性读进来。只要 load_onnx() 或 load_scaling_factors() 任一失败,直接返回 INIT_FAILED,EA 不会上线跑单,避免用错权重做决策。 OnDeinit 只做一件事:OnnxRelease 释放模型句柄,再 ExpertRemove 自卸。不清理显存/内存句柄,多次加载策略容易把终端拖死。 OnTick 的逻辑很直白:先 update_market_prices() 刷最新价,再 model_predict() 取模型信号。无持仓时,forecast 为 -1 就 Trade.Sell(0.2, _Symbol, ask, 0, 0, "USDCAD AI") 做空 0.2 手,为 1 则同参数 Buy 做多,并把 state 记成对应方向。 有持仓后若 state 与最新 model_forecast 不一致,视为反转,Alert 弹窗后 PositionClose 平掉当前符号所有仓位。外汇与贵金属杠杆高,这类硬平可能落在滑点扩大的瞬间,实盘前建议在策略测试器用 USDCAD 历史数据跑至少 3 个月验证信号翻转频率。

MQL5 / C++
class=class="str">"cmt">//| Expert initialization function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- This function will load our ONNX model
   if(!load_onnx())
      {
      class="kw">return(INIT_FAILED);
      }
class=class="str">"cmt">//--- This function will load our scaling factors
   if(!load_scaling_factors())
      {
      class="kw">return(INIT_FAILED);
      }
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(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//--- Free up the resources we do not need
   OnnxRelease(onnx_model);
   ExpertRemove();
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Update the market prices
   update_market_prices();
class=class="str">"cmt">//--- Fetch a forecast from our model
   model_predict();
class=class="str">"cmt">//--- Find a trading oppurtunity
   if(PositionsTotal() == class="num">0)
      {
      if(model_forecast == -class="num">1)
      {
      Trade.Sell(class="num">0.2,_Symbol,ask,class="num">0,class="num">0,"USDCAD AI");
      state = -class="num">1;
      }
      else
      if(model_forecast == class="num">1)
      {
            Trade.Buy(class="num">0.2,_Symbol,ask,class="num">0,class="num">0,"USDCAD AI");
            state = class="num">1;
      }
      }
class=class="str">"cmt">//--- Check for reversals
   if(PositionsTotal() > class="num">0)
      {
      if(state != model_forecast)
      {
      Alert("Reversal detected by our AI system, closing all positions now!");
      Trade.PositionClose(_Symbol);
      }
      }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 把这条线请下神坛

这套跨市场关联分析跑下来,实证结果偏向一个反直觉结论:普通行情数据加简单线性模型,往往够用。噪声是主因——分钟级 USDCAD、XAUUSD、XPDUSD 这类品种序列里干扰项太多,复杂模型对输入微变过于敏感,反而容易过拟合。 附件里 USDCAD_AI.mq5(7.77 KB)和对应的 onnx 浮点模型(0.32 KB)可以直接拖进 MT5 验证:用默认参数跑 USDCAD 的 M1,对比你手头任意神经网络 EA,夏普未必更好。外汇与贵金属杠杆高、回撤凶,验证时请先用模拟盘。 所以别把隐藏关系当圣杯。开 MT5 把那几个缩放因子 csv(0.56 KB)配上,跑一遍自有样本,比信任何‘关联暴利’说辞都实在。

让小布替你跑这套跨市校验
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到XAUUSD、XPDUSD与USDCAD的实时相关面板,把重复劳动交给小布,你专注决策。

常见问题

意味着样本期内两者倾向反向移动,但-0.5只是中等强度,单凭它下单概率上并不稳,需结合钯金与量价结构过滤。
说明线性可视维度下边界高度重叠,关系可能非线性或受第三方变量干扰,这也是后续要导ONNX让模型学隐结构的原因。
MetaTrader 5原生支持ONNX推理,用Python训好导出后,MQL5侧通过标准接口载入权重即可,无需外接进程。
可以,品种页的AIGC模块会跟踪XAU/XPD偏离带,并标注外汇端可能的滞后反应,省去自己拉CSV算相关的步骤。
中等相关说明它们驱动外汇的路径不同,组合进因子能覆盖更多 regimens,比单金属更抗样本漂移。