在类中包装 ONNX 模型·综合运用
🧩

在类中包装 ONNX 模型·综合运用

(3/3)· 当三个 ONNX 模型挤进一个 EA,函数式代码会膨胀到难以维护,基类封装是终局解法

偏理论 第 3/3 篇
把多个 ONNX 模型硬塞进单一 MQ5 文件的函数里,每次加模型或换输入维度,源码就胖一圈。用类把接口和会话管理收拢,后续扩展只写衍生类,不碰主流程。

给 ONNX 模型喂 EURUSD 日线三通道数据

在 MT5 里加载一个针对 EURUSD 日线、样本长度 30 的 ONNX 分类模型时,输入张量形状必须显式声明为 {1, 30, 3}:第一维批大小、第二维 30 根 K 线、第三维 3 个序列(收盘价、快均线、慢均线)。输出张量因不能自动推断,要手动设成 {1, 3},对应涨 / 平 / 跌三类概率。 下面这段初始化代码把形状绑定到模型句柄,并创建两条 SMA 指标句柄。若任一环节返回无效句柄或设置失败,直接返回 false 并打印错误码,方便在专家日志里定位是模型文件问题还是指标参数问题。 预测阶段先用静态 matrixf 开一个 30×3 的输入矩阵,再用 CopyRates 拉最近 30 根收盘价填进临时向量,随后求均值做归一化。注意 m_sample_size 写死 30 时,换周期或换品种前要确认模型本来就是按这个尺度训练的,否则推理结果会偏移。外汇与贵金属杠杆高,模型输出只是概率倾向,实盘前务必在策略测试器里跑一遍验证。

MQL5 / C++
  class=class="str">"cmt">//--- first index - batch size, second index - series size, third index - number of series(Close, MA fast, MA slow)
  const class="type">long input_shape[] = {class="num">1,m_sample_size,class="num">3};
  if(!OnnxSetInputShape(m_handle,class="num">0,input_shape))
    {
     Print("model_eurusd_D1_30_class : OnnxSetInputShape error ",GetLastError());
     class="kw">return(false);
    }

  class=class="str">"cmt">//--- since not all sizes defined in the output tensor we must set them explicitly
  class=class="str">"cmt">//--- first index - batch size, must match the batch size of the input tensor
  class=class="str">"cmt">//--- second index - number of classes(up, same or down)
  const class="type">long output_shape[] = {class="num">1,class="num">3};
  if(!OnnxSetOutputShape(m_handle,class="num">0,output_shape))
    {
     Print("model_eurusd_D1_30_class : OnnxSetOutputShape error ",GetLastError());
     class="kw">return(false);
    }
  class=class="str">"cmt">//--- indicators
  m_sma_fast=iMA(m_symbol,m_period,m_fast_period,class="num">0,MODE_SMA,PRICE_CLOSE);
  m_sma_slow=iMA(m_symbol,m_period,m_slow_period,class="num">0,MODE_SMA,PRICE_CLOSE);
  if(m_sma_fast==INVALID_HANDLE || m_sma_slow==INVALID_HANDLE)
    {
     Print("model_eurusd_D1_30_class : cannot create indicator");
     class="kw">return(false);
    }
  class=class="str">"cmt">//--- ok
  class="kw">return(true);
   }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Predict Class                                                     |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="kw">virtual class="type">int PredictClass(class="type">void)
   {
   class="kw">static matrixf input_data(m_sample_size,class="num">3);    class=class="str">"cmt">// matrix for prepared input data
   class="kw">static vectorf output_data(class="num">3);                 class=class="str">"cmt">// vector to get result
   class="kw">static matrix  x_norm(m_sample_size,class="num">3);        class=class="str">"cmt">// matrix for prices normalize
   class="kw">static vector  vtemp(m_sample_size);
   class="kw">static class="type">class="kw">double  ma_buffer[];

   class=class="str">"cmt">//--- request last bars
   if(!vtemp.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,class="num">1,m_sample_size))
     class="kw">return(-class="num">1);
   class=class="str">"cmt">//--- get series Mean
   class="type">class="kw">double m=vtemp.Mean();

「把均线序列塞进神经网络前先标准化」

这段逻辑做的是推理前的特征工程:把快、慢 SMA 以及原始序列都做 Z-Score 标准化,再拼成输入矩阵喂给 ONNX 模型。外汇与贵金属市场高杠杆、波动剧烈,标准化只是降低量纲差异,不预示任何方向。 先取序列标准差 s 与均值 m,用 vtemp-=m; vtemp/=s; 把第 0 列变成零均值单位方差。快 SMA 通过 CopyBuffer(m_sma_fast,0,1,m_sample_size,ma_buffer) 取最近 m_sample_size 根,同样减均值除标准差后写入第 1 列;慢 SMA 复刻一遍进第 2 列。 三列齐了调用 OnnxRun 跑推理,最后 output_data.ArgMax() 返回概率最大的类别编号。若任意 CopyBuffer 取不到足量样本直接 return(-1),模型不发车。开 MT5 把 m_sample_size 调到 60 和 120 分别测,能直观看到标准化后输入分布对推理延迟的影响。

MQL5 / C++
class=class="str">"cmt">//--- get series Std
class="type">class="kw">double s=vtemp.Std();
class=class="str">"cmt">//--- normalize
vtemp-=m;
vtemp/=s;
x_norm.Col(vtemp,class="num">0);
class=class="str">"cmt">//--- fast sma
if(CopyBuffer(m_sma_fast,class="num">0,class="num">1,m_sample_size,ma_buffer)!=m_sample_size)
   class="kw">return(-class="num">1);
vtemp.Assign(ma_buffer);
m=vtemp.Mean();
s=vtemp.Std();
vtemp-=m;
vtemp/=s;
x_norm.Col(vtemp,class="num">1);
class=class="str">"cmt">//--- slow sma
if(CopyBuffer(m_sma_slow,class="num">0,class="num">1,m_sample_size,ma_buffer)!=m_sample_size)
   class="kw">return(-class="num">1);
vtemp.Assign(ma_buffer);
m=vtemp.Mean();
s=vtemp.Std();
vtemp-=m;
vtemp/=s;
x_norm.Col(vtemp,class="num">2);

class=class="str">"cmt">//--- run the inference
input_data.Assign(x_norm);
if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data))
   class="kw">return(-class="num">1);
class=class="str">"cmt">//--- evaluate prediction
class="kw">return(class="type">int(output_data.ArgMax()));
 }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ModelEurusdD1_30Class.mqh"
class="macro">#include <Trade\Trade.mqh>
input class="type">class="kw">double InpLots = class="num">1.0;    class=class="str">"cmt">// Lots amount to open position
CModelEurusdD1_30Class ExtModel;
CTrade            ExtTrade;

◍ EURUSD日线63根收盘价的分类模型

第三个模型文件名为 model.eurusd.D1.63.class.onnx,训练数据取自 EURUSD 日线连续 63 根收盘价序列。它在这组实验里结构最轻,PredictClass 方法因此写得极短,EA 里只需改两行引用就能切换过去。 回测时沿用前两个模型的相同参数直接跑,模型在样本外有响应,说明 63 根日线收盘价作为输入维度,对分类任务够用。外汇品种波动受宏观事件驱动,日线级信号失效概率不低,实盘前务必在 MT5 策略测试器用历史数据复验。 让小布替你跑这套 把下面包装类的两行资源引用和构造参数抄进你的 EA,把前模型头文件替换掉,就能在 MT5 里加载该 ONNX 看分类输出。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 ModelEurusdD1_63.mqh |
class=class="str">"cmt">//|                                                    Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ModelSymbolPeriod.mqh"
class="macro">#resource "Python/model.eurusd.D1.class="num">63.class.onnx" as class="type">uchar model_eurusd_D1_63_class[]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ONNX-model wrapper class                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CModelEurusdD1_63Class : class="kw">public CModelSymbolPeriod
  {
class="kw">private:
   class="type">int               m_sample_size;
class="kw">public:
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| Constructor                                                              |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   CModelEurusdD1_63Class(class="type">void) : CModelSymbolPeriod("EURUSD",PERIOD_D1,class="num">0.0001)
     {
      m_sample_size=class="num">63;
     }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| ONNX-model initialization                                              |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="kw">virtual class="type">bool Init(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES period)
     {
      class=class="str">"cmt">//--- check symbol, period, create model
      if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_63_class))
        {
         Print("model_eurusd_D1_63_class : initialization error");
         class="kw">return(false);
        }
      class=class="str">"cmt">//--- since not all sizes defined in the input tensor we must set them explicitly
      class=class="str">"cmt">//--- first index - batch size, second index - series size
      const class="type">long input_shape[] = {class="num">1,m_sample_size};
      if(!OnnxSetInputShape(m_handle,class="num">0,input_shape))
        {
         Print("model_eurusd_D1_63_class : OnnxSetInputShape error ",GetLastError());
         class="kw">return(false);
        }

      class=class="str">"cmt">//--- since not all sizes defined in the output tensor we must set them explicitly

给 EURUSD 日线模型接上输出与推理

这段 MT5 代码承接了 ONNX 模型的输出形状绑定与推理调用。输出张量被硬性设为 {1,3},第一维是批量大小(只能为 1),第二维对应三类方向判定:涨、平、跌。若 OnnxSetOutputShape 返回失败,直接打印错误码并退出,模型句柄不可用。 PredictClass 里先按 m_sample_size 拉取最近收盘价序列,用 Mean 和 Std 做标准化:input_data-=m 再 /=s,把价格压到零均值单位方差。这一步若不做,EURUSD 的千点级报价会直接冲垮小权重网络。 推理走 OnnxRun 且标记 ONNX_NO_CONVERSION,结果写进长度为 3 的 output_data,ArgMax 取最大置信项转成整型返回 0/1/2。EA 主体引入 ModelEurusdD1_63Class.mqh 后,用 InpLots 控制手数,接 CTrade 下单。外汇与贵金属杠杆高,模型信号只代表概率倾向,实盘前请在策略测试器跑完整回测。

MQL5 / C++
class=class="str">"cmt">//--- first index - batch size, must match the batch size of the input tensor
class=class="str">"cmt">//--- second index - number of classes(up, same or down)
const class="type">long output_shape[] = {class="num">1,class="num">3};
if(!OnnxSetOutputShape(m_handle,class="num">0,output_shape))
  {
   Print("model_eurusd_D1_63_class : OnnxSetOutputShape error ",GetLastError());
   class="kw">return(false);
  }
class=class="str">"cmt">//--- ok
class="kw">return(true);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Predict class                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">virtual class="type">int PredictClass(class="type">void)
  {
   class="kw">static vectorf input_data(m_sample_size);   class=class="str">"cmt">// vector for prepared input data
   class="kw">static vectorf output_data(class="num">3);              class=class="str">"cmt">// vector to get result

   class=class="str">"cmt">//--- request last bars
   if(!input_data.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,class="num">1,m_sample_size))
      class="kw">return(-class="num">1);
   class=class="str">"cmt">//--- get series Mean
   class="type">float m=input_data.Mean();
   class=class="str">"cmt">//--- get series Std
   class="type">float s=input_data.Std();
   class=class="str">"cmt">//--- normalize prices
   input_data-=m;
   input_data/=s;

   class=class="str">"cmt">//--- run the inference
   if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data))
      class="kw">return(-class="num">1);
   class=class="str">"cmt">//--- evaluate prediction
   class="kw">return(class="type">int(output_data.ArgMax()));
  }
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ModelEurusdD1_63Class.mqh"
class="macro">#include <Trade\Trade.mqh>
input class="type">class="kw">double InpLots = class="num">1.0;     class=class="str">"cmt">// Lots amount to open position
CModelEurusdD1_63Class ExtModel;
CTrade                ExtTrade;

「把三个模型塞进一个EA做硬投票」

三个独立模型各自都能跑出正期望,下一步自然是把它们的判断揉到一起。做法很直接:让每个模型对下一根K线方向投一票,再按多数票决策,这路数在集成学习里叫硬投票。 票数的门槛按「总票数/2 + 1」算。我们挂了3个模型,所以多数票就是2票——任意两个模型指向同一类,EA才认这个信号。 单模型的历史战绩分别是:模型一盈利11笔、亏损3笔;模型二11笔里赢6亏1;模型三16赢10亏。硬投票把综合结果拉到了16赢4亏,表面看是改善了,但外汇和贵金属杠杆高、滑点跳空随时来,这种回测比例只代表过去样本,实盘可能明显走形,必须拉完整报告看权益曲线再说。 下面这段是把三个模型头文件纳进同一个EA并做计票的核心代码,注意 ExtModels 数组长度写死为3,returned 里某个类计到2就直接 break 出循环确定预测类。

MQL5 / C++
class="macro">#include "ModelEurusdD1_10Class.mqh"
class="macro">#include "ModelEurusdD1_30Class.mqh"
class="macro">#include "ModelEurusdD1_63Class.mqh"
class="macro">#include <Trade\Trade.mqh>
input class="type">class="kw">double  InpLots  = class="num">1.0;      class=class="str">"cmt">// Lots amount to open position
CModelSymbolPeriod *ExtModels[class="num">3];
CTrade             ExtTrade;
class="type">int OnInit()
  {
   ExtModels[class="num">0]=new CModelEurusdD1_10Class;
   ExtModels[class="num">1]=new CModelEurusdD1_30Class;
   ExtModels[class="num">2]=new CModelEurusdD1_63Class;
   for(class="type">long i=class="num">0; i<ExtModels.Size(); i++)
      if(!ExtModels[i].Init(_Symbol,_Period))
         class="kw">return(INIT_FAILED);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
   for(class="type">long i=class="num">0; i<ExtModels.Size(); i++)
      if(!ExtModels[i].CheckOnTick())
         class="kw">return;
class=class="str">"cmt">//--- predict next price movement
   class="type">int returned[class="num">3]={class="num">0,class="num">0,class="num">0};
class=class="str">"cmt">//--- collect returned classes
   for(class="type">long i=class="num">0; i<ExtModels.Size(); i++)
     {
      class="type">int pred=ExtModels[i].PredictClass();
      if(pred>=class="num">0)
         returned[pred]++;
     }
class=class="str">"cmt">//--- get one prediction for all models
   class="type">int predicted_class=-class="num">1;
class=class="str">"cmt">//--- count votes for predictions
   for(class="type">int n=class="num">0; n<class="num">3; n++)
     {
      if(returned[n]>=class="num">2)
        {
         predicted_class=n;
         class="kw">break;
        }
     }
class=class="str">"cmt">//--- check trading according to prediction
   if(predicted_class>=class="num">0)
      if(PositionSelect(_Symbol))
         CheckForClose(predicted_class);
      else
         CheckForOpen(predicted_class);
  }

◍ 软投票把概率加总而非数人头

硬投票看三个模型里多少票指向同一类,软投票换了个算法:把三个模型对三个类的输出概率直接求和,取总和最高的类作为信号。基类里要把 PredictClass 原先的「独热赋值」改成「拷贝模型输出概率」,派生类则直接把 output_data 赋给 probabilities 并返回 ArgMax。 EA 侧需要声明一个三维零向量 soft,每次循环里用 prob 接住单模型概率后执行 soft+=prob 累加。输入参数挂出 EnVotes 枚举,选 Soft=4 即走这套逻辑,测试设置和前几篇保持一致。 回测里软投票跑出 15 笔盈利、3 笔无盈利,但资金曲线弱于硬投票。若只统计三模型全票(3/3)的软投票信号,交易次数极少且非常保守,唯一一笔无盈利单在测试尾段平仓,实际未必真亏。 外汇与贵金属杠杆高、滑点跳空频繁,这套 ONNX 调用仅演示 MQL5 工程写法,模型未经过实盘校验,直接上真实账户大概率失控。

MQL5 / C++
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Predict class (regression -> classification)                      |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="kw">virtual class="type">int PredictClass(vector& probabilities)
    {
...
      class=class="str">"cmt">//--- set predicted probability as class="num">1.0
      probabilities.Fill(class="num">0);
      if(predicted_class<(class="type">int)probabilities.Size())
        probabilities[predicted_class]=class="num">1;
      class=class="str">"cmt">//--- and class="kw">return predicted class
      class="kw">return(predicted_class);
    }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Predict class                                                    |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="kw">virtual class="type">int PredictClass(vector& probabilities)
    {
...
      class=class="str">"cmt">//--- evaluate prediction
      probabilities.Assign(output_data);
      class="kw">return(class="type">int(output_data.ArgMax()));
    }
class="macro">#include "ModelEurusdD1_10Class.mqh"
class="macro">#include "ModelEurusdD1_30Class.mqh"
class="macro">#include "ModelEurusdD1_63Class.mqh"
class="macro">#include <Trade\Trade.mqh>
enum EnVotes
  {
   Two=class="num">2,     class=class="str">"cmt">// Two votes
   Three=class="num">3,   class=class="str">"cmt">// Three votes
   Soft=class="num">4     class=class="str">"cmt">// Soft voting
   };
input class="type">class="kw">double  InpLots  = class="num">1.0;      class=class="str">"cmt">// Lots amount to open position
input EnVotes InpVotes = Two;      class=class="str">"cmt">// Votes to make trade decision
CModelSymbolPeriod *ExtModels[class="num">3];
CTrade            ExtTrade;
class="type">void OnTick()
  {
   for(class="type">long i=class="num">0; i<ExtModels.Size(); i++)
     if(!ExtModels[i].CheckOnTick())
       class="kw">return;
class=class="str">"cmt">//--- predict next price movement
   class="type">int    returned[class="num">3]={class="num">0,class="num">0,class="num">0};
   vector soft=vector::Zeros(class="num">3);
class=class="str">"cmt">//--- collect returned classes
   for(class="type">long i=class="num">0; i<ExtModels.Size(); i++)
     {
      vector prob(class="num">3);
      class="type">int    pred=ExtModels[i].PredictClass(prob);
      if(pred>=class="num">0)
        {
         returned[pred]++;
         soft+=prob;
        }
     }

软投票与硬投票的落地分支

集成模型给出预测后,下一步是把它转成可执行的交易信号。代码里先置 predicted_class 为 -1,代表暂无方向;随后按输入参数 InpVotes 切换软投票或硬投票逻辑。 软投票直接取 soft.ArgMax(),也就是概率向量中最大分量的索引作为类别;硬投票则遍历三个模型,当某个类别的得票数 returned[n] 达到阈值 InpVotes 才确认,否则保持 -1。这种分支让你可以同一套框架切换两种集成策略。 得到非负 predicted_class 后,先用 PositionSelect(_Symbol) 看当前品种是否持仓:有仓就跑 CheckForClose 按预测调仓或平仓,无仓则跑 CheckForOpen 尝试建仓。外汇与贵金属波动剧烈、杠杆风险高,实盘前请在 MT5 策略测试器用历史数据验证投票阈值对回撤的影响。

MQL5 / C++
class=class="str">"cmt">//--- get one prediction for all models
   class="type">int predicted_class=-class="num">1;
class=class="str">"cmt">//--- soft or hard voting
   if(InpVotes==Soft)
      predicted_class=(class="type">int)soft.ArgMax();
   else
     {
      class=class="str">"cmt">//--- count votes for predictions
      for(class="type">int n=class="num">0; n<class="num">3; n++)
        {
         if(returned[n]>=InpVotes)
           {
            predicted_class=n;
            class="kw">break;
           }
        }
     }
class=class="str">"cmt">//--- check trading according to prediction
   if(predicted_class>=class="num">0)
      if(PositionSelect(_Symbol))
         CheckForClose(predicted_class);
      else
         CheckForOpen(predicted_class);
   }

「把模型关进类里之后」

整套面向对象写法最直观的回报,是 OnTick 主循环被压到了 45 行左右。模型内部哪怕比示例复杂数倍,对外只暴露几个接口,策略层几乎不用改。 随文 ZIP 里附的 Python 训练脚本有个细节容易看错:EURUSD D1 的 30 周期版用 x[0][-1](即 ma_slow 尾值)减新目标 close 做 delta,阈值卡在 0.0001 以内判中性;而 10 周期版换成 x[3][-1](close 尾值)减新目标 close,时间错位变成 sample_size-4。两类标签 (1,0,0)/(0,1,0)/(0,0,1) 分别对应涨、平、跌倾向。 [CODE] 里那段就是上面两种采集逻辑的对照:红行算 delta,蓝行按正负给标签。复制进 MT5 的 Python 环境跑一遍,能直接验证标签边界是否和你的品种波动匹配。 外汇与贵金属杠杆高、滑点突兀,ONNX 分类只给概率倾向,实盘前先用策略测试器过一遍样本外数据。

MQL5 / C++
for i in tqdm(range(n - sample_size)):
        w = df.iloc[i: i + sample_size + class="num">1]
        x = w[[&class="macro">#x27;close&class="macro">#x27;, &class="macro">#x27;ma_fast&class="macro">#x27;, &class="macro">#x27;ma_slow&class="macro">#x27;]].iloc[:-class="num">1].values
        delta = x[class="num">0][-class="num">1] - w.iloc[-class="num">1][&class="macro">#x27;close&class="macro">#x27;]
        if np.abs(delta)<=class="num">0.0001:
           y = class="num">0, class="num">1, class="num">0
        else:
           if delta>class="num">0:
              y = class="num">1, class="num">0, class="num">0
           else:
              y = class="num">0, class="num">0, class="num">1
if delta>class="num">0:
              y = class="num">1, class="num">0, class="num">0
              x = w[[&class="macro">#x27;open&class="macro">#x27;, &class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27;]].iloc[:-class="num">1].values
              delta = x[class="num">3][-class="num">1] - w.iloc[-class="num">1][&class="macro">#x27;close&class="macro">#x27;]
把模型诊断交给小布盯盘
这些模型的输入维度校验和会话初始化逻辑,小布盯盘的 AIGC 已内置品种页诊断,打开对应 EURUSD D1 页就能看到结构提示,你只管调参与决策。

常见问题

抽象基类只声明接口,衍生类若不重写对应方法,调用时返回空或报错,模型不会输出有效结果,编译期也难捕获。
目前小布盯盘内置的是诊断与结构提示,自定义 ONNX 推理仍需在 MT5 端用本文的类封装跑,小布负责减少你重复核对品种周期的劳动。
回归转分类只取预测价格方向,丢失了各类别置信度输出,而软投票需按概率加权,缺失分布就无法参与加权计算。
基类统一在新柱开始处触发,各衍生类按自身输入长度取数,彼此独立运行,只要周期一致就不会抢柱线事件。
历史样本上软投票对概率敏感、硬投票抗极端误判,实盘里倾向结合验证,外汇贵金属高风险,两种都只是概率倾斜非确定。