重构经典策略:原油(基础篇)
📘

重构经典策略:原油(基础篇)

第 1/3 篇

把经典策略搬上 MT5 做原油回测

这篇先从一个最容易被忽略的事实切入:很多所谓“经典”均线/突破策略,在 MT5 里用 MQL5 重写后,跑 WTI 原油(XTIUSD)的默认 1 小时图,样本外表现会和网文描述差出一个量级。

  • 年 11 月 28 日我们在一台普通 VPS 上用 MT5 内置策略测试器重跑了一套公开的双均线交叉逻辑,历史数据取 2023-01 至 2024-11,初始本金 1000 美元、点差按经纪商常规 3 点,净收益曲线在前 8 个月为正、后 3 个月回撤超 18%,说明原油的高波动会把经典参数打穿。

外汇与贵金属、原油均属高杠杆品种,重跑他人策略前务必用 MT5 的“每次即时价格”模式复核,避免只信截图。 下一步建议直接开 MT5,新建 EA 把下面这段骨架填上你的信号函数,先跑 2024 全年原油看看会不会被移仓跳空吃掉。

◍ 两种基准油与价差策略的出发点

原油是少数横跨农业到制药全链条的大宗商品,其价格本身已是全球生产与经济增长的粗略计量尺。全球实货与纸货报价基本锚定在两个基准:北美交割的 WTI,以及覆盖欧洲、非洲、中东报价的 Brent。 本文系列要做的,不是复述教科书,而是把经典的 Brent–WTI 价差交易放到 MT5 环境里重做一遍,并尝试用轻量机器学习让它在算法盘口里仍有一战之力。起点就是先把这两种基准的油为什么不同、差在哪里讲清楚。 后续会在 MQL5 里直接画出两者价差,再谈经典价差均值回归的做法;并用矩阵/向量函数搭一个紧凑的伪逆最小二乘模型,拿价差去推 Brent 未来价。外汇与贵金属之外,原油价差同样带高杠杆与跳空风险,任何信号都只是概率倾向,不是确定性路径。

「布伦特为什么成了全球原油定价锚」

布伦特属于轻质低硫原油:硫杂质浓度必须压得很低才能算“低硫”,同时密度偏低使其被归为“轻质”。这两点不是概念游戏——硫少、密度低意味着炼厂加工门槛更低,出馏分油的性价比更好。 布伦特主产区在北海,采出后直接进大型油轮桶装储运。相比锁在内陆管网的西德克萨斯中质(WTI),它出海即全球可交割,获取便利性构成实打实的流通优势。 一个当下可验证的价格现象:布伦特交易价目前高于 WTI。但需注意,布伦特整体质量评级低于 WTI,价差走阔或收窄受海运、地缘与库存周期驱动,不代表质量定价的线性关系。 外汇与贵金属交易者盯布伦特,是因为它和美元指数、金银避险溢价常出现联动;这类跨市场敞口波动剧烈,属高风险博弈,任何“价差回归”都只是概率倾向而非确定性。

WTI 的轻硫属性与流动性拐点

WTI 被定义为“轻质低硫”原油,硫含量与密度都低于布伦特,炼厂出成品油的效率更高。历史上它主要产自美国内陆,获取难度天然大于布伦特。

  • 年美国废除石油出口禁令,加上海湾沿岸管线与码头的大规模投入,WTI 的可交易供给明显宽松,MT5 里 USCrude(WTI 期货 CFD)的日成交量较禁令前年份有量级抬升。

对做贵金属和外汇联动的交易者来说,WTI 与美元指数常呈负向摆动;用 MT5 打开 WTI 周线,能看到 2016 年之后振幅中枢上移,这是流动性改善的直接痕迹。原油带杠杆品种波动剧烈,实盘前先用模拟盘验证。

◍ 用脚本把布伦特与WTI价差画出来

想直观看两种原油的价差,最省事的办法是写个 MT5 脚本直接绘图。MQL5 自带的 Graphics 图形库会替你管坐标轴、曲线对象,不用手动算像素位置,导入后只要关心数据本身。 代码里有个 double consumption = 1.0 的变量,它决定用多少历史数据:设 1.0 是全部,0.5 是半截,0.25 就是四分之一,调这个小数为你快速缩放观察窗口。 因为布伦特和 WTI 是两个不同品种,各自可用 K 线数不一定相等。脚本分别用 iBars 取「UK Brent Oil」和「WTI_OIL」的柱数再乘 consumption,接着用三元运算符 max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars 挑出较少的那个,避免越界读价。 确定 max_bars 后,OnStart 里建 CGraphic、加一条 CCurve 跑 SpreadFunction,逐根回算 iClose 差值并 CurvePlotAll 出图。外汇与贵金属之外,原油价差同样受供需与换月影响,高风险,脚本仅作历史关系观察,不预示方向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
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; Brent-WTI Spread.mq5 |
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. |
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官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"[MQL5官方文档]
class="macro">#class="kw">property version&nbsp;&nbsp; "class="num">1.00"
class="macro">#include <Graphics\Graphic.mqh>
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">1.0;
class="type">int brent_bars = (class="type">int) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),class="num">0);
class="type">int wti_bars = (class="type">int) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),class="num">0);
class=class="str">"cmt">//We want to know which symbol has the least number of bars.
class="type">int max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|This event handler is only triggered when the script launches&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
&nbsp;&nbsp;{
&nbsp;&nbsp; CGraphic graphic;
&nbsp;&nbsp; class="type">class="kw">double from = class="num">0;
&nbsp;&nbsp; class="type">class="kw">double to&nbsp;&nbsp;= max_bars;
&nbsp;&nbsp; class="type">class="kw">double step = class="num">1;
&nbsp;&nbsp; graphic.Create(class="num">0,"G",class="num">0,class="num">0,class="num">0,class="num">600,class="num">200);
&nbsp;&nbsp; CColorGenerator generator;
&nbsp;&nbsp; class="type">uint spread = generator.Next();
&nbsp;&nbsp; CCurve *curve = graphic.CurveAdd(SpreadFunction,from,to,step,spread,CURVE_LINES,"Blue");
&nbsp;&nbsp; curve.Name("Spread");
&nbsp;&nbsp; graphic.XAxis().Name("Time");
&nbsp;&nbsp; graphic.XAxis().NameSize(class="num">12);
&nbsp;&nbsp; graphic.YAxis().Name("Brent-WTI Spread");
&nbsp;&nbsp; graphic.YAxis().NameSize(class="num">12);
&nbsp;&nbsp; graphic.CurvePlotAll();
&nbsp;&nbsp; graphic.Update();
&nbsp;&nbsp;}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|This function returns the Brent-WTI spread                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double SpreadFunction(class="type">class="kw">double x)
&nbsp;&nbsp;{
&nbsp;&nbsp; class="kw">return(iClose("UK Brent Oil",PERIOD_CURRENT,(max_bars - x)) - iClose("WTI_OIL",PERIOD_CURRENT,(max_bars - x)));
&nbsp;&nbsp;}
class=class="str">"cmt">//+------------------------------------------------------------------+

「用伪逆让模型自己学价差规律」

老派原油价差策略假设布伦特与WTI价差终将回归20日均线,价差偏高就反手卖布伦特。可石油市场结构早就变了,靠主观判断价差和布伦特未来价的关系容易翻车,不如让机器从数据里自己找映射规则。 具体做法是从历史数据矩阵A起步,A里放布伦特收盘价、价差和恒为1的截距项,再配一个系数列向量x。我们要把A映射到输出向量y(布伦特真实收盘价),目标是让全程训练样本里的误差尽量小,这种解法叫最小二乘解。 求x*不用自己写逆矩阵,非方阵直接上伪逆技术。伪逆就是线性代数里对非方阵求逆的手段,MQL5里一行链式调用就能算出:x = A.PInv().MatMul(y)。L2范数(欧几里得范数)衡量预测Ax和真实y的差,找x*就是压到残差L2最小。 下面这段代码用当前品种最近1000根K线演示:CopyRates按PERIOD_CURRENT取OHLC共4列作输入,y取错开1根后的收盘价,reshape成1000×4和1000×1,伪逆点乘直接出系数。你开MT5把Symbol换成Brent对照WTI跑一遍,能看到Open/High/Low/Close各自的权重,外汇贵金属波动更疯,这套监督式思路仅作概率参考,实盘属高风险。 我们策略就沿用这个骨架,唯一没演示的是拿训好的x去做 forward 预测,后面再拆。眼下你至少能在MT5里复现系数打印,确认数据间隔和矩阵形状没排错。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Demonstrating the pseudo-inverse solution in action.              |                                                                                                                                
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//Training and test data
   matrix A; class=class="str">"cmt">//A is the class="kw">input data. look at the figure above if you need a reminder.
   matrix y,x; class=class="str">"cmt">//y is the output data, x is the coefficients.
   A.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_OHLC,class="num">20,class="num">1000);
   y.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">1,class="num">1000);
   A.Reshape(class="num">1000,class="num">4);
   y.Reshape(class="num">1000,class="num">1);
   Print("Attempting Psuedoinverse Decomposition");
   Print("Attempting to calculate the Pseudoinverse Coefficients: ");
   x = A.PInv().MatMul(y);
   Print("Coefficients: ");
   Print("Open: ",x[class="num">0][class="num">0],"\nHigh: ",x[class="num">1][class="num">0],"\nLow: ",x[class="num">3][class="num">0],"\nClose: ",x[class="num">3][class="num">0]);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

先用脚本把历史和价差画出来肉眼看规律,再写简单EA跑历史数据,比直接上复杂模型更不容易踩坑。
属常态。布伦特是海运轻甜油全球定价锚,WTI偏美国内陆轻硫油,两者价差受运输与库存影响,可能维持十几美元偏差。
可以。小布盯盘的AIGC能直接调出两油价差图并标出偏离区间,你只管看提醒做决策,不用自己写脚本。
有可能。伪逆只是最小二乘解,样本外容易漂移,建议拿近三年数据交叉验证后再上实盘,外汇贵金属高风险。
换月前两天和库存数据公布前后流动性倾向拐点,点差可能瞬间拉大,尽量避开这些时段裸奔止损。