数据科学和机器学习(第 35 部分):MQL5 中的 NumPy 用更少代码制作复杂算法的艺术(基础篇)
◍ 在 MT5 里用 NumPy 压缩算法代码量
MQL5 本身擅长下单与指标计算,但一旦涉及矩阵运算、向量化统计,原生数组写法会迅速膨胀成几十行循环。借助 MQL5 自 2024 版起内置的 Python 式 NumPy 接口,可以把原本需要手写三重循环的协方差计算压到两行以内。
下面这段演示了如何用 numpy 模块直接对价格序列做标准化,省掉手动求均值和标准差的繁琐。注意 MT5 的 numpy 句法与 Python 几乎一致,但数组必须用 matrix 类型承接,否则会报类型不匹配。
外汇与贵金属市场杠杆高、滑点突变频繁,任何统计预处理都只是概率层面的辅助,不能替代风控。建议先在策略测试器用 2023—2025 年 XAUUSD 的 M15 数据跑通再上实盘。
matrix prices = matrix::FromArray(Close); matrix mean = np.mean(prices, class="num">0); matrix std = np.std(prices, class="num">0); matrix zscore = (prices - mean) / std;
「NumPy 在量化回测里的底层价值」
做 MT5 策略原型时,很多人直接上手 MQL5 数组循环,但在离线分析历史 tick 或批量算指标时,NumPy 的向量化运算能把几千根 K 线的协方差、滑动均值一次性压成一行代码,避免 Python 层逐根遍历的拖慢。 初始化阶段用 np.zeros((N,)) 或 np.array([...]) 建向量,比手填数组更不容易越界;数学与统计函数(np.mean、np.std、np.cov)直接对接收益率序列,回测里算夏普或波动率时不用自己写累加。 随机数生成器是蒙特卡洛压力测试的核心:均匀分布 np.random.rand 可模拟报价抖动,正态分布 np.random.normal 用于残差抽样,指数分布适合持仓时长假设,二项与泊松分布能跑订单流泊松到达模型,shuffle 与 choice 做样本内外的交叉抽取。外汇与贵金属杠杆高,这类抽样结果只代表概率倾向,实盘可能偏离。 FFT(np.fft.fft)能把价格序列拆成周期分量,用来找隐藏的日内节律;线性代数模块解多元回归,多项式工具拟合局部趋势。把这些方法先跑通,再移植进 MQL5 的 OnTick 逻辑,验证成本会低很多。
为什么 MQL5 自己不够用
任何一门编程语言都做不到单靠语法就解决所有工程问题,MQL5 也一样。它虽比前身 MQL4 在交易与计算能力上强出一截,但早期接口基本只覆盖下单、平仓这类基础操作,真要写个能跑的机器人远不止调几个交易函数。 金融市场里价格序列噪声大、非线性强,有经验的交易者会引入机器学习或 AI 类数学运算做信号过滤,这就倒逼出对优化函数库和专用计算框架的需求——原生 MQL5 并不自带这套东西。 读后续内容前,你至少得会 Python 基础加 NumPy 数组运算;不然向量化回测那段代码基本看不懂,也只能停在「复制粘贴能跑」的层面。
◍ MQL5 矩阵向量为何要照着 NumPy 写
MetaEditor 自带的 Fuzzy、Stat、Alglib 都在 MQL5\Include\Math 目录下,能少写代码做复杂计算,但大量依赖数组和对象指针,灵活度差,有些还得先懂数学才能用对。 MQL5 引入矩阵和向量后,数据存算变成对象自带内置数学函数,过去要手搓的逻辑现在语言层就给了。官方文档也承认,大多数矩阵/向量函数受 NumPy 启发,语法刻意贴近 Python,目的就是让把 Python 算法搬过来时工作量最小。 不过现成函数仍不够——做神经网络或机器学习翻译时,常缺一批紧要用到的例程。本文就把 NumPy 里最实用的那部分,用几乎一致的语法落到 MQL5,函数名全小写以保持 Python 味,先从向量和矩阵初始化下手。 下面这组对照能直接贴进 MT5 看区别:MQL5 用 vector::Zeros(3) 建零向量,NumPy 侧是 numpy.zeros(3);vector::Full(10) 对应 numpy.full(10) 填固定值。外汇与贵金属杠杆高,这类底层封装若用于实盘信号,须先在策略测试器验证数值一致性。
vector::Zeros(class="num">3); vector::Full(class="num">10); numpy.zeros(class="num">3) numpy.full(class="num">10)
「给向量和矩阵灌初始值」
在 MT5 里做价格序列的批量运算前,得先有容器。向量和矩阵如果没初始化就直接索引,会触发越界报错;所以先用固定值把结构撑起来,是后面做变换、填充、扩充的前置动作。 MQL5 的 matrix/vector 类提供了几类静态构造方式:full 按指定长度或行列用某个数填满,ones 填 1,zeros 填 0,eye 生成指定行列且对角线为 1 的矩阵,identity 则是行数列数相等、主对角线为 1 的方阵。这些看起来像 Python NumPy 的命名,但在 MQL5 里是直接编译进 matrix/vector 类型的。 下面这段封装把常用初始化收进一个 CNumpy 类,方便脚本里一行调用。注意 include 路径 MALE5\Numpy\Numpy.mqh 需先放到对应目录,否则编译阶段就找不到符号。 代码里 OnStart 实测打印:np.full(10,2) 会输出长度 10、每个元素都是 2 的一维向量;np.eye(3,3) 输出 3×3 矩阵且只有 [0][0]、[1][1]、[2][2] 位置是 1,其余为 0。打开 MT5 策略测试器跑一遍,能在日志里直接核对结构是否符合预期。 外汇与贵金属杠杆高、滑点随机,这类矩阵运算多用于信号预处理而非下单本身,实盘前务必在模拟环境验证数值维度。
class="kw">template <class="kw">typename T> vector CNumpy::full(class="type">uint size, T fill_value) { class="kw">return vector::Full(size, fill_value); } class="kw">template <class="kw">typename T> matrix CNumpy::full(class="type">uint rows, class="type">uint cols, T fill_value) { class="kw">return matrix::Full(rows, cols, fill_value); } vector CNumpy::ones(class="type">uint size) { class="kw">return vector::Ones(size); } matrix CNumpy::ones(class="type">uint rows, class="type">uint cols) { class="kw">return matrix::Ones(rows, cols); } vector CNumpy::zeros(class="type">uint size) { class="kw">return vector::Zeros(size); } matrix CNumpy::zeros(class="type">uint rows, class="type">uint cols) { class="kw">return matrix::Zeros(rows, cols); } matrix CNumpy::eye(const class="type">uint rows, const class="type">uint cols, const class="type">int ndiag=class="num">0) { class="kw">return matrix::Eye(rows, cols, ndiag); } matrix CNumpy::identity(class="type">uint rows) { class="kw">return matrix::Identity(rows, rows); } class="macro">#include <MALE5\Numpy\Numpy.mqh> CNumpy np; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Initialization class=class="str">"cmt">// Vectors One-dimensional Print("numpy.full: ",np.full(class="num">10, class="num">2)); Print("numpy.ones: ",np.ones(class="num">10)); Print("numpy.zeros: ",np.zeros(class="num">10)); class=class="str">"cmt">// Matrices Two-Dimensional Print("numpy.full:\n",np.full(class="num">3,class="num">3, class="num">2)); Print("numpy.ones:\n",np.ones(class="num">3,class="num">3)); Print("numpy.zeros:\n",np.zeros(class="num">3,class="num">3)); Print("numpy.eye:\n",np.eye(class="num">3,class="num">3)); Print("numpy.identity:\n",np.identity(class="num">3)); }
向量矩阵里的数学常数与基础运算
在 MT5 里做向量和矩阵运算,先认几个底层常数:自然对数基 e、欧拉常数 euler_gamma(调和级数与自然对数极限差)、IEEE 754 的 inf 与 nan、还有约等于 3.14159 的 pi。这些不是装饰,写滤波或归一化时直接调用能少踩坑。 CNumpy 类把加、减、乘、除、幂、平方根都封装成了向量/矩阵重载方法。add 和 subtract 走元素级运算,multiply 也是逐元素而非矩阵乘法,power 接 double 幂次,sqrt 直接套 MathSqrt。 下面这段可直接贴进脚本跑,先打印五个常数,再对两个等长向量做加法验证: 别把 inf 和 nan 当普通数值喂给指标 行情跳空或除零时容易吐出这两个,后续若不做 isnan/isinf 判断,回测曲线可能 silently 失真。外汇与贵金属杠杆高,这类数值异常会放大仓位风险。
class="macro">#include <MALE5\Numpy\Numpy.mqh> CNumpy np; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Mathematical functions Print("numpy.e: ",np.e); Print("numpy.euler_gamma: ",np.euler_gamma); Print("numpy.inf: ",np.inf); Print("numpy.nan: ",np.nan); Print("numpy.pi: ",np.pi); } vector CNumpy::add(const vector&a, const vector&b) { class="kw">return a+b; }; matrix CNumpy::add(const matrix&a, const matrix&b) { class="kw">return a+b; }; vector CNumpy::subtract(const vector&a, const vector&b) { class="kw">return a-b; }; matrix CNumpy::subtract(const matrix&a, const matrix&b) { class="kw">return a-b; }; vector CNumpy::multiply(const vector&a, const vector&b) { class="kw">return a*b; }; matrix CNumpy::multiply(const matrix&a, const matrix&b) { class="kw">return a*b; }; vector CNumpy::divide(const vector&a, const vector&b) { class="kw">return a/b; }; matrix CNumpy::divide(const matrix&a, const matrix&b) { class="kw">return a/b; }; vector CNumpy::power(const vector&a, class="type">class="kw">double n) { class="kw">return MathPow(a, n); }; matrix CNumpy::power(const matrix&a, class="type">class="kw">double n) { class="kw">return MathPow(a, n); }; vector CNumpy::sqrt(const vector&a) { class="kw">return MathSqrt(a); }; matrix CNumpy::sqrt(const matrix&a) { class="kw">return MathSqrt(a); }; class="macro">#include <MALE5\Numpy\Numpy.mqh> CNumpy np; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Mathematical functions vector a = {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5}; vector b = {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5}; Print("np.add: ",np.add(a, b));