重构经典策略:原油·进阶篇
🛢️

重构经典策略:原油·进阶篇

(2/3)·经典价差策略在算法盘里失灵?本篇用机器学习重构WTI-布伦特价差预测模型

偏理论进阶 第 2/3 篇
很多老交易员还在用固定阈值做布伦特与WTI价差回归,却忽略两种基准油的可获取性和质量差已随出口禁令废除而改变。把历史价差直接套今天,容易在算法主导的波动里吃假突破。先厘清基准差异,再谈模型才站得住。

「EA 骨架与回测前的参数约定」

把跨市场回归 EA 搭起来,第一步是引入交易与尾随止损库,再用四个输入参数框住风险与仓位逻辑:lot_multiple 决定每笔手数是最小交易量的几倍,profit_target 设统一平仓盈利点,max_loss 控账户总回撤上限,position_size 规定同方向开仓数量。这些量直接决定你在 MT5 里跑这个 EA 时的暴露程度,外汇与贵金属类品种杠杆高,回撤触发后可能迅速离场。 为了在两个标的间对齐历史,代码里用 consumption=0.01 只取 1% 可用 K 线,再分别算 UK Brent Oil 与 WTI_OIL 的 iBars 乘该比例,取较小值赋给 max_bars。look_ahead 被设为 max_bars/4,也就是用四分之一的柱数做前瞻预测步长,剩下部分用于拟合。 模型侧,A 是 fetch×6 的零矩阵存输入(含价差与截距),y 是 fetch×1 的输出列(布伦特收盘价),wti_price 是对应长度的向量。OnInit 只查布伦特最小交易量,OnTick 里先刷新买卖价,未训练就拟合、已训练就查持仓并按预测方向开仓或检查止盈止损。 回测可直接用 MT5 策略测试器加载,图例显示历史收益率曲线来自该 EA 对布伦特与 WTI 价差回归后的信号。注意这仅是历史样本表现,未来同向价差结构可能断裂,实盘前务必用更小 consumption 验证过拟合程度。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Brent EA.mq5 |</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Copyright class="num">2024, MetaQuotes Ltd. |</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [MQL5官方文档] |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Copyright class="num">2024, MetaQuotes Ltd."</span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">link</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"[MQL5官方文档]
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">version</span>&nbsp;&nbsp; <span class="class="type">class="kw">string">"class="num">1.00"</span>
<span class="comment">class=class="str">"cmt">//Libraries</span>
<span class="preprocessor">class="macro">#include&nbsp;&nbsp;</span>&lt;Trade\Trade.mqh&gt;
CTrade ExtTrade;
<span class="preprocessor">class="macro">#include </span>&lt;TrailingStop\ATRTrailingStop3.mqh&gt;
ATRTrailingStop ExtATRTrailingStop;
<span class="comment">class=class="str">"cmt">//Inputs</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> lot_multiple = <span class="number">class="num">1.0</span>;
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> profit_target = <span class="number">class="num">10</span>;
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> max_loss = <span class="number">class="num">20</span>;
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> position_size = <span class="number">class="num">2</span>;
<span class="comment">class=class="str">"cmt">//Set this value between class="num">0 and class="num">1 to control how much data is used</span>
<span class="keyword">class="type">class="kw">double</span> consumption = <span class="number">class="num">0.01</span>;
<span class="comment">class=class="str">"cmt">//We want to know which symbol has the least number of bars.</span>
<span class="keyword">class="type">class="kw">double</span> brent_bars = (<span class="keyword">class="type">class="kw">double</span>) <span class="functions">NormalizeDouble</span>((<span class="functions">iBars</span>(<span class="class="type">class="kw">string">"UK Brent Oil"</span>,<span class="macro">PERIOD_CURRENT</span>) * consumption),<span class="number">class="num">0</span>);
<span class="keyword">class="type">class="kw">double</span> wti_bars = (<span class="keyword">class="type">class="kw">double</span>) <span class="functions">NormalizeDouble</span>((<span class="functions">iBars</span>(<span class="class="type">class="kw">string">"WTI_OIL"</span>,<span class="macro">PERIOD_CURRENT</span>) * consumption),<span class="number">class="num">0</span>);
<span class="comment">class=class="str">"cmt">//Select the lowest</span>
<span class="keyword">class="type">class="kw">double</span> max_bars = (brent_bars &lt; wti_bars) ? brent_bars : wti_bars;
<span class="comment">class=class="str">"cmt">//How far into the future are we forecasting</span>
<span class="keyword">class="type">class="kw">double</span> look_ahead = <span class="functions">NormalizeDouble</span>((max_bars / <span class="number">class="num">4</span>),<span class="number">class="num">0</span>);
<span class="comment">class=class="str">"cmt">//How many bars should we fetch? </span>
<span class="keyword">class="type">int</span> fetch = (<span class="keyword">class="type">int</span>) (max_bars - look_ahead) - <span class="number">class="num">1</span>;
<span class="comment">class=class="str">"cmt">//Matrix A stores our inputs. y is the output. x is the coefficients.</span>
<span class="keyword">matrix</span> A = <span class="keyword">matrix</span>::Zeros(fetch,<span class="number">class="num">6</span>);
<span class="keyword">matrix</span> y = <span class="keyword">matrix</span>::Zeros(fetch,<span class="number">class="num">1</span>);
<span class="keyword">vector</span> wti_price = <span class="keyword">vector</span>::Zeros(fetch);

◍ 布伦特模型下的持仓与下单骨架

这段逻辑把 UK Brent Oil 和 WTI_OIL 两个品种分开存字符串,用 vector 和 matrix 给回归预测留了占位:brent_price 长度由 fetch 决定,x 是 6 行 1 列的零矩阵,intercept 是全 1 向量。外汇与贵金属杠杆高,直接用真仓跑前务必在策略测试器里先验证。 OnInit 里只做两件事:atr_multiple 大于 0 时初始化 ATR trailing stop,再读 brent 品种的最小成交量 min_volume,返回 INIT_SUCCEEDED。 OnTick 是总调度:未初始化就调 InitializeModel,已初始化且无持仓就跑 ModelForecast 再 InterpretForecast;有持仓则进 ManageTrades。ManageTrades 只看账户浮盈是否突破 profit_target 或浮亏低于 -max_loss,触发就 CloseAll,属于硬止损止盈。 InterpretForecast 把 forecast 和当前 K 线收盘价比:高于就 check_buy,低于就 check_sell。check_buy 在空仓时按 position_size 次数循环,每次下 lot_multiple * min_volume 手多单,说明手数是最小单位的整数倍,避免券商拒单。

MQL5 / C++
vector brent_price = vector::Zeros(fetch);
vector spread;
vector intercept = vector::Ones(fetch);
matrix x = matrix::Zeros(class="num">6,class="num">1);
class="type">class="kw">double forecast = class="num">0;
class="type">class="kw">double ask = class="num">0;
class="type">class="kw">double bid = class="num">0;
class="type">class="kw">double min_volume = class="num">0;
class="type">class="kw">string brent = "UK Brent Oil";
class="type">class="kw">string wti = "WTI_OIL";
class="type">bool model_initialized = false;
class="type">int OnInit()
  {
class=class="str">"cmt">//Initialise trailing stops
   if(atr_multiple > class="num">0)
       ExtATRTrailingStop.Init(atr_multiple);
   min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN);
   class="kw">return(INIT_SUCCEEDED);
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">//---
   ask = SymbolInfoDouble(brent,SYMBOL_ASK);
   bid = SymbolInfoDouble(brent,SYMBOL_BID);
   if(model_initialized)
    {
     if(PositionsTotal() == class="num">0)
       {
        forecast = class="num">0;
        forecast = ModelForecast();
        InterpretForecast();
       }
     else
       {
        ManageTrades();
       }
    }
   else
    {
     model_initialized = InitializeModel();
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ManageTrades()
  {
   if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target)
      CloseAll();
   if(AccountInfoDouble(ACCOUNT_PROFIT) < (-class="num">1 * max_loss))
      CloseAll();
  }
class="type">void InterpretForecast()
  {
   if(forecast != class="num">0)
     {
      if(forecast > iClose(_Symbol,PERIOD_CURRENT,class="num">0))
        {
         check_buy();
        }
      else
       if(forecast < iClose(_Symbol,PERIOD_CURRENT,class="num">0))
         {
          check_sell();
         }
     }
  }
class="type">void check_buy()
  {
   if(PositionsTotal() == class="num">0)
     {
      for(class="type">int i = class="num">0; i < position_size; i++)
        {
         ExtTrade.Buy(lot_multiple * min_volume,brent,ask,class="num">0,class="num">0,"BUY");
        }
     }
  }
class="type">void check_sell()
  {

无持仓时按倍数铺空单与定向平仓

这段代码暴露了一个典型的网格式建仓逻辑:当账户里 Brent 相关持仓数为 0 时,循环 position_size 次,以 lot_multiple * min_volume 的手数在 bid 价挂空单。position_size 若设成 5、lot_multiple 设成 2,就意味着一口气打出 5 张加倍最小手的空单,原油这种高波动品种上杠杆暴露会很快放大,属于高风险操作。 CloseAll 与 close_buy / close_sell 三个函数把平仓拆得很细:CloseAll 只清 Brent 品种的全部持仓,close_buy 和 close_sell 则分别按 POSITION_TYPE_BUY / SELL 精准剔除某一方向。实盘里如果同时跑多品种 EA,这种按符号过滤的写法能避免误平其他品种。 InitializeModel 里用 SymbolSelect 确认 brent 和 wti 可见后,才通过 CopyRates 拉取收盘价序列,其中 y 取 1 到 fetch 根、A 和 brent_price 偏移 look_ahead 根起步。把 look_ahead 调大,模型输入就相对输出右移,回测里常用来模拟预测滞后,但实盘滑点可能让这种错位更明显。 别把平仓函数当保险丝 close_buy 只关多单、close_sell 只关空单,若你手动反手后忘了调函数,脚本可能只平掉一半仓位,留着反向单裸奔。

MQL5 / C++
if(PositionsTotal() == class="num">0)
   {
     for(class="type">int i = class="num">0; i < position_size; i++)
       {
        ExtTrade.Sell(lot_multiple * min_volume,brent,bid,class="num">0,class="num">0,"SELL");
       }
   }
class="type">void CloseAll(class="type">void)
  {
  for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--)
    {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetSymbol(i) == brent)
           {
            class="type">ulong ticket;
            ticket = PositionGetTicket(i);
            ExtTrade.PositionClose(ticket);
           }
        }
    }
  }
class="type">void close_buy()
  {
   class="type">ulong ticket;
   class="type">int type;
   if(PositionsTotal() > class="num">0)
     {
       for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
         {
          ticket = PositionGetTicket(i);
          type = (class="type">int)PositionGetInteger(POSITION_TYPE);
          if(type == POSITION_TYPE_BUY)
            {
               ExtTrade.PositionClose(ticket);
            }
         }
     }
  }
class="type">void close_sell()
  {
   class="type">ulong ticket;
   class="type">int type;
   if(PositionsTotal() > class="num">0)
     {
       for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
         {
          ticket = PositionGetTicket(i);
          type = (class="type">int)PositionGetInteger(POSITION_TYPE);
          if(type == POSITION_TYPE_SELL)
            {
               ExtTrade.PositionClose(ticket);
            }
         }
     }
  }
class="type">bool InitializeModel()
  {
class=class="str">"cmt">//Try select the symbols
   if(SymbolSelect(brent,true) && SymbolSelect(wti,true))
     {
       Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead);
       class=class="str">"cmt">//Get historical data on Brent , our model output
       y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">1,fetch);
       class=class="str">"cmt">//model class="kw">input
       A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(class="num">1 + look_ahead),fetch);
       brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(class="num">1+look_ahead),fetch);

「布伦特与WTI价差怎么喂进回归模型」

做跨品种套利或相关性分析时,Brent 对 WTI 的价差是最直接的输入特征。上面这段逻辑先把两个品种的收盘价按当前周期抓取,用 brent_price 减 wti_price 得到 spread,再打印出来方便在 MT5 终端实时盯。 矩阵 A 被重塑为 3 行 fetch 列:第 0 行放 Brent 收盘价,第 1 行塞 spread,第 2 行补一列 intercept(全 1 截距项)。转置后和标签 y 对齐,若 A 或 y 列数为 0 就说明历史拷贝失败,直接返回 false 并把维度打印出来排错。 拟合阶段走的是 x = A.PInv().MatMul(y),即伪逆乘以目标向量的最小二乘解,跑完把系数 x 打印出来。预测函数 ModelForecast 只抓最新 1 根 K 线,重算 spread 后用手算点积 (A[0][0]*x[0][0])+(A[1][0]*x[1][0])+(A[2][0]*x[2][0]) 得出单点预报值。外汇与贵金属类价差策略杠杆高、跳空频繁,回测吻合不代表实盘概率占优,开 MT5 用真实品种名替换 brent / wti 变量再验证。

MQL5 / C++
wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(class="num">1+look_ahead),fetch);
class=class="str">"cmt">//Calculate the spread
spread = brent_price - wti_price;
Print("The Current Spread: ",spread);
A.Reshape(class="num">3,fetch);
class=class="str">"cmt">//Add the spread to the class="kw">input matrix
A.Row(spread,class="num">1);
class=class="str">"cmt">//Add a column for the intercept
A.Row(intercept,class="num">2);
class=class="str">"cmt">//Reshape the matrices
A = A.Transpose();
y = y.Transpose();
class=class="str">"cmt">//Inspect the matrices
if((A.Cols() == class="num">0 || y.Cols() == class="num">0))
  {
   Print("Error occured when copying historical data");
   Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols());
   Print("A");
   Print(A);
   Print("y");
   Print(y);
   class="kw">return(false);
  }
else
  {
   Print("No errors occured when copying historical data");
   x = A.PInv().MatMul(y);
   Print("Finished Fitting The Model");
   Print(x);
   class="kw">return(true);
  }
   }
 Print("Faield to select symbols");
 class="kw">return(false);
}
class="type">class="kw">double ModelForecast()
  {
   if(model_initialized)
     {
     class=class="str">"cmt">//model class="kw">input
     A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">0,class="num">1);
     brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">0,class="num">1);
     wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">0,class="num">1);
     class=class="str">"cmt">//Calculate the spread
     spread = brent_price - wti_price;
     Print("The Spread: ",spread);
     A.Reshape(class="num">3,fetch);
     class=class="str">"cmt">//Add the spread to the class="kw">input matrix
     A.Row(spread,class="num">1);
     class=class="str">"cmt">//Add a column for the intercept
     A.Row(intercept,class="num">2);
     class=class="str">"cmt">//Reshape the matrices
     A = A.Transpose();
     class="type">class="kw">double _forecast = (A[class="num">0][class="num">0]*x[class="num">0][class="num">0]) + (A[class="num">1][class="num">0]*x[class="num">1][class="num">0]) + (A[class="num">2][class="num">0]*x[class="num">2][class="num">0]);
     class="kw">return(_forecast);
     }
   class="kw">return(class="num">0);
  }

◍ 布伦特与WTI价差回归的矩阵搭建

这段 EA 头文件把跨品种套利的数据骨架先立起来了:同时抓取 UK Brent Oil 与 WTI_OIL 的当前周期 BAR 数,乘以 consumption=0.01 的采样率,取两者较小值作为 max_bars,避免长历史对齐时一方数据不足。 look_ahead 直接取 max_bars 的四分之一并取整,意味着模型用约 75% 的重叠样本去推剩余 25% 的价差方向;fetch 再减 1 作为矩阵行数,实盘里若 Brent 当前有 4000 根 BAR,采样后 max_bars=40,look_ahead=10,fetch 即为 29。 A 是 29×6 的零矩阵,y 是 29×1 的输出列,x 是 6×1 的系数向量,intercept 全 1 充当偏置项——典型的多元线性回归布局,用 WTI 价、Brent 价、价差及滞后项凑出 6 个特征。 OnInit 里仅在 atr_multiple>0 时加载 ATR trailing(默认 5.0 倍),并读出 Brent 最小交易量 min_volume,外汇与原油价差策略杠杆敏感,实盘前务必在 MT5 品种规格里核对 SYMBOL_VOLUME_MIN,否则市价单可能直接拒单。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Brent EA.mq5 |
class=class="str">"cmt">//|                                         Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                              [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//Libraries
class="macro">#include  <Trade\Trade.mqh>
CTrade ExtTrade;
class="macro">#include <TrailingStop\ATRTrailingStop3.mqh>
ATRTrailingStop ExtATRTrailingStop;
class=class="str">"cmt">//Inputs
class="kw">input class="type">class="kw">double atr_multiple = class="num">5.0;
class="kw">input class="type">class="kw">double lot_multiple = class="num">1.0;
class="kw">input class="type">class="kw">double profit_target = class="num">10;
class="kw">input class="type">class="kw">double max_loss = class="num">20;
class="kw">input class="type">int position_size = class="num">2;
class=class="str">"cmt">//Set this value between class="num">0 and class="num">1 to control how much data is used
class="type">class="kw">double consumption = class="num">0.01;
class=class="str">"cmt">//We want to know which symbol has the least number of bars.
class="type">class="kw">double brent_bars = (class="type">class="kw">double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),class="num">0);
class="type">class="kw">double wti_bars = (class="type">class="kw">double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),class="num">0);
class=class="str">"cmt">//Select the lowest
class="type">class="kw">double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars;
class=class="str">"cmt">//How far into the future are we forecasting
class="type">class="kw">double look_ahead = NormalizeDouble((max_bars / class="num">4),class="num">0);
class=class="str">"cmt">//How many bars should we fetch?
class="type">int fetch = (class="type">int)(max_bars - look_ahead) - class="num">1;
class=class="str">"cmt">//Matrix A stores our inputs. y is the output. x is the coefficients.
matrix A = matrix::Zeros(fetch,class="num">6);
matrix y = matrix::Zeros(fetch,class="num">1);
vector wti_price = vector::Zeros(fetch);
vector brent_price = vector::Zeros(fetch);
vector spread;
vector intercept = vector::Ones(fetch);
matrix x = matrix::Zeros(class="num">6,class="num">1);
class="type">class="kw">double forecast = class="num">0;
class="type">class="kw">double ask = class="num">0;
class="type">class="kw">double bid = class="num">0;
class="type">class="kw">double min_volume = class="num">0;
class="type">class="kw">string brent = "UK Brent Oil";
class="type">class="kw">string wti = "WTI_OIL";
class="type">bool model_initialized = false;
class="type">int OnInit()
  {
class=class="str">"cmt">//Initialise trailing stops
   if(atr_multiple > class="num">0)
      ExtATRTrailingStop.Init(atr_multiple);
   min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN);
   class="kw">return(INIT_SUCCEEDED);
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

主循环与风控退场的接线方式

EA 的生命周期里,OnDeinit 留空不代表没用——它只是不在这里做清理,真正干活的是 OnTick。每次报价进来先抓 brent 的卖价和买价:ask = SymbolInfoDouble(brent, SYMBOL_ASK) 与 bid = SymbolInfoDouble(brent, SYMBOL_BID),这两行决定了后面所有价差判断的基准。 模型没初始化就先 InitializeModel(),初始化成功后看仓位:PositionsTotal()==0 就跑 ModelForecast() 拿预测值再 InterpretForecast();有仓就交给 ManageTrades() 管。这个分支让「无仓预测、有仓管理」严格分开,避免重复开单。 ManageTrades 的风控很直白:账户浮盈超 profit_target 或浮亏低于 -1*max_loss 就 CloseAll()。外汇与贵金属杠杆高,这种硬止损能在单日黑天鹅里把本金回撤锁死,但参数设太窄会被噪音洗出场。 InterpretForecast 把 forecast 和当前 K 线收盘价比:预测价高于 iClose(_Symbol, PERIOD_CURRENT, 0) 走 check_buy,低于就 check_sell。注意它比的是当前品种收盘而非 brent,跨品种信号映射到本品种执行时这块最容易出偏差,上机前建议打印 forecast 与 close 的差值日志。

MQL5 / C++
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">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   ask = SymbolInfoDouble(brent,SYMBOL_ASK);
   bid = SymbolInfoDouble(brent,SYMBOL_BID);
   if(model_initialized)
     {
      if(PositionsTotal() == class="num">0)
        {
         forecast = class="num">0;
         forecast = ModelForecast();
         InterpretForecast();
        }
      else
        {
         ManageTrades();
        }
     }
   else
     {
      model_initialized = InitializeModel();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|This function closes trades if we reach our profit or loss limit                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ManageTrades()
  {
   if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target)
      CloseAll();
   if(AccountInfoDouble(ACCOUNT_PROFIT) < (-class="num">1 * max_loss))
      CloseAll();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|This function judges if our model is giving a class="type">long or class="type">short signal               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InterpretForecast()
  {
   if(forecast != class="num">0)
     {
      if(forecast > iClose(_Symbol,PERIOD_CURRENT,class="num">0))
        {
         check_buy();
        }
      else
        if(forecast < iClose(_Symbol,PERIOD_CURRENT,class="num">0))
          {
           check_sell();
          }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|This function checks if we can open buy positions                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void check_buy()
  {
把价差监测交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到布伦特-WTI价差偏离与伪逆预测提示,你专注决策即可。

常见问题

布伦特硫略高、密度略大且易海运获取,WTI更轻更低硫但曾受内陆限制;可获取性变化会改变价差中枢,建模时需作为时变因子而非常量。
可以,小布盯盘品种页内置了基于MQL5矩阵函数的价差偏离与伪逆解预测视图,省去自建脚本的重复劳动。
伪逆在矩阵不满秩或样本少时仍给出稳定最小二乘解,适合紧凑价差模型,避免直接求逆报错。
矩阵和向量原生类型让价差特征与权重运算写成数行而非循环,易维护且方便在EA里复用。
不能,原油与外汇贵金属驱动因子不同,且贵金属外汇高风险,价差模型仅作跨市场情绪参考,概率上辅助而非替代判断。